From a7e55cc5e399f85b7f5c131fb1828781fd3549a8 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 11:44:08 +0100 Subject: [PATCH] add signatures and frame for peer approval --- go.mod | 2 +- go.sum | 14 +++++++-- management/server/account.go | 35 ++++++++++++++++++++-- management/server/activity/codes.go | 12 ++++++++ management/server/http/accounts_handler.go | 26 +++++++++++----- management/server/http/api/openapi.yml | 17 +++++++++++ management/server/http/api/types.gen.go | 19 ++++++++++++ management/server/http/peers_handler.go | 7 +++++ management/server/peer.go | 24 +++++++++++++-- 9 files changed, 140 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index c6c8221e1..d4b76653e 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 diff --git a/go.sum b/go.sum index 84b8816e9..f316fa058 100644 --- a/go.sum +++ b/go.sum @@ -495,8 +495,18 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 h1:zhe8qseauBuYOS910jpl5sv8Tb+36zxQPXrwYXqll0g= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944 h1:n7o2/NCZzn0+73LGdl/VMh7DOTdWZ98le2woeZ4HlB0= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a h1:6aipBr80s0GPKO9Wl+f5TUOSwebQ91uX2thk9tElyqc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127165733-0d0650c84683 h1:jJMO8KL2u3ok5VtGgZtFpuVK0GBEXXIb84idlmqGe68= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127165733-0d0650c84683/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127170523-a11fee39970a h1:DIe9xdl6RcxeZFu5Pr3OPC8SHM6yadF212W3LJlzfhQ= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127170523-a11fee39970a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a h1:iMEPP7MC3/7DTs/BNMshsBoviG3yWSTRbIzXKdrUwHw= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 h1:PTQ2SSijkoN8Qkctq9oLzEdzCLLv7WoD2dqScmpb15o= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= diff --git a/management/server/account.go b/management/server/account.go index bea1886a9..a08416e2c 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -17,6 +17,7 @@ import ( "github.com/eko/gocache/v3/cache" cacheStore "github.com/eko/gocache/v3/store" + "github.com/netbirdio/management-integrations/integrations" gocache "github.com/patrickmn/go-cache" "github.com/rs/xid" log "github.com/sirupsen/logrus" @@ -159,12 +160,12 @@ type Settings struct { // JWTGroupsClaimName from which we extract groups name to add it to account groups JWTGroupsClaimName string - // Extra contains additional settings that are not supported in the open-source version + // Extra is a dictionary of Account settings Extra *ExtraSettings } type ExtraSettings struct { - // PeerApprovalEnabled enables or disables the need for peers to be approved by an administrator + // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator PeerApprovalEnabled bool } @@ -352,7 +353,20 @@ func (a *Account) GetGroup(groupID string) *Group { // GetPeerNetworkMap returns a group by ID if exists, nil otherwise func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { + peer := a.Peers[peerID] + if peer == nil { + return &NetworkMap{ + Network: a.Network.Copy(), + } + } + validatedPeers := integrations.ValidatePeers([]*Peer{peer}, a) + if len(validatedPeers) == 0 { + return &NetworkMap{ + Network: a.Network.Copy(), + } + } aclPeers, firewallRules := a.getPeerConnectionResources(peerID) + aclPeers = integrations.ValidatePeers(aclPeers, a) // exclude expired peers var peersToConnect []*Peer var expiredPeers []*Peer @@ -879,6 +893,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } + err = integrations.ValidateExtraSettings(newSettings.Extra, account, am) + if err != nil { + return nil, err + } + user, err := account.FindUser(userID) if err != nil { return nil, err @@ -905,6 +924,18 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, am.checkAndSchedulePeerLoginExpiration(account) } + // if oldSettings.PeerApprovalEnabled != newSettings.PeerApprovalEnabled { + // event := activity.AccountPeerApprovalEnabled + // if !newSettings.PeerApprovalEnabled { + // event = activity.AccountPeerApprovalDisabled + // } + // am.StoreEvent(userID, accountID, accountID, event, nil) + // + // for _, peer := range account.Peers { + // peer.Status.RequiresApproval = false + // } + // } + updatedAccount := account.UpdateSettings(newSettings) err = am.Store.SaveAccount(account) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 7bad9f627..bcd788b6f 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -120,6 +120,14 @@ const ( IntegrationUpdated // IntegrationDeleted indicates that the user deleted an integration IntegrationDeleted + // AccountPeerApprovalEnabled indicates that the user enabled peer approval for the account + AccountPeerApprovalEnabled + // AccountPeerApprovalDisabled indicates that the user disabled peer approval for the account + AccountPeerApprovalDisabled + // PeerApproved indicates that the peer has been approved + PeerApproved + // PeerApprovalRevoked indicates that the peer approval has been revoked + PeerApprovalRevoked ) var activityMap = map[Activity]Code{ @@ -178,6 +186,10 @@ var activityMap = map[Activity]Code{ IntegrationCreated: {"Integration created", "integration.create"}, IntegrationUpdated: {"Integration updated", "integration.update"}, IntegrationDeleted: {"Integration deleted", "integration.delete"}, + AccountPeerApprovalEnabled: {"Account peer approval enabled", "account.setting.peer.approval.enable"}, + AccountPeerApprovalDisabled: {"Account peer approval disabled", "account.setting.peer.approval.disable"}, + PeerApproved: {"Peer approved", "peer.approve"}, + PeerApprovalRevoked: {"Peer approval revoked", "peer.approval.revoke"}, } // StringCode returns a string code of the activity diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index a5d7a9501..8c356bb7a 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -77,6 +77,10 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)), } + if req.Settings.Extra != nil { + settings.Extra = &server.ExtraSettings{PeerApprovalEnabled: *req.Settings.Extra.PeerApprovalEnabled} + } + if req.Settings.JwtGroupsEnabled != nil { settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled } @@ -99,14 +103,20 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) } func toAccountResponse(account *server.Account) *api.Account { + settings := api.AccountSettings{ + PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), + PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled, + GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, + JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, + JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, + } + + if account.Settings.Extra != nil { + settings.Extra = &api.AccountExtraSettings{PeerApprovalEnabled: &account.Settings.Extra.PeerApprovalEnabled} + } + return &api.Account{ - Id: account.Id, - Settings: api.AccountSettings{ - PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), - PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled, - GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, - JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, - JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, - }, + Id: account.Id, + Settings: settings, } } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 64e97426a..6e98f4c7e 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -66,9 +66,18 @@ components: description: Name of the claim from which we extract groups names to add it to account groups. type: string example: "roles" + extra: + $ref: '#/components/schemas/AccountExtraSettings' required: - peer_login_expiration_enabled - peer_login_expiration + AccountExtraSettings: + type: object + properties: + peer_approval_enabled: + description: Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + type: boolean + example: true AccountRequest: type: object properties: @@ -213,6 +222,10 @@ components: login_expiration_enabled: type: boolean example: false + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true required: - name - ssh_enabled @@ -281,6 +294,10 @@ components: type: string format: date-time example: 2023-05-05T09:00:35.477782Z + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true required: - ip - connected diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index ea70b7f3a..5298e887b 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -142,6 +142,12 @@ type Account struct { Settings AccountSettings `json:"settings"` } +// AccountExtraSettings defines model for AccountExtraSettings. +type AccountExtraSettings struct { + // PeerApprovalEnabled Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + PeerApprovalEnabled *bool `json:"peer_approval_enabled,omitempty"` +} + // AccountRequest defines model for AccountRequest. type AccountRequest struct { Settings AccountSettings `json:"settings"` @@ -149,6 +155,8 @@ type AccountRequest struct { // AccountSettings defines model for AccountSettings. type AccountSettings struct { + Extra *AccountExtraSettings `json:"extra,omitempty"` + // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` @@ -323,6 +331,9 @@ type Peer struct { // AccessiblePeers List of accessible peers AccessiblePeers []AccessiblePeer `json:"accessible_peers"` + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -374,6 +385,9 @@ type Peer struct { // PeerBase defines model for PeerBase. type PeerBase struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -428,6 +442,9 @@ type PeerBatch struct { // AccessiblePeersCount Number of accessible peers AccessiblePeersCount int `json:"accessible_peers_count"` + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -488,6 +505,8 @@ type PeerMinimum struct { // PeerRequest defines model for PeerRequest. type PeerRequest struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` LoginExpirationEnabled bool `json:"login_expiration_enabled"` Name string `json:"name"` SshEnabled bool `json:"ssh_enabled"` diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 3d0d735ec..c143a8076 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -81,6 +81,11 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, LoginExpirationEnabled: req.LoginExpirationEnabled} + + if req.ApprovalRequired != nil { + update.Status = &server.PeerStatus{RequiresApproval: *req.ApprovalRequired} + } + peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) if err != nil { util.WriteError(err, w) @@ -248,6 +253,7 @@ func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsD LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeers: accessiblePeer, + Approved: &peer.Status.Approved, } } @@ -270,6 +276,7 @@ func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dn LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeersCount: accessiblePeersCount, + Approved: peer.Status.Approved, } } diff --git a/management/server/peer.go b/management/server/peer.go index 27912f1f4..6268ae794 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/netbirdio/management-integrations/integrations" "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" @@ -46,6 +47,8 @@ type PeerStatus struct { Connected bool // LoginExpired LoginExpired bool + // RequiresApproval indicates whether peer requires approval or not + RequiresApproval bool } // PeerSync used as a data object between the gRPC API and AccountManager on Sync request. @@ -192,9 +195,10 @@ func (p *Peer) EventMeta(dnsDomain string) map[string]any { // Copy PeerStatus func (p *PeerStatus) Copy() *PeerStatus { return &PeerStatus{ - LastSeen: p.LastSeen, - Connected: p.Connected, - LoginExpired: p.LoginExpired, + LastSeen: p.LastSeen, + Connected: p.Connected, + LoginExpired: p.LoginExpired, + RequiresApproval: p.RequiresApproval, } } @@ -304,6 +308,11 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } + update, err = integrations.ValidatePeersUpdateRequest(update, peer, am) + if err != nil { + return nil, err + } + if peer.SSHEnabled != update.SSHEnabled { peer.SSHEnabled = update.SSHEnabled event := activity.PeerSSHEnabled @@ -562,6 +571,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* Ephemeral: ephemeral, } + if account.Settings.Extra.PeerApprovalEnabled { + newPeer.Status.RequiresApproval = true + } + // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { @@ -632,6 +645,11 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } + validatedPeers := integrations.ValidatePeers([]*Peer{peer}, account) + if len(validatedPeers) == 0 { + return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") + } + err = checkIfPeerOwnerIsBlocked(peer, account) if err != nil { return nil, nil, err