diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index cba47326f..40fd86f1d 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -13,6 +13,7 @@ import ( "google.golang.org/grpc" + "github.com/netbirdio/management-integrations/integrations" clientProto "github.com/netbirdio/netbird/client/proto" client "github.com/netbirdio/netbird/client/server" mgmtProto "github.com/netbirdio/netbird/management/proto" @@ -78,7 +79,8 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste if err != nil { return nil, nil } - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false) + iv := integrations.NewIntegratedApproval() + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, iv) if err != nil { t.Fatal(err) } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index ee0380db7..ffcb8fabb 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/keepalive" + "github.com/netbirdio/management-integrations/integrations" "github.com/netbirdio/netbird/client/internal/dns" "github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/routemanager" @@ -1050,7 +1051,7 @@ func startManagement(dataDir string) (*grpc.Server, string, error) { if err != nil { return nil, "", err } - accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false) + accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, integrations.NewIntegratedApproval()) if err != nil { return nil, "", err } diff --git a/go.mod b/go.mod index d435e4eb8..79862cab2 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 github.com/google/gopacket v1.1.19 + github.com/google/martian/v3 v3.0.0 github.com/google/nftables v0.0.0-20220808154552-2eca00135732 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 @@ -57,8 +58,8 @@ 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/additions v0.0.0-20240212121739-8ea8c89a4552 - github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552 + github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450 + github.com/netbirdio/management-integrations/integrations v0.0.0-20240226151841-2e4fe2407450 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/oschwald/maxminddb-golang v1.12.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index cc7a52ed6..15c8db563 100644 --- a/go.sum +++ b/go.sum @@ -255,6 +255,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A= github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc= @@ -376,10 +377,10 @@ 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/additions v0.0.0-20240212121739-8ea8c89a4552 h1:yzcQKizAK9YufCHMMCIsr467Dw/OU/4xyHbWizGb1E4= -github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552 h1:OFlzVZtkXCoJsfDKrMigFpuad8ZXTm8epq6x27K0irA= -github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= +github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450 h1:qA4S5YFt6/s0kQ8wKLjq8faLxuBSte1WzjWfmQmyJTU= +github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/integrations v0.0.0-20240226151841-2e4fe2407450 h1:jEepZRVo60IN+us4E8BvNUbasoViFwqJ7exKix4aQyc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20240226151841-2e4fe2407450/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= 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/client/client_test.go b/management/client/client_test.go index 81dd7bc8d..b97d7862d 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -2,6 +2,7 @@ package client import ( "context" + "github.com/netbirdio/management-integrations/integrations" "net" "path/filepath" "sync" @@ -60,7 +61,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { peersUpdateManager := mgmt.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false) + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, integrations.NewIntegratedApproval()) if err != nil { t.Fatal(err) } diff --git a/management/cmd/management.go b/management/cmd/management.go index 002cf36d8..ac9503401 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -171,8 +171,10 @@ var ( log.Infof("geo location service has been initialized from %s", config.Datadir) } + integratedPeerValidator := integrations.NewIntegratedApproval() + accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, - dnsDomain, eventStore, geo, userDeleteFromIDPEnabled) + dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator) if err != nil { return fmt.Errorf("failed to build default manager: %v", err) } @@ -245,7 +247,7 @@ var ( ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() - httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg) + httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg, integratedPeerValidator) if err != nil { return fmt.Errorf("failed creating HTTP API handler: %v", err) } diff --git a/management/server/account.go b/management/server/account.go index 1d9fd5d48..31b93df74 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -22,13 +22,13 @@ import ( log "github.com/sirupsen/logrus" "github.com/netbirdio/management-integrations/additions" - "github.com/netbirdio/netbird/base62" nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/account" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/idp" + "github.com/netbirdio/netbird/management/server/integrated_approval" "github.com/netbirdio/netbird/management/server/jwtclaims" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/posture" @@ -126,6 +126,8 @@ type AccountManager interface { SavePostureChecks(accountID, userID string, postureChecks *posture.Checks) error DeletePostureChecks(accountID, postureChecksID, userID string) error ListPostureChecks(accountID, userID string) ([]*posture.Checks, error) + UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error + GroupValidation(accountId string, groups []string) (bool, error) } type DefaultAccountManager struct { @@ -154,6 +156,8 @@ type DefaultAccountManager struct { // userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account userDeleteFromIDPEnabled bool + + integratedPeerValidator integrated_approval.IntegratedApproval } // Settings represents Account settings structure that can be modified via API and Dashboard @@ -380,19 +384,21 @@ 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 { +func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string, integratedValidator integrated_approval.IntegratedApproval) *NetworkMap { peer := a.Peers[peerID] if peer == nil { return &NetworkMap{ Network: a.Network.Copy(), } } + validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer}) if len(validatedPeers) == 0 { return &NetworkMap{ Network: a.Network.Copy(), } } + aclPeers, firewallRules := a.getPeerConnectionResources(peerID) // exclude expired peers var peersToConnect []*nbpeer.Peer @@ -571,6 +577,20 @@ func (a *Account) FindSetupKey(setupKey string) (*SetupKey, error) { return key, nil } +// GetPeerGroupsList return with the list of groups ID. +func (a *Account) GetPeerGroupsList(peerID string) []string { + var grps []string + for groupID, group := range a.Groups { + for _, id := range group.Peers { + if id == peerID { + grps = append(grps, groupID) + break + } + } + } + return grps +} + func (a *Account) getUserGroups(userID string) ([]string, error) { user, err := a.FindUser(userID) if err != nil { @@ -824,6 +844,7 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) { func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager, singleAccountModeDomain string, dnsDomain string, eventStore activity.Store, geo *geolocation.Geolocation, userDeleteFromIDPEnabled bool, + integratedPeerValidator integrated_approval.IntegratedApproval, ) (*DefaultAccountManager, error) { am := &DefaultAccountManager{ Store: store, @@ -837,6 +858,7 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage eventStore: eventStore, peerLoginExpiry: NewDefaultScheduler(), userDeleteFromIDPEnabled: userDeleteFromIDPEnabled, + integratedPeerValidator: integratedPeerValidator, } allAccounts := store.GetAllAccounts() // enable single account mode only if configured by user and number of existing accounts is not grater than 1 diff --git a/management/server/account/account.go b/management/server/account/account.go index b8b71a6de..eb812802c 100644 --- a/management/server/account/account.go +++ b/management/server/account/account.go @@ -3,11 +3,17 @@ package account type ExtraSettings struct { // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator PeerApprovalEnabled bool + + // IntegratedApprovalGroups list of group IDs to be used with integrated approval configurations + IntegratedApprovalGroups []string `gorm:"serializer:json"` } // Copy copies the ExtraSettings struct func (e *ExtraSettings) Copy() *ExtraSettings { + var cpGroup []string + return &ExtraSettings{ - PeerApprovalEnabled: e.PeerApprovalEnabled, + PeerApprovalEnabled: e.PeerApprovalEnabled, + IntegratedApprovalGroups: append(cpGroup, e.IntegratedApprovalGroups...), } } diff --git a/management/server/account_test.go b/management/server/account_test.go index 1890d3060..b528006e1 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -12,20 +12,30 @@ import ( "time" "github.com/golang-jwt/jwt" - - nbdns "github.com/netbirdio/netbird/dns" - "github.com/netbirdio/netbird/management/server/activity" - nbpeer "github.com/netbirdio/netbird/management/server/peer" - "github.com/netbirdio/netbird/management/server/posture" - "github.com/netbirdio/netbird/route" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/account" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/posture" + "github.com/netbirdio/netbird/route" ) +type MocIntegratedApproval struct { +} + +func (MocIntegratedApproval) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer { + return peer +} + +func (MocIntegratedApproval) ValidatePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, integratedApprovalGroups []string) (bool, error) { + return true, nil +} + func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { t.Helper() peer := &nbpeer.Peer{ @@ -363,7 +373,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { account.Groups[all.ID].Peers = append(account.Groups[all.ID].Peers, peer.ID) } - networkMap := account.GetPeerNetworkMap(testCase.peerID, "netbird.io") + networkMap := account.GetPeerNetworkMap(testCase.peerID, "netbird.io", MocIntegratedApproval{}) assert.Len(t, networkMap.Peers, len(testCase.expectedPeers)) assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers)) } @@ -2218,7 +2228,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedApproval{}) } func createStore(t *testing.T) (Store, error) { diff --git a/management/server/dns_test.go b/management/server/dns_test.go index aac35308c..599210c73 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -193,7 +193,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedApproval{}) } func createDNSStore(t *testing.T) (Store, error) { diff --git a/management/server/group.go b/management/server/group.go index be8d3fb0e..6984df45c 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -274,6 +274,15 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) } } + // check integrated peer approval + if account.Settings.Extra != nil { + for _, integratedPeerApprovalGroups := range account.Settings.Extra.IntegratedApprovalGroups { + if groupID == integratedPeerApprovalGroups { + return &GroupLinkError{"integrated approval", g.Name} + } + } + } + delete(account.Groups, groupID) account.Network.IncSerial() diff --git a/management/server/http/handler.go b/management/server/http/handler.go index 4aab513a7..5e262916f 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -9,10 +9,10 @@ import ( "github.com/rs/cors" "github.com/netbirdio/management-integrations/integrations" - s "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/http/middleware" + "github.com/netbirdio/netbird/management/server/integrated_approval" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/telemetry" ) @@ -32,6 +32,7 @@ type apiHandler struct { AccountManager s.AccountManager geolocationManager *geolocation.Geolocation AuthCfg AuthCfg + integratedPeerValidator integrated_approval.IntegratedApproval } // EmptyObject is an empty struct used to return empty JSON object @@ -39,7 +40,7 @@ type emptyObject struct { } // APIHandler creates the Management service HTTP API handler registering all the available endpoints. -func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationManager *geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg) (http.Handler, error) { +func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationManager *geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg, integratedPeerValidator integrated_approval.IntegratedApproval) (http.Handler, error) { claimsExtractor := jwtclaims.NewClaimsExtractor( jwtclaims.WithAudience(authCfg.Audience), jwtclaims.WithUserIDClaim(authCfg.UserIDClaim), @@ -74,6 +75,7 @@ func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationMa AccountManager: accountManager, geolocationManager: LocationManager, AuthCfg: authCfg, + integratedPeerValidator: integratedPeerValidator, } if _, err := integrations.RegisterHandlers(ctx, prefix, api.Router, accountManager, claimsExtractor); err != nil { @@ -127,7 +129,7 @@ func (apiHandler *apiHandler) addAccountsEndpoint() { } func (apiHandler *apiHandler) addPeersEndpoint() { - peersHandler := NewPeersHandler(apiHandler.AccountManager, apiHandler.AuthCfg) + peersHandler := NewPeersHandler(apiHandler.AccountManager, apiHandler.AuthCfg, apiHandler.integratedPeerValidator) apiHandler.Router.HandleFunc("/peers", peersHandler.GetAllPeers).Methods("GET", "OPTIONS") apiHandler.Router.HandleFunc("/peers/{peerId}", peersHandler.HandlePeer). Methods("GET", "PUT", "DELETE", "OPTIONS") diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index d4d2558e8..31c6f392d 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -10,6 +10,7 @@ import ( "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" + "github.com/netbirdio/netbird/management/server/integrated_approval" "github.com/netbirdio/netbird/management/server/jwtclaims" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" @@ -19,16 +20,18 @@ import ( type PeersHandler struct { accountManager server.AccountManager claimsExtractor *jwtclaims.ClaimsExtractor + peerValidator integrated_approval.IntegratedApproval } // NewPeersHandler creates a new PeersHandler HTTP handler -func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg) *PeersHandler { +func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg, peerValidator integrated_approval.IntegratedApproval) *PeersHandler { return &PeersHandler{ accountManager: accountManager, claimsExtractor: jwtclaims.NewClaimsExtractor( jwtclaims.WithAudience(authCfg.Audience), jwtclaims.WithUserIDClaim(authCfg.UserIDClaim), ), + peerValidator: peerValidator, } } @@ -61,7 +64,7 @@ func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w groupsInfo := toGroupsInfo(account.Groups, peer.ID) - netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain()) + netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain(), h.peerValidator) accessiblePeers := toAccessiblePeers(netMap, dnsDomain) util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers)) @@ -91,7 +94,7 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID) - netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain()) + netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain(), h.peerValidator) accessiblePeers := toAccessiblePeers(netMap, dnsDomain) util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers)) @@ -176,7 +179,7 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) { } func (h *PeersHandler) accessiblePeersNumber(account *server.Account, peerID string) int { - netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain()) + netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain(), h.peerValidator) return len(netMap.Peers) + len(netMap.OfflinePeers) } diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 27978c487..27193d8b4 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -3,6 +3,7 @@ package http import ( "bytes" "encoding/json" + "github.com/netbirdio/management-integrations/integrations" "io" "net" "net/http" @@ -102,6 +103,7 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { } }), ), + peerValidator: integrations.NewIntegratedApproval(), } } diff --git a/management/server/integrated_approval.go b/management/server/integrated_approval.go new file mode 100644 index 000000000..80e1a9ce2 --- /dev/null +++ b/management/server/integrated_approval.go @@ -0,0 +1,76 @@ +package server + +import ( + "errors" + + "github.com/google/martian/v3/log" + + "github.com/netbirdio/netbird/management/server/account" +) + +// UpdateIntegratedApprovalGroups updates the integrated approval groups for a specified account. +// It retrieves the account associated with the provided userID, then updates the integrated approval groups +// with the provided list of group ids. The updated account is then saved. +// +// Parameters: +// - accountID: The ID of the account for which integrated approval groups are to be updated. +// - userID: The ID of the user whose account is being updated. +// - groups: A slice of strings representing the ids of integrated approval groups to be updated. +// +// Returns: +// - error: An error if any occurred during the process, otherwise returns nil +func (am *DefaultAccountManager) UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error { + ok, err := am.GroupValidation(accountID, groups) + if err != nil { + log.Debugf("error validating groups: %s", err.Error()) + return err + } + + if !ok { + log.Debugf("invalid groups") + return errors.New("invalid groups") + } + + unlock := am.Store.AcquireAccountLock(accountID) + defer unlock() + + a, err := am.Store.GetAccountByUser(userID) + if err != nil { + return err + } + + var extra *account.ExtraSettings + + if a.Settings.Extra != nil { + extra = a.Settings.Extra + } else { + extra = &account.ExtraSettings{} + a.Settings.Extra = extra + } + extra.IntegratedApprovalGroups = groups + return am.Store.SaveAccount(a) +} + +func (am *DefaultAccountManager) GroupValidation(accountId string, groups []string) (bool, error) { + if len(groups) == 0 { + return true, nil + } + accountsGroups, err := am.ListGroups(accountId) + if err != nil { + return false, err + } + for _, group := range groups { + var found bool + for _, accountGroup := range accountsGroups { + if accountGroup.ID == group { + found = true + break + } + } + if !found { + return false, nil + } + } + + return true, nil +} diff --git a/management/server/integrated_approval/interface.go b/management/server/integrated_approval/interface.go new file mode 100644 index 000000000..bf1219f47 --- /dev/null +++ b/management/server/integrated_approval/interface.go @@ -0,0 +1,12 @@ +package integrated_approval + +import ( + "github.com/netbirdio/netbird/management/server/account" + nbpeer "github.com/netbirdio/netbird/management/server/peer" +) + +// IntegratedApproval interface exists to avoid the circle dependencies +type IntegratedApproval interface { + PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer + SyncPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, bool) +} diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index e5457db02..98c3f086d 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -9,8 +9,6 @@ import ( "testing" "time" - "github.com/netbirdio/netbird/management/server/activity" - "github.com/stretchr/testify/require" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc" @@ -19,6 +17,7 @@ import ( "github.com/netbirdio/netbird/encryption" mgmtProto "github.com/netbirdio/netbird/management/proto" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/util" ) @@ -413,7 +412,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error) peersUpdateManager := NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "", - eventStore, nil, false) + eventStore, nil, false, MocIntegratedApproval{}) if err != nil { return nil, "", err } diff --git a/management/server/management_test.go b/management/server/management_test.go index f45354877..557d3da0f 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -2,6 +2,8 @@ package server_test import ( "context" + "github.com/netbirdio/netbird/management/server/account" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "math/rand" "net" "os" @@ -10,24 +12,19 @@ import ( sync2 "sync" "time" - "github.com/netbirdio/netbird/management/server/activity" - - "google.golang.org/grpc/credentials/insecure" - - "github.com/netbirdio/netbird/management/server" - pb "github.com/golang/protobuf/proto" //nolint - log "github.com/sirupsen/logrus" - - "github.com/netbirdio/netbird/encryption" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/keepalive" + "github.com/netbirdio/netbird/encryption" mgmtProto "github.com/netbirdio/netbird/management/proto" + "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/util" ) @@ -448,6 +445,17 @@ var _ = Describe("Management service", func() { }) }) +type MocIntegratedApproval struct { +} + +func (MocIntegratedApproval) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer { + return peer +} + +func (MocIntegratedApproval) ValidatePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, integratedApprovalGroups []string) (bool, error) { + return true, nil +} + func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse { defer GinkgoRecover() @@ -504,7 +512,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) { peersUpdateManager := server.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", - eventStore, nil, false) + eventStore, nil, false, MocIntegratedApproval{}) if err != nil { log.Fatalf("failed creating a manager: %v", err) } diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 7d4161d3b..a5628da46 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -93,6 +93,8 @@ type MockAccountManager struct { DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error) GetUsageFunc func(ctx context.Context, accountID string, start, end time.Time) (*server.AccountUsageStats, error) + UpdateIntegratedApprovalGroupsFunc func(accountID string, userID string, groups []string) error + GroupValidationFunc func(accountId string, groups []string) (bool, error) } // GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface @@ -712,3 +714,19 @@ func (am *MockAccountManager) GetUsage(ctx context.Context, accountID string, st } return nil, status.Errorf(codes.Unimplemented, "method GetUsage is not implemented") } + +// UpdateIntegratedApprovalGroups mocks UpdateIntegratedApprovalGroups of the AccountManager interface +func (am *MockAccountManager) UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error { + if am.UpdateIntegratedApprovalGroupsFunc != nil { + return am.UpdateIntegratedApprovalGroupsFunc(accountID, userID, groups) + } + return status.Errorf(codes.Unimplemented, "method UpdateIntegratedApprovalGroups is not implemented") +} + +// GroupValidation mocks GroupValidation of the AccountManager interface +func (am *MockAccountManager) GroupValidation(accountId string, groups []string) (bool, error) { + if am.GroupValidationFunc != nil { + return am.GroupValidationFunc(accountId, groups) + } + return false, status.Errorf(codes.Unimplemented, "method GroupValidation is not implemented") +} diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 3327869b4..421137d6b 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -759,7 +759,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false, MocIntegratedApproval{}) } func createNSStore(t *testing.T) (Store, error) { diff --git a/management/server/peer.go b/management/server/peer.go index 1a7b06a79..c408046c7 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -7,16 +7,13 @@ import ( "time" "github.com/rs/xid" + log "github.com/sirupsen/logrus" "github.com/netbirdio/management-integrations/additions" - + "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" - - log "github.com/sirupsen/logrus" - - "github.com/netbirdio/netbird/management/proto" ) // PeerSync used as a data object between the gRPC API and AccountManager on Sync request. @@ -299,7 +296,8 @@ func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, erro if peer == nil { return nil, status.Errorf(status.NotFound, "peer with ID %s not found", peerID) } - return account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil + + return account.GetPeerNetworkMap(peer.ID, am.dnsDomain, am.integratedPeerValidator), nil } // GetPeerNetwork returns the Network for a given peer @@ -427,10 +425,6 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P Ephemeral: ephemeral, } - if account.Settings.Extra != nil { - newPeer = additions.PreparePeer(newPeer, account.Settings.Extra) - } - // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { @@ -459,6 +453,8 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P } } + newPeer = am.integratedPeerValidator.PreparePeer(account.Id, newPeer, account.GetPeerGroupsList(newPeer.ID), account.Settings.Extra) + if addedByUser { user, err := account.FindUser(userID) if err != nil { @@ -484,7 +480,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P am.updateAccountPeers(account) - networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain) + networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain, am.integratedPeerValidator) return newPeer, networkMap, nil } @@ -521,7 +517,18 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network if peerLoginExpired(peer, account) { return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more") } - return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil + + peer, updated := am.integratedPeerValidator.SyncPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) + + if updated { + account.UpdatePeer(peer) + err = am.Store.SaveAccount(account) + if err != nil { + return nil, nil, err + } + } + + return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain, am.integratedPeerValidator), nil } // LoginPeer logs in or registers a peer. @@ -587,7 +594,12 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) } - peer, updated := updatePeerMeta(peer, login.Meta, account) + peer, updated := am.integratedPeerValidator.SyncPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) + if updated { + shouldStoreAccount = true + } + + peer, updated = updatePeerMeta(peer, login.Meta, account) if updated { shouldStoreAccount = true } @@ -607,7 +619,8 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw if updateRemotePeers { am.updateAccountPeers(account) } - return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil + + return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain, am.integratedPeerValidator), nil } func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error { @@ -778,7 +791,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) { peers := account.GetPeers() for _, peer := range peers { - remotePeerNetworkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain) + remotePeerNetworkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain, am.integratedPeerValidator) update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain()) am.peersUpdateManager.SendUpdate(peer.ID, &UpdateMessage{Update: update}) } diff --git a/management/server/policy.go b/management/server/policy.go index 291a4f1f7..fff71995e 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -5,9 +5,10 @@ import ( "strconv" "strings" - "github.com/netbirdio/management-integrations/additions" log "github.com/sirupsen/logrus" + "github.com/netbirdio/management-integrations/additions" + "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" nbpeer "github.com/netbirdio/netbird/management/server/peer" diff --git a/management/server/route_test.go b/management/server/route_test.go index a5db2ca07..b910ac650 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -1014,7 +1014,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false, MocIntegratedApproval{}) } func createRouterStore(t *testing.T) (Store, error) {