Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include signed ip address in TIER2 handshake. #8902 #9100

Merged
15 commits merged into from May 29, 2023
Merged

Include signed ip address in TIER2 handshake. #8902 #9100

15 commits merged into from May 29, 2023

Conversation

ghost
Copy link

@ghost ghost commented May 23, 2023

#8902

The problem being solved

  • Prevent man in middle attacks on Tier2 network

The approach taken

  • Pass the signed ip address of the sender

Remaining steps to fully deploy this solution after this PR is merged

  • Fail the handshake only after all nodes on both mainnet and testnet have updated to the latest version of Tier2 handshake that includes signing the ip address. It currently only fails if the signed ip address is present and faulty but doesn't fail if the signed ip address is absent (for backward compatibility temporary migration purposes)

Also needed to run auto-linter to pass buildkite tests

cargo fmt -- --emit files 
cargo clippy --fix -- -A clippy::all -W clippy::useless_format -W clippy::clone_on_copy
cargo clippy --fix -- -A clippy::all -W clippy::useless_format -W clippy::redundant_clone
./scripts/formatting --fix

@ghost ghost self-requested a review as a code owner May 23, 2023 17:55
@ghost ghost requested a review from mzhangmzz May 23, 2023 17:55
@ghost ghost self-assigned this May 23, 2023
@ghost ghost requested a review from saketh-are May 23, 2023 17:55
@ghost
Copy link
Author

ghost commented May 23, 2023

current sanity test error is due to peer signing on its local address of 0.0.0.0 but being advertised as 127.0.0.1, which is only the case for localnet. Will think of a fix for this and update code

@@ -90,6 +90,45 @@ impl std::str::FromStr for PeerAddr {
}
}

// Wrapper around std::net::IpAddr, which constructs a SignedOwnedIpAddress
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct OwnedIpAddress {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the need for this struct wrapping a single field. I would rather redesign the functions you have placed below in the impl to be static functions accepting an instance of std::net::IpAddr as an argument.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, it's needed to implement the interface in order to hide the serialization of ip_addr from the user and in order to form the message (SignedOwnedIpAddress) to be sent over the network to contain both required ip address and signature

If i understand correctly, there always needs to be a wrapper class in order to introduce new member methods to existing class such as the examples below

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Sorry if we already covered this, but what's the issue with defining a constructor for SignedIpAddress which takes two args (private_key and ip address) and returns an object with the ip address and signature?

  2. One other concern I have with the OwnedIPAddress wrapper is the "owned" language which has been borrowed from OwnedAccount. In the context of accounts it makes sense to say "owned" but not really for IP Addresses. If we really cannot get rid of this intermediate layer (please see question 1 first) consider naming it something like WrappedIpAddress for clarity.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding 1. good point, I thought about it before but somehow forgot it. I guess I preferred the other approach at the time as I could refer to protobuf serialization of OwnedAccount. Updated to SignedIpAddress with constructors as suggested.

Ignoring 2. since it's redundant thanks to 1.

chain/network/src/network_protocol/mod.rs Outdated Show resolved Hide resolved
chain/network/src/network_protocol/mod.rs Outdated Show resolved Hide resolved
chain/network/src/network_protocol/network.proto Outdated Show resolved Hide resolved
chain/network/src/network_protocol/proto_conv/net.rs Outdated Show resolved Hide resolved
chain/network/src/peer/peer_actor.rs Outdated Show resolved Hide resolved
chain/network/src/peer/tests/communication.rs Outdated Show resolved Hide resolved
@saketh-are
Copy link
Collaborator

Hey @soonnear, thanks for sharing this. I left some stylistic comments to start, but this looks pretty good overall. Also still need to review tests.

chain/network/src/network_protocol/tests.rs Outdated Show resolved Hide resolved
net::IpAddr::V6(ip) => ip.octets().to_vec(),
};
let signature = node_key.sign(&ip_bytes);
assert!(signature.verify(&ip_bytes, &node_key.public_key()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a test of the sign function (which accepts an arbitrary sequence of bytes), which should have its own unit tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sign function is independent of ip data, this test is specific to checking the sign function works with our serialized approach for ip data that we discussed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this test catch? The fact that .octets().to_vec() produces a Vec<u8> is something checked by the compiler. The fact that sign will work on an arbitrary sequence of bytes is a test of the sign function. I don't think this test justifies its existence here.

Copy link
Author

@ghost ghost May 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test also shows how the interface of the new SignedIpAddress object looks like from the client code perspective.
It also tests that the interface works as expected.
Removed the redundant code but maintained the test of the introduced interface SignedIpAddress which tests both that the sign and verify() works deterministically with its hidden serialization function of octets().to_vec()

chain/network/src/network_protocol/tests.rs Outdated Show resolved Hide resolved
chain/network/src/network_protocol/tests.rs Outdated Show resolved Hide resolved
chain/network/src/peer_manager/tests/connection_pool.rs Outdated Show resolved Hide resolved
let mut rng = make_rng(921853233);
let rng = &mut rng;
let mut clock = time::FakeClock::default();
let chain = Arc::new(data::Chain::make(&mut clock, rng, 10));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is quite long and it would benefit from some visual segmentation for readability. For example, the other tests in this file put an empty line after these first four lines to visually separate the setup of these basic variables from the rest of the test-specific code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some white spaces, hopefully that helps.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure which test you changed but test_backward_compatible_handshake_without_signed_ip_address still looks to have this issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah lol, the test test_backward_compatible_handshake_without_signed_ip_address is in a different file, this comment is pointing towards connection_pool.rs whereas that test is in the file communication.rs

I previously updated the test_signed_ip_address which is being referred to by this code review comment.

Updated the test as suggested, however, do note that this backward compatibility test is just temporary and will be removed once all nodes have fully migrated to signing ip address.

chain/network/src/config.rs Outdated Show resolved Hide resolved
chain/network/src/config.rs Outdated Show resolved Hide resolved
chain/network/src/peer/peer_actor.rs Outdated Show resolved Hide resolved
@ghost ghost removed the request for review from mzhangmzz May 26, 2023 10:33
@ghost
Copy link
Author

ghost commented May 26, 2023

updated to address first draft review comments, thanks! @saketh-are

@@ -286,7 +290,7 @@ impl PeerActor {
};
let my_node_info = PeerInfo {
id: network_state.config.node_id(),
addr: network_state.config.node_addr.as_ref().map(|a| **a),
addr: Some(stream.local_addr), // update own local address based on required stream
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the address stored previously here and why do we need to change it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it fails the handshake test for localnet.
Previously stores the listening address, which can be set as 0.0.0.0 and optiona, now will store the actual connection address which will be set as 127.0.0.1 for localnet and is mandatory

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I understand what's going on from some of your other comments. network_state.config.node_addr has the ip address on which the node listens for new connections. When that listener does accept a connection and create a new stream, it is more correct/direct to just take the address for the stream from the stream object rather than assume it will be identical to the one for the listener and indirectly get the address through the network config.

Can you remove or modify this comment? It is not clear to me what is being said (for example we are not "updating" anything here, it is the spawn logic and everything is being set for the first time).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you understood it perfectly. Took me some time diving into the code to figure out the differences between both socket addresses as well.
I removed the comment instead of updating the comment.

@saketh-are
Copy link
Collaborator

A better title for this PR would be something like "Include signed ip address in TIER2 handshake."

Also, please add some description which explains what the PR is doing and why. You've linked #8902, the issue being addressed, but have taken a different approach than suggested there ("by implementing a 3-way handshake"). The description should include a few sentences very briefly summarizing:

  • The problem being solved
  • The approach taken
  • Remaining steps to fully deploy this solution after this PR is merged

@ghost
Copy link
Author

ghost commented May 29, 2023

note that the unrelated code were added as part of clippy linter auto updates.
I believe a new rule was added, but it did not update the code in that commit (and that merged commit did fail the buildkite) so i've to update here or it wont pass the nayduck clippy tests.

@ghost ghost changed the title sign and verify ip address #8902 Include signed ip address in TIER2 handshake. #8902 May 29, 2023
@ghost ghost changed the title Include signed ip address in TIER2 handshake. #8902 Include signed ip address in TIER2 handshake. #8902 May 29, 2023
@ghost
Copy link
Author

ghost commented May 29, 2023

Thanks! Updated both the PR title and description as suggested.

Also updated code based on 2nd revision comments, thanks! @saketh-are

@saketh-are
Copy link
Collaborator

Thanks @soonnear, this is looking good. I've left just a few final comments; this should be ready to merge once you've taken a look.

Copy link
Collaborator

@saketh-are saketh-are left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM

@@ -90,6 +90,36 @@ impl std::str::FromStr for PeerAddr {
}
}

/// Proof that a given peer_id owns an ip address, included in Handshake message
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revise this comment to say what's really going on here? As mentioned previously ip addresses aren't really "owned." I would suggest something like:

// Signed ip address included as part of the Handshake message.
// Used to authenticate identity of connected peers.
// Necessary because peer discovery happens via unauthenticated PeerInfos.

return ip_bytes;
}

fn ip_bytes(&self) -> Vec<u8> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the separate helper function? Can we just inline it here?

Copy link
Collaborator

@saketh-are saketh-are left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM

@ghost ghost merged commit bc9ba97 into near:master May 29, 2023
@ghost ghost deleted the soon_sign_ip_address branch May 29, 2023 18:38
nikurt pushed a commit that referenced this pull request May 31, 2023
* Algorithm Design: Sign, Verify Ip Address With Interface SignedIpAddress
* Implemented Protocol Buffer Serialization Deserialization For std::net::IpAddr and SignedIpAddress
* Implemented serialization deserialization of Handshake message containing SignedIpAddress for Protocol Buffer
* Implemented and unit tested verifying ip address is properly signed and properly handles errors otherwise
* Tested backwards compatibility with lack of signature for ip address
marcelo-gonzalez pushed a commit to marcelo-gonzalez/nearcore that referenced this pull request Jun 15, 2023
ghost pushed a commit that referenced this pull request Jun 15, 2023
…9191)

* Revert "Include signed ip address in TIER2 handshake. #8902  (#9100)"
This reverts commit bc9ba97.
* reserved field number for backward compatibility of protocol buffer
* Added comment on purpose for reservation
marcelo-gonzalez pushed a commit to marcelo-gonzalez/nearcore that referenced this pull request Jun 16, 2023
…#9100)" (near#9191)

* Revert "Include signed ip address in TIER2 handshake. near#8902  (near#9100)"
This reverts commit bc9ba97.
* reserved field number for backward compatibility of protocol buffer
* Added comment on purpose for reservation
jakmeier pushed a commit to jakmeier/nearcore that referenced this pull request Jun 19, 2023
…#9100)" (near#9191)

* Revert "Include signed ip address in TIER2 handshake. near#8902  (near#9100)"
This reverts commit bc9ba97.
* reserved field number for backward compatibility of protocol buffer
* Added comment on purpose for reservation
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant