Replace VNC JWT auth with a Noise_IK handshake bound to ACL-pushed pubkeys

This commit is contained in:
Viktor Liu
2026-05-21 17:36:15 +02:00
parent 9fd977c000
commit c0a3a2ee6d
36 changed files with 2014 additions and 1118 deletions
+2 -2
View File
@@ -849,7 +849,7 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) map
// GetPeerConnectionResources for a given peer
//
// This function returns the list of peers and firewall rules that are applicable to a given peer.
func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.Peer, validatedPeersMap map[string]struct{}, groupIDToUserIDs map[string][]string) ([]*nbpeer.Peer, []*FirewallRule, map[string]map[string]struct{}, map[string]map[string]struct{}, bool) {
func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.Peer, validatedPeersMap map[string]struct{}, groupIDToUserIDs map[string][]string) ([]*nbpeer.Peer, []*FirewallRule, map[string]map[string]struct{}, map[string]map[string]struct{}, []VNCSessionPubKey, bool) {
generateResources, getAccumulatedResources := a.connResourcesGenerator(ctx, peer)
ctxState := &peerConnResolveState{
authorizedUsers: make(map[string]map[string]struct{}),
@@ -869,7 +869,7 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.P
}
peers, fwRules := getAccumulatedResources()
return peers, fwRules, ctxState.authorizedUsers, ctxState.vncAuthorizedUsers, ctxState.sshEnabled
return peers, fwRules, ctxState.authorizedUsers, ctxState.vncAuthorizedUsers, ctxState.vncSessionPubKeys, ctxState.sshEnabled
}
func (a *Account) applyPolicyRule(
+1
View File
@@ -49,6 +49,7 @@ type NetworkMap struct {
ForwardingRules []*ForwardingRule
AuthorizedUsers map[string]map[string]struct{}
VNCAuthorizedUsers map[string]map[string]struct{}
VNCSessionPubKeys []VNCSessionPubKey
EnableSSH bool
}
@@ -167,6 +167,7 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
RoutesFirewallRules: append(networkResourcesFirewallRules, routesFirewallRules...),
AuthorizedUsers: connRes.authorizedUsers,
VNCAuthorizedUsers: connRes.vncAuthorizedUsers,
VNCSessionPubKeys: connRes.vncSessionPubKeys,
EnableSSH: connRes.sshEnabled,
}
}
@@ -177,6 +178,7 @@ type peerConnectionResult struct {
firewallRules []*FirewallRule
authorizedUsers map[string]map[string]struct{}
vncAuthorizedUsers map[string]map[string]struct{}
vncSessionPubKeys []VNCSessionPubKey
sshEnabled bool
}
@@ -210,6 +212,7 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) p
firewallRules: fwRules,
authorizedUsers: state.authorizedUsers,
vncAuthorizedUsers: state.vncAuthorizedUsers,
vncSessionPubKeys: state.vncSessionPubKeys,
sshEnabled: state.sshEnabled,
}
}
@@ -15,9 +15,21 @@ import (
type peerConnResolveState struct {
authorizedUsers map[string]map[string]struct{}
vncAuthorizedUsers map[string]map[string]struct{}
vncSessionPubKeys []VNCSessionPubKey
sshEnabled bool
}
// VNCSessionPubKey carries an ephemeral X25519 static public key the
// dashboard registered via temporary-access. The daemon uses it as the
// allowed-client side of a Noise_IK handshake; a successful handshake
// authenticates the connection as UserID.
type VNCSessionPubKey struct {
// PubKey is the base64-encoded 32-byte X25519 public key.
PubKey string
// UserID is the unhashed user identity the pubkey authenticates as.
UserID string
}
// ruleAuthCallbacks lets Account and NetworkMapComponents share the per-rule
// direction-and-auth logic while keeping their own context/state plumbing for
// authorized-user collection and allowed-user lookups.
@@ -57,6 +69,12 @@ func applyResolvedRuleToState(
return
}
cb.collectVNCUsers(rule, state.vncAuthorizedUsers)
if rule.SessionPubKey != "" && rule.AuthorizedUser != "" {
state.vncSessionPubKeys = append(state.vncSessionPubKeys, VNCSessionPubKey{
PubKey: rule.SessionPubKey,
UserID: rule.AuthorizedUser,
})
}
case policyRuleImpliesLegacySSH(rule) && targetPeerSSHEnabled:
if !peerInDestinations {
return
+9 -1
View File
@@ -88,6 +88,12 @@ type PolicyRule struct {
// AuthorizedUser is a list of userIDs that are authorized to access local resources via ssh
AuthorizedUser string
// SessionPubKey is the base64 Ed25519 public key the AuthorizedUser
// will sign session-binding challenges with. Set together with
// AuthorizedUser when the rule was created via temporary-access for
// a VNC scope; empty otherwise.
SessionPubKey string
}
// Copy returns a copy of a policy rule
@@ -109,6 +115,7 @@ func (pm *PolicyRule) Copy() *PolicyRule {
PortRanges: make([]RulePortRange, len(pm.PortRanges)),
AuthorizedGroups: make(map[string][]string, len(pm.AuthorizedGroups)),
AuthorizedUser: pm.AuthorizedUser,
SessionPubKey: pm.SessionPubKey,
}
copy(rule.Destinations, pm.Destinations)
copy(rule.Sources, pm.Sources)
@@ -136,7 +143,8 @@ func (pm *PolicyRule) Equal(other *PolicyRule) bool {
pm.Protocol != other.Protocol ||
pm.SourceResource != other.SourceResource ||
pm.DestinationResource != other.DestinationResource ||
pm.AuthorizedUser != other.AuthorizedUser {
pm.AuthorizedUser != other.AuthorizedUser ||
pm.SessionPubKey != other.SessionPubKey {
return false
}