Resolve VNC authorized users on the components path and fix uinput, X11 and macOS input gaps

This commit is contained in:
Viktor Liu
2026-08-29 09:04:02 +02:00
parent 84e88e0371
commit 8b719f0b4e
6 changed files with 194 additions and 36 deletions
@@ -7,7 +7,11 @@ import (
"github.com/netbirdio/netbird/shared/management/types"
)
type sshRequirements struct {
// authRequirements records the authorization inputs a peer's policies actually
// need, so the components carry the group-to-user mapping and the allowed-user
// set only when some rule resolves users from them. Both the SSH and the VNC
// marker protocols do.
type authRequirements struct {
neededGroupIDs map[string]struct{}
needAllowedUserIDs bool
}
@@ -57,12 +61,12 @@ func (nmd *NetworkMapData) GetPeerNetworkMapComponents(peerID string, peersCusto
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
}
relevantPeers, relevantGroups, relevantPolicies, relevantRoutes, sshReqs := nmd.getPeersGroupsPoliciesRoutes(peerID, peer.SSHEnabled, &components.PostureFailedPeers)
relevantPeers, relevantGroups, relevantPolicies, relevantRoutes, authReqs := nmd.getPeersGroupsPoliciesRoutes(peerID, peer.SSHEnabled, &components.PostureFailedPeers)
if len(sshReqs.neededGroupIDs) > 0 {
components.GroupIDToUserIDs = filterGroupIDToUserIDs(nmd.GroupIDToUserIDs, sshReqs.neededGroupIDs)
if len(authReqs.neededGroupIDs) > 0 {
components.GroupIDToUserIDs = filterGroupIDToUserIDs(nmd.GroupIDToUserIDs, authReqs.neededGroupIDs)
}
if sshReqs.needAllowedUserIDs {
if authReqs.needAllowedUserIDs {
components.AllowedUserIDs = nmd.getAllowedUserIDs()
}
@@ -204,12 +208,12 @@ func (nmd *NetworkMapData) getPeersGroupsPoliciesRoutes(
peerID string,
peerSSHEnabled bool,
postureFailedPeers *map[string]map[string]struct{},
) (map[string]*nmdata.Peer, map[string]*nmdata.Group, []*nmdata.Policy, []*nmdata.Route, sshRequirements) {
) (map[string]*nmdata.Peer, map[string]*nmdata.Group, []*nmdata.Policy, []*nmdata.Route, authRequirements) {
relevantPeerIDs := make(map[string]*nmdata.Peer, len(nmd.Peers)/4)
relevantGroupIDs := make(map[string]*nmdata.Group, len(nmd.Groups)/4)
relevantPolicies := make([]*nmdata.Policy, 0, len(nmd.Policies))
relevantRoutes := make([]*nmdata.Route, 0, len(nmd.Routes))
sshReqs := sshRequirements{neededGroupIDs: make(map[string]struct{})}
authReqs := authRequirements{neededGroupIDs: make(map[string]struct{})}
relevantPeerIDs[peerID] = nmd.Peers[peerID]
@@ -358,18 +362,25 @@ func (nmd *NetworkMapData) getPeersGroupsPoliciesRoutes(
}
}
if rule.Protocol == string(types.PolicyRuleProtocolNetbirdSSH) {
// Both marker protocols resolve authorized users the same way,
// so a VNC rule needs the same inputs an SSH rule does. Leaving
// VNC out here strips the group mapping and the allowed-user set
// from the components, and the rule then reaches the resolver
// with nobody authorized.
if rule.Protocol == string(types.PolicyRuleProtocolNetbirdSSH) ||
rule.Protocol == string(types.PolicyRuleProtocolNetbirdVNC) {
switch {
case len(rule.AuthorizedGroups) > 0:
for groupID := range rule.AuthorizedGroups {
sshReqs.neededGroupIDs[groupID] = struct{}{}
authReqs.neededGroupIDs[groupID] = struct{}{}
}
case rule.AuthorizedUser != "":
// Carries its own user; no lookup inputs needed.
default:
sshReqs.needAllowedUserIDs = true
authReqs.needAllowedUserIDs = true
}
} else if nmdata.PolicyRuleImpliesLegacySSH(rule) && peerSSHEnabled {
sshReqs.needAllowedUserIDs = true
authReqs.needAllowedUserIDs = true
}
}
}
@@ -378,7 +389,7 @@ func (nmd *NetworkMapData) getPeersGroupsPoliciesRoutes(
}
}
return relevantPeerIDs, relevantGroupIDs, relevantPolicies, relevantRoutes, sshReqs
return relevantPeerIDs, relevantGroupIDs, relevantPolicies, relevantRoutes, authReqs
}
func (nmd *NetworkMapData) getPeersFromGroups(groups []string, peerID string, sourcePostureChecksIDs []string,
@@ -968,6 +968,39 @@ func TestGetPeerNetworkMapComponents_SSHRequirements(t *testing.T) {
},
targetInSrc: true,
},
// VNC resolves authorized users exactly the way SSH does, so it needs
// the same inputs carried into the components. Leaving it out strips
// them and the rule reaches the resolver with nobody authorized.
{
name: "netbird-vnc with authorized groups",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdVNC)
r.AuthorizedGroups = map[string][]string{"g-auth": nil}
},
wantGroupsMap: map[string][]string{"g-auth": {"user-a"}},
},
{
name: "netbird-vnc default needs allowed users",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdVNC)
},
wantAllowed: true,
},
{
name: "netbird-vnc with authorized user carries its own",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdVNC)
r.AuthorizedUser = "user-1"
},
},
{
name: "netbird-vnc only counts on the destination side",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdVNC)
},
targetInSrc: true,
},
}
for _, tc := range cases {