2021-07-30 17:46:38 +02:00
package server
import (
2022-06-05 21:36:42 +02:00
"context"
2023-03-16 15:57:44 +01:00
"crypto/sha256"
2023-03-29 15:21:53 +02:00
b64 "encoding/base64"
2022-05-09 14:30:20 +02:00
"fmt"
2023-03-16 15:57:44 +01:00
"hash/crc32"
2023-02-03 21:47:20 +01:00
"math/rand"
"net"
"net/netip"
"reflect"
"regexp"
2024-06-13 08:55:09 +02:00
"slices"
2023-02-03 21:47:20 +01:00
"strings"
"sync"
"time"
2022-10-13 18:26:31 +02:00
"github.com/eko/gocache/v3/cache"
cacheStore "github.com/eko/gocache/v3/store"
2024-08-07 10:52:31 +02:00
"github.com/hashicorp/go-multierror"
"github.com/miekg/dns"
2024-06-20 13:52:32 +02:00
gocache "github.com/patrickmn/go-cache"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
2023-05-16 12:57:56 +02:00
"github.com/netbirdio/netbird/base62"
2022-09-30 13:47:11 +02:00
nbdns "github.com/netbirdio/netbird/dns"
2024-06-13 13:24:24 +02:00
"github.com/netbirdio/netbird/management/domain"
2023-11-28 14:34:57 +01:00
"github.com/netbirdio/netbird/management/server/account"
2023-01-02 15:11:32 +01:00
"github.com/netbirdio/netbird/management/server/activity"
2024-02-20 09:59:56 +01:00
"github.com/netbirdio/netbird/management/server/geolocation"
2024-03-27 18:48:48 +01:00
nbgroup "github.com/netbirdio/netbird/management/server/group"
2022-03-26 12:08:54 +01:00
"github.com/netbirdio/netbird/management/server/idp"
2024-03-27 18:48:48 +01:00
"github.com/netbirdio/netbird/management/server/integrated_validator"
"github.com/netbirdio/netbird/management/server/integration_reference"
2022-03-26 12:08:54 +01:00
"github.com/netbirdio/netbird/management/server/jwtclaims"
2023-11-28 13:45:26 +01:00
nbpeer "github.com/netbirdio/netbird/management/server/peer"
2024-02-20 09:59:56 +01:00
"github.com/netbirdio/netbird/management/server/posture"
2022-11-11 20:36:45 +01:00
"github.com/netbirdio/netbird/management/server/status"
2024-08-07 10:52:31 +02:00
"github.com/netbirdio/netbird/management/server/telemetry"
2022-08-18 18:22:15 +02:00
"github.com/netbirdio/netbird/route"
2021-07-30 17:46:38 +02:00
)
2022-03-01 15:22:18 +01:00
const (
2023-02-13 12:21:02 +01:00
PublicCategory = "public"
PrivateCategory = "private"
UnknownCategory = "unknown"
CacheExpirationMax = 7 * 24 * 3600 * time . Second // 7 days
CacheExpirationMin = 3 * 24 * 3600 * time . Second // 3 days
DefaultPeerLoginExpiration = 24 * time . Hour
2022-03-01 15:22:18 +01:00
)
2024-04-22 11:10:27 +02:00
type userLoggedInOnce bool
2023-11-13 14:04:18 +01:00
type ExternalCacheManager cache . CacheInterface [ * idp . UserData ]
2022-10-13 18:26:31 +02:00
func cacheEntryExpiration ( ) time . Duration {
r := rand . Intn ( int ( CacheExpirationMax . Milliseconds ( ) - CacheExpirationMin . Milliseconds ( ) ) ) + int ( CacheExpirationMin . Milliseconds ( ) )
return time . Duration ( r ) * time . Millisecond
}
2022-02-22 11:28:19 +01:00
type AccountManager interface {
2024-07-03 11:33:02 +02:00
GetOrCreateAccountByUser ( ctx context . Context , userId , domain string ) ( * Account , error )
CreateSetupKey ( ctx context . Context , accountID string , keyName string , keyType SetupKeyType , expiresIn time . Duration ,
2023-09-04 11:37:39 +02:00
autoGroups [ ] string , usageLimit int , userID string , ephemeral bool ) ( * SetupKey , error )
2024-07-03 11:33:02 +02:00
SaveSetupKey ( ctx context . Context , accountID string , key * SetupKey , userID string ) ( * SetupKey , error )
CreateUser ( ctx context . Context , accountID , initiatorUserID string , key * UserInfo ) ( * UserInfo , error )
DeleteUser ( ctx context . Context , accountID , initiatorUserID string , targetUserID string ) error
2024-08-08 17:01:38 +02:00
DeleteRegularUsers ( ctx context . Context , accountID , initiatorUserID string , targetUserIDs [ ] string ) error
2024-07-03 11:33:02 +02:00
InviteUser ( ctx context . Context , accountID string , initiatorUserID string , targetUserID string ) error
ListSetupKeys ( ctx context . Context , accountID , userID string ) ( [ ] * SetupKey , error )
SaveUser ( ctx context . Context , accountID , initiatorUserID string , update * User ) ( * UserInfo , error )
SaveOrAddUser ( ctx context . Context , accountID , initiatorUserID string , update * User , addIfNotExists bool ) ( * UserInfo , error )
2024-07-15 16:04:06 +02:00
SaveOrAddUsers ( ctx context . Context , accountID , initiatorUserID string , updates [ ] * User , addIfNotExists bool ) ( [ ] * UserInfo , error )
2024-07-03 11:33:02 +02:00
GetSetupKey ( ctx context . Context , accountID , userID , keyID string ) ( * SetupKey , error )
GetAccountByUserOrAccountID ( ctx context . Context , userID , accountID , domain string ) ( * Account , error )
2024-09-18 15:40:47 +02:00
GetAccountFromToken ( ctx context . Context , claims jwtclaims . AuthorizationClaims ) ( * Account , * User , error )
2024-07-03 11:33:02 +02:00
CheckUserAccessByJWTGroups ( ctx context . Context , claims jwtclaims . AuthorizationClaims ) error
GetAccountFromPAT ( ctx context . Context , pat string ) ( * Account , * User , * PersonalAccessToken , error )
DeleteAccount ( ctx context . Context , accountID , userID string ) error
MarkPATUsed ( ctx context . Context , tokenID string ) error
2024-09-16 12:33:36 +02:00
GetUserByID ( ctx context . Context , userID string ) ( * User , error )
2024-07-03 11:33:02 +02:00
GetUser ( ctx context . Context , claims jwtclaims . AuthorizationClaims ) ( * User , error )
ListUsers ( ctx context . Context , accountID string ) ( [ ] * User , error )
GetPeers ( ctx context . Context , accountID , userID string ) ( [ ] * nbpeer . Peer , error )
MarkPeerConnected ( ctx context . Context , peerKey string , connected bool , realIP net . IP , account * Account ) error
DeletePeer ( ctx context . Context , accountID , peerID , userID string ) error
UpdatePeer ( ctx context . Context , accountID , userID string , peer * nbpeer . Peer ) ( * nbpeer . Peer , error )
GetNetworkMap ( ctx context . Context , peerID string ) ( * NetworkMap , error )
GetPeerNetwork ( ctx context . Context , peerID string ) ( * Network , error )
AddPeer ( ctx context . Context , setupKey , userID string , peer * nbpeer . Peer ) ( * nbpeer . Peer , * NetworkMap , [ ] * posture . Checks , error )
CreatePAT ( ctx context . Context , accountID string , initiatorUserID string , targetUserID string , tokenName string , expiresIn int ) ( * PersonalAccessTokenGenerated , error )
DeletePAT ( ctx context . Context , accountID string , initiatorUserID string , targetUserID string , tokenID string ) error
GetPAT ( ctx context . Context , accountID string , initiatorUserID string , targetUserID string , tokenID string ) ( * PersonalAccessToken , error )
GetAllPATs ( ctx context . Context , accountID string , initiatorUserID string , targetUserID string ) ( [ ] * PersonalAccessToken , error )
UpdatePeerSSHKey ( ctx context . Context , peerID string , sshKey string ) error
GetUsersFromAccount ( ctx context . Context , accountID , userID string ) ( [ ] * UserInfo , error )
GetGroup ( ctx context . Context , accountId , groupID , userID string ) ( * nbgroup . Group , error )
GetAllGroups ( ctx context . Context , accountID , userID string ) ( [ ] * nbgroup . Group , error )
GetGroupByName ( ctx context . Context , groupName , accountID string ) ( * nbgroup . Group , error )
SaveGroup ( ctx context . Context , accountID , userID string , group * nbgroup . Group ) error
2024-07-15 16:04:06 +02:00
SaveGroups ( ctx context . Context , accountID , userID string , newGroups [ ] * nbgroup . Group ) error
2024-07-03 11:33:02 +02:00
DeleteGroup ( ctx context . Context , accountId , userId , groupID string ) error
2024-08-08 17:01:38 +02:00
DeleteGroups ( ctx context . Context , accountId , userId string , groupIDs [ ] string ) error
2024-07-03 11:33:02 +02:00
ListGroups ( ctx context . Context , accountId string ) ( [ ] * nbgroup . Group , error )
GroupAddPeer ( ctx context . Context , accountId , groupID , peerID string ) error
GroupDeletePeer ( ctx context . Context , accountId , groupID , peerID string ) error
GetPolicy ( ctx context . Context , accountID , policyID , userID string ) ( * Policy , error )
SavePolicy ( ctx context . Context , accountID , userID string , policy * Policy ) error
DeletePolicy ( ctx context . Context , accountID , policyID , userID string ) error
ListPolicies ( ctx context . Context , accountID , userID string ) ( [ ] * Policy , error )
GetRoute ( ctx context . Context , accountID string , routeID route . ID , userID string ) ( * route . Route , error )
CreateRoute ( ctx context . Context , accountID string , prefix netip . Prefix , networkType route . NetworkType , domains domain . List , peerID string , peerGroupIDs [ ] string , description string , netID route . NetID , masquerade bool , metric int , groups [ ] string , enabled bool , userID string , keepRoute bool ) ( * route . Route , error )
SaveRoute ( ctx context . Context , accountID , userID string , route * route . Route ) error
DeleteRoute ( ctx context . Context , accountID string , routeID route . ID , userID string ) error
ListRoutes ( ctx context . Context , accountID , userID string ) ( [ ] * route . Route , error )
GetNameServerGroup ( ctx context . Context , accountID , userID , nsGroupID string ) ( * nbdns . NameServerGroup , error )
CreateNameServerGroup ( ctx context . Context , accountID string , name , description string , nameServerList [ ] nbdns . NameServer , groups [ ] string , primary bool , domains [ ] string , enabled bool , userID string , searchDomainsEnabled bool ) ( * nbdns . NameServerGroup , error )
SaveNameServerGroup ( ctx context . Context , accountID , userID string , nsGroupToSave * nbdns . NameServerGroup ) error
DeleteNameServerGroup ( ctx context . Context , accountID , nsGroupID , userID string ) error
ListNameServerGroups ( ctx context . Context , accountID string , userID string ) ( [ ] * nbdns . NameServerGroup , error )
2022-11-21 11:14:42 +01:00
GetDNSDomain ( ) string
2024-07-03 11:33:02 +02:00
StoreEvent ( ctx context . Context , initiatorID , targetID , accountID string , activityID activity . ActivityDescriber , meta map [ string ] any )
GetEvents ( ctx context . Context , accountID , userID string ) ( [ ] * activity . Event , error )
GetDNSSettings ( ctx context . Context , accountID string , userID string ) ( * DNSSettings , error )
SaveDNSSettings ( ctx context . Context , accountID string , userID string , dnsSettingsToSave * DNSSettings ) error
GetPeer ( ctx context . Context , accountID , peerID , userID string ) ( * nbpeer . Peer , error )
UpdateAccountSettings ( ctx context . Context , accountID , userID string , newSettings * Settings ) ( * Account , error )
LoginPeer ( ctx context . Context , login PeerLogin ) ( * nbpeer . Peer , * NetworkMap , [ ] * posture . Checks , error ) // used by peer gRPC API
SyncPeer ( ctx context . Context , sync PeerSync , account * Account ) ( * nbpeer . Peer , * NetworkMap , [ ] * posture . Checks , error ) // used by peer gRPC API
2023-10-11 18:11:45 +02:00
GetAllConnectedPeers ( ) ( map [ string ] struct { } , error )
2023-12-05 14:17:56 +01:00
HasConnectedChannel ( peerID string ) bool
2023-11-13 14:04:18 +01:00
GetExternalCacheManager ( ) ExternalCacheManager
2024-07-03 11:33:02 +02:00
GetPostureChecks ( ctx context . Context , accountID , postureChecksID , userID string ) ( * posture . Checks , error )
SavePostureChecks ( ctx context . Context , accountID , userID string , postureChecks * posture . Checks ) error
DeletePostureChecks ( ctx context . Context , accountID , postureChecksID , userID string ) error
ListPostureChecks ( ctx context . Context , accountID , userID string ) ( [ ] * posture . Checks , error )
2024-02-28 16:57:35 +01:00
GetIdpManager ( ) idp . Manager
2024-07-03 11:33:02 +02:00
UpdateIntegratedValidatorGroups ( ctx context . Context , accountID string , userID string , groups [ ] string ) error
GroupValidation ( ctx context . Context , accountId string , groups [ ] string ) ( bool , error )
2024-03-27 18:48:48 +01:00
GetValidatedPeers ( account * Account ) ( map [ string ] struct { } , error )
2024-08-01 16:21:43 +02:00
SyncAndMarkPeer ( ctx context . Context , accountID string , peerPubKey string , meta nbpeer . PeerSystemMeta , realIP net . IP ) ( * nbpeer . Peer , * NetworkMap , [ ] * posture . Checks , error )
OnPeerDisconnected ( ctx context . Context , accountID string , peerPubKey string ) error
2024-07-03 11:33:02 +02:00
SyncPeerMeta ( ctx context . Context , peerPubKey string , meta nbpeer . PeerSystemMeta ) error
2024-05-30 15:22:42 +02:00
FindExistingPostureCheck ( accountID string , checks * posture . ChecksDefinition ) ( * posture . Checks , error )
2024-07-03 11:33:02 +02:00
GetAccountIDForPeerKey ( ctx context . Context , peerKey string ) ( string , error )
2022-02-22 11:28:19 +01:00
}
type DefaultAccountManager struct {
2021-07-30 17:46:38 +02:00
Store Store
2022-10-13 18:26:31 +02:00
// cacheMux and cacheLoading helps to make sure that only a single cache reload runs at a time per accountID
cacheMux sync . Mutex
// cacheLoading keeps the accountIDs that are currently reloading. The accountID has to be removed once cache has been reloaded
2023-11-13 14:04:18 +01:00
cacheLoading map [ string ] chan struct { }
peersUpdateManager * PeersUpdateManager
idpManager idp . Manager
cacheManager cache . CacheInterface [ [ ] * idp . UserData ]
externalCacheManager ExternalCacheManager
ctx context . Context
eventStore activity . Store
2024-02-20 09:59:56 +01:00
geo * geolocation . Geolocation
2022-10-19 17:43:28 +02:00
2024-08-21 11:44:52 +02:00
requestBuffer * AccountRequestBuffer
2024-08-20 20:06:01 +02:00
2022-10-19 17:43:28 +02:00
// 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
2022-11-07 15:38:21 +01:00
// dnsDomain is used for peer resolution. This is appended to the peer's name
2023-02-27 16:44:26 +01:00
dnsDomain string
peerLoginExpiry Scheduler
2023-09-19 18:08:40 +02:00
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
userDeleteFromIDPEnabled bool
2024-03-27 18:48:48 +01:00
integratedPeerValidator integrated_validator . IntegratedValidator
2024-08-07 10:52:31 +02:00
metrics telemetry . AppMetrics
2021-07-30 17:46:38 +02:00
}
2023-02-13 15:07:15 +01:00
// Settings represents Account settings structure that can be modified via API and Dashboard
type Settings struct {
// PeerLoginExpirationEnabled globally enables or disables peer login expiration
PeerLoginExpirationEnabled bool
2023-08-07 17:44:51 +02:00
2023-02-13 15:07:15 +01:00
// PeerLoginExpiration is a setting that indicates when peer login expires.
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
PeerLoginExpiration time . Duration
2023-06-27 16:51:05 +02:00
2024-03-27 16:11:45 +01:00
// RegularUsersViewBlocked allows to block regular users from viewing even their own peers and some UI elements
RegularUsersViewBlocked bool
2023-08-07 17:44:51 +02:00
// GroupsPropagationEnabled allows to propagate auto groups from the user to the peer
GroupsPropagationEnabled bool
2023-06-27 16:51:05 +02:00
// JWTGroupsEnabled allows extract groups from JWT claim, which name defined in the JWTGroupsClaimName
// and add it to account groups.
JWTGroupsEnabled bool
// JWTGroupsClaimName from which we extract groups name to add it to account groups
JWTGroupsClaimName string
2023-11-27 17:04:40 +01:00
2023-12-11 16:59:15 +01:00
// JWTAllowGroups list of groups to which users are allowed access
JWTAllowGroups [ ] string ` gorm:"serializer:json" `
2023-11-28 11:44:08 +01:00
// Extra is a dictionary of Account settings
2023-11-30 11:51:35 +01:00
Extra * account . ExtraSettings ` gorm:"embedded;embeddedPrefix:extra_" `
2023-11-27 17:04:40 +01:00
}
2023-02-13 15:07:15 +01:00
// Copy copies the Settings struct
func ( s * Settings ) Copy ( ) * Settings {
2023-11-28 13:45:26 +01:00
settings := & Settings {
2023-02-13 15:07:15 +01:00
PeerLoginExpirationEnabled : s . PeerLoginExpirationEnabled ,
PeerLoginExpiration : s . PeerLoginExpiration ,
2023-06-27 16:51:05 +02:00
JWTGroupsEnabled : s . JWTGroupsEnabled ,
JWTGroupsClaimName : s . JWTGroupsClaimName ,
2023-08-07 17:44:51 +02:00
GroupsPropagationEnabled : s . GroupsPropagationEnabled ,
2023-12-11 16:59:15 +01:00
JWTAllowGroups : s . JWTAllowGroups ,
2024-03-27 16:11:45 +01:00
RegularUsersViewBlocked : s . RegularUsersViewBlocked ,
2023-02-13 15:07:15 +01:00
}
2023-11-28 13:45:26 +01:00
if s . Extra != nil {
settings . Extra = s . Extra . Copy ( )
}
return settings
}
2021-07-30 17:46:38 +02:00
// Account represents a unique account of the system
type Account struct {
2023-10-12 15:42:36 +02:00
// we have to name column to aid as it collides with Network.Id when work with associations
Id string ` gorm:"primaryKey" `
2021-12-27 13:17:15 +01:00
// User.Id it was created by
2022-03-01 15:22:18 +01:00
CreatedBy string
2024-03-02 13:49:40 +01:00
CreatedAt time . Time
2023-10-12 15:42:36 +02:00
Domain string ` gorm:"index" `
2022-03-01 15:22:18 +01:00
DomainCategory string
IsDomainPrimaryAccount bool
2023-10-12 15:42:36 +02:00
SetupKeys map [ string ] * SetupKey ` gorm:"-" `
SetupKeysG [ ] SetupKey ` json:"-" gorm:"foreignKey:AccountID;references:id" `
Network * Network ` gorm:"embedded;embeddedPrefix:network_" `
2023-11-28 13:45:26 +01:00
Peers map [ string ] * nbpeer . Peer ` gorm:"-" `
PeersG [ ] nbpeer . Peer ` json:"-" gorm:"foreignKey:AccountID;references:id" `
2023-10-12 15:42:36 +02:00
Users map [ string ] * User ` gorm:"-" `
UsersG [ ] User ` json:"-" gorm:"foreignKey:AccountID;references:id" `
2024-03-27 18:48:48 +01:00
Groups map [ string ] * nbgroup . Group ` gorm:"-" `
GroupsG [ ] nbgroup . Group ` json:"-" gorm:"foreignKey:AccountID;references:id" `
2023-10-12 15:42:36 +02:00
Policies [ ] * Policy ` gorm:"foreignKey:AccountID;references:id" `
2024-05-06 14:47:49 +02:00
Routes map [ route . ID ] * route . Route ` gorm:"-" `
2023-10-12 15:42:36 +02:00
RoutesG [ ] route . Route ` json:"-" gorm:"foreignKey:AccountID;references:id" `
NameServerGroups map [ string ] * nbdns . NameServerGroup ` gorm:"-" `
NameServerGroupsG [ ] nbdns . NameServerGroup ` json:"-" gorm:"foreignKey:AccountID;references:id" `
DNSSettings DNSSettings ` gorm:"embedded;embeddedPrefix:dns_settings_" `
2024-02-20 09:59:56 +01:00
PostureChecks [ ] * posture . Checks ` gorm:"foreignKey:AccountID;references:id" `
2023-02-13 15:07:15 +01:00
// Settings is a dictionary of Account settings
2023-10-12 15:42:36 +02:00
Settings * Settings ` gorm:"embedded;embeddedPrefix:settings_" `
2021-12-27 13:17:15 +01:00
}
2024-05-31 16:41:12 +02:00
// Subclass used in gorm to only load settings and not whole account
type AccountSettings struct {
Settings * Settings ` gorm:"embedded;embeddedPrefix:settings_" `
}
2024-09-16 15:47:03 +02:00
// Subclass used in gorm to only load network and not whole account
type AccountNetwork struct {
Network * Network ` gorm:"embedded;embeddedPrefix:network_" `
}
2024-03-27 16:11:45 +01:00
type UserPermissions struct {
DashboardView string ` json:"dashboard_view" `
}
2022-05-05 08:58:34 +02:00
type UserInfo struct {
2024-04-11 10:02:51 +02:00
ID string ` json:"id" `
Email string ` json:"email" `
Name string ` json:"name" `
Role string ` json:"role" `
AutoGroups [ ] string ` json:"auto_groups" `
Status string ` json:"-" `
IsServiceUser bool ` json:"is_service_user" `
IsBlocked bool ` json:"is_blocked" `
NonDeletable bool ` json:"non_deletable" `
LastLogin time . Time ` json:"last_login" `
Issued string ` json:"issued" `
2024-03-27 18:48:48 +01:00
IntegrationReference integration_reference . IntegrationReference ` json:"-" `
2024-04-11 10:02:51 +02:00
Permissions UserPermissions ` json:"permissions" `
2022-05-05 08:58:34 +02:00
}
2022-12-06 10:11:57 +01:00
// getRoutesToSync returns the enabled routes for the peer ID and the routes
2023-02-03 10:33:28 +01:00
// from the ACL peers that have distribution groups associated with the peer ID.
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
2024-07-03 11:33:02 +02:00
func ( a * Account ) getRoutesToSync ( ctx context . Context , peerID string , aclPeers [ ] * nbpeer . Peer ) [ ] * route . Route {
routes , peerDisabledRoutes := a . getRoutingPeerRoutes ( ctx , peerID )
2022-12-08 15:15:50 +01:00
peerRoutesMembership := make ( lookupMap )
for _ , r := range append ( routes , peerDisabledRoutes ... ) {
2024-06-13 13:24:24 +02:00
peerRoutesMembership [ string ( r . GetHAUniqueID ( ) ) ] = struct { } { }
2022-12-08 15:15:50 +01:00
}
2022-12-06 10:11:57 +01:00
groupListMap := a . getPeerGroups ( peerID )
for _ , peer := range aclPeers {
2024-07-03 11:33:02 +02:00
activeRoutes , _ := a . getRoutingPeerRoutes ( ctx , peer . ID )
2022-12-08 15:15:50 +01:00
groupFilteredRoutes := a . filterRoutesByGroups ( activeRoutes , groupListMap )
filteredRoutes := a . filterRoutesFromPeersOfSameHAGroup ( groupFilteredRoutes , peerRoutesMembership )
2022-12-06 10:11:57 +01:00
routes = append ( routes , filteredRoutes ... )
}
return routes
}
2024-04-09 13:18:38 +02:00
// filterRoutesFromPeersOfSameHAGroup filters and returns a list of routes that don't share the same HA route membership
2022-12-08 15:15:50 +01:00
func ( a * Account ) filterRoutesFromPeersOfSameHAGroup ( routes [ ] * route . Route , peerMemberships lookupMap ) [ ] * route . Route {
var filteredRoutes [ ] * route . Route
for _ , r := range routes {
2024-06-13 13:24:24 +02:00
_ , found := peerMemberships [ string ( r . GetHAUniqueID ( ) ) ]
2022-12-08 15:15:50 +01:00
if ! found {
filteredRoutes = append ( filteredRoutes , r )
}
}
return filteredRoutes
}
2022-12-06 10:11:57 +01:00
// filterRoutesByGroups returns a list with routes that have distribution groups in the group's map
func ( a * Account ) filterRoutesByGroups ( routes [ ] * route . Route , groupListMap lookupMap ) [ ] * route . Route {
var filteredRoutes [ ] * route . Route
for _ , r := range routes {
for _ , groupID := range r . Groups {
_ , found := groupListMap [ groupID ]
if found {
filteredRoutes = append ( filteredRoutes , r )
break
2022-11-07 12:10:56 +01:00
}
}
}
2022-12-06 10:11:57 +01:00
return filteredRoutes
2022-11-07 12:10:56 +01:00
}
2023-10-09 14:39:41 +02:00
// getRoutingPeerRoutes returns the enabled and disabled lists of routes that the given routing peer serves
2023-02-03 10:33:28 +01:00
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
2023-10-09 14:39:41 +02:00
// If the given is not a routing peer, then the lists are empty.
2024-07-03 11:33:02 +02:00
func ( a * Account ) getRoutingPeerRoutes ( ctx context . Context , peerID string ) ( enabledRoutes [ ] * route . Route , disabledRoutes [ ] * route . Route ) {
2023-10-09 14:39:41 +02:00
peer := a . GetPeer ( peerID )
if peer == nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "peer %s that doesn't exist under account %s" , peerID , a . Id )
2023-10-09 14:39:41 +02:00
return enabledRoutes , disabledRoutes
}
// currently we support only linux routing peers
if peer . Meta . GoOS != "linux" {
return enabledRoutes , disabledRoutes
}
2024-05-06 14:47:49 +02:00
seenRoute := make ( map [ route . ID ] struct { } )
2023-09-28 14:32:36 +02:00
takeRoute := func ( r * route . Route , id string ) {
2023-10-09 14:39:41 +02:00
if _ , ok := seenRoute [ r . ID ] ; ok {
2023-09-28 14:32:36 +02:00
return
}
2023-10-09 14:39:41 +02:00
seenRoute [ r . ID ] = struct { } { }
2023-09-28 14:32:36 +02:00
if r . Enabled {
2023-10-09 14:39:41 +02:00
r . Peer = peer . Key
2023-09-28 14:32:36 +02:00
enabledRoutes = append ( enabledRoutes , r )
return
}
disabledRoutes = append ( disabledRoutes , r )
}
2022-11-07 12:10:56 +01:00
for _ , r := range a . Routes {
2023-10-09 14:39:41 +02:00
for _ , groupID := range r . PeerGroups {
group := a . GetGroup ( groupID )
if group == nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "route %s has peers group %s that doesn't exist under account %s" , r . ID , groupID , a . Id )
2023-10-09 14:39:41 +02:00
continue
}
for _ , id := range group . Peers {
if id != peerID {
2023-09-28 14:32:36 +02:00
continue
}
2023-10-09 14:39:41 +02:00
newPeerRoute := r . Copy ( )
newPeerRoute . Peer = id
newPeerRoute . PeerGroups = nil
2024-05-06 14:47:49 +02:00
newPeerRoute . ID = route . ID ( string ( r . ID ) + ":" + id ) // we have to provide unique route id when distribute network map
2023-10-09 14:39:41 +02:00
takeRoute ( newPeerRoute , id )
break
2022-12-08 15:15:50 +01:00
}
2023-09-28 14:32:36 +02:00
}
if r . Peer == peerID {
2023-10-09 14:39:41 +02:00
takeRoute ( r . Copy ( ) , peerID )
2022-11-07 12:10:56 +01:00
}
}
2023-10-09 14:39:41 +02:00
2022-12-08 15:15:50 +01:00
return enabledRoutes , disabledRoutes
2022-11-07 12:10:56 +01:00
}
2024-06-13 13:24:24 +02:00
// GetRoutesByPrefixOrDomains return list of routes by account and route prefix
func ( a * Account ) GetRoutesByPrefixOrDomains ( prefix netip . Prefix , domains domain . List ) [ ] * route . Route {
2022-11-07 12:10:56 +01:00
var routes [ ] * route . Route
for _ , r := range a . Routes {
2024-06-20 13:52:32 +02:00
dynamic := r . IsDynamic ( )
if dynamic && r . Domains . PunycodeString ( ) == domains . PunycodeString ( ) ||
! dynamic && r . Network . String ( ) == prefix . String ( ) {
2022-11-07 12:10:56 +01:00
routes = append ( routes , r )
}
}
return routes
}
2023-01-02 15:11:32 +01:00
// GetGroup returns a group by ID if exists, nil otherwise
2024-03-27 18:48:48 +01:00
func ( a * Account ) GetGroup ( groupID string ) * nbgroup . Group {
2023-01-02 15:11:32 +01:00
return a . Groups [ groupID ]
}
2024-08-07 10:52:31 +02:00
// GetPeerNetworkMap returns the networkmap for the given peer ID.
func ( a * Account ) GetPeerNetworkMap (
ctx context . Context ,
peerID string ,
peersCustomZone nbdns . CustomZone ,
validatedPeersMap map [ string ] struct { } ,
metrics * telemetry . AccountManagerMetrics ,
) * NetworkMap {
start := time . Now ( )
2023-11-28 11:44:08 +01:00
peer := a . Peers [ peerID ]
if peer == nil {
return & NetworkMap {
Network : a . Network . Copy ( ) ,
}
}
2024-03-27 18:48:48 +01:00
if _ , ok := validatedPeersMap [ peerID ] ; ! ok {
2023-11-28 11:44:08 +01:00
return & NetworkMap {
Network : a . Network . Copy ( ) ,
}
}
2024-03-27 18:48:48 +01:00
2024-07-03 11:33:02 +02:00
aclPeers , firewallRules := a . getPeerConnectionResources ( ctx , peerID , validatedPeersMap )
2023-03-03 18:35:38 +01:00
// exclude expired peers
2023-11-28 13:45:26 +01:00
var peersToConnect [ ] * nbpeer . Peer
var expiredPeers [ ] * nbpeer . Peer
2023-03-03 18:35:38 +01:00
for _ , p := range aclPeers {
expired , _ := p . LoginExpired ( a . Settings . PeerLoginExpiration )
2023-03-09 11:24:42 +01:00
if a . Settings . PeerLoginExpirationEnabled && expired {
2023-03-07 10:17:25 +01:00
expiredPeers = append ( expiredPeers , p )
2023-03-03 18:35:38 +01:00
continue
}
peersToConnect = append ( peersToConnect , p )
}
2023-09-28 14:32:36 +02:00
2024-07-03 11:33:02 +02:00
routesUpdate := a . getRoutesToSync ( ctx , peerID , peersToConnect )
2023-03-03 18:35:38 +01:00
dnsManagementStatus := a . getPeerDNSManagementStatus ( peerID )
dnsUpdate := nbdns . Config {
ServiceEnable : dnsManagementStatus ,
}
if dnsManagementStatus {
var zones [ ] nbdns . CustomZone
2024-08-07 10:52:31 +02:00
2023-03-03 18:35:38 +01:00
if peersCustomZone . Domain != "" {
zones = append ( zones , peersCustomZone )
}
dnsUpdate . CustomZones = zones
dnsUpdate . NameServerGroups = getPeerNSGroups ( a , peerID )
}
2024-08-07 10:52:31 +02:00
nm := & NetworkMap {
2023-05-29 16:00:18 +02:00
Peers : peersToConnect ,
Network : a . Network . Copy ( ) ,
Routes : routesUpdate ,
DNSConfig : dnsUpdate ,
OfflinePeers : expiredPeers ,
FirewallRules : firewallRules ,
2023-03-03 18:35:38 +01:00
}
2024-08-07 10:52:31 +02:00
if metrics != nil {
objectCount := int64 ( len ( peersToConnect ) + len ( expiredPeers ) + len ( routesUpdate ) + len ( firewallRules ) )
metrics . CountNetworkMapObjects ( objectCount )
metrics . CountGetPeerNetworkMapDuration ( time . Since ( start ) )
2024-08-23 18:42:55 +02:00
if objectCount > 5000 {
log . WithContext ( ctx ) . Tracef ( "account: %s has a total resource count of %d objects, " +
"peers to connect: %d, expired peers: %d, routes: %d, firewall rules: %d" ,
a . Id , objectCount , len ( peersToConnect ) , len ( expiredPeers ) , len ( routesUpdate ) , len ( firewallRules ) )
}
2024-08-07 10:52:31 +02:00
}
return nm
}
func ( a * Account ) GetPeersCustomZone ( ctx context . Context , dnsDomain string ) nbdns . CustomZone {
var merr * multierror . Error
if dnsDomain == "" {
log . WithContext ( ctx ) . Error ( "no dns domain is set, returning empty zone" )
return nbdns . CustomZone { }
}
customZone := nbdns . CustomZone {
Domain : dns . Fqdn ( dnsDomain ) ,
Records : make ( [ ] nbdns . SimpleRecord , 0 , len ( a . Peers ) ) ,
}
domainSuffix := "." + dnsDomain
var sb strings . Builder
for _ , peer := range a . Peers {
if peer . DNSLabel == "" {
merr = multierror . Append ( merr , fmt . Errorf ( "peer %s has an empty DNS label" , peer . Name ) )
continue
}
sb . Grow ( len ( peer . DNSLabel ) + len ( domainSuffix ) )
sb . WriteString ( peer . DNSLabel )
sb . WriteString ( domainSuffix )
customZone . Records = append ( customZone . Records , nbdns . SimpleRecord {
Name : sb . String ( ) ,
Type : int ( dns . TypeA ) ,
Class : nbdns . DefaultClass ,
TTL : defaultTTL ,
RData : peer . IP . String ( ) ,
} )
sb . Reset ( )
}
go func ( ) {
if merr != nil {
log . WithContext ( ctx ) . Errorf ( "error generating custom zone for account %s: %v" , a . Id , merr )
}
} ( )
return customZone
2023-03-03 18:35:38 +01:00
}
2023-02-27 16:44:26 +01:00
// GetExpiredPeers returns peers that have been expired
2023-11-28 13:45:26 +01:00
func ( a * Account ) GetExpiredPeers ( ) [ ] * nbpeer . Peer {
var peers [ ] * nbpeer . Peer
2023-02-27 16:44:26 +01:00
for _ , peer := range a . GetPeersWithExpiration ( ) {
expired , _ := peer . LoginExpired ( a . Settings . PeerLoginExpiration )
if expired {
peers = append ( peers , peer )
}
}
return peers
}
// GetNextPeerExpiration returns the minimum duration in which the next peer of the account will expire if it was found.
// If there is no peer that expires this function returns false and a duration of 0.
// This function only considers peers that haven't been expired yet and that are connected.
func ( a * Account ) GetNextPeerExpiration ( ) ( time . Duration , bool ) {
peersWithExpiry := a . GetPeersWithExpiration ( )
if len ( peersWithExpiry ) == 0 {
return 0 , false
}
var nextExpiry * time . Duration
for _ , peer := range peersWithExpiry {
// consider only connected peers because others will require login on connecting to the management server
if peer . Status . LoginExpired || ! peer . Status . Connected {
continue
}
_ , duration := peer . LoginExpired ( a . Settings . PeerLoginExpiration )
if nextExpiry == nil || duration < * nextExpiry {
2024-03-06 15:18:53 +01:00
// if expiration is below 1s return 1s duration
// this avoids issues with ticker that can't be set to < 0
if duration < time . Second {
return time . Second , true
}
2023-02-27 16:44:26 +01:00
nextExpiry = & duration
}
}
if nextExpiry == nil {
return 0 , false
}
return * nextExpiry , true
}
2023-03-23 17:47:53 +01:00
// GetPeersWithExpiration returns a list of peers that have Peer.LoginExpirationEnabled set to true and that were added by a user
2023-11-28 13:45:26 +01:00
func ( a * Account ) GetPeersWithExpiration ( ) [ ] * nbpeer . Peer {
peers := make ( [ ] * nbpeer . Peer , 0 )
2023-02-27 16:44:26 +01:00
for _ , peer := range a . Peers {
2023-03-23 17:47:53 +01:00
if peer . LoginExpirationEnabled && peer . AddedWithSSOLogin ( ) {
2023-02-27 16:44:26 +01:00
peers = append ( peers , peer )
}
}
return peers
}
2022-11-07 12:10:56 +01:00
// GetPeers returns a list of all Account peers
2023-11-28 13:45:26 +01:00
func ( a * Account ) GetPeers ( ) [ ] * nbpeer . Peer {
var peers [ ] * nbpeer . Peer
2022-11-07 12:10:56 +01:00
for _ , peer := range a . Peers {
peers = append ( peers , peer )
}
return peers
}
2023-02-16 12:00:41 +01:00
// UpdateSettings saves new account settings
func ( a * Account ) UpdateSettings ( update * Settings ) * Account {
a . Settings = update . Copy ( )
return a
}
2022-11-07 12:10:56 +01:00
// UpdatePeer saves new or replaces existing peer
2023-11-28 13:45:26 +01:00
func ( a * Account ) UpdatePeer ( update * nbpeer . Peer ) {
2023-02-03 10:33:28 +01:00
a . Peers [ update . ID ] = update
2022-11-07 12:10:56 +01:00
}
// DeletePeer deletes peer from the account cleaning up all the references
2023-02-03 10:33:28 +01:00
func ( a * Account ) DeletePeer ( peerID string ) {
2022-11-07 12:10:56 +01:00
// delete peer from groups
for _ , g := range a . Groups {
for i , pk := range g . Peers {
2023-02-03 10:33:28 +01:00
if pk == peerID {
2022-11-07 12:10:56 +01:00
g . Peers = append ( g . Peers [ : i ] , g . Peers [ i + 1 : ] ... )
break
}
}
}
2022-11-25 18:11:07 +01:00
for _ , r := range a . Routes {
2023-02-03 10:33:28 +01:00
if r . Peer == peerID {
2022-11-25 18:11:07 +01:00
r . Enabled = false
r . Peer = ""
}
}
2023-02-03 10:33:28 +01:00
delete ( a . Peers , peerID )
2022-11-07 12:10:56 +01:00
a . Network . IncSerial ( )
}
// FindPeerByPubKey looks for a Peer by provided WireGuard public key in the Account or returns error if it wasn't found.
// It will return an object copy of the peer.
2023-11-28 13:45:26 +01:00
func ( a * Account ) FindPeerByPubKey ( peerPubKey string ) ( * nbpeer . Peer , error ) {
2022-11-07 12:10:56 +01:00
for _ , peer := range a . Peers {
if peer . Key == peerPubKey {
return peer . Copy ( ) , nil
}
}
2022-11-11 20:36:45 +01:00
return nil , status . Errorf ( status . NotFound , "peer with the public key %s not found" , peerPubKey )
2022-11-07 12:10:56 +01:00
}
2023-02-07 20:11:08 +01:00
// FindUserPeers returns a list of peers that user owns (created)
2023-11-28 13:45:26 +01:00
func ( a * Account ) FindUserPeers ( userID string ) ( [ ] * nbpeer . Peer , error ) {
peers := make ( [ ] * nbpeer . Peer , 0 )
2023-02-07 20:11:08 +01:00
for _ , peer := range a . Peers {
if peer . UserID == userID {
peers = append ( peers , peer )
}
}
return peers , nil
}
2022-11-05 10:24:50 +01:00
// FindUser looks for a given user in the Account or returns error if user wasn't found.
func ( a * Account ) FindUser ( userID string ) ( * User , error ) {
user := a . Users [ userID ]
if user == nil {
2022-11-11 20:36:45 +01:00
return nil , status . Errorf ( status . NotFound , "user %s not found" , userID )
2022-11-05 10:24:50 +01:00
}
return user , nil
}
2024-03-17 11:13:39 +01:00
// FindGroupByName looks for a given group in the Account by name or returns error if the group wasn't found.
2024-03-27 18:48:48 +01:00
func ( a * Account ) FindGroupByName ( groupName string ) ( * nbgroup . Group , error ) {
2024-03-17 11:13:39 +01:00
for _ , group := range a . Groups {
if group . Name == groupName {
return group , nil
}
}
return nil , status . Errorf ( status . NotFound , "group %s not found" , groupName )
}
2022-11-08 16:14:36 +01:00
// FindSetupKey looks for a given SetupKey in the Account or returns error if it wasn't found.
func ( a * Account ) FindSetupKey ( setupKey string ) ( * SetupKey , error ) {
key := a . SetupKeys [ setupKey ]
if key == nil {
2022-11-11 20:36:45 +01:00
return nil , status . Errorf ( status . NotFound , "setup key not found" )
2022-11-08 16:14:36 +01:00
}
return key , nil
}
2024-03-27 18:48:48 +01:00
// 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
}
2023-01-17 17:34:40 +01:00
func ( a * Account ) getPeerDNSManagementStatus ( peerID string ) bool {
peerGroups := a . getPeerGroups ( peerID )
enabled := true
2023-10-11 23:00:56 +02:00
for _ , groupID := range a . DNSSettings . DisabledManagementGroups {
_ , found := peerGroups [ groupID ]
if found {
enabled = false
break
2023-01-17 17:34:40 +01:00
}
}
return enabled
}
2022-12-06 10:11:57 +01:00
func ( a * Account ) getPeerGroups ( peerID string ) lookupMap {
groupList := make ( lookupMap )
for groupID , group := range a . Groups {
for _ , id := range group . Peers {
if id == peerID {
groupList [ groupID ] = struct { } { }
break
}
}
}
return groupList
}
2022-11-08 16:14:36 +01:00
func ( a * Account ) getTakenIPs ( ) [ ] net . IP {
var takenIps [ ] net . IP
for _ , existingPeer := range a . Peers {
takenIps = append ( takenIps , existingPeer . IP )
}
return takenIps
}
2022-11-07 15:38:21 +01:00
func ( a * Account ) getPeerDNSLabels ( ) lookupMap {
existingLabels := make ( lookupMap )
for _ , peer := range a . Peers {
if peer . DNSLabel != "" {
existingLabels [ peer . DNSLabel ] = struct { } { }
}
}
return existingLabels
}
2021-12-27 13:17:15 +01:00
func ( a * Account ) Copy ( ) * Account {
2023-11-28 13:45:26 +01:00
peers := map [ string ] * nbpeer . Peer { }
2021-12-27 13:17:15 +01:00
for id , peer := range a . Peers {
peers [ id ] = peer . Copy ( )
}
users := map [ string ] * User { }
for id , user := range a . Users {
users [ id ] = user . Copy ( )
}
setupKeys := map [ string ] * SetupKey { }
for id , key := range a . SetupKeys {
setupKeys [ id ] = key . Copy ( )
}
2024-03-27 18:48:48 +01:00
groups := map [ string ] * nbgroup . Group { }
2022-05-21 15:21:39 +02:00
for id , group := range a . Groups {
groups [ id ] = group . Copy ( )
}
2023-03-13 15:14:18 +01:00
policies := [ ] * Policy { }
for _ , policy := range a . Policies {
policies = append ( policies , policy . Copy ( ) )
}
2024-05-06 14:47:49 +02:00
routes := map [ route . ID ] * route . Route { }
2023-10-02 19:18:08 +02:00
for id , r := range a . Routes {
routes [ id ] = r . Copy ( )
2022-09-30 13:47:11 +02:00
}
nsGroups := map [ string ] * nbdns . NameServerGroup { }
for id , nsGroup := range a . NameServerGroups {
nsGroups [ id ] = nsGroup . Copy ( )
}
2023-10-11 23:00:56 +02:00
dnsSettings := a . DNSSettings . Copy ( )
2023-02-13 15:07:15 +01:00
var settings * Settings
if a . Settings != nil {
settings = a . Settings . Copy ( )
2023-01-17 17:34:40 +01:00
}
2024-02-20 09:59:56 +01:00
postureChecks := [ ] * posture . Checks { }
for _ , postureCheck := range a . PostureChecks {
postureChecks = append ( postureChecks , postureCheck . Copy ( ) )
}
2021-12-27 13:17:15 +01:00
return & Account {
2022-11-07 12:10:56 +01:00
Id : a . Id ,
CreatedBy : a . CreatedBy ,
2024-03-02 13:49:40 +01:00
CreatedAt : a . CreatedAt ,
2022-11-07 12:10:56 +01:00
Domain : a . Domain ,
DomainCategory : a . DomainCategory ,
IsDomainPrimaryAccount : a . IsDomainPrimaryAccount ,
SetupKeys : setupKeys ,
Network : a . Network . Copy ( ) ,
Peers : peers ,
Users : users ,
Groups : groups ,
2023-03-13 15:14:18 +01:00
Policies : policies ,
2022-11-07 12:10:56 +01:00
Routes : routes ,
NameServerGroups : nsGroups ,
2023-01-17 17:34:40 +01:00
DNSSettings : dnsSettings ,
2024-02-20 09:59:56 +01:00
PostureChecks : postureChecks ,
2023-02-13 15:07:15 +01:00
Settings : settings ,
2021-12-27 13:17:15 +01:00
}
2021-07-30 17:46:38 +02:00
}
2024-03-27 18:48:48 +01:00
func ( a * Account ) GetGroupAll ( ) ( * nbgroup . Group , error ) {
2022-05-21 15:21:39 +02:00
for _ , g := range a . Groups {
if g . Name == "All" {
return g , nil
}
}
return nil , fmt . Errorf ( "no group ALL found" )
}
2023-02-03 10:33:28 +01:00
// GetPeer looks up a Peer by ID
2023-11-28 13:45:26 +01:00
func ( a * Account ) GetPeer ( peerID string ) * nbpeer . Peer {
2023-02-03 10:33:28 +01:00
return a . Peers [ peerID ]
}
2024-06-13 08:55:09 +02:00
// SetJWTGroups updates the user's auto groups by synchronizing JWT groups.
// Returns true if there are changes in the JWT group membership.
2023-08-18 15:36:05 +02:00
func ( a * Account ) SetJWTGroups ( userID string , groupsNames [ ] string ) bool {
2023-08-07 17:44:51 +02:00
user , ok := a . Users [ userID ]
if ! ok {
return false
}
2024-03-27 18:48:48 +01:00
existedGroupsByName := make ( map [ string ] * nbgroup . Group )
2023-08-07 17:44:51 +02:00
for _ , group := range a . Groups {
existedGroupsByName [ group . Name ] = group
}
2024-06-13 08:55:09 +02:00
newAutoGroups , jwtGroupsMap := separateGroups ( user . AutoGroups , a . Groups )
groupsToAdd := difference ( groupsNames , maps . Keys ( jwtGroupsMap ) )
groupsToRemove := difference ( maps . Keys ( jwtGroupsMap ) , groupsNames )
// If no groups are added or removed, we should not sync account
if len ( groupsToAdd ) == 0 && len ( groupsToRemove ) == 0 {
return false
2023-06-27 16:51:05 +02:00
}
2023-08-07 17:44:51 +02:00
var modified bool
2024-06-13 08:55:09 +02:00
for _ , name := range groupsToAdd {
group , exists := existedGroupsByName [ name ]
if ! exists {
2024-03-27 18:48:48 +01:00
group = & nbgroup . Group {
2023-08-07 17:44:51 +02:00
ID : xid . New ( ) . String ( ) ,
2023-06-27 16:51:05 +02:00
Name : name ,
2024-03-27 18:48:48 +01:00
Issued : nbgroup . GroupIssuedJWT ,
2023-06-27 16:51:05 +02:00
}
2023-08-07 17:44:51 +02:00
a . Groups [ group . ID ] = group
}
2024-03-27 18:48:48 +01:00
if group . Issued == nbgroup . GroupIssuedJWT {
2024-06-13 08:55:09 +02:00
newAutoGroups = append ( newAutoGroups , group . ID )
modified = true
2023-06-27 16:51:05 +02:00
}
}
2023-08-07 17:44:51 +02:00
2024-06-13 08:55:09 +02:00
for name , id := range jwtGroupsMap {
if ! slices . Contains ( groupsToRemove , name ) {
newAutoGroups = append ( newAutoGroups , id )
continue
}
2023-08-18 15:36:05 +02:00
modified = true
}
2024-06-13 08:55:09 +02:00
user . AutoGroups = newAutoGroups
2023-08-18 15:36:05 +02:00
2023-08-07 17:44:51 +02:00
return modified
}
// UserGroupsAddToPeers adds groups to all peers of user
func ( a * Account ) UserGroupsAddToPeers ( userID string , groups ... string ) {
userPeers := make ( map [ string ] struct { } )
for pid , peer := range a . Peers {
if peer . UserID == userID {
userPeers [ pid ] = struct { } { }
}
}
for _ , gid := range groups {
group , ok := a . Groups [ gid ]
if ! ok {
continue
}
groupPeers := make ( map [ string ] struct { } )
for _ , pid := range group . Peers {
groupPeers [ pid ] = struct { } { }
}
for pid := range userPeers {
groupPeers [ pid ] = struct { } { }
}
group . Peers = group . Peers [ : 0 ]
for pid := range groupPeers {
group . Peers = append ( group . Peers , pid )
}
}
}
// UserGroupsRemoveFromPeers removes groups from all peers of user
func ( a * Account ) UserGroupsRemoveFromPeers ( userID string , groups ... string ) {
for _ , gid := range groups {
group , ok := a . Groups [ gid ]
2024-08-12 12:48:05 +02:00
if ! ok || group . Name == "All" {
2023-08-07 17:44:51 +02:00
continue
}
update := make ( [ ] string , 0 , len ( group . Peers ) )
for _ , pid := range group . Peers {
peer , ok := a . Peers [ pid ]
if ! ok {
continue
}
if peer . UserID != userID {
update = append ( update , pid )
}
}
group . Peers = update
}
2023-06-27 16:51:05 +02:00
}
2022-05-21 15:21:39 +02:00
// BuildManager creates a new DefaultAccountManager with a provided Store
2024-08-07 10:52:31 +02:00
func BuildManager (
ctx context . Context ,
store Store ,
peersUpdateManager * PeersUpdateManager ,
idpManager idp . Manager ,
singleAccountModeDomain string ,
dnsDomain string ,
eventStore activity . Store ,
geo * geolocation . Geolocation ,
2024-02-20 09:59:56 +01:00
userDeleteFromIDPEnabled bool ,
2024-03-27 18:48:48 +01:00
integratedPeerValidator integrated_validator . IntegratedValidator ,
2024-08-07 10:52:31 +02:00
metrics telemetry . AppMetrics ,
2023-02-03 21:47:20 +01:00
) ( * DefaultAccountManager , error ) {
2022-06-05 21:36:42 +02:00
am := & DefaultAccountManager {
2023-09-19 18:08:40 +02:00
Store : store ,
2024-02-20 09:59:56 +01:00
geo : geo ,
2023-09-19 18:08:40 +02:00
peersUpdateManager : peersUpdateManager ,
idpManager : idpManager ,
ctx : context . Background ( ) ,
cacheMux : sync . Mutex { } ,
cacheLoading : map [ string ] chan struct { } { } ,
dnsDomain : dnsDomain ,
eventStore : eventStore ,
peerLoginExpiry : NewDefaultScheduler ( ) ,
userDeleteFromIDPEnabled : userDeleteFromIDPEnabled ,
2024-03-27 18:48:48 +01:00
integratedPeerValidator : integratedPeerValidator ,
2024-08-07 10:52:31 +02:00
metrics : metrics ,
2024-08-21 11:44:52 +02:00
requestBuffer : NewAccountRequestBuffer ( ctx , store ) ,
2021-07-30 17:46:38 +02:00
}
2024-07-03 11:33:02 +02:00
allAccounts := store . GetAllAccounts ( ctx )
2022-10-19 17:43:28 +02:00
// 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 {
2022-12-13 13:43:29 +01:00
if ! isDomainValid ( singleAccountModeDomain ) {
2023-02-27 16:44:26 +01:00
return nil , status . Errorf ( status . InvalidArgument , "invalid domain \"%s\" provided for a single account mode. Please review your input for --single-account-mode-domain" , singleAccountModeDomain )
2022-12-13 13:43:29 +01:00
}
2022-10-19 17:43:28 +02:00
am . singleAccountModeDomain = singleAccountModeDomain
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "single account mode enabled, accounts number %d" , len ( allAccounts ) )
2022-10-19 17:43:28 +02:00
} else {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "single account mode disabled, accounts number %d" , len ( allAccounts ) )
2022-10-19 17:43:28 +02:00
}
2022-05-21 15:21:39 +02:00
2022-10-19 17:43:28 +02:00
// if account doesn't have a default group
2022-06-06 13:45:59 +02:00
// we create 'all' group and add all peers into it
// also we create default rule with source as destination
2022-10-19 17:43:28 +02:00
for _ , account := range allAccounts {
2022-11-07 15:38:21 +01:00
shouldSave := false
2022-06-06 13:45:59 +02:00
_ , err := account . GetGroupAll ( )
if err != nil {
2023-03-13 15:14:18 +01:00
if err := addAllGroup ( account ) ; err != nil {
return nil , err
}
2022-11-07 15:38:21 +01:00
shouldSave = true
}
if shouldSave {
2024-07-03 11:33:02 +02:00
err = store . SaveAccount ( ctx , account )
2022-11-07 15:38:21 +01:00
if err != nil {
2022-06-06 13:45:59 +02:00
return nil , err
}
2022-05-21 15:21:39 +02:00
}
}
2022-10-19 17:43:28 +02:00
goCacheClient := gocache . New ( CacheExpirationMax , 30 * time . Minute )
goCacheStore := cacheStore . NewGoCache ( goCacheClient )
am . cacheManager = cache . NewLoadable [ [ ] * idp . UserData ] ( am . loadAccount , cache . New [ [ ] * idp . UserData ] ( goCacheStore ) )
2022-06-06 12:05:44 +02:00
2023-11-13 14:04:18 +01:00
// TODO: what is max expiration time? Should be quite long
am . externalCacheManager = cache . New [ * idp . UserData ] (
cacheStore . NewGoCache ( goCacheClient ) ,
)
2022-06-06 12:05:44 +02:00
if ! isNil ( am . idpManager ) {
go func ( ) {
2024-07-03 11:33:02 +02:00
err := am . warmupIDPCache ( ctx )
2022-06-06 12:05:44 +02:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "failed warming up cache due to error: %v" , err )
2023-02-03 21:47:20 +01:00
// todo retry?
2022-06-06 12:05:44 +02:00
return
}
} ( )
}
2024-07-03 11:33:02 +02:00
am . integratedPeerValidator . SetPeerInvalidationListener ( func ( accountID string ) {
am . onPeersInvalidated ( ctx , accountID )
} )
2024-03-27 18:48:48 +01:00
2022-06-05 21:36:42 +02:00
return am , nil
2021-07-30 17:46:38 +02:00
}
2023-11-13 14:04:18 +01:00
func ( am * DefaultAccountManager ) GetExternalCacheManager ( ) ExternalCacheManager {
return am . externalCacheManager
}
2024-02-28 16:57:35 +01:00
func ( am * DefaultAccountManager ) GetIdpManager ( ) idp . Manager {
return am . idpManager
}
2023-02-16 12:00:41 +01:00
// UpdateAccountSettings updates Account settings.
// Only users with role UserRoleAdmin can update the account.
// User that performs the update has to belong to the account.
// Returns an updated Account
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) UpdateAccountSettings ( ctx context . Context , accountID , userID string , newSettings * Settings ) ( * Account , error ) {
2023-02-16 12:00:41 +01:00
halfYearLimit := 180 * 24 * time . Hour
if newSettings . PeerLoginExpiration > halfYearLimit {
return nil , status . Errorf ( status . InvalidArgument , "peer login expiration can't be larger than 180 days" )
}
if newSettings . PeerLoginExpiration < time . Hour {
return nil , status . Errorf ( status . InvalidArgument , "peer login expiration can't be smaller than one hour" )
}
2024-07-31 14:53:32 +02:00
unlock := am . Store . AcquireWriteLockByUID ( ctx , accountID )
2023-02-16 12:00:41 +01:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2023-11-28 11:44:08 +01:00
if err != nil {
return nil , err
}
2023-02-16 12:00:41 +01:00
user , err := account . FindUser ( userID )
if err != nil {
return nil , err
}
2023-12-01 17:24:57 +01:00
if ! user . HasAdminPower ( ) {
2023-02-16 12:00:41 +01:00
return nil , status . Errorf ( status . PermissionDenied , "user is not allowed to update account" )
}
2024-07-03 11:33:02 +02:00
err = am . integratedPeerValidator . ValidateExtraSettings ( ctx , newSettings . Extra , account . Settings . Extra , account . Peers , userID , accountID )
2024-02-27 14:17:22 +01:00
if err != nil {
return nil , err
}
2023-02-16 12:00:41 +01:00
oldSettings := account . Settings
if oldSettings . PeerLoginExpirationEnabled != newSettings . PeerLoginExpirationEnabled {
event := activity . AccountPeerLoginExpirationEnabled
if ! newSettings . PeerLoginExpirationEnabled {
event = activity . AccountPeerLoginExpirationDisabled
2024-07-03 11:33:02 +02:00
am . peerLoginExpiry . Cancel ( ctx , [ ] string { accountID } )
2023-02-27 16:44:26 +01:00
} else {
2024-07-03 11:33:02 +02:00
am . checkAndSchedulePeerLoginExpiration ( ctx , account )
2023-02-16 12:00:41 +01:00
}
2024-07-03 11:33:02 +02:00
am . StoreEvent ( ctx , userID , accountID , accountID , event , nil )
2023-02-16 12:00:41 +01:00
}
if oldSettings . PeerLoginExpiration != newSettings . PeerLoginExpiration {
2024-07-03 11:33:02 +02:00
am . StoreEvent ( ctx , userID , accountID , accountID , activity . AccountPeerLoginExpirationDurationUpdated , nil )
am . checkAndSchedulePeerLoginExpiration ( ctx , account )
2023-02-16 12:00:41 +01:00
}
updatedAccount := account . UpdateSettings ( newSettings )
2024-07-03 11:33:02 +02:00
err = am . Store . SaveAccount ( ctx , account )
2023-02-16 12:00:41 +01:00
if err != nil {
return nil , err
}
return updatedAccount , nil
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) peerLoginExpirationJob ( ctx context . Context , accountID string ) func ( ) ( time . Duration , bool ) {
2023-02-27 16:44:26 +01:00
return func ( ) ( time . Duration , bool ) {
2024-07-31 14:53:32 +02:00
unlock := am . Store . AcquireWriteLockByUID ( ctx , accountID )
2023-02-27 16:44:26 +01:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2023-02-27 16:44:26 +01:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "failed getting account %s expiring peers" , accountID )
2023-02-27 16:44:26 +01:00
return account . GetNextPeerExpiration ( )
}
2023-09-19 18:08:40 +02:00
expiredPeers := account . GetExpiredPeers ( )
2023-02-27 16:44:26 +01:00
var peerIDs [ ] string
2023-09-19 18:08:40 +02:00
for _ , peer := range expiredPeers {
2023-02-27 16:44:26 +01:00
peerIDs = append ( peerIDs , peer . ID )
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "discovered %d peers to expire for account %s" , len ( peerIDs ) , account . Id )
2023-02-27 16:44:26 +01:00
2024-07-03 11:33:02 +02:00
if err := am . expireAndUpdatePeers ( ctx , account , expiredPeers ) ; err != nil {
log . WithContext ( ctx ) . Errorf ( "failed updating account peers while expiring peers for account %s" , account . Id )
2023-09-19 18:08:40 +02:00
return account . GetNextPeerExpiration ( )
2023-02-27 16:44:26 +01:00
}
2023-09-19 18:08:40 +02:00
2023-02-27 16:44:26 +01:00
return account . GetNextPeerExpiration ( )
}
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) checkAndSchedulePeerLoginExpiration ( ctx context . Context , account * Account ) {
am . peerLoginExpiry . Cancel ( ctx , [ ] string { account . Id } )
2023-02-27 16:44:26 +01:00
if nextRun , ok := account . GetNextPeerExpiration ( ) ; ok {
2024-07-03 11:33:02 +02:00
go am . peerLoginExpiry . Schedule ( ctx , nextRun , account . Id , am . peerLoginExpirationJob ( ctx , account . Id ) )
2023-02-27 16:44:26 +01:00
}
}
2022-06-20 18:20:43 +02:00
// newAccount creates a new Account with a generated ID and generated default setup keys.
// If ID is already in use (due to collision) we try one more time before returning error
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) newAccount ( ctx context . Context , userID , domain string ) ( * Account , error ) {
2022-06-20 18:20:43 +02:00
for i := 0 ; i < 2 ; i ++ {
accountId := xid . New ( ) . String ( )
2024-07-03 11:33:02 +02:00
_ , err := am . Store . GetAccount ( ctx , accountId )
2022-06-20 18:20:43 +02:00
statusErr , _ := status . FromError ( err )
2023-11-27 16:40:02 +01:00
switch {
case err == nil :
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "an account with ID already exists, retrying..." )
2022-06-20 18:20:43 +02:00
continue
2023-11-27 16:40:02 +01:00
case statusErr . Type ( ) == status . NotFound :
2024-07-03 11:33:02 +02:00
newAccount := newAccountWithId ( ctx , accountId , userID , domain )
am . StoreEvent ( ctx , userID , newAccount . Id , accountId , activity . AccountCreated , nil )
2023-01-02 15:11:32 +01:00
return newAccount , nil
2023-11-27 16:40:02 +01:00
default :
2022-06-20 18:20:43 +02:00
return nil , err
}
}
2022-11-11 20:36:45 +01:00
return nil , status . Errorf ( status . Internal , "error while creating new account" )
2022-06-20 18:20:43 +02:00
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) warmupIDPCache ( ctx context . Context ) error {
userData , err := am . idpManager . GetAllAccounts ( ctx )
2022-06-06 12:05:44 +02:00
if err != nil {
return err
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "%d entries received from IdP management" , len ( userData ) )
2022-06-06 12:05:44 +02:00
2023-10-03 16:40:28 +02:00
// If the Identity Provider does not support writing AppMetadata,
// in cases like this, we expect it to return all users in an "unset" field.
// We iterate over the users in the "unset" field, look up their AccountID in our store, and
// update their AppMetadata with the AccountID.
if unsetData , ok := userData [ idp . UnsetAccountID ] ; ok {
for _ , user := range unsetData {
2024-07-03 11:33:02 +02:00
accountID , err := am . Store . GetAccountByUser ( ctx , user . ID )
2023-10-03 16:40:28 +02:00
if err == nil {
data := userData [ accountID . Id ]
if data == nil {
data = make ( [ ] * idp . UserData , 0 , 1 )
}
user . AppMetadata . WTAccountID = accountID . Id
userData [ accountID . Id ] = append ( data , user )
}
}
}
delete ( userData , idp . UnsetAccountID )
2024-04-22 11:10:27 +02:00
rcvdUsers := 0
2022-06-06 12:05:44 +02:00
for accountID , users := range userData {
2024-04-22 11:10:27 +02:00
rcvdUsers += len ( users )
2022-10-13 18:26:31 +02:00
err = am . cacheManager . Set ( am . ctx , accountID , users , cacheStore . WithExpiration ( cacheEntryExpiration ( ) ) )
2022-06-06 12:05:44 +02:00
if err != nil {
return err
}
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "warmed up IDP cache with %d entries for %d accounts" , rcvdUsers , len ( userData ) )
2022-06-06 12:05:44 +02:00
return nil
}
2023-11-28 14:23:38 +01:00
// DeleteAccount deletes an account and all its users from local store and from the remote IDP if the requester is an admin and account owner
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) DeleteAccount ( ctx context . Context , accountID , userID string ) error {
2024-07-31 14:53:32 +02:00
unlock := am . Store . AcquireWriteLockByUID ( ctx , accountID )
2023-11-28 14:23:38 +01:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2023-11-28 14:23:38 +01:00
if err != nil {
return err
}
user , err := account . FindUser ( userID )
if err != nil {
return err
}
2023-12-01 17:24:57 +01:00
if ! user . HasAdminPower ( ) {
2023-11-28 14:23:38 +01:00
return status . Errorf ( status . PermissionDenied , "user is not allowed to delete account" )
}
2024-04-11 10:02:51 +02:00
if user . Role != UserRoleOwner {
2023-11-28 14:23:38 +01:00
return status . Errorf ( status . PermissionDenied , "user is not allowed to delete account. Only account owner can delete account" )
}
for _ , otherUser := range account . Users {
if otherUser . IsServiceUser {
continue
}
if otherUser . Id == userID {
continue
}
2024-07-03 11:33:02 +02:00
deleteUserErr := am . deleteRegularUser ( ctx , account , userID , otherUser . Id )
2023-11-28 14:23:38 +01:00
if deleteUserErr != nil {
return deleteUserErr
}
}
2024-07-03 11:33:02 +02:00
err = am . deleteRegularUser ( ctx , account , userID , userID )
2023-11-28 14:23:38 +01:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "failed deleting user %s. error: %s" , userID , err )
2023-11-28 14:23:38 +01:00
return err
}
2024-07-03 11:33:02 +02:00
err = am . Store . DeleteAccount ( ctx , account )
2023-11-28 14:23:38 +01:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "failed deleting account %s. error: %s" , accountID , err )
2023-11-28 14:23:38 +01:00
return err
}
2024-01-04 17:10:55 +01:00
// cancel peer login expiry job
2024-07-03 11:33:02 +02:00
am . peerLoginExpiry . Cancel ( ctx , [ ] string { account . Id } )
2024-01-04 17:10:55 +01:00
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "account %s deleted" , accountID )
2023-11-28 14:23:38 +01:00
return nil
}
2022-11-07 17:52:23 +01:00
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
// userID doesn't have an account associated with it, one account is created
2024-02-22 12:27:08 +01:00
// domain is used to create a new account if no account is found
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) GetAccountByUserOrAccountID ( ctx context . Context , userID , accountID , domain string ) ( * Account , error ) {
2022-11-07 17:52:23 +01:00
if accountID != "" {
2024-07-03 11:33:02 +02:00
return am . Store . GetAccount ( ctx , accountID )
2022-11-07 17:52:23 +01:00
} else if userID != "" {
2024-07-03 11:33:02 +02:00
account , err := am . GetOrCreateAccountByUser ( ctx , userID , domain )
2022-01-24 11:21:30 +01:00
if err != nil {
2022-11-11 20:36:45 +01:00
return nil , status . Errorf ( status . NotFound , "account not found using user id: %s" , userID )
2022-01-24 11:21:30 +01:00
}
2024-07-03 11:33:02 +02:00
err = am . addAccountIDToIDPAppMeta ( ctx , userID , account )
2022-03-01 15:22:18 +01:00
if err != nil {
return nil , err
2022-01-24 11:21:30 +01:00
}
return account , nil
}
2022-11-11 20:36:45 +01:00
return nil , status . Errorf ( status . NotFound , "no valid user or account Id provided" )
2022-01-24 11:21:30 +01:00
}
2022-05-05 08:58:34 +02:00
func isNil ( i idp . Manager ) bool {
return i == nil || reflect . ValueOf ( i ) . IsNil ( )
}
2022-10-13 18:26:31 +02:00
// addAccountIDToIDPAppMeta update user's app metadata in idp manager
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) addAccountIDToIDPAppMeta ( ctx context . Context , userID string , account * Account ) error {
2022-05-05 08:58:34 +02:00
if ! isNil ( am . idpManager ) {
2022-10-13 18:26:31 +02:00
// user can be nil if it wasn't found (e.g., just created)
2024-07-03 11:33:02 +02:00
user , err := am . lookupUserInCache ( ctx , userID , account )
2022-10-13 18:26:31 +02:00
if err != nil {
return err
}
if user != nil && user . AppMetadata . WTAccountID == account . Id {
// it was already set, so we skip the unnecessary update
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "skipping IDP App Meta update because accountID %s has been already set for user %s" ,
2022-10-13 18:26:31 +02:00
account . Id , userID )
return nil
}
2024-07-03 11:33:02 +02:00
err = am . idpManager . UpdateUserAppMetadata ( ctx , userID , idp . AppMetadata { WTAccountID : account . Id } )
2022-03-01 15:22:18 +01:00
if err != nil {
2022-11-11 20:36:45 +01:00
return status . Errorf ( status . Internal , "updating user's app metadata failed with: %v" , err )
2022-03-01 15:22:18 +01:00
}
2022-10-13 18:26:31 +02:00
// refresh cache to reflect the update
2024-07-03 11:33:02 +02:00
_ , err = am . refreshCache ( ctx , account . Id )
2022-10-13 18:26:31 +02:00
if err != nil {
return err
}
2022-03-01 15:22:18 +01:00
}
return nil
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) loadAccount ( ctx context . Context , accountID interface { } ) ( [ ] * idp . UserData , error ) {
log . WithContext ( ctx ) . Debugf ( "account %s not found in cache, reloading" , accountID )
2023-10-02 19:18:08 +02:00
accountIDString := fmt . Sprintf ( "%v" , accountID )
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountIDString )
2023-10-02 19:18:08 +02:00
if err != nil {
return nil , err
}
2024-07-03 11:33:02 +02:00
userData , err := am . idpManager . GetAccount ( ctx , accountIDString )
2023-10-02 19:18:08 +02:00
if err != nil {
return nil , err
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "%d entries received from IdP management" , len ( userData ) )
2023-10-02 19:18:08 +02:00
dataMap := make ( map [ string ] * idp . UserData , len ( userData ) )
for _ , datum := range userData {
dataMap [ datum . ID ] = datum
}
matchedUserData := make ( [ ] * idp . UserData , 0 )
for _ , user := range account . Users {
if user . IsServiceUser {
continue
}
datum , ok := dataMap [ user . Id ]
if ! ok {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "user %s not found in IDP" , user . Id )
2023-10-02 19:18:08 +02:00
continue
}
matchedUserData = append ( matchedUserData , datum )
}
return matchedUserData , nil
2022-06-05 21:36:42 +02:00
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) lookupUserInCacheByEmail ( ctx context . Context , email string , accountID string ) ( * idp . UserData , error ) {
data , err := am . getAccountFromCache ( ctx , accountID , false )
2022-09-22 09:06:32 +02:00
if err != nil {
return nil , err
}
2022-10-13 18:26:31 +02:00
for _ , datum := range data {
if datum . Email == email {
2022-09-22 09:06:32 +02:00
return datum , nil
}
}
2023-09-04 17:03:44 +02:00
return nil , nil //nolint:nilnil
2022-09-22 09:06:32 +02:00
}
2022-10-13 18:26:31 +02:00
// lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) lookupUserInCache ( ctx context . Context , userID string , account * Account ) ( * idp . UserData , error ) {
2024-04-22 11:10:27 +02:00
users := make ( map [ string ] userLoggedInOnce , len ( account . Users ) )
2023-11-13 14:04:18 +01:00
// ignore service users and users provisioned by integrations than are never logged in
2022-10-13 18:26:31 +02:00
for _ , user := range account . Users {
2023-11-13 14:04:18 +01:00
if user . IsServiceUser {
continue
}
2023-11-20 12:05:32 +01:00
if user . Issued == UserIssuedIntegration {
2023-11-13 14:04:18 +01:00
continue
2023-05-10 19:27:17 +02:00
}
2024-04-22 11:10:27 +02:00
users [ user . Id ] = userLoggedInOnce ( ! user . LastLogin . IsZero ( ) )
2022-10-13 18:26:31 +02:00
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "looking up user %s of account %s in cache" , userID , account . Id )
userData , err := am . lookupCache ( ctx , users , account . Id )
2022-06-05 21:36:42 +02:00
if err != nil {
return nil , err
}
2022-10-13 18:26:31 +02:00
for _ , datum := range userData {
if datum . ID == userID {
return datum , nil
}
}
2024-02-13 13:10:17 +01:00
// add extra check on external cache manager. We may get to this point when the user is not yet findable in IDP,
// or it didn't have its metadata updated with am.addAccountIDToIDPAppMeta
2024-02-07 16:14:30 +01:00
user , err := account . FindUser ( userID )
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "failed finding user %s in account %s" , userID , account . Id )
2024-02-07 16:14:30 +01:00
return nil , err
}
key := user . IntegrationReference . CacheKey ( account . Id , userID )
ud , err := am . externalCacheManager . Get ( am . ctx , key )
2024-02-12 21:54:16 +01:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "failed to get externalCache for key: %s, error: %s" , key , err )
2024-02-07 16:14:30 +01:00
}
2024-02-12 21:54:16 +01:00
return ud , nil
2022-10-13 18:26:31 +02:00
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) refreshCache ( ctx context . Context , accountID string ) ( [ ] * idp . UserData , error ) {
return am . getAccountFromCache ( ctx , accountID , true )
2022-10-13 18:26:31 +02:00
}
// getAccountFromCache returns user data for a given account ensuring that cache load happens only once
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) getAccountFromCache ( ctx context . Context , accountID string , forceReload bool ) ( [ ] * idp . UserData , error ) {
2022-10-13 18:26:31 +02:00
am . cacheMux . Lock ( )
loadingChan := am . cacheLoading [ accountID ]
if loadingChan == nil {
loadingChan = make ( chan struct { } )
am . cacheLoading [ accountID ] = loadingChan
am . cacheMux . Unlock ( )
defer func ( ) {
am . cacheMux . Lock ( )
delete ( am . cacheLoading , accountID )
close ( loadingChan )
am . cacheMux . Unlock ( )
} ( )
if forceReload {
err := am . cacheManager . Delete ( am . ctx , accountID )
if err != nil {
return nil , err
}
}
return am . cacheManager . Get ( am . ctx , accountID )
}
am . cacheMux . Unlock ( )
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "one request to get account %s is already running" , accountID )
2022-10-13 18:26:31 +02:00
select {
case <- loadingChan :
// channel has been closed meaning cache was loaded => simply return from cache
return am . cacheManager . Get ( am . ctx , accountID )
case <- time . After ( 5 * time . Second ) :
return nil , fmt . Errorf ( "timeout while waiting for account %s cache to reload" , accountID )
}
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) lookupCache ( ctx context . Context , accountUsers map [ string ] userLoggedInOnce , accountID string ) ( [ ] * idp . UserData , error ) {
2024-04-23 19:23:43 +02:00
var data [ ] * idp . UserData
var err error
maxAttempts := 2
2024-07-03 11:33:02 +02:00
data , err = am . getAccountFromCache ( ctx , accountID , false )
2022-10-13 18:26:31 +02:00
if err != nil {
return nil , err
}
2022-06-05 21:36:42 +02:00
2024-04-23 19:23:43 +02:00
for attempt := 1 ; attempt <= maxAttempts ; attempt ++ {
2024-07-03 11:33:02 +02:00
if am . isCacheFresh ( ctx , accountUsers , data ) {
2024-04-23 19:23:43 +02:00
return data , nil
}
if attempt > 1 {
time . Sleep ( 200 * time . Millisecond )
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "refreshing cache for account %s" , accountID )
data , err = am . refreshCache ( ctx , accountID )
2024-04-23 19:23:43 +02:00
if err != nil {
return nil , err
}
if attempt == maxAttempts {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "cache for account %s reached maximum refresh attempts (%d)" , accountID , maxAttempts )
2024-04-23 19:23:43 +02:00
}
}
return data , nil
}
// isCacheFresh checks if the cache is refreshed already by comparing the accountUsers with the cache data by user count and user invite status
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) isCacheFresh ( ctx context . Context , accountUsers map [ string ] userLoggedInOnce , data [ ] * idp . UserData ) bool {
2024-04-22 11:10:27 +02:00
userDataMap := make ( map [ string ] * idp . UserData , len ( data ) )
2022-10-13 18:26:31 +02:00
for _ , datum := range data {
2024-04-22 11:10:27 +02:00
userDataMap [ datum . ID ] = datum
2022-06-05 21:36:42 +02:00
}
2023-11-20 16:47:11 +01:00
// the accountUsers ID list of non integration users from store, we check if cache has all of them
// as result of for loop knownUsersCount will have number of users are not presented in the cashed
knownUsersCount := len ( accountUsers )
2024-04-22 11:10:27 +02:00
for user , loggedInOnce := range accountUsers {
if datum , ok := userDataMap [ user ] ; ok {
// check if the matching user data has a pending invite and if the user has logged in once, forcing the cache to be refreshed
if datum . AppMetadata . WTPendingInvite != nil && * datum . AppMetadata . WTPendingInvite && loggedInOnce == true { //nolint:gosimple
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "user %s has a pending invite and has logged in once, cache invalid" , user )
2024-04-23 19:23:43 +02:00
return false
2024-04-22 11:10:27 +02:00
}
2023-11-20 16:47:11 +01:00
knownUsersCount --
continue
2022-06-05 21:36:42 +02:00
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "cache doesn't know about %s user" , user )
2022-06-05 21:36:42 +02:00
}
2023-11-20 16:47:11 +01:00
// if we know users that are not yet in cache more likely cache is outdated
2024-04-23 19:23:43 +02:00
if knownUsersCount > 0 {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "cache invalid. Users unknown to the cache: %d" , knownUsersCount )
2024-04-23 19:23:43 +02:00
return false
2022-06-05 21:36:42 +02:00
}
2024-04-23 19:23:43 +02:00
return true
2022-06-05 21:36:42 +02:00
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) removeUserFromCache ( ctx context . Context , accountID , userID string ) error {
data , err := am . getAccountFromCache ( ctx , accountID , false )
2024-01-01 19:17:44 +01:00
if err != nil {
return err
}
for i , datum := range data {
if datum . ID == userID {
data = append ( data [ : i ] , data [ i + 1 : ] ... )
break
}
}
return am . cacheManager . Set ( am . ctx , accountID , data , cacheStore . WithExpiration ( cacheEntryExpiration ( ) ) )
}
2022-03-01 15:22:18 +01:00
// updateAccountDomainAttributes updates the account domain attributes and then, saves the account
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) updateAccountDomainAttributes ( ctx context . Context , account * Account , claims jwtclaims . AuthorizationClaims ,
2023-02-03 21:47:20 +01:00
primaryDomain bool ,
) error {
2022-06-20 18:20:43 +02:00
2024-03-14 14:17:22 +01:00
if claims . Domain != "" {
account . IsDomainPrimaryAccount = primaryDomain
lowerDomain := strings . ToLower ( claims . Domain )
userObj := account . Users [ claims . UserId ]
if account . Domain != lowerDomain && userObj . Role == UserRoleAdmin {
account . Domain = lowerDomain
}
// prevent updating category for different domain until admin logs in
if account . Domain == lowerDomain {
account . DomainCategory = claims . DomainCategory
}
} else {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "claims don't contain a valid domain, skipping domain attributes update. Received claims: %v" , claims )
2022-06-20 18:20:43 +02:00
}
2024-07-03 11:33:02 +02:00
err := am . Store . SaveAccount ( ctx , account )
2022-03-01 15:22:18 +01:00
if err != nil {
2022-11-11 20:36:45 +01:00
return err
2022-03-01 15:22:18 +01:00
}
return nil
}
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
2022-05-03 16:02:51 +02:00
func ( am * DefaultAccountManager ) handleExistingUserAccount (
2024-07-03 11:33:02 +02:00
ctx context . Context ,
2022-05-03 16:02:51 +02:00
existingAcc * Account ,
2024-04-23 19:09:58 +02:00
primaryDomain bool ,
2022-05-03 16:02:51 +02:00
claims jwtclaims . AuthorizationClaims ,
) error {
2024-07-03 11:33:02 +02:00
err := am . updateAccountDomainAttributes ( ctx , existingAcc , claims , primaryDomain )
2024-04-23 19:09:58 +02:00
if err != nil {
return err
2022-03-01 15:22:18 +01:00
}
// we should register the account ID to this user's metadata in our IDP manager
2024-07-03 11:33:02 +02:00
err = am . addAccountIDToIDPAppMeta ( ctx , claims . UserId , existingAcc )
2022-03-01 15:22:18 +01:00
if err != nil {
return err
}
return nil
}
// handleNewUserAccount validates if there is an existing primary account for the domain, if so it adds the new user to that account,
// otherwise it will create a new account and make it primary account for the domain.
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) handleNewUserAccount ( ctx context . Context , domainAcc * Account , claims jwtclaims . AuthorizationClaims ) ( * Account , error ) {
2023-02-03 21:47:20 +01:00
if claims . UserId == "" {
return nil , fmt . Errorf ( "user ID is empty" )
}
2022-03-01 15:22:18 +01:00
var (
2022-03-10 13:47:36 +01:00
account * Account
err error
2022-03-01 15:22:18 +01:00
)
lowerDomain := strings . ToLower ( claims . Domain )
// if domain already has a primary account, add regular user
if domainAcc != nil {
account = domainAcc
account . Users [ claims . UserId ] = NewRegularUser ( claims . UserId )
2024-07-03 11:33:02 +02:00
err = am . Store . SaveAccount ( ctx , account )
2022-05-09 14:30:20 +02:00
if err != nil {
2022-11-11 20:36:45 +01:00
return nil , err
2022-05-09 14:30:20 +02:00
}
2022-03-01 15:22:18 +01:00
} else {
2024-07-03 11:33:02 +02:00
account , err = am . newAccount ( ctx , claims . UserId , lowerDomain )
2022-06-20 18:20:43 +02:00
if err != nil {
return nil , err
}
2024-07-03 11:33:02 +02:00
err = am . updateAccountDomainAttributes ( ctx , account , claims , true )
2022-03-10 13:47:36 +01:00
if err != nil {
return nil , err
}
2022-03-01 15:22:18 +01:00
}
2024-07-03 11:33:02 +02:00
err = am . addAccountIDToIDPAppMeta ( ctx , claims . UserId , account )
2022-10-13 18:26:31 +02:00
if err != nil {
return nil , err
}
2024-07-03 11:33:02 +02:00
am . StoreEvent ( ctx , claims . UserId , claims . UserId , account . Id , activity . UserJoined , nil )
2023-01-02 15:11:32 +01:00
2022-10-13 18:26:31 +02:00
return account , nil
}
// redeemInvite checks whether user has been invited and redeems the invite
2024-09-18 14:55:52 +02:00
func ( am * DefaultAccountManager ) redeemInvite ( ctx context . Context , account * Account , userID string ) error {
2022-10-13 18:26:31 +02:00
// only possible with the enabled IdP manager
if am . idpManager == nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "invites only work with enabled IdP manager" )
2022-10-13 18:26:31 +02:00
return nil
}
2024-07-03 11:33:02 +02:00
user , err := am . lookupUserInCache ( ctx , userID , account )
2022-10-13 18:26:31 +02:00
if err != nil {
return err
}
if user == nil {
2022-11-11 20:36:45 +01:00
return status . Errorf ( status . NotFound , "user %s not found in the IdP" , userID )
2022-10-13 18:26:31 +02:00
}
2022-10-19 17:51:41 +02:00
if user . AppMetadata . WTPendingInvite != nil && * user . AppMetadata . WTPendingInvite {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Infof ( "redeeming invite for user %s account %s" , userID , account . Id )
2022-10-13 18:26:31 +02:00
// User has already logged in, meaning that IdP should have set wt_pending_invite to false.
// Our job is to just reload cache.
go func ( ) {
2024-07-03 11:33:02 +02:00
_ , err = am . refreshCache ( ctx , account . Id )
2022-10-13 18:26:31 +02:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "failed reloading cache when redeeming user %s under account %s" , userID , account . Id )
2022-10-13 18:26:31 +02:00
return
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "user %s of account %s redeemed invite" , user . ID , account . Id )
am . StoreEvent ( ctx , userID , userID , account . Id , activity . UserJoined , nil )
2022-10-13 18:26:31 +02:00
} ( )
}
return nil
}
2023-03-30 17:32:44 +02:00
// MarkPATUsed marks a personal access token as used
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) MarkPATUsed ( ctx context . Context , tokenID string ) error {
2023-03-30 10:54:09 +02:00
2024-07-03 11:33:02 +02:00
user , err := am . Store . GetUserByTokenID ( ctx , tokenID )
2023-03-30 10:54:09 +02:00
if err != nil {
return err
}
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccountByUser ( ctx , user . Id )
2023-03-30 10:54:09 +02:00
if err != nil {
return err
}
2024-07-31 14:53:32 +02:00
unlock := am . Store . AcquireWriteLockByUID ( ctx , account . Id )
2023-03-31 12:03:53 +02:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
account , err = am . Store . GetAccountByUser ( ctx , user . Id )
2023-03-31 12:03:53 +02:00
if err != nil {
return err
}
2023-03-30 10:54:09 +02:00
pat , ok := account . Users [ user . Id ] . PATs [ tokenID ]
if ! ok {
return fmt . Errorf ( "token not found" )
}
2023-04-03 15:09:35 +02:00
pat . LastUsed = time . Now ( ) . UTC ( )
2023-03-30 10:54:09 +02:00
2024-07-03 11:33:02 +02:00
return am . Store . SaveAccount ( ctx , account )
2023-03-30 10:54:09 +02:00
}
2023-03-16 15:57:44 +01:00
// GetAccountFromPAT returns Account and User associated with a personal access token
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) GetAccountFromPAT ( ctx context . Context , token string ) ( * Account , * User , * PersonalAccessToken , error ) {
2023-03-16 15:57:44 +01:00
if len ( token ) != PATLength {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , fmt . Errorf ( "token has wrong length" )
2023-03-16 15:57:44 +01:00
}
prefix := token [ : len ( PATPrefix ) ]
if prefix != PATPrefix {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , fmt . Errorf ( "token has wrong prefix" )
2023-03-16 15:57:44 +01:00
}
2023-03-20 11:44:12 +01:00
secret := token [ len ( PATPrefix ) : len ( PATPrefix ) + PATSecretLength ]
encodedChecksum := token [ len ( PATPrefix ) + PATSecretLength : len ( PATPrefix ) + PATSecretLength + PATChecksumLength ]
2023-03-16 15:57:44 +01:00
2023-05-16 12:57:56 +02:00
verificationChecksum , err := base62 . Decode ( encodedChecksum )
2023-03-16 15:57:44 +01:00
if err != nil {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , fmt . Errorf ( "token checksum decoding failed: %w" , err )
2023-03-16 15:57:44 +01:00
}
secretChecksum := crc32 . ChecksumIEEE ( [ ] byte ( secret ) )
if secretChecksum != verificationChecksum {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , fmt . Errorf ( "token checksum does not match" )
2023-03-16 15:57:44 +01:00
}
hashedToken := sha256 . Sum256 ( [ ] byte ( token ) )
2023-03-29 15:21:53 +02:00
encodedHashedToken := b64 . StdEncoding . EncodeToString ( hashedToken [ : ] )
2024-07-03 11:33:02 +02:00
tokenID , err := am . Store . GetTokenIDByHashedToken ( ctx , encodedHashedToken )
2023-03-16 15:57:44 +01:00
if err != nil {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , err
2023-03-16 15:57:44 +01:00
}
2024-07-03 11:33:02 +02:00
user , err := am . Store . GetUserByTokenID ( ctx , tokenID )
2023-03-16 15:57:44 +01:00
if err != nil {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , err
2023-03-16 15:57:44 +01:00
}
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccountByUser ( ctx , user . Id )
2023-03-16 15:57:44 +01:00
if err != nil {
2023-03-29 15:21:53 +02:00
return nil , nil , nil , err
}
pat := user . PATs [ tokenID ]
if pat == nil {
return nil , nil , nil , fmt . Errorf ( "personal access token not found" )
2023-03-16 15:57:44 +01:00
}
2023-03-29 15:21:53 +02:00
return account , user , pat , nil
2023-03-16 15:57:44 +01:00
}
2024-09-17 22:15:54 +02:00
// GetAccountFromToken returns an account associated with this token.
2024-09-18 15:40:47 +02:00
func ( am * DefaultAccountManager ) GetAccountFromToken ( ctx context . Context , claims jwtclaims . AuthorizationClaims ) ( * Account , * User , error ) {
2023-02-03 21:47:20 +01:00
if claims . UserId == "" {
2024-09-18 15:40:47 +02:00
return nil , nil , fmt . Errorf ( "user ID is empty" )
2023-02-03 21:47:20 +01:00
}
2022-10-19 17:43:28 +02:00
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
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "overriding JWT Domain and DomainCategory claims since single account mode is enabled" )
2022-10-19 17:43:28 +02:00
}
2024-09-18 14:55:52 +02:00
account , err := am . getAccountWithAuthorizationClaims ( ctx , claims )
2024-01-20 23:50:57 +01:00
if err != nil {
2024-09-18 15:40:47 +02:00
return nil , nil , err
2024-01-20 23:50:57 +01:00
}
2024-09-18 15:11:20 +02:00
user , err := am . Store . GetUserByUserID ( ctx , LockingStrengthShare , claims . UserId )
2022-10-13 18:26:31 +02:00
if err != nil {
2022-11-11 20:36:45 +01:00
// this is not really possible because we got an account by user ID
2024-09-18 15:40:47 +02:00
return nil , nil , status . Errorf ( status . NotFound , "user %s not found" , claims . UserId )
2022-10-13 18:26:31 +02:00
}
2024-04-22 11:10:27 +02:00
if ! user . IsServiceUser && claims . Invited {
2024-09-18 14:55:52 +02:00
err = am . redeemInvite ( ctx , account , user . Id )
2023-04-22 12:57:51 +02:00
if err != nil {
2024-09-18 15:40:47 +02:00
return nil , nil , err
2023-04-22 12:57:51 +02:00
}
2022-03-01 15:22:18 +01:00
}
2024-09-18 14:55:52 +02:00
if err = am . syncJWTGroups ( ctx , claims , account . Id ) ; err != nil {
2024-09-18 15:40:47 +02:00
return nil , nil , err
2024-09-18 13:24:39 +02:00
}
2024-09-18 15:40:47 +02:00
return account , user , nil
2024-09-18 13:24:39 +02:00
}
// syncJWTGroups processes the JWT groups for a user, updates the account based on the groups,
// and propagates changes to peers if group propagation is enabled.
func ( am * DefaultAccountManager ) syncJWTGroups ( ctx context . Context , claims jwtclaims . AuthorizationClaims , accountID string ) error {
2024-09-18 15:11:20 +02:00
settings , err := am . Store . GetAccountSettings ( ctx , LockingStrengthShare , accountID )
2024-09-18 13:24:39 +02:00
if err != nil {
return err
}
if ! settings . JWTGroupsEnabled {
return nil
}
if settings . JWTGroupsClaimName == "" {
log . WithContext ( ctx ) . Errorf ( "JWT groups are enabled but no claim name is set" )
return nil
}
jwtGroupsNames := extractJWTGroups ( ctx , settings . JWTGroupsClaimName , claims )
unlock := am . Store . AcquireWriteLockByUID ( ctx , accountID )
defer unlock ( )
account , err := am . Store . GetAccount ( ctx , accountID )
if err != nil {
return err
}
user , err := account . FindUser ( claims . UserId )
if err != nil {
return nil
}
oldGroups := make ( [ ] string , len ( user . AutoGroups ) )
copy ( oldGroups , user . AutoGroups )
// Update the account if group membership changes
if account . SetJWTGroups ( claims . UserId , jwtGroupsNames ) {
addNewGroups := difference ( user . AutoGroups , oldGroups )
removeOldGroups := difference ( oldGroups , user . AutoGroups )
if settings . GroupsPropagationEnabled {
account . UserGroupsAddToPeers ( claims . UserId , addNewGroups ... )
account . UserGroupsRemoveFromPeers ( claims . UserId , removeOldGroups ... )
account . Network . IncSerial ( )
}
if err := am . Store . SaveAccount ( ctx , account ) ; err != nil {
log . WithContext ( ctx ) . Errorf ( "failed to save account: %v" , err )
return nil
}
// Propagate changes to peers if group propagation is enabled
if settings . GroupsPropagationEnabled {
log . WithContext ( ctx ) . Tracef ( "user %s: JWT group membership changed, updating account peers" , claims . UserId )
am . updateAccountPeers ( ctx , account )
}
for _ , g := range addNewGroups {
if group := account . GetGroup ( g ) ; group != nil {
am . StoreEvent ( ctx , user . Id , user . Id , account . Id , activity . GroupAddedToUser ,
map [ string ] any {
"group" : group . Name ,
"group_id" : group . ID ,
"is_service_user" : user . IsServiceUser ,
"user_name" : user . ServiceUserName } )
}
}
for _ , g := range removeOldGroups {
if group := account . GetGroup ( g ) ; group != nil {
am . StoreEvent ( ctx , user . Id , user . Id , account . Id , activity . GroupRemovedFromUser ,
map [ string ] any {
"group" : group . Name ,
"group_id" : group . ID ,
"is_service_user" : user . IsServiceUser ,
"user_name" : user . ServiceUserName } )
2023-06-27 16:51:05 +02:00
}
}
}
2024-09-18 13:24:39 +02:00
return nil
2022-03-01 15:22:18 +01:00
}
2022-10-13 18:26:31 +02:00
// getAccountWithAuthorizationClaims retrievs an account using JWT Claims.
2022-03-01 15:22:18 +01:00
// if domain is of the PrivateCategory category, it will evaluate
// if account is new, existing or if there is another account with the same domain
//
// Use cases:
//
// New user + New account + New domain -> create account, user role = admin (if private domain, index domain)
//
// New user + New account + Existing Private Domain -> add user to the existing account, user role = regular (not admin)
//
// New user + New account + Existing Public Domain -> create account, user role = admin
//
// Existing user + Existing account + Existing Domain -> Nothing changes (if private, index domain)
//
// Existing user + Existing account + Existing Indexed Domain -> Nothing changes
//
// Existing user + Existing account + Existing domain reclassified Domain as private -> Nothing changes (index domain)
2024-09-18 14:55:52 +02:00
func ( am * DefaultAccountManager ) getAccountWithAuthorizationClaims ( ctx context . Context , claims jwtclaims . AuthorizationClaims ) ( * Account , error ) {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Tracef ( "getting account with authorization claims. User ID: \"%s\", Account ID: \"%s\", Domain: \"%s\", Domain Category: \"%s\"" ,
2024-05-27 12:29:28 +02:00
claims . UserId , claims . AccountId , claims . Domain , claims . DomainCategory )
2023-02-03 21:47:20 +01:00
if claims . UserId == "" {
2024-09-18 14:55:52 +02:00
return nil , fmt . Errorf ( "user ID is empty" )
2023-02-03 21:47:20 +01:00
}
2024-09-17 22:15:54 +02:00
2022-03-01 15:22:18 +01:00
// if Account ID is part of the claims
// it means that we've already classified the domain and user has an account
2022-09-29 10:51:18 +02:00
if claims . DomainCategory != PrivateCategory || ! isDomainValid ( claims . Domain ) {
2024-09-18 14:55:52 +02:00
return am . GetAccountByUserOrAccountID ( ctx , claims . UserId , claims . AccountId , claims . Domain )
2022-03-09 13:31:42 +01:00
} else if claims . AccountId != "" {
2024-07-03 11:33:02 +02:00
accountFromID , err := am . Store . GetAccount ( ctx , claims . AccountId )
2022-03-09 13:31:42 +01:00
if err != nil {
2024-09-18 14:55:52 +02:00
return nil , err
2022-03-09 13:31:42 +01:00
}
2022-05-09 14:30:20 +02:00
if _ , ok := accountFromID . Users [ claims . UserId ] ; ! ok {
2024-09-18 14:55:52 +02:00
return nil , fmt . Errorf ( "user %s is not part of the account id %s" , claims . UserId , claims . AccountId )
2022-05-09 14:30:20 +02:00
}
2023-08-16 16:11:26 +02:00
if accountFromID . DomainCategory == PrivateCategory || claims . DomainCategory != PrivateCategory || accountFromID . Domain != claims . Domain {
2024-09-18 14:55:52 +02:00
return accountFromID , nil
2022-03-09 13:31:42 +01:00
}
2022-03-01 15:22:18 +01:00
}
2024-05-23 17:09:58 +02:00
start := time . Now ( )
2024-07-03 11:33:02 +02:00
unlock := am . Store . AcquireGlobalLock ( ctx )
2022-11-07 17:52:23 +01:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "Acquired global lock in %s for user %s" , time . Since ( start ) , claims . UserId )
2022-03-01 15:22:18 +01:00
// We checked if the domain has a primary account already
2024-09-18 14:55:52 +02:00
domainAccountID , err := am . Store . GetAccountIDByPrivateDomain ( ctx , claims . Domain )
2022-11-11 20:36:45 +01:00
if err != nil {
// if NotFound we are good to continue, otherwise return error
e , ok := status . FromError ( err )
if ! ok || e . Type ( ) != status . NotFound {
2024-09-18 14:55:52 +02:00
return nil , err
2022-11-11 20:36:45 +01:00
}
2022-03-01 15:22:18 +01:00
}
2024-09-18 14:55:52 +02:00
userAccountID , err := am . Store . GetAccountIDByUserID ( claims . UserId )
2022-03-01 15:22:18 +01:00
if err == nil {
2024-09-18 14:55:52 +02:00
unlockAccount := am . Store . AcquireWriteLockByUID ( ctx , userAccountID )
2024-04-23 19:09:58 +02:00
defer unlockAccount ( )
2024-09-18 14:55:52 +02:00
account , err := am . Store . GetAccountByUser ( ctx , claims . UserId )
2024-04-23 19:09:58 +02:00
if err != nil {
2024-09-18 14:55:52 +02:00
return nil , err
2024-04-23 19:09:58 +02:00
}
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
// was previously unclassified or classified as public so N users that logged int that time, has they own account
// and peers that shouldn't be lost.
2024-09-18 14:55:52 +02:00
primaryDomain := domainAccountID == "" || account . Id == domainAccountID
2024-04-23 19:09:58 +02:00
2024-07-03 11:33:02 +02:00
err = am . handleExistingUserAccount ( ctx , account , primaryDomain , claims )
2022-03-01 15:22:18 +01:00
if err != nil {
2024-09-18 14:55:52 +02:00
return nil , err
2022-03-01 15:22:18 +01:00
}
2024-09-18 14:55:52 +02:00
return account , nil
2022-11-11 20:36:45 +01:00
} else if s , ok := status . FromError ( err ) ; ok && s . Type ( ) == status . NotFound {
2024-09-18 14:55:52 +02:00
unlockAccount := am . Store . AcquireWriteLockByUID ( ctx , domainAccountID )
defer unlockAccount ( )
domainAccount , err := am . Store . GetAccountByPrivateDomain ( ctx , claims . Domain )
2024-09-18 13:24:39 +02:00
if err != nil {
2024-09-18 14:55:52 +02:00
return nil , err
2024-09-18 13:24:39 +02:00
}
2024-09-18 14:55:52 +02:00
return am . handleNewUserAccount ( ctx , domainAccount , claims )
2022-03-01 15:22:18 +01:00
} else {
// other error
2024-09-18 14:55:52 +02:00
return nil , err
2022-03-01 15:22:18 +01:00
}
}
2024-08-01 16:21:43 +02:00
func ( am * DefaultAccountManager ) SyncAndMarkPeer ( ctx context . Context , accountID string , peerPubKey string , meta nbpeer . PeerSystemMeta , realIP net . IP ) ( * nbpeer . Peer , * NetworkMap , [ ] * posture . Checks , error ) {
2024-07-31 14:53:32 +02:00
accountUnlock := am . Store . AcquireReadLockByUID ( ctx , accountID )
defer accountUnlock ( )
2024-08-01 16:21:43 +02:00
peerUnlock := am . Store . AcquireWriteLockByUID ( ctx , peerPubKey )
defer peerUnlock ( )
2024-05-07 14:30:03 +02:00
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2024-05-07 14:30:03 +02:00
if err != nil {
2024-06-22 16:41:16 +02:00
return nil , nil , nil , err
2024-05-07 14:30:03 +02:00
}
2024-07-03 11:33:02 +02:00
peer , netMap , postureChecks , err := am . SyncPeer ( ctx , PeerSync { WireGuardPubKey : peerPubKey , Meta : meta } , account )
2024-05-07 14:30:03 +02:00
if err != nil {
2024-06-22 16:41:16 +02:00
return nil , nil , nil , err
2024-05-07 14:30:03 +02:00
}
2024-07-03 11:33:02 +02:00
err = am . MarkPeerConnected ( ctx , peerPubKey , true , realIP , account )
2024-05-07 14:30:03 +02:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Warnf ( "failed marking peer as connected %s %v" , peerPubKey , err )
2024-05-07 14:30:03 +02:00
}
2024-06-22 16:41:16 +02:00
return peer , netMap , postureChecks , nil
2024-05-07 14:30:03 +02:00
}
2024-08-01 16:21:43 +02:00
func ( am * DefaultAccountManager ) OnPeerDisconnected ( ctx context . Context , accountID string , peerPubKey string ) error {
2024-07-31 14:53:32 +02:00
accountUnlock := am . Store . AcquireReadLockByUID ( ctx , accountID )
defer accountUnlock ( )
2024-08-01 16:21:43 +02:00
peerUnlock := am . Store . AcquireWriteLockByUID ( ctx , peerPubKey )
defer peerUnlock ( )
2024-05-07 14:30:03 +02:00
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2024-05-07 14:30:03 +02:00
if err != nil {
return err
}
2024-07-31 21:51:45 +02:00
err = am . MarkPeerConnected ( ctx , peerPubKey , false , nil , account )
2024-05-07 14:30:03 +02:00
if err != nil {
2024-07-31 21:51:45 +02:00
log . WithContext ( ctx ) . Warnf ( "failed marking peer as connected %s %v" , peerPubKey , err )
2024-05-07 14:30:03 +02:00
}
return nil
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) SyncPeerMeta ( ctx context . Context , peerPubKey string , meta nbpeer . PeerSystemMeta ) error {
accountID , err := am . Store . GetAccountIDByPeerPubKey ( ctx , peerPubKey )
2024-06-13 13:24:24 +02:00
if err != nil {
return err
}
2024-07-31 14:53:32 +02:00
unlock := am . Store . AcquireReadLockByUID ( ctx , accountID )
2024-06-13 13:24:24 +02:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2024-06-13 13:24:24 +02:00
if err != nil {
return err
}
2024-07-03 11:33:02 +02:00
_ , _ , _ , err = am . SyncPeer ( ctx , PeerSync { WireGuardPubKey : peerPubKey , Meta : meta , UpdateAccountPeers : true } , account )
2024-06-13 13:24:24 +02:00
if err != nil {
2024-07-03 11:33:02 +02:00
return mapError ( ctx , err )
2024-06-13 13:24:24 +02:00
}
return nil
}
2023-10-11 18:11:45 +02:00
// GetAllConnectedPeers returns connected peers based on peersUpdateManager.GetAllConnectedPeers()
func ( am * DefaultAccountManager ) GetAllConnectedPeers ( ) ( map [ string ] struct { } , error ) {
return am . peersUpdateManager . GetAllConnectedPeers ( ) , nil
}
2023-12-05 14:17:56 +01:00
// HasConnectedChannel returns true if peers has channel in update manager, otherwise false
func ( am * DefaultAccountManager ) HasConnectedChannel ( peerID string ) bool {
return am . peersUpdateManager . HasChannel ( peerID )
}
2023-11-27 13:01:00 +01:00
var invalidDomainRegexp = regexp . MustCompile ( ` ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z] { 2,}$ ` )
2022-09-29 10:51:18 +02:00
func isDomainValid ( domain string ) bool {
2023-11-27 13:01:00 +01:00
return invalidDomainRegexp . MatchString ( domain )
2022-09-29 10:51:18 +02:00
}
2022-11-21 11:14:42 +01:00
// GetDNSDomain returns the configured dnsDomain
func ( am * DefaultAccountManager ) GetDNSDomain ( ) string {
return am . dnsDomain
}
2023-12-13 11:18:35 +01:00
// CheckUserAccessByJWTGroups checks if the user has access, particularly in cases where the admin enabled JWT
// group propagation and set the list of groups with access permissions.
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) CheckUserAccessByJWTGroups ( ctx context . Context , claims jwtclaims . AuthorizationClaims ) error {
2024-09-18 15:40:47 +02:00
account , _ , err := am . GetAccountFromToken ( ctx , claims )
2024-09-17 22:15:54 +02:00
if err != nil {
return err
2024-09-16 12:33:36 +02:00
}
2024-09-18 15:40:47 +02:00
settings , err := am . Store . GetAccountSettings ( ctx , LockingStrengthShare , account . Id )
2023-12-13 11:18:35 +01:00
if err != nil {
return err
}
// Ensures JWT group synchronization to the management is enabled before,
// filtering access based on the allowed groups.
2024-09-16 12:33:36 +02:00
if settings != nil && settings . JWTGroupsEnabled {
if allowedGroups := settings . JWTAllowGroups ; len ( allowedGroups ) > 0 {
userJWTGroups := extractJWTGroups ( ctx , settings . JWTGroupsClaimName , claims )
2023-12-13 11:18:35 +01:00
if ! userHasAllowedGroup ( allowedGroups , userJWTGroups ) {
return fmt . Errorf ( "user does not belong to any of the allowed JWT groups" )
}
}
}
return nil
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) onPeersInvalidated ( ctx context . Context , accountID string ) {
log . WithContext ( ctx ) . Debugf ( "validated peers has been invalidated for account %s" , accountID )
updatedAccount , err := am . Store . GetAccount ( ctx , accountID )
2024-03-27 18:48:48 +01:00
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "failed to get account %s: %v" , accountID , err )
2024-03-27 18:48:48 +01:00
return
}
2024-07-03 11:33:02 +02:00
am . updateAccountPeers ( ctx , updatedAccount )
2024-03-27 18:48:48 +01:00
}
2024-05-30 15:22:42 +02:00
func ( am * DefaultAccountManager ) FindExistingPostureCheck ( accountID string , checks * posture . ChecksDefinition ) ( * posture . Checks , error ) {
return am . Store . GetPostureCheckByChecksDefinition ( accountID , checks )
}
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) GetAccountIDForPeerKey ( ctx context . Context , peerKey string ) ( string , error ) {
return am . Store . GetAccountIDByPeerPubKey ( ctx , peerKey )
}
2024-08-19 12:50:11 +02:00
func ( am * DefaultAccountManager ) handleUserPeer ( ctx context . Context , peer * nbpeer . Peer , settings * Settings ) ( bool , error ) {
2024-09-16 15:47:03 +02:00
user , err := am . Store . GetUserByUserID ( ctx , LockingStrengthShare , peer . UserID )
2024-08-19 12:50:11 +02:00
if err != nil {
return false , err
}
err = checkIfPeerOwnerIsBlocked ( peer , user )
if err != nil {
return false , err
}
if peerLoginExpired ( ctx , peer , settings ) {
err = am . handleExpiredPeer ( ctx , user , peer )
if err != nil {
return false , err
}
return true , nil
}
return false , nil
}
2024-09-16 15:47:03 +02:00
func ( am * DefaultAccountManager ) getFreeDNSLabel ( ctx context . Context , store Store , accountID string , peerHostName string ) ( string , error ) {
existingLabels , err := store . GetPeerLabelsInAccount ( ctx , LockingStrengthShare , accountID )
if err != nil {
return "" , fmt . Errorf ( "failed to get peer dns labels: %w" , err )
}
labelMap := ConvertSliceToMap ( existingLabels )
newLabel , err := getPeerHostLabel ( peerHostName , labelMap )
if err != nil {
return "" , fmt . Errorf ( "failed to get new host label: %w" , err )
}
if newLabel == "" {
return "" , fmt . Errorf ( "failed to get new host label: %w" , err )
}
return newLabel , nil
}
2024-02-22 12:27:08 +01:00
// addAllGroup to account object if it doesn't exist
2023-03-13 15:14:18 +01:00
func addAllGroup ( account * Account ) error {
2022-05-21 15:21:39 +02:00
if len ( account . Groups ) == 0 {
2024-03-27 18:48:48 +01:00
allGroup := & nbgroup . Group {
2023-06-27 16:51:05 +02:00
ID : xid . New ( ) . String ( ) ,
Name : "All" ,
2024-03-27 18:48:48 +01:00
Issued : nbgroup . GroupIssuedAPI ,
2022-05-21 15:21:39 +02:00
}
for _ , peer := range account . Peers {
2023-02-03 10:33:28 +01:00
allGroup . Peers = append ( allGroup . Peers , peer . ID )
2022-05-21 15:21:39 +02:00
}
2024-03-27 18:48:48 +01:00
account . Groups = map [ string ] * nbgroup . Group { allGroup . ID : allGroup }
2022-05-21 15:21:39 +02:00
2024-02-19 17:17:36 +01:00
id := xid . New ( ) . String ( )
defaultPolicy := & Policy {
ID : id ,
2022-06-14 10:32:54 +02:00
Name : DefaultRuleName ,
Description : DefaultRuleDescription ,
2024-02-19 17:17:36 +01:00
Enabled : true ,
Rules : [ ] * PolicyRule {
{
ID : id ,
Name : DefaultRuleName ,
Description : DefaultRuleDescription ,
Enabled : true ,
Sources : [ ] string { allGroup . ID } ,
Destinations : [ ] string { allGroup . ID } ,
Bidirectional : true ,
Protocol : PolicyRuleProtocolALL ,
Action : PolicyTrafficActionAccept ,
} ,
} ,
2022-05-21 15:21:39 +02:00
}
2023-03-13 15:14:18 +01:00
account . Policies = [ ] * Policy { defaultPolicy }
2022-05-21 15:21:39 +02:00
}
2023-03-13 15:14:18 +01:00
return nil
2022-05-21 15:21:39 +02:00
}
2021-08-12 12:49:10 +02:00
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
2024-07-03 11:33:02 +02:00
func newAccountWithId ( ctx context . Context , accountID , userID , domain string ) * Account {
log . WithContext ( ctx ) . Debugf ( "creating new account" )
2021-08-01 19:06:01 +02:00
2022-01-14 14:34:27 +01:00
network := NewNetwork ( )
2023-11-28 13:45:26 +01:00
peers := make ( map [ string ] * nbpeer . Peer )
2021-12-27 13:17:15 +01:00
users := make ( map [ string ] * User )
2024-05-06 14:47:49 +02:00
routes := make ( map [ route . ID ] * route . Route )
2023-06-27 17:17:24 +02:00
setupKeys := map [ string ] * SetupKey { }
2022-09-30 13:47:11 +02:00
nameServersGroups := make ( map [ string ] * nbdns . NameServerGroup )
2023-12-01 17:24:57 +01:00
users [ userID ] = NewOwnerUser ( userID )
2023-10-11 23:00:56 +02:00
dnsSettings := DNSSettings {
2023-01-17 17:34:40 +01:00
DisabledManagementGroups : make ( [ ] string , 0 ) ,
}
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Debugf ( "created new account %s" , accountID )
2021-08-01 19:06:01 +02:00
2022-06-09 13:14:34 +02:00
acc := & Account {
2023-06-27 17:17:24 +02:00
Id : accountID ,
2024-03-02 13:49:40 +01:00
CreatedAt : time . Now ( ) . UTC ( ) ,
2023-02-13 15:07:15 +01:00
SetupKeys : setupKeys ,
Network : network ,
Peers : peers ,
Users : users ,
2023-06-27 17:17:24 +02:00
CreatedBy : userID ,
2023-02-13 15:07:15 +01:00
Domain : domain ,
Routes : routes ,
NameServerGroups : nameServersGroups ,
DNSSettings : dnsSettings ,
Settings : & Settings {
PeerLoginExpirationEnabled : true ,
PeerLoginExpiration : DefaultPeerLoginExpiration ,
2024-01-15 19:26:27 +01:00
GroupsPropagationEnabled : true ,
2024-03-27 16:11:45 +01:00
RegularUsersViewBlocked : true ,
2023-02-13 15:07:15 +01:00
} ,
2022-02-11 17:18:18 +01:00
}
2022-06-09 13:14:34 +02:00
2023-03-13 15:14:18 +01:00
if err := addAllGroup ( acc ) ; err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "error adding all group to account %s: %v" , acc . Id , err )
2023-03-13 15:14:18 +01:00
}
2022-06-09 13:14:34 +02:00
return acc
2021-08-12 12:49:10 +02:00
}
2023-12-13 11:18:35 +01:00
2024-09-16 12:33:36 +02:00
// extractJWTGroups extracts the group names from a JWT token's claims.
func extractJWTGroups ( ctx context . Context , claimName string , claims jwtclaims . AuthorizationClaims ) [ ] string {
userJWTGroups := make ( [ ] string , 0 )
if claim , ok := claims . Raw [ claimName ] ; ok {
if claimGroups , ok := claim . ( [ ] interface { } ) ; ok {
for _ , g := range claimGroups {
if group , ok := g . ( string ) ; ok {
userJWTGroups = append ( userJWTGroups , group )
} else {
log . WithContext ( ctx ) . Debugf ( "JWT claim %q contains a non-string group (type: %T): %v" , claimName , g , g )
}
}
}
}
return userJWTGroups
}
2023-12-13 11:18:35 +01:00
// userHasAllowedGroup checks if a user belongs to any of the allowed groups.
func userHasAllowedGroup ( allowedGroups [ ] string , userGroups [ ] string ) bool {
for _ , userGroup := range userGroups {
for _ , allowedGroup := range allowedGroups {
if userGroup == allowedGroup {
return true
}
}
}
return false
}
2024-06-13 08:55:09 +02:00
// separateGroups separates user's auto groups into non-JWT and JWT groups.
// Returns the list of standard auto groups and a map of JWT auto groups,
// where the keys are the group names and the values are the group IDs.
func separateGroups ( autoGroups [ ] string , allGroups map [ string ] * nbgroup . Group ) ( [ ] string , map [ string ] string ) {
newAutoGroups := make ( [ ] string , 0 )
jwtAutoGroups := make ( map [ string ] string ) // map of group name to group ID
for _ , id := range autoGroups {
if group , ok := allGroups [ id ] ; ok {
if group . Issued == nbgroup . GroupIssuedJWT {
jwtAutoGroups [ group . Name ] = id
} else {
newAutoGroups = append ( newAutoGroups , id )
}
}
}
return newAutoGroups , jwtAutoGroups
}