Repository navigation
Conversation
Member
Author
I have only used 78% of my AI credits!! Please learn to compute! |
meling
force-pushed
the
feat/325/peer-identity
branch
from
October 11, 2026 20:40
f8677d9 to
4cc9887
Compare
meling
added this pull request to stack #362
October 11, 2026 20:40
meling
force-pushed
the
feat/325/peer-identity
branch
from
October 12, 2026 20:10
4cc9887 to
38665fe
Compare
An outbound channel trusted whichever server answered at the dialed address. Add PeerVerifier and OutboundOptions.Verify: when set, the channel checks the remote end of each new stream against its node ID, using the stream's context, before it sends requests on the stream. A failed check closes the stream and fails the waiting request with codes.Unauthenticated; the channel then retries as for any failed stream.
A server attached an inbound stream to peer X whenever the stream's metadata claimed X, so any client could pose as a configured peer. NewInboundManager now takes an optional PeerVerifier. With it, AcceptPeer attaches a stream that claims a peer's node ID only after the verifier accepts the claim, and refuses claims for unknown node IDs, both with codes.Unauthenticated. The verifier runs without the manager's lock. The outbound manager hands the same verifier to the channels of the configured peers that the server dials.
Servers trusted the node ID that a peer claimed when it connected and whoever answered at a peer's address, so ServerContext.SenderID was not authenticated. WithPeerVerifier installs a PeerVerifier that checks each configured peer on every stream between the two servers, in both directions, before the stream carries calls. NewServer panics for a nil verifier and for a verifier without WithPeers. The user guide gains an Authenticating Peers section.
Add a PeerVerifier that binds each node ID to a SAN of the peer's verified mTLS certificate. It reads the certificate only from the verified chains, so a certificate that no trusted CA signed never passes. An identity with a URI scheme matches only a URI SAN with the same canonical form; any other identity matches only a DNS SAN, without regard to case. Wildcard DNS SANs, IP SANs, the Common Name, and empty identities never match. The user guide shows the certificate and TLS configuration that replicas need.
Run peer groups whose servers check each other with TLSPeerVerifier and certificates from a test CA, with and without stream deduplication. The tests cover peers that connect, a server that presents another node's certificate (refused by the dial-side and the inbound check), an impostor that claims a lower peer's ID and must not take over its stream, and a client without a certificate that is still served.
A verifier on a dialing node runs on the channel's goroutine, so one that blocks delays the channel's sends and its Close. State that it should return promptly and when its context ends, in the stream package and in the root PeerVerifier alias. State the invariant behind calling the stream's Context before the check: it commits the stream to its transport, so requests reach the checked peer, and turns off gRPC's transparent retry for the stream. Add a test that pins re-verification of the replacement stream after the server kills the first one.
If two node IDs have the same identity, one certificate matches both, so either replica could claim the other's ID. TLSPeerVerifier now panics at construction in that case. Two identities are the same when one SAN matches both: equal canonical URIs, or DNS names that are equal without regard to case. IDs are compared in increasing order so the panic message names the same pair on every run.
A refused peer does not see codes.Unauthenticated on its calls: it resends them on new streams until their contexts end, and the refusal with the verifier's text appears in Node.LastErr. State this in the WithPeerVerifier docs and the user guide, with the need for deadlines and the brief effect on ConnectedPeers and WaitForAll. Add a subtest that pins the refusal text in the refused peer's Node.LastErr. State that TLSPeerVerifier is only as strong as the CA pools, and which TLS settings verify no chain. Tabulate which SenderID values are authenticated, state that an unknown claimed ID is refused under a verifier, and show an interceptor check with Config.Contains, since a nonzero SenderID also covers unauthenticated back-channel clients.
* Make the cross-type duplicate test case able to fail by using a DNS identity that strings.EqualFold equates with a URI identity. * Document that TLSPeerVerifier panics on duplicate identities. * Fix wording: rewrap the SenderID comment, say that only two-way calls are resent, say "every peer stream is refused", and split a paragraph in the user guide.
meling
force-pushed
the
feat/325/peer-identity
branch
from
October 12, 2026 20:22
38665fe to
6e77169
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #325.
Stacked on #361 (per-server options for
NewLocalServersand thegorumstest.LocalServerschange), which must merge first.Summary
Servers configured with
WithPeerstrusted the node ID that a peer claimed in its stream metadata, and whoever answered at a peer's address.Any client that could reach a server could pose as a configured peer.
Under
WithStreamDedup, a forged claim for a lower ID also redirected the server's own calls to the attacker.This PR lets a server authenticate its peers:
WithPeerVerifier(verify PeerVerifier)checks the identity of each peer configured byWithPeerson every stream between the two servers, in both directions.codes.Unauthenticated.TLSPeerVerifier(identities map[ID]string)binds each node ID to a SAN of the peer's mTLS certificate that the TLS handshake verified against a trusted CA.An identity with a URI scheme (for example
spiffe://relab/node/2) matches only a URI SAN in the same canonical form; any other identity matches only a DNS SAN, without regard to case.Wildcard DNS SANs, IP SANs, the Common Name, and empty identities never match, and two node IDs with the same identity make it panic.
ServerContext.SenderIDis authenticated for every configured peer, with and without stream deduplication.Design and decisions: see the design comment on #325.
API Changes
PeerVerifier,WithPeerVerifier,TLSPeerVerifier.conn.NewInboundManagertakes the verifier;stream.OutboundOptionsgainsVerify.Documentation
SenderIDguarantee, client authentication with TLS settings and interceptors, and certificate and CA requirements forTLSPeerVerifier); "Finding the Sender of a Request" is updated.Known Limitation
A peer that a server refuses resends its pending calls on new streams until their contexts end, so those calls fail with the context's error rather than
codes.Unauthenticated, and the retries are not paced.This retry policy predates this PR; the PR documents it, and a fix follows in a stacked PR.
Testing
internal/stream, the inbound check and dial-side wiring ininternal/conn,TLSPeerVerifiermatching rules, construction errors, and the wiring in both directions with and without stream deduplication.internal/tests/tlswith a test CA issuing per-replica certificates: peers connect and handlers see the rightSenderID; a server presenting another node's certificate is refused by both checks; an impostor claiming a lower peer's ID cannot take over its stream; clients without certificates are still served.make testandmake integrationtestpass; the touched packages pass under-race;make modernizechanges nothing andmake goplscheckis clean.