Add embedded VNC server with JWT auth, DXGI capture, and dashboard integration

This commit is contained in:
Viktor Liu
2026-04-14 12:31:00 +02:00
parent 13539543af
commit dfe1bba287
74 changed files with 8430 additions and 2011 deletions

View File

@@ -54,6 +54,9 @@ const (
// defaultSSHPortString defines the standard SSH port number as a string, commonly used for default SSH connections.
defaultSSHPortString = "22"
defaultSSHPortNumber = 22
// vncInternalPort is the internal port the VNC server listens on (behind DNAT from 5900).
vncInternalPort = 25900
)
type supportedFeatures struct {
@@ -304,7 +307,7 @@ func (a *Account) GetPeerNetworkMap(
peerGroups := a.GetPeerGroups(peerID)
aclPeers, firewallRules, authorizedUsers, enableSSH := a.GetPeerConnectionResources(ctx, peer, validatedPeersMap, groupIDToUserIDs)
aclPeers, firewallRules, authorizedUsers, vncAuthorizedUsers, enableSSH := a.GetPeerConnectionResources(ctx, peer, validatedPeersMap, groupIDToUserIDs)
// exclude expired peers
var peersToConnect []*nbpeer.Peer
var expiredPeers []*nbpeer.Peer
@@ -358,6 +361,7 @@ func (a *Account) GetPeerNetworkMap(
FirewallRules: firewallRules,
RoutesFirewallRules: slices.Concat(networkResourcesFirewallRules, routesFirewallRules),
AuthorizedUsers: authorizedUsers,
VNCAuthorizedUsers: vncAuthorizedUsers,
EnableSSH: enableSSH,
}
@@ -1042,9 +1046,10 @@ 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{}, 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{}, bool) {
generateResources, getAccumulatedResources := a.connResourcesGenerator(ctx, peer)
authorizedUsers := make(map[string]map[string]struct{}) // machine user to list of userIDs
authorizedUsers := make(map[string]map[string]struct{})
vncAuthorizedUsers := make(map[string]map[string]struct{})
sshEnabled := false
for _, policy := range a.Policies {
@@ -1091,36 +1096,9 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.P
if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdSSH {
sshEnabled = true
switch {
case len(rule.AuthorizedGroups) > 0:
for groupID, localUsers := range rule.AuthorizedGroups {
userIDs, ok := groupIDToUserIDs[groupID]
if !ok {
log.WithContext(ctx).Tracef("no user IDs found for group ID %s", groupID)
continue
}
if len(localUsers) == 0 {
localUsers = []string{auth.Wildcard}
}
for _, localUser := range localUsers {
if authorizedUsers[localUser] == nil {
authorizedUsers[localUser] = make(map[string]struct{})
}
for _, userID := range userIDs {
authorizedUsers[localUser][userID] = struct{}{}
}
}
}
case rule.AuthorizedUser != "":
if authorizedUsers[auth.Wildcard] == nil {
authorizedUsers[auth.Wildcard] = make(map[string]struct{})
}
authorizedUsers[auth.Wildcard][rule.AuthorizedUser] = struct{}{}
default:
authorizedUsers[auth.Wildcard] = a.getAllowedUserIDs()
}
a.collectAuthorizedUsers(ctx, rule, groupIDToUserIDs, authorizedUsers)
} else if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdVNC {
a.collectAuthorizedUsers(ctx, rule, groupIDToUserIDs, vncAuthorizedUsers)
} else if peerInDestinations && policyRuleImpliesLegacySSH(rule) && peer.SSHEnabled {
sshEnabled = true
authorizedUsers[auth.Wildcard] = a.getAllowedUserIDs()
@@ -1129,7 +1107,41 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.P
}
peers, fwRules := getAccumulatedResources()
return peers, fwRules, authorizedUsers, sshEnabled
return peers, fwRules, authorizedUsers, vncAuthorizedUsers, sshEnabled
}
// collectAuthorizedUsers populates the target map with authorized user mappings from the rule.
func (a *Account) collectAuthorizedUsers(ctx context.Context, rule *PolicyRule, groupIDToUserIDs map[string][]string, target map[string]map[string]struct{}) {
switch {
case len(rule.AuthorizedGroups) > 0:
for groupID, localUsers := range rule.AuthorizedGroups {
userIDs, ok := groupIDToUserIDs[groupID]
if !ok {
log.WithContext(ctx).Tracef("no user IDs found for group ID %s", groupID)
continue
}
if len(localUsers) == 0 {
localUsers = []string{auth.Wildcard}
}
for _, localUser := range localUsers {
if target[localUser] == nil {
target[localUser] = make(map[string]struct{})
}
for _, userID := range userIDs {
target[localUser][userID] = struct{}{}
}
}
}
case rule.AuthorizedUser != "":
if target[auth.Wildcard] == nil {
target[auth.Wildcard] = make(map[string]struct{})
}
target[auth.Wildcard][rule.AuthorizedUser] = struct{}{}
default:
target[auth.Wildcard] = a.getAllowedUserIDs()
}
}
func (a *Account) getAllowedUserIDs() map[string]struct{} {
@@ -1165,7 +1177,7 @@ func (a *Account) connResourcesGenerator(ctx context.Context, targetPeer *nbpeer
}
protocol := rule.Protocol
if protocol == PolicyRuleProtocolNetbirdSSH {
if protocol == PolicyRuleProtocolNetbirdSSH || protocol == PolicyRuleProtocolNetbirdVNC {
protocol = PolicyRuleProtocolTCP
}

View File

@@ -39,6 +39,7 @@ type NetworkMap struct {
RoutesFirewallRules []*RouteFirewallRule
ForwardingRules []*ForwardingRule
AuthorizedUsers map[string]map[string]struct{}
VNCAuthorizedUsers map[string]map[string]struct{}
EnableSSH bool
}

View File

@@ -112,7 +112,8 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
peerGroups := c.GetPeerGroups(targetPeerID)
aclPeers, firewallRules, authorizedUsers, sshEnabled := c.getPeerConnectionResources(targetPeerID)
connRes := c.getPeerConnectionResources(targetPeerID)
aclPeers := connRes.peers
peersToConnect, expiredPeers := c.filterPeersByLoginExpiration(aclPeers)
@@ -161,21 +162,32 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
Routes: append(networkResourcesRoutes, routesUpdate...),
DNSConfig: dnsUpdate,
OfflinePeers: expiredPeers,
FirewallRules: firewallRules,
FirewallRules: connRes.firewallRules,
RoutesFirewallRules: append(networkResourcesFirewallRules, routesFirewallRules...),
AuthorizedUsers: authorizedUsers,
EnableSSH: sshEnabled,
AuthorizedUsers: connRes.authorizedUsers,
VNCAuthorizedUsers: connRes.vncAuthorizedUsers,
EnableSSH: connRes.sshEnabled,
}
}
func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) ([]*nbpeer.Peer, []*FirewallRule, map[string]map[string]struct{}, bool) {
// peerConnectionResult holds the output of getPeerConnectionResources.
type peerConnectionResult struct {
peers []*nbpeer.Peer
firewallRules []*FirewallRule
authorizedUsers map[string]map[string]struct{}
vncAuthorizedUsers map[string]map[string]struct{}
sshEnabled bool
}
func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) peerConnectionResult {
targetPeer := c.GetPeerInfo(targetPeerID)
if targetPeer == nil {
return nil, nil, nil, false
return peerConnectionResult{}
}
generateResources, getAccumulatedResources := c.connResourcesGenerator(targetPeer)
authorizedUsers := make(map[string]map[string]struct{})
vncAuthorizedUsers := make(map[string]map[string]struct{})
sshEnabled := false
for _, policy := range c.Policies {
@@ -222,35 +234,9 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) (
if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdSSH {
sshEnabled = true
switch {
case len(rule.AuthorizedGroups) > 0:
for groupID, localUsers := range rule.AuthorizedGroups {
userIDs, ok := c.GroupIDToUserIDs[groupID]
if !ok {
continue
}
if len(localUsers) == 0 {
localUsers = []string{auth.Wildcard}
}
for _, localUser := range localUsers {
if authorizedUsers[localUser] == nil {
authorizedUsers[localUser] = make(map[string]struct{})
}
for _, userID := range userIDs {
authorizedUsers[localUser][userID] = struct{}{}
}
}
}
case rule.AuthorizedUser != "":
if authorizedUsers[auth.Wildcard] == nil {
authorizedUsers[auth.Wildcard] = make(map[string]struct{})
}
authorizedUsers[auth.Wildcard][rule.AuthorizedUser] = struct{}{}
default:
authorizedUsers[auth.Wildcard] = c.getAllowedUserIDs()
}
c.collectAuthorizedUsers(rule, authorizedUsers)
} else if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdVNC {
c.collectAuthorizedUsers(rule, vncAuthorizedUsers)
} else if peerInDestinations && policyRuleImpliesLegacySSH(rule) && targetPeer.SSHEnabled {
sshEnabled = true
authorizedUsers[auth.Wildcard] = c.getAllowedUserIDs()
@@ -259,7 +245,46 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) (
}
peers, fwRules := getAccumulatedResources()
return peers, fwRules, authorizedUsers, sshEnabled
return peerConnectionResult{
peers: peers,
firewallRules: fwRules,
authorizedUsers: authorizedUsers,
vncAuthorizedUsers: vncAuthorizedUsers,
sshEnabled: sshEnabled,
}
}
// collectAuthorizedUsers populates the target map with authorized user mappings from the rule.
func (c *NetworkMapComponents) collectAuthorizedUsers(rule *PolicyRule, target map[string]map[string]struct{}) {
switch {
case len(rule.AuthorizedGroups) > 0:
for groupID, localUsers := range rule.AuthorizedGroups {
userIDs, ok := c.GroupIDToUserIDs[groupID]
if !ok {
continue
}
if len(localUsers) == 0 {
localUsers = []string{auth.Wildcard}
}
for _, localUser := range localUsers {
if target[localUser] == nil {
target[localUser] = make(map[string]struct{})
}
for _, userID := range userIDs {
target[localUser][userID] = struct{}{}
}
}
}
case rule.AuthorizedUser != "":
if target[auth.Wildcard] == nil {
target[auth.Wildcard] = make(map[string]struct{})
}
target[auth.Wildcard][rule.AuthorizedUser] = struct{}{}
default:
target[auth.Wildcard] = c.getAllowedUserIDs()
}
}
func (c *NetworkMapComponents) getAllowedUserIDs() map[string]struct{} {
@@ -279,7 +304,7 @@ func (c *NetworkMapComponents) connResourcesGenerator(targetPeer *nbpeer.Peer) (
return func(rule *PolicyRule, groupPeers []*nbpeer.Peer, direction int) {
protocol := rule.Protocol
if protocol == PolicyRuleProtocolNetbirdSSH {
if protocol == PolicyRuleProtocolNetbirdSSH || protocol == PolicyRuleProtocolNetbirdVNC {
protocol = PolicyRuleProtocolTCP
}
@@ -526,7 +551,6 @@ func (c *NetworkMapComponents) getRoutingPeerRoutes(peerID string) (enabledRoute
return enabledRoutes, disabledRoutes
}
func (c *NetworkMapComponents) filterRoutesByGroups(routes []*route.Route, groupListMap LookupMap) []*route.Route {
var filteredRoutes []*route.Route
for _, r := range routes {

View File

@@ -1019,7 +1019,7 @@ func (b *NetworkMapBuilder) buildAllowedUserIDs(account *Account) map[string]str
}
func firewallRuleProtocol(protocol PolicyRuleProtocolType) string {
if protocol == PolicyRuleProtocolNetbirdSSH {
if protocol == PolicyRuleProtocolNetbirdSSH || protocol == PolicyRuleProtocolNetbirdVNC {
return string(PolicyRuleProtocolTCP)
}
return string(protocol)

View File

@@ -25,6 +25,8 @@ const (
PolicyRuleProtocolICMP = PolicyRuleProtocolType("icmp")
// PolicyRuleProtocolNetbirdSSH type of traffic
PolicyRuleProtocolNetbirdSSH = PolicyRuleProtocolType("netbird-ssh")
// PolicyRuleProtocolNetbirdVNC type of traffic
PolicyRuleProtocolNetbirdVNC = PolicyRuleProtocolType("netbird-vnc")
)
const (
@@ -171,6 +173,8 @@ func ParseRuleString(rule string) (PolicyRuleProtocolType, RulePortRange, error)
return "", RulePortRange{}, errors.New("icmp does not accept ports; use 'icmp' without '/…'")
case "netbird-ssh":
return PolicyRuleProtocolNetbirdSSH, RulePortRange{Start: nativeSSHPortNumber, End: nativeSSHPortNumber}, nil
case "netbird-vnc":
return PolicyRuleProtocolNetbirdVNC, RulePortRange{Start: vncInternalPort, End: vncInternalPort}, nil
default:
return "", RulePortRange{}, fmt.Errorf("invalid protocol: %q", protoStr)
}