Address CodeRabbit review on embedded VNC PR

This commit is contained in:
Viktor Liu
2026-05-24 18:52:57 +02:00
parent 4e3e3ce6d3
commit bf2fb2fd44
7 changed files with 58 additions and 14 deletions
+16 -4
View File
@@ -155,8 +155,10 @@ func (e *Engine) startVNCServer() error {
} }
// updateVNCServerAuth updates VNC fine-grained access control from management. // updateVNCServerAuth updates VNC fine-grained access control from management.
// A nil vncAuth clears all authorized users and session pubkeys so management
// can revoke access by omitting the field on the next sync.
func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) { func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) {
if vncAuth == nil || e.vncSrv == nil { if e.vncSrv == nil {
return return
} }
@@ -165,6 +167,11 @@ func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) {
return return
} }
if vncAuth == nil {
vncSrv.UpdateVNCAuth(&sshauth.Config{})
return
}
protoUsers := vncAuth.GetAuthorizedUsers() protoUsers := vncAuth.GetAuthorizedUsers()
authorizedUsers := make([]sshuserhash.UserIDHash, len(protoUsers)) authorizedUsers := make([]sshuserhash.UserIDHash, len(protoUsers))
for i, hash := range protoUsers { for i, hash := range protoUsers {
@@ -207,12 +214,17 @@ func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) {
} }
// GetVNCServerStatus returns whether the VNC server is running and the list // GetVNCServerStatus returns whether the VNC server is running and the list
// of active VNC sessions. // of active VNC sessions. The pointer is captured under syncMsgMux so a
// concurrent updateVNC/stopVNCServer cannot swap it out between the nil
// check and the ActiveSessions call.
func (e *Engine) GetVNCServerStatus() (enabled bool, sessions []vncserver.ActiveSessionInfo) { func (e *Engine) GetVNCServerStatus() (enabled bool, sessions []vncserver.ActiveSessionInfo) {
if e.vncSrv == nil { e.syncMsgMux.Lock()
vncSrv := e.vncSrv
e.syncMsgMux.Unlock()
if vncSrv == nil {
return false, nil return false, nil
} }
return true, e.vncSrv.ActiveSessions() return true, vncSrv.ActiveSessions()
} }
func (e *Engine) stopVNCServer() error { func (e *Engine) stopVNCServer() error {
+5 -1
View File
@@ -324,7 +324,11 @@ func spawnAgentInSession(sessionID uint32, socketPath, authToken string, jobHand
} }
if _, err := windows.ResumeThread(pi.Thread); err != nil { if _, err := windows.ResumeThread(pi.Thread); err != nil {
log.Warnf("resume agent main thread: %v", err) _ = windows.CloseHandle(pi.Thread)
_ = windows.TerminateProcess(pi.Process, 1)
_ = windows.CloseHandle(pi.Process)
_ = windows.CloseHandle(stderrRead)
return 0, fmt.Errorf("ResumeThread: %w", err)
} }
_ = windows.CloseHandle(pi.Thread) _ = windows.CloseHandle(pi.Thread)
+2
View File
@@ -243,6 +243,7 @@ func TestNoise_WrongServerStatic_HandshakeFails(t *testing.T) {
CipherSuite: vncNoiseSuite, CipherSuite: vncNoiseSuite,
Pattern: noise.HandshakeIK, Pattern: noise.HandshakeIK,
Initiator: true, Initiator: true,
Prologue: BuildVNCNoisePrologue(ModeAttach, ""),
StaticKeypair: clientKey, StaticKeypair: clientKey,
PeerStatic: bogusServerKey.Public, PeerStatic: bogusServerKey.Public,
}) })
@@ -382,6 +383,7 @@ func TestNoise_NoIdentityKey_FailsClosed(t *testing.T) {
CipherSuite: vncNoiseSuite, CipherSuite: vncNoiseSuite,
Pattern: noise.HandshakeIK, Pattern: noise.HandshakeIK,
Initiator: true, Initiator: true,
Prologue: BuildVNCNoisePrologue(ModeAttach, ""),
StaticKeypair: clientKey, StaticKeypair: clientKey,
PeerStatic: fakeServerKey.Public, PeerStatic: fakeServerKey.Public,
}) })
+13 -1
View File
@@ -180,6 +180,10 @@ type Server struct {
netstackNet *netstack.Net netstackNet *netstack.Net
// agentToken holds the raw token bytes for agent-mode auth. // agentToken holds the raw token bytes for agent-mode auth.
agentToken []byte agentToken []byte
// invalidAgentToken latches when AgentTokenHex was provided but failed
// to decode. Start refuses to listen in that case so the daemon never
// silently downgrades the local IPC hop to unauthenticated access.
invalidAgentToken bool
// identityKey is the daemon's static X25519 private key used in the // identityKey is the daemon's static X25519 private key used in the
// Noise_IK handshake. Nil disables the handshake. // Noise_IK handshake. Nil disables the handshake.
identityKey []byte identityKey []byte
@@ -356,6 +360,7 @@ func New(cfg Config) *Server {
if b, err := hex.DecodeString(cfg.AgentTokenHex); err == nil { if b, err := hex.DecodeString(cfg.AgentTokenHex); err == nil {
s.agentToken = b s.agentToken = b
} else { } else {
s.invalidAgentToken = true
s.log.Warnf("invalid agent token: %v", err) s.log.Warnf("invalid agent token: %v", err)
} }
} }
@@ -578,6 +583,9 @@ func (s *Server) Start(ctx context.Context, addr netip.AddrPort, network netip.P
if s.listener != nil { if s.listener != nil {
return fmt.Errorf("server already running") return fmt.Errorf("server already running")
} }
if s.invalidAgentToken {
return fmt.Errorf("invalid agent token configuration")
}
s.ctx, s.cancel = context.WithCancel(ctx) s.ctx, s.cancel = context.WithCancel(ctx)
s.vmgr = s.platformSessionManager() s.vmgr = s.platformSessionManager()
@@ -686,13 +694,17 @@ func (s *Server) acceptLoop() {
continue continue
} }
// Track before any early-reject path so a concurrent Stop's
// closeActiveSessions snapshot can never miss a just-accepted
// socket and let it survive shutdown.
s.trackConn(conn)
if !s.tryAcquireConnSlot() { if !s.tryAcquireConnSlot() {
s.untrackConn(conn)
s.log.Warnf("rejecting VNC connection from %s: %d concurrent connections in flight", conn.RemoteAddr(), maxConcurrentVNCConns) s.log.Warnf("rejecting VNC connection from %s: %d concurrent connections in flight", conn.RemoteAddr(), maxConcurrentVNCConns)
_ = conn.Close() _ = conn.Close()
continue continue
} }
enableTCPKeepAlive(conn, s.log) enableTCPKeepAlive(conn, s.log)
s.trackConn(conn)
go func(c net.Conn) { go func(c net.Conn) {
defer s.releaseConnSlot() defer s.releaseConnSlot()
defer s.untrackConn(c) defer s.untrackConn(c)
+11 -4
View File
@@ -54,6 +54,11 @@ func sasSecurityAttributes() (*windows.SecurityAttributes, error) {
type sasOriginalState struct { type sasOriginalState struct {
had bool // true if the value existed before we wrote had bool // true if the value existed before we wrote
value uint32 // its prior DWORD value, if had == true value uint32 // its prior DWORD value, if had == true
// captured stays true once we have read the genuine pre-enable state
// for the first time, so a second enableSoftwareSAS call (e.g. after
// a daemon restart with no intervening disable) cannot overwrite the
// snapshot with our own forced value.
captured bool
} }
var savedSASState sasOriginalState var savedSASState sasOriginalState
@@ -74,10 +79,12 @@ func enableSoftwareSAS() {
} }
defer key.Close() defer key.Close()
if prev, _, err := key.GetIntegerValue("SoftwareSASGeneration"); err == nil { if !savedSASState.captured {
savedSASState = sasOriginalState{had: true, value: uint32(prev)} if prev, _, err := key.GetIntegerValue("SoftwareSASGeneration"); err == nil {
} else { savedSASState = sasOriginalState{had: true, value: uint32(prev), captured: true}
savedSASState = sasOriginalState{had: false} } else {
savedSASState = sasOriginalState{had: false, captured: true}
}
} }
if err := key.SetDWordValue("SoftwareSASGeneration", 1); err != nil { if err := key.SetDWordValue("SoftwareSASGeneration", 1); err != nil {
@@ -243,7 +243,7 @@ func (c *NetworkMapComponents) resolveRuleEndpoint(
postureChecks []string, postureChecks []string,
) ([]*nbpeer.Peer, bool) { ) ([]*nbpeer.Peer, bool) {
if resource.Type == ResourceTypePeer && resource.ID != "" { if resource.Type == ResourceTypePeer && resource.ID != "" {
return c.getPeerFromResource(resource, peerID) return c.getPeerFromResource(resource, peerID, postureChecks)
} }
return c.getAllPeersFromGroups(groups, peerID, postureChecks) return c.getAllPeersFromGroups(groups, peerID, postureChecks)
} }
@@ -385,8 +385,11 @@ func (c *NetworkMapComponents) getUniquePeerIDsFromGroupsIDs(groups []string) []
return ids return ids
} }
func (c *NetworkMapComponents) getPeerFromResource(resource Resource, peerID string) ([]*nbpeer.Peer, bool) { func (c *NetworkMapComponents) getPeerFromResource(resource Resource, peerID string, postureChecks []string) ([]*nbpeer.Peer, bool) {
if resource.ID == peerID { if resource.ID == peerID {
if len(postureChecks) > 0 && !c.ValidatePostureChecksOnPeer(peerID, postureChecks) {
return []*nbpeer.Peer{}, false
}
return []*nbpeer.Peer{}, true return []*nbpeer.Peer{}, true
} }
@@ -394,6 +397,9 @@ func (c *NetworkMapComponents) getPeerFromResource(resource Resource, peerID str
if peerInfo == nil { if peerInfo == nil {
return []*nbpeer.Peer{}, false return []*nbpeer.Peer{}, false
} }
if len(postureChecks) > 0 && !c.ValidatePostureChecksOnPeer(resource.ID, postureChecks) {
return []*nbpeer.Peer{}, false
}
return []*nbpeer.Peer{peerInfo}, false return []*nbpeer.Peer{peerInfo}, false
} }
@@ -59,9 +59,10 @@ func applyResolvedRuleToState(
) { ) {
emitRuleDirections(rule, sourcePeers, destPeers, peerInSources, peerInDestinations, generateResources) emitRuleDirections(rule, sourcePeers, destPeers, peerInSources, peerInDestinations, generateResources)
receivingPeer := peerInDestinations || (rule.Bidirectional && peerInSources)
switch { switch {
case rule.Protocol == PolicyRuleProtocolNetbirdSSH: case rule.Protocol == PolicyRuleProtocolNetbirdSSH:
if !peerInDestinations { if !receivingPeer {
return return
} }
state.sshEnabled = true state.sshEnabled = true
@@ -69,7 +70,7 @@ func applyResolvedRuleToState(
case rule.Protocol == PolicyRuleProtocolNetbirdVNC: case rule.Protocol == PolicyRuleProtocolNetbirdVNC:
cb.handleVNCRule(rule, peerInSources, peerInDestinations, state) cb.handleVNCRule(rule, peerInSources, peerInDestinations, state)
case policyRuleImpliesLegacySSH(rule) && targetPeerSSHEnabled: case policyRuleImpliesLegacySSH(rule) && targetPeerSSHEnabled:
if !peerInDestinations { if !receivingPeer {
return return
} }
state.sshEnabled = true state.sshEnabled = true