diff --git a/management/internals/shared/grpc/components_encoder.go b/management/internals/shared/grpc/components_encoder.go index 4dcd69fe4..3587b5e16 100644 --- a/management/internals/shared/grpc/components_encoder.go +++ b/management/internals/shared/grpc/components_encoder.go @@ -57,14 +57,16 @@ func EncodeNetworkMapEnvelope(in ComponentsEnvelopeInput) *proto.NetworkMapEnvel // Network populated). The receiver gets an envelope it can decode // without crashing; AccountSettings stays non-nil so client-side // dereferences are safe. - if c == nil { + if c == nil || c.IsEmpty() { // Match legacy missing-peer minimum: a NetworkMap with only Network // populated. The receiver gets enough to bootstrap (Network - // identifier, dns_domain, account_settings) and nothing else. + // identifier, dns_domain, account_settings) and the peer itself. return &proto.NetworkMapEnvelope{ Payload: &proto.NetworkMapEnvelope_Full{ Full: &proto.NetworkMapComponentsFull{ - PeerConfig: in.PeerConfig, + PeerConfig: in.PeerConfig, + // components.Peers always contains the target peer + Peers: []*proto.PeerCompact{toPeerCompact(c.Peers[c.PeerID])}, DnsDomain: in.DNSDomain, DnsForwarderPort: in.DNSForwarderPort, UserIdClaim: in.UserIDClaim, diff --git a/management/internals/shared/grpc/components_envelope_response.go b/management/internals/shared/grpc/components_envelope_response.go index 95fb87271..89f957eaf 100644 --- a/management/internals/shared/grpc/components_envelope_response.go +++ b/management/internals/shared/grpc/components_envelope_response.go @@ -25,8 +25,6 @@ import ( // computed by the client from the envelope's GroupIDToUserIDs / AllowedUserIDs // inside Calculate(), so the SshConfig.SshEnabled bit may flip true on the // client even though the server-side PeerConfig reports false. -// -// components parameter is expected to be !nil func ToComponentSyncResponse( ctx context.Context, config *nbconfig.Config, @@ -44,6 +42,12 @@ func ToComponentSyncResponse( peerGroups []string, dnsFwdPort int64, ) *proto.SyncResponse { + // + // 'component' parameter is expected to never be nil + // 'peer' parameter is expected to never be nil + // + // TODO (dmitri) consider using invariants? + // enableSSH := computeSSHEnabledForPeer(components, peer) peerConfig := toPeerConfig(peer, components.Network, dnsName, settings, httpConfig, deviceFlowConfig, enableSSH) diff --git a/management/server/types/account_components.go b/management/server/types/account_components.go index e2fc9b94c..fa7f93b01 100644 --- a/management/server/types/account_components.go +++ b/management/server/types/account_components.go @@ -104,9 +104,20 @@ func (a *Account) GetPeerNetworkMapComponents( routers map[string]map[string]*routerTypes.NetworkRouter, groupIDToUserIDs map[string][]string, ) *NetworkMapComponents { - peer := a.Peers[peerID] + // this can never happen, things are very wrong if it did + // TODO (dmitri) maybe consider using invariants? if peer == nil { + log.WithField("peer id", peerID).Error("NetworkMapComponents are computed for a peer missing from the account") + return EmptyNetworkMapComponents(&NetworkMapComponents{ + PeerID: peerID, + Network: a.Network.Copy(), + // must include the target peer as it's required on the client + Peers: map[string]*nbpeer.Peer{peerID: peer}, + }) + } + + if _, ok := validatedPeersMap[peerID]; !ok { // Mirror legacy graceful-degrade: GetPeerNetworkMapFromComponents // returns &NetworkMap{Network: a.Network.Copy()} when components is // nil. Match that floor so the receiving client always sees the @@ -114,13 +125,8 @@ func (a *Account) GetPeerNetworkMapComponents( return EmptyNetworkMapComponents(&NetworkMapComponents{ PeerID: peerID, Network: a.Network.Copy(), - }) - } - - if _, ok := validatedPeersMap[peerID]; !ok { - return EmptyNetworkMapComponents(&NetworkMapComponents{ - PeerID: peerID, - Network: a.Network.Copy(), + // must include the target peer as it's required on the client + Peers: map[string]*nbpeer.Peer{peerID: peer}, }) } @@ -157,6 +163,7 @@ func (a *Account) GetPeerNetworkMapComponents( components.DNSSettings = &a.DNSSettings + // relevantPeers always contains the target peer (peerID) relevantPeers, relevantGroups, relevantPolicies, relevantRoutes, sshReqs := a.getPeersGroupsPoliciesRoutes(ctx, peerID, peer.SSHEnabled, validatedPeersMap, &components.PostureFailedPeers) if len(sshReqs.neededGroupIDs) > 0 {