From 7218a3d5632288128f232969b4e3ca82a516b9b1 Mon Sep 17 00:00:00 2001 From: Misha Bragin Date: Wed, 19 Oct 2022 17:43:28 +0200 Subject: [PATCH] Management single account mode (#511) --- client/cmd/testutil.go | 2 +- client/internal/engine_test.go | 2 +- infrastructure_files/base.setup.env | 2 + infrastructure_files/docker-compose.yml.tmpl | 2 +- management/client/client_test.go | 2 +- management/cmd/management.go | 16 +++++--- management/cmd/root.go | 35 +++++++++-------- management/server/account.go | 40 ++++++++++++++++---- management/server/account_test.go | 2 +- management/server/management_proto_test.go | 2 +- management/server/management_test.go | 2 +- management/server/nameserver_test.go | 2 +- management/server/route_test.go | 2 +- 13 files changed, 73 insertions(+), 38 deletions(-) diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index f4e01782b..b3e4a1610 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -68,7 +68,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste } peersUpdateManager := mgmt.NewPeersUpdateManager() - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil) + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "") if err != nil { t.Fatal(err) } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index e68da6fb8..d111af76e 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -761,7 +761,7 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) { log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) } peersUpdateManager := server.NewPeersUpdateManager() - accountManager, err := server.BuildManager(store, peersUpdateManager, nil) + accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "") if err != nil { return nil, err } diff --git a/infrastructure_files/base.setup.env b/infrastructure_files/base.setup.env index 61efeffd8..a4dcc92d6 100644 --- a/infrastructure_files/base.setup.env +++ b/infrastructure_files/base.setup.env @@ -10,6 +10,8 @@ NETBIRD_MGMT_API_ENDPOINT=https://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT NETBIRD_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem" # Management Certficate key file path. NETBIRD_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem" +# By default Management single account mode is enabled and domain set to $NETBIRD_DOMAIN, you may want to set this to your user's email domain +NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN=$NETBIRD_DOMAIN # Turn credentials diff --git a/infrastructure_files/docker-compose.yml.tmpl b/infrastructure_files/docker-compose.yml.tmpl index add470c16..948732c3c 100644 --- a/infrastructure_files/docker-compose.yml.tmpl +++ b/infrastructure_files/docker-compose.yml.tmpl @@ -48,7 +48,7 @@ services: # # port and command for Let's Encrypt validation without dashboard container # - 443:443 # command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"] - command: ["--port", "443", "--log-file", "console", "--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS"] + command: ["--port", "443", "--log-file", "console", "--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS", "--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN"] # Coturn coturn: image: coturn/coturn diff --git a/management/client/client_test.go b/management/client/client_test.go index 19421d19f..8c0af487b 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -55,7 +55,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { } peersUpdateManager := mgmt.NewPeersUpdateManager() - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil) + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "") if err != nil { t.Fatal(err) } diff --git a/management/cmd/management.go b/management/cmd/management.go index 82cf01f34..6ed511da3 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -41,11 +41,12 @@ import ( const ManagementLegacyPort = 33073 var ( - mgmtPort int - mgmtLetsencryptDomain string - certFile string - certKey string - config *server.Config + mgmtPort int + mgmtLetsencryptDomain string + mgmtSingleAccModeDomain string + certFile string + certKey string + config *server.Config kaep = keepalive.EnforcementPolicy{ MinTime: 15 * time.Second, @@ -121,7 +122,10 @@ var ( } } - accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager) + if disableSingleAccMode { + mgmtSingleAccModeDomain = "" + } + accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain) if err != nil { return fmt.Errorf("failed to build default manager: %v", err) } diff --git a/management/cmd/root.go b/management/cmd/root.go index d9b6e7cb7..22675f004 100644 --- a/management/cmd/root.go +++ b/management/cmd/root.go @@ -13,21 +13,23 @@ const ( ) var ( - defaultMgmtConfigDir string - defaultMgmtDataDir string - defaultMgmtConfig string - defaultLogDir string - defaultLogFile string - oldDefaultMgmtConfigDir string - oldDefaultMgmtDataDir string - oldDefaultMgmtConfig string - oldDefaultLogDir string - oldDefaultLogFile string - mgmtDataDir string - mgmtConfig string - logLevel string - logFile string - disableMetrics bool + defaultMgmtConfigDir string + defaultMgmtDataDir string + defaultMgmtConfig string + defaultSingleAccModeDomain string + defaultLogDir string + defaultLogFile string + oldDefaultMgmtConfigDir string + oldDefaultMgmtDataDir string + oldDefaultMgmtConfig string + oldDefaultLogDir string + oldDefaultLogFile string + mgmtDataDir string + mgmtConfig string + logLevel string + logFile string + disableMetrics bool + disableSingleAccMode bool rootCmd = &cobra.Command{ Use: "netbird-mgmt", @@ -48,6 +50,7 @@ func init() { stopCh = make(chan int) defaultMgmtDataDir = "/var/lib/netbird/" + defaultSingleAccModeDomain = "netbird.selfhosted" defaultMgmtConfigDir = "/etc/netbird" defaultLogDir = "/var/log/netbird" @@ -65,6 +68,8 @@ func init() { mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location") mgmtCmd.Flags().StringVar(&mgmtConfig, "config", defaultMgmtConfig, "Netbird config file location. Config params specified via command line (e.g. datadir) have a precedence over configuration from this file") mgmtCmd.Flags().StringVar(&mgmtLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS") + mgmtCmd.Flags().StringVar(&mgmtSingleAccModeDomain, "single-account-mode-domain", defaultSingleAccModeDomain, "Enables single account mode. This means that all the users will be under the same account grouped by the specified domain. If the installation has more than one account, the property is ineffective. Enabled by default with the default domain "+defaultSingleAccModeDomain) + mgmtCmd.Flags().BoolVar(&disableSingleAccMode, "disable-single-account-mode", false, "If set to true, disables single account mode. The --single-account-mode-domain property will be ignored and every new user will have a separate NetBird account.") mgmtCmd.Flags().StringVar(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect") mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect") mgmtCmd.Flags().BoolVar(&disableMetrics, "disable-anonymous-metrics", false, "disables push of anonymous usage metrics to NetBird") diff --git a/management/server/account.go b/management/server/account.go index 923da63b9..ddbc41f3f 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -106,6 +106,13 @@ type DefaultAccountManager struct { idpManager idp.Manager cacheManager cache.CacheInterface[[]*idp.UserData] ctx context.Context + + // singleAccountMode indicates whether the instance has a single account. + // If true, then every new user will end up under the same account. + // This value will be set to false if management service has more than one account. + singleAccountMode bool + // singleAccountModeDomain is a domain to use in singleAccountMode setup + singleAccountModeDomain string } // Account represents a unique account of the system @@ -195,9 +202,8 @@ func (a *Account) GetGroupAll() (*Group, error) { } // BuildManager creates a new DefaultAccountManager with a provided Store -func BuildManager( - store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager, -) (*DefaultAccountManager, error) { +func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager, + singleAccountModeDomain string) (*DefaultAccountManager, error) { am := &DefaultAccountManager{ Store: store, mux: sync.Mutex{}, @@ -207,11 +213,20 @@ func BuildManager( cacheMux: sync.Mutex{}, cacheLoading: map[string]chan struct{}{}, } + allAccounts := store.GetAllAccounts() + // enable single account mode only if configured by user and number of existing accounts is not grater than 1 + am.singleAccountMode = singleAccountModeDomain != "" && len(allAccounts) <= 1 + if am.singleAccountMode { + am.singleAccountModeDomain = singleAccountModeDomain + log.Infof("single account mode enabled, accounts number %d", len(allAccounts)) + } else { + log.Infof("single account mode disabled, accounts number %d", len(allAccounts)) + } - // if account has not default group + // if account doesn't have a default group // we create 'all' group and add all peers into it // also we create default rule with source as destination - for _, account := range store.GetAllAccounts() { + for _, account := range allAccounts { _, err := account.GetGroupAll() if err != nil { addAllGroup(account) @@ -221,10 +236,10 @@ func BuildManager( } } - gocacheClient := gocache.New(CacheExpirationMax, 30*time.Minute) - gocacheStore := cacheStore.NewGoCache(gocacheClient) + goCacheClient := gocache.New(CacheExpirationMax, 30*time.Minute) + goCacheStore := cacheStore.NewGoCache(goCacheClient) - am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](gocacheStore)) + am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](goCacheStore)) if !isNil(am.idpManager) { go func() { @@ -604,6 +619,15 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e // GetAccountFromToken returns an account associated with this token func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, error) { + + if am.singleAccountMode && am.singleAccountModeDomain != "" { + // This section is mostly related to self-hosted installations. + // We override incoming domain claims to group users under a single account. + claims.Domain = am.singleAccountModeDomain + claims.DomainCategory = PrivateCategory + log.Infof("overriding JWT Domain and DomainCategory claims since single account mode is enabled") + } + account, err := am.getAccountWithAuthorizationClaims(claims) if err != nil { return nil, err diff --git a/management/server/account_test.go b/management/server/account_test.go index c51eaf6a2..503a36805 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -962,7 +962,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) { if err != nil { return nil, err } - return BuildManager(store, NewPeersUpdateManager(), nil) + return BuildManager(store, NewPeersUpdateManager(), nil, "") } func createStore(t *testing.T) (Store, error) { diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index 115e18627..ae26eb486 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -403,7 +403,7 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro return nil, err } peersUpdateManager := NewPeersUpdateManager() - accountManager, err := BuildManager(store, peersUpdateManager, nil) + accountManager, err := BuildManager(store, peersUpdateManager, nil, "") if err != nil { return nil, err } diff --git a/management/server/management_test.go b/management/server/management_test.go index 954a039da..8a2d0a1c6 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -493,7 +493,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) { log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) } peersUpdateManager := server.NewPeersUpdateManager() - accountManager, err := server.BuildManager(store, peersUpdateManager, nil) + accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "") if err != nil { log.Fatalf("failed creating a manager: %v", err) } diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 73512d898..d4bdb70f1 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -865,7 +865,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) { if err != nil { return nil, err } - return BuildManager(store, NewPeersUpdateManager(), nil) + return BuildManager(store, NewPeersUpdateManager(), nil, "") } func createNSStore(t *testing.T) (Store, error) { diff --git a/management/server/route_test.go b/management/server/route_test.go index c11122c80..6e96c9db2 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -778,7 +778,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { if err != nil { return nil, err } - return BuildManager(store, NewPeersUpdateManager(), nil) + return BuildManager(store, NewPeersUpdateManager(), nil, "") } func createRouterStore(t *testing.T) (Store, error) {