mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-26 20:26:39 +00:00
Add embedded VNC server with JWT auth, DXGI capture, and dashboard integration
This commit is contained in:
@@ -184,6 +184,25 @@ func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJS
|
||||
PeerExposeGroups: req.Settings.PeerExposeGroups,
|
||||
}
|
||||
|
||||
if req.Settings.RecordingEnabled != nil {
|
||||
returnSettings.RecordingEnabled = *req.Settings.RecordingEnabled
|
||||
}
|
||||
if req.Settings.RecordingGroups != nil {
|
||||
returnSettings.RecordingGroups = *req.Settings.RecordingGroups
|
||||
}
|
||||
if req.Settings.RecordingMaxSessions != nil {
|
||||
returnSettings.RecordingMaxSessions = int32(*req.Settings.RecordingMaxSessions)
|
||||
}
|
||||
if req.Settings.RecordingMaxTotalSizeMb != nil {
|
||||
returnSettings.RecordingMaxTotalSizeMB = int64(*req.Settings.RecordingMaxTotalSizeMb)
|
||||
}
|
||||
if req.Settings.RecordingInputEnabled != nil {
|
||||
returnSettings.RecordingInputEnabled = req.Settings.RecordingInputEnabled
|
||||
}
|
||||
if req.Settings.RecordingEncryptionKey != nil {
|
||||
returnSettings.RecordingEncryptionKey = *req.Settings.RecordingEncryptionKey
|
||||
}
|
||||
|
||||
if req.Settings.Extra != nil {
|
||||
returnSettings.Extra = &types.ExtraSettings{
|
||||
PeerApprovalEnabled: req.Settings.Extra.PeerApprovalEnabled,
|
||||
@@ -348,6 +367,12 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
|
||||
RoutingPeerDnsResolutionEnabled: &settings.RoutingPeerDNSResolutionEnabled,
|
||||
PeerExposeEnabled: settings.PeerExposeEnabled,
|
||||
PeerExposeGroups: settings.PeerExposeGroups,
|
||||
RecordingEnabled: &settings.RecordingEnabled,
|
||||
RecordingGroups: &settings.RecordingGroups,
|
||||
RecordingMaxSessions: toIntPtr(settings.RecordingMaxSessions),
|
||||
RecordingMaxTotalSizeMb: &settings.RecordingMaxTotalSizeMB,
|
||||
RecordingInputEnabled: settings.RecordingInputEnabled,
|
||||
RecordingEncryptionKey: &settings.RecordingEncryptionKey,
|
||||
LazyConnectionEnabled: &settings.LazyConnectionEnabled,
|
||||
DnsDomain: &settings.DNSDomain,
|
||||
AutoUpdateVersion: &settings.AutoUpdateVersion,
|
||||
@@ -386,3 +411,8 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
|
||||
Onboarding: apiOnboarding,
|
||||
}
|
||||
}
|
||||
|
||||
func toIntPtr(v int32) *int {
|
||||
i := int(v)
|
||||
return &i
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request)
|
||||
PortRanges: []types.RulePortRange{portRange},
|
||||
}},
|
||||
}
|
||||
if protocol == types.PolicyRuleProtocolNetbirdSSH {
|
||||
if protocol == types.PolicyRuleProtocolNetbirdSSH || protocol == types.PolicyRuleProtocolNetbirdVNC {
|
||||
policy.Rules[0].AuthorizedUser = userAuth.UserId
|
||||
}
|
||||
|
||||
@@ -581,6 +581,8 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
||||
RosenpassEnabled: &peer.Meta.Flags.RosenpassEnabled,
|
||||
RosenpassPermissive: &peer.Meta.Flags.RosenpassPermissive,
|
||||
ServerSshAllowed: &peer.Meta.Flags.ServerSSHAllowed,
|
||||
ServerVncAllowed: &peer.Meta.Flags.ServerVNCAllowed,
|
||||
DisableVncAuth: &peer.Meta.Flags.DisableVNCAuth,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -635,6 +637,8 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
||||
RosenpassEnabled: &peer.Meta.Flags.RosenpassEnabled,
|
||||
RosenpassPermissive: &peer.Meta.Flags.RosenpassPermissive,
|
||||
ServerSshAllowed: &peer.Meta.Flags.ServerSSHAllowed,
|
||||
ServerVncAllowed: &peer.Meta.Flags.ServerVNCAllowed,
|
||||
DisableVncAuth: &peer.Meta.Flags.DisableVNCAuth,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,8 @@ func (h *handler) savePolicy(w http.ResponseWriter, r *http.Request, accountID s
|
||||
pr.Protocol = types.PolicyRuleProtocolICMP
|
||||
case api.PolicyRuleUpdateProtocolNetbirdSsh:
|
||||
pr.Protocol = types.PolicyRuleProtocolNetbirdSSH
|
||||
case api.PolicyRuleUpdateProtocolNetbirdVnc:
|
||||
pr.Protocol = types.PolicyRuleProtocolNetbirdVNC
|
||||
default:
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "unknown protocol type: %v", rule.Protocol), w)
|
||||
return
|
||||
@@ -256,7 +258,8 @@ func (h *handler) savePolicy(w http.ResponseWriter, r *http.Request, accountID s
|
||||
}
|
||||
}
|
||||
|
||||
if pr.Protocol == types.PolicyRuleProtocolNetbirdSSH && rule.AuthorizedGroups != nil && len(*rule.AuthorizedGroups) != 0 {
|
||||
isNetBirdService := pr.Protocol == types.PolicyRuleProtocolNetbirdSSH || pr.Protocol == types.PolicyRuleProtocolNetbirdVNC
|
||||
if isNetBirdService && rule.AuthorizedGroups != nil && len(*rule.AuthorizedGroups) != 0 {
|
||||
for _, sourceGroupID := range pr.Sources {
|
||||
_, ok := (*rule.AuthorizedGroups)[sourceGroupID]
|
||||
if !ok {
|
||||
|
||||
@@ -714,6 +714,7 @@ func Test_LoginPerformance(t *testing.T) {
|
||||
RosenpassEnabled: meta.GetFlags().GetRosenpassEnabled(),
|
||||
RosenpassPermissive: meta.GetFlags().GetRosenpassPermissive(),
|
||||
ServerSSHAllowed: meta.GetFlags().GetServerSSHAllowed(),
|
||||
ServerVNCAllowed: meta.GetFlags().GetServerVNCAllowed(),
|
||||
DisableClientRoutes: meta.GetFlags().GetDisableClientRoutes(),
|
||||
DisableServerRoutes: meta.GetFlags().GetDisableServerRoutes(),
|
||||
DisableDNS: meta.GetFlags().GetDisableDNS(),
|
||||
|
||||
@@ -93,7 +93,7 @@ func (am *DefaultAccountManager) getUserAccessiblePeers(ctx context.Context, acc
|
||||
|
||||
// fetch all the peers that have access to the user's peers
|
||||
for _, peer := range peers {
|
||||
aclPeers, _, _, _ := account.GetPeerConnectionResources(ctx, peer, approvedPeersMap, account.GetActiveGroupUsers())
|
||||
aclPeers, _, _, _, _ := account.GetPeerConnectionResources(ctx, peer, approvedPeersMap, account.GetActiveGroupUsers())
|
||||
for _, p := range aclPeers {
|
||||
peersMap[p.ID] = p
|
||||
}
|
||||
@@ -1277,7 +1277,7 @@ func (am *DefaultAccountManager) checkIfUserOwnsPeer(ctx context.Context, accoun
|
||||
}
|
||||
|
||||
for _, p := range userPeers {
|
||||
aclPeers, _, _, _ := account.GetPeerConnectionResources(ctx, p, approvedPeersMap, account.GetActiveGroupUsers())
|
||||
aclPeers, _, _, _, _ := account.GetPeerConnectionResources(ctx, p, approvedPeersMap, account.GetActiveGroupUsers())
|
||||
for _, aclPeer := range aclPeers {
|
||||
if aclPeer.ID == peer.ID {
|
||||
return peer, nil
|
||||
|
||||
@@ -108,6 +108,7 @@ type Flags struct {
|
||||
RosenpassEnabled bool
|
||||
RosenpassPermissive bool
|
||||
ServerSSHAllowed bool
|
||||
ServerVNCAllowed bool
|
||||
|
||||
DisableClientRoutes bool
|
||||
DisableServerRoutes bool
|
||||
@@ -117,6 +118,8 @@ type Flags struct {
|
||||
BlockInbound bool
|
||||
|
||||
LazyConnectionEnabled bool
|
||||
|
||||
DisableVNCAuth bool
|
||||
}
|
||||
|
||||
// PeerSystemMeta is a metadata of a Peer machine system
|
||||
@@ -363,11 +366,13 @@ func (f Flags) isEqual(other Flags) bool {
|
||||
return f.RosenpassEnabled == other.RosenpassEnabled &&
|
||||
f.RosenpassPermissive == other.RosenpassPermissive &&
|
||||
f.ServerSSHAllowed == other.ServerSSHAllowed &&
|
||||
f.ServerVNCAllowed == other.ServerVNCAllowed &&
|
||||
f.DisableClientRoutes == other.DisableClientRoutes &&
|
||||
f.DisableServerRoutes == other.DisableServerRoutes &&
|
||||
f.DisableDNS == other.DisableDNS &&
|
||||
f.DisableFirewall == other.DisableFirewall &&
|
||||
f.BlockLANAccess == other.BlockLANAccess &&
|
||||
f.BlockInbound == other.BlockInbound &&
|
||||
f.LazyConnectionEnabled == other.LazyConnectionEnabled
|
||||
f.LazyConnectionEnabled == other.LazyConnectionEnabled &&
|
||||
f.DisableVNCAuth == other.DisableVNCAuth
|
||||
}
|
||||
|
||||
@@ -246,14 +246,14 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
|
||||
t.Run("check that all peers get map", func(t *testing.T) {
|
||||
for _, p := range account.Peers {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), p, validatedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), p, validatedPeers, account.GetActiveGroupUsers())
|
||||
assert.GreaterOrEqual(t, len(peers), 1, "minimum number peers should present")
|
||||
assert.GreaterOrEqual(t, len(firewallRules), 1, "minimum number of firewall rules should present")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("check first peer map details", func(t *testing.T) {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], validatedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], validatedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 8)
|
||||
assert.Contains(t, peers, account.Peers["peerA"])
|
||||
assert.Contains(t, peers, account.Peers["peerC"])
|
||||
@@ -509,7 +509,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("check port ranges support for older peers", func(t *testing.T) {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerK"], validatedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerK"], validatedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 1)
|
||||
assert.Contains(t, peers, account.Peers["peerI"])
|
||||
|
||||
@@ -635,7 +635,7 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("check first peer map", func(t *testing.T) {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Contains(t, peers, account.Peers["peerC"])
|
||||
|
||||
expectedFirewallRules := []*types.FirewallRule{
|
||||
@@ -665,7 +665,7 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("check second peer map", func(t *testing.T) {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Contains(t, peers, account.Peers["peerB"])
|
||||
|
||||
expectedFirewallRules := []*types.FirewallRule{
|
||||
@@ -697,7 +697,7 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
||||
account.Policies[1].Rules[0].Bidirectional = false
|
||||
|
||||
t.Run("check first peer map directional only", func(t *testing.T) {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Contains(t, peers, account.Peers["peerC"])
|
||||
|
||||
expectedFirewallRules := []*types.FirewallRule{
|
||||
@@ -719,7 +719,7 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("check second peer map directional only", func(t *testing.T) {
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Contains(t, peers, account.Peers["peerB"])
|
||||
|
||||
expectedFirewallRules := []*types.FirewallRule{
|
||||
@@ -917,7 +917,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
t.Run("verify peer's network map with default group peer list", func(t *testing.T) {
|
||||
// peerB doesn't fulfill the NB posture check but is included in the destination group Swarm,
|
||||
// will establish a connection with all source peers satisfying the NB posture check.
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 4)
|
||||
assert.Len(t, firewallRules, 4)
|
||||
assert.Contains(t, peers, account.Peers["peerA"])
|
||||
@@ -927,7 +927,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
|
||||
// peerC satisfy the NB posture check, should establish connection to all destination group peer's
|
||||
// We expect a single permissive firewall rule which all outgoing connections
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, len(account.Groups["GroupSwarm"].Peers))
|
||||
assert.Len(t, firewallRules, 7)
|
||||
expectedFirewallRules := []*types.FirewallRule{
|
||||
@@ -992,7 +992,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
|
||||
// peerE doesn't fulfill the NB posture check and exists in only destination group Swarm,
|
||||
// all source group peers satisfying the NB posture check should establish connection
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerE"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerE"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 4)
|
||||
assert.Len(t, firewallRules, 4)
|
||||
assert.Contains(t, peers, account.Peers["peerA"])
|
||||
@@ -1002,7 +1002,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
|
||||
// peerI doesn't fulfill the OS version posture check and exists in only destination group Swarm,
|
||||
// all source group peers satisfying the NB posture check should establish connection
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerI"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerI"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 4)
|
||||
assert.Len(t, firewallRules, 4)
|
||||
assert.Contains(t, peers, account.Peers["peerA"])
|
||||
@@ -1017,19 +1017,19 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
|
||||
// peerB doesn't satisfy the NB posture check, and doesn't exist in destination group peer's
|
||||
// no connection should be established to any peer of destination group
|
||||
peers, firewallRules, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 0)
|
||||
assert.Len(t, firewallRules, 0)
|
||||
|
||||
// peerI doesn't satisfy the OS version posture check, and doesn't exist in destination group peer's
|
||||
// no connection should be established to any peer of destination group
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerI"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerI"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 0)
|
||||
assert.Len(t, firewallRules, 0)
|
||||
|
||||
// peerC satisfy the NB posture check, should establish connection to all destination group peer's
|
||||
// We expect a single permissive firewall rule which all outgoing connections
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, len(account.Groups["GroupSwarm"].Peers))
|
||||
assert.Len(t, firewallRules, len(account.Groups["GroupSwarm"].Peers))
|
||||
|
||||
@@ -1044,14 +1044,14 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
|
||||
// peerE doesn't fulfill the NB posture check and exists in only destination group Swarm,
|
||||
// all source group peers satisfying the NB posture check should establish connection
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerE"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerE"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 3)
|
||||
assert.Len(t, firewallRules, 3)
|
||||
assert.Contains(t, peers, account.Peers["peerA"])
|
||||
assert.Contains(t, peers, account.Peers["peerC"])
|
||||
assert.Contains(t, peers, account.Peers["peerD"])
|
||||
|
||||
peers, firewallRules, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerA"], approvedPeers, account.GetActiveGroupUsers())
|
||||
peers, firewallRules, _, _, _ = account.GetPeerConnectionResources(context.Background(), account.Peers["peerA"], approvedPeers, account.GetActiveGroupUsers())
|
||||
assert.Len(t, peers, 5)
|
||||
// assert peers from Group Swarm
|
||||
assert.Contains(t, peers, account.Peers["peerD"])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,19 @@ type Settings struct {
|
||||
// PeerExposeGroups list of peer group IDs allowed to expose services
|
||||
PeerExposeGroups []string `gorm:"serializer:json"`
|
||||
|
||||
// RecordingEnabled enables session recording for peers in RecordingGroups
|
||||
RecordingEnabled bool
|
||||
// RecordingGroups list of peer group IDs that have session recording enabled
|
||||
RecordingGroups []string `gorm:"serializer:json"`
|
||||
// RecordingMaxSessions limits the number of recording files kept per peer (0 = unlimited)
|
||||
RecordingMaxSessions int32
|
||||
// RecordingMaxTotalSizeMB limits total recording size in MB per peer (0 = unlimited)
|
||||
RecordingMaxTotalSizeMB int64
|
||||
// RecordingInputEnabled controls whether keyboard input is captured (default true)
|
||||
RecordingInputEnabled *bool
|
||||
// RecordingEncryptionKey is a base64-encoded public key for encrypting recordings
|
||||
RecordingEncryptionKey string
|
||||
|
||||
// Extra is a dictionary of Account settings
|
||||
Extra *ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
|
||||
|
||||
@@ -91,6 +104,12 @@ func (s *Settings) Copy() *Settings {
|
||||
RoutingPeerDNSResolutionEnabled: s.RoutingPeerDNSResolutionEnabled,
|
||||
PeerExposeEnabled: s.PeerExposeEnabled,
|
||||
PeerExposeGroups: slices.Clone(s.PeerExposeGroups),
|
||||
RecordingEnabled: s.RecordingEnabled,
|
||||
RecordingGroups: slices.Clone(s.RecordingGroups),
|
||||
RecordingMaxSessions: s.RecordingMaxSessions,
|
||||
RecordingMaxTotalSizeMB: s.RecordingMaxTotalSizeMB,
|
||||
RecordingInputEnabled: s.RecordingInputEnabled,
|
||||
RecordingEncryptionKey: s.RecordingEncryptionKey,
|
||||
LazyConnectionEnabled: s.LazyConnectionEnabled,
|
||||
DNSDomain: s.DNSDomain,
|
||||
NetworkRange: s.NetworkRange,
|
||||
|
||||
Reference in New Issue
Block a user