mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-15 03:11:02 +01:00
3b4bcdf5a4
Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com>
1061 lines
30 KiB
Go
1061 lines
30 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/netbirdio/netbird/dns"
|
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
"github.com/netbirdio/netbird/management/server/posture"
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
"github.com/netbirdio/netbird/route"
|
|
"github.com/rs/xid"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/util"
|
|
)
|
|
|
|
// storeFileName Store file name. Stored in the datadir
|
|
const storeFileName = "store.json"
|
|
|
|
// FileStore represents an account storage backed by a file persisted to disk
|
|
type FileStore struct {
|
|
Accounts map[string]*Account
|
|
SetupKeyID2AccountID map[string]string `json:"-"`
|
|
PeerKeyID2AccountID map[string]string `json:"-"`
|
|
PeerID2AccountID map[string]string `json:"-"`
|
|
UserID2AccountID map[string]string `json:"-"`
|
|
PrivateDomain2AccountID map[string]string `json:"-"`
|
|
HashedPAT2TokenID map[string]string `json:"-"`
|
|
TokenID2UserID map[string]string `json:"-"`
|
|
InstallationID string
|
|
|
|
// mutex to synchronise Store read/write operations
|
|
mux sync.Mutex `json:"-"`
|
|
storeFile string `json:"-"`
|
|
|
|
// sync.Mutex indexed by resource ID
|
|
resourceLocks sync.Map `json:"-"`
|
|
globalAccountLock sync.Mutex `json:"-"`
|
|
|
|
metrics telemetry.AppMetrics `json:"-"`
|
|
}
|
|
|
|
func (s *FileStore) ExecuteInTransaction(ctx context.Context, f func(store Store) error) error {
|
|
return f(s)
|
|
}
|
|
|
|
func (s *FileStore) IncrementSetupKeyUsage(ctx context.Context, setupKeyID string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.SetupKeyID2AccountID[strings.ToUpper(setupKeyID)]
|
|
if !ok {
|
|
return status.NewSetupKeyNotFoundError()
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
account.SetupKeys[setupKeyID].UsedTimes++
|
|
|
|
return s.SaveAccount(ctx, account)
|
|
}
|
|
|
|
func (s *FileStore) AddPeerToAllGroup(ctx context.Context, accountID string, peerID string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allGroup, err := account.GetGroupAll()
|
|
if err != nil || allGroup == nil {
|
|
return errors.New("all group not found")
|
|
}
|
|
|
|
allGroup.Peers = append(allGroup.Peers, peerID)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *FileStore) AddPeerToGroup(ctx context.Context, accountId string, peerId string, groupID string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
account.Groups[groupID].Peers = append(account.Groups[groupID].Peers, peerId)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *FileStore) AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, ok := s.Accounts[peer.AccountID]
|
|
if !ok {
|
|
return status.NewAccountNotFoundError(peer.AccountID)
|
|
}
|
|
|
|
account.Peers[peer.ID] = peer
|
|
return s.SaveAccount(ctx, account)
|
|
}
|
|
|
|
func (s *FileStore) IncrementNetworkSerial(ctx context.Context, _ LockingStrength, accountId string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, ok := s.Accounts[accountId]
|
|
if !ok {
|
|
return status.NewAccountNotFoundError(accountId)
|
|
}
|
|
|
|
account.Network.Serial++
|
|
|
|
return s.SaveAccount(ctx, account)
|
|
}
|
|
|
|
func (s *FileStore) GetSetupKeyBySecret(ctx context.Context, lockStrength LockingStrength, key string) (*SetupKey, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.SetupKeyID2AccountID[strings.ToUpper(key)]
|
|
if !ok {
|
|
return nil, status.NewSetupKeyNotFoundError()
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setupKey, ok := account.SetupKeys[key]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "setup key not found")
|
|
}
|
|
|
|
return setupKey, nil
|
|
}
|
|
|
|
func (s *FileStore) GetTakenIPs(ctx context.Context, lockStrength LockingStrength, accountID string) ([]net.IP, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var takenIps []net.IP
|
|
for _, existingPeer := range account.Peers {
|
|
takenIps = append(takenIps, existingPeer.IP)
|
|
}
|
|
|
|
return takenIps, nil
|
|
}
|
|
|
|
func (s *FileStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountID string) ([]string, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existingLabels := []string{}
|
|
for _, peer := range account.Peers {
|
|
if peer.DNSLabel != "" {
|
|
existingLabels = append(existingLabels, peer.DNSLabel)
|
|
}
|
|
}
|
|
return existingLabels, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountNetwork(ctx context.Context, lockStrength LockingStrength, accountID string) (*Network, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Network, nil
|
|
}
|
|
|
|
type StoredAccount struct{}
|
|
|
|
// NewFileStore restores a store from the file located in the datadir
|
|
func NewFileStore(ctx context.Context, dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
|
fs, err := restore(ctx, filepath.Join(dataDir, storeFileName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fs.metrics = metrics
|
|
return fs, nil
|
|
}
|
|
|
|
// NewFilestoreFromSqliteStore restores a store from Sqlite and stores to Filestore json in the file located in datadir
|
|
func NewFilestoreFromSqliteStore(ctx context.Context, sqlStore *SqlStore, dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
|
store, err := NewFileStore(ctx, dataDir, metrics)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = store.SaveInstallationID(ctx, sqlStore.GetInstallationID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, account := range sqlStore.GetAllAccounts(ctx) {
|
|
store.Accounts[account.Id] = account
|
|
}
|
|
|
|
return store, store.persist(ctx, store.storeFile)
|
|
}
|
|
|
|
// restore the state of the store from the file.
|
|
// Creates a new empty store file if doesn't exist
|
|
func restore(ctx context.Context, file string) (*FileStore, error) {
|
|
if _, err := os.Stat(file); os.IsNotExist(err) {
|
|
// create a new FileStore if previously didn't exist (e.g. first run)
|
|
s := &FileStore{
|
|
Accounts: make(map[string]*Account),
|
|
mux: sync.Mutex{},
|
|
globalAccountLock: sync.Mutex{},
|
|
SetupKeyID2AccountID: make(map[string]string),
|
|
PeerKeyID2AccountID: make(map[string]string),
|
|
UserID2AccountID: make(map[string]string),
|
|
PrivateDomain2AccountID: make(map[string]string),
|
|
PeerID2AccountID: make(map[string]string),
|
|
HashedPAT2TokenID: make(map[string]string),
|
|
TokenID2UserID: make(map[string]string),
|
|
storeFile: file,
|
|
}
|
|
|
|
err = s.persist(ctx, file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
read, err := util.ReadJson(file, &FileStore{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
store := read.(*FileStore)
|
|
store.storeFile = file
|
|
store.SetupKeyID2AccountID = make(map[string]string)
|
|
store.PeerKeyID2AccountID = make(map[string]string)
|
|
store.UserID2AccountID = make(map[string]string)
|
|
store.PrivateDomain2AccountID = make(map[string]string)
|
|
store.PeerID2AccountID = make(map[string]string)
|
|
store.HashedPAT2TokenID = make(map[string]string)
|
|
store.TokenID2UserID = make(map[string]string)
|
|
|
|
for accountID, account := range store.Accounts {
|
|
if account.Settings == nil {
|
|
account.Settings = &Settings{
|
|
PeerLoginExpirationEnabled: false,
|
|
PeerLoginExpiration: DefaultPeerLoginExpiration,
|
|
}
|
|
}
|
|
|
|
for setupKeyId := range account.SetupKeys {
|
|
store.SetupKeyID2AccountID[strings.ToUpper(setupKeyId)] = accountID
|
|
}
|
|
|
|
for _, peer := range account.Peers {
|
|
store.PeerKeyID2AccountID[peer.Key] = accountID
|
|
store.PeerID2AccountID[peer.ID] = accountID
|
|
}
|
|
for _, user := range account.Users {
|
|
store.UserID2AccountID[user.Id] = accountID
|
|
if user.Issued == "" {
|
|
user.Issued = UserIssuedAPI
|
|
account.Users[user.Id] = user
|
|
}
|
|
|
|
for _, pat := range user.PATs {
|
|
store.TokenID2UserID[pat.ID] = user.Id
|
|
store.HashedPAT2TokenID[pat.HashedToken] = pat.ID
|
|
}
|
|
}
|
|
|
|
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
|
account.IsDomainPrimaryAccount {
|
|
store.PrivateDomain2AccountID[account.Domain] = accountID
|
|
}
|
|
|
|
// TODO: delete this block after migration
|
|
policies := make(map[string]int, len(account.Policies))
|
|
for i, policy := range account.Policies {
|
|
policies[policy.ID] = i
|
|
policy.UpgradeAndFix()
|
|
}
|
|
if account.Policies == nil {
|
|
account.Policies = make([]*Policy, 0)
|
|
}
|
|
|
|
// for data migration. Can be removed once most base will be with labels
|
|
existingLabels := account.getPeerDNSLabels()
|
|
if len(existingLabels) != len(account.Peers) {
|
|
addPeerLabelsToAccount(ctx, account, existingLabels)
|
|
}
|
|
|
|
// TODO: delete this block after migration
|
|
// Set API as issuer for groups which has not this field
|
|
for _, group := range account.Groups {
|
|
if group.Issued == "" {
|
|
group.Issued = nbgroup.GroupIssuedAPI
|
|
}
|
|
}
|
|
|
|
allGroup, err := account.GetGroupAll()
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err)
|
|
// if the All group didn't exist we probably don't have routes to update
|
|
continue
|
|
}
|
|
|
|
for _, route := range account.Routes {
|
|
if len(route.Groups) == 0 {
|
|
route.Groups = []string{allGroup.ID}
|
|
}
|
|
}
|
|
|
|
// migration to Peer.ID from Peer.Key.
|
|
// Old peers that require migration have an empty Peer.ID in the store.json.
|
|
// Generate new ID with xid for these peers.
|
|
// Set the Peer.ID to the newly generated value.
|
|
// Replace all the mentions of Peer.Key as ID (groups and routes).
|
|
// Swap Peer.Key with Peer.ID in the Account.Peers map.
|
|
migrationPeers := make(map[string]*nbpeer.Peer) // key to Peer
|
|
for key, peer := range account.Peers {
|
|
// set LastLogin for the peers that were onboarded before the peer login expiration feature
|
|
if peer.LastLogin.IsZero() {
|
|
peer.LastLogin = time.Now().UTC()
|
|
}
|
|
if peer.ID != "" {
|
|
continue
|
|
}
|
|
id := xid.New().String()
|
|
peer.ID = id
|
|
migrationPeers[key] = peer
|
|
}
|
|
|
|
if len(migrationPeers) > 0 {
|
|
// swap Peer.Key with Peer.ID in the Account.Peers map.
|
|
for key, peer := range migrationPeers {
|
|
delete(account.Peers, key)
|
|
account.Peers[peer.ID] = peer
|
|
store.PeerID2AccountID[peer.ID] = accountID
|
|
}
|
|
|
|
// detect groups that have Peer.Key as a reference and replace it with ID.
|
|
for _, group := range account.Groups {
|
|
for i, peer := range group.Peers {
|
|
if p, ok := migrationPeers[peer]; ok {
|
|
group.Peers[i] = p.ID
|
|
}
|
|
}
|
|
}
|
|
|
|
// detect routes that have Peer.Key as a reference and replace it with ID.
|
|
for _, route := range account.Routes {
|
|
if peer, ok := migrationPeers[route.Peer]; ok {
|
|
route.Peer = peer.ID
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// we need this persist to apply changes we made to account.Peers (we set them to Disconnected)
|
|
err = store.persist(ctx, store.storeFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return store, nil
|
|
}
|
|
|
|
// persist account data to a file
|
|
// It is recommended to call it with locking FileStore.mux
|
|
func (s *FileStore) persist(ctx context.Context, file string) error {
|
|
start := time.Now()
|
|
err := util.WriteJson(file, s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
took := time.Since(start)
|
|
if s.metrics != nil {
|
|
s.metrics.StoreMetrics().CountPersistenceDuration(took)
|
|
}
|
|
log.WithContext(ctx).Debugf("took %d ms to persist the FileStore", took.Milliseconds())
|
|
return nil
|
|
}
|
|
|
|
// AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock
|
|
func (s *FileStore) AcquireGlobalLock(ctx context.Context) (unlock func()) {
|
|
log.WithContext(ctx).Debugf("acquiring global lock")
|
|
start := time.Now()
|
|
s.globalAccountLock.Lock()
|
|
|
|
unlock = func() {
|
|
s.globalAccountLock.Unlock()
|
|
log.WithContext(ctx).Debugf("released global lock in %v", time.Since(start))
|
|
}
|
|
|
|
took := time.Since(start)
|
|
log.WithContext(ctx).Debugf("took %v to acquire global lock", took)
|
|
if s.metrics != nil {
|
|
s.metrics.StoreMetrics().CountGlobalLockAcquisitionDuration(took)
|
|
}
|
|
|
|
return unlock
|
|
}
|
|
|
|
// AcquireWriteLockByUID acquires an ID lock for writing to a resource and returns a function that releases the lock
|
|
func (s *FileStore) AcquireWriteLockByUID(ctx context.Context, uniqueID string) (unlock func()) {
|
|
log.WithContext(ctx).Debugf("acquiring lock for ID %s", uniqueID)
|
|
start := time.Now()
|
|
value, _ := s.resourceLocks.LoadOrStore(uniqueID, &sync.Mutex{})
|
|
mtx := value.(*sync.Mutex)
|
|
mtx.Lock()
|
|
|
|
unlock = func() {
|
|
mtx.Unlock()
|
|
log.WithContext(ctx).Debugf("released lock for ID %s in %v", uniqueID, time.Since(start))
|
|
}
|
|
|
|
return unlock
|
|
}
|
|
|
|
// AcquireReadLockByUID acquires an ID lock for reading a resource and returns a function that releases the lock
|
|
// This method is still returns a write lock as file store can't handle read locks
|
|
func (s *FileStore) AcquireReadLockByUID(ctx context.Context, uniqueID string) (unlock func()) {
|
|
return s.AcquireWriteLockByUID(ctx, uniqueID)
|
|
}
|
|
|
|
func (s *FileStore) SaveAccount(ctx context.Context, account *Account) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
if account.Id == "" {
|
|
return status.Errorf(status.InvalidArgument, "account id should not be empty")
|
|
}
|
|
|
|
accountCopy := account.Copy()
|
|
|
|
s.Accounts[accountCopy.Id] = accountCopy
|
|
|
|
// todo check that account.Id and keyId are not exist already
|
|
// because if keyId exists for other accounts this can be bad
|
|
for keyID := range accountCopy.SetupKeys {
|
|
s.SetupKeyID2AccountID[strings.ToUpper(keyID)] = accountCopy.Id
|
|
}
|
|
|
|
// enforce peer to account index and delete peer to route indexes for rebuild
|
|
for _, peer := range accountCopy.Peers {
|
|
s.PeerKeyID2AccountID[peer.Key] = accountCopy.Id
|
|
s.PeerID2AccountID[peer.ID] = accountCopy.Id
|
|
}
|
|
|
|
for _, user := range accountCopy.Users {
|
|
s.UserID2AccountID[user.Id] = accountCopy.Id
|
|
for _, pat := range user.PATs {
|
|
s.TokenID2UserID[pat.ID] = user.Id
|
|
s.HashedPAT2TokenID[pat.HashedToken] = pat.ID
|
|
}
|
|
}
|
|
|
|
if accountCopy.DomainCategory == PrivateCategory && accountCopy.IsDomainPrimaryAccount {
|
|
s.PrivateDomain2AccountID[accountCopy.Domain] = accountCopy.Id
|
|
}
|
|
|
|
return s.persist(ctx, s.storeFile)
|
|
}
|
|
|
|
func (s *FileStore) DeleteAccount(ctx context.Context, account *Account) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
if account.Id == "" {
|
|
return status.Errorf(status.InvalidArgument, "account id should not be empty")
|
|
}
|
|
|
|
for keyID := range account.SetupKeys {
|
|
delete(s.SetupKeyID2AccountID, strings.ToUpper(keyID))
|
|
}
|
|
|
|
// enforce peer to account index and delete peer to route indexes for rebuild
|
|
for _, peer := range account.Peers {
|
|
delete(s.PeerKeyID2AccountID, peer.Key)
|
|
delete(s.PeerID2AccountID, peer.ID)
|
|
}
|
|
|
|
for _, user := range account.Users {
|
|
for _, pat := range user.PATs {
|
|
delete(s.TokenID2UserID, pat.ID)
|
|
delete(s.HashedPAT2TokenID, pat.HashedToken)
|
|
}
|
|
delete(s.UserID2AccountID, user.Id)
|
|
}
|
|
|
|
if account.DomainCategory == PrivateCategory && account.IsDomainPrimaryAccount {
|
|
delete(s.PrivateDomain2AccountID, account.Domain)
|
|
}
|
|
|
|
delete(s.Accounts, account.Id)
|
|
|
|
return s.persist(ctx, s.storeFile)
|
|
}
|
|
|
|
// DeleteHashedPAT2TokenIDIndex removes an entry from the indexing map HashedPAT2TokenID
|
|
func (s *FileStore) DeleteHashedPAT2TokenIDIndex(hashedToken string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
delete(s.HashedPAT2TokenID, hashedToken)
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteTokenID2UserIDIndex removes an entry from the indexing map TokenID2UserID
|
|
func (s *FileStore) DeleteTokenID2UserIDIndex(tokenID string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
delete(s.TokenID2UserID, tokenID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAccountByPrivateDomain returns account by private domain
|
|
func (s *FileStore) GetAccountByPrivateDomain(_ context.Context, domain string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.PrivateDomain2AccountID[strings.ToLower(domain)]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "account not found: provided domain is not registered or is not private")
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Copy(), nil
|
|
}
|
|
|
|
// GetAccountBySetupKey returns account by setup key id
|
|
func (s *FileStore) GetAccountBySetupKey(_ context.Context, setupKey string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.SetupKeyID2AccountID[strings.ToUpper(setupKey)]
|
|
if !ok {
|
|
return nil, status.NewSetupKeyNotFoundError()
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Copy(), nil
|
|
}
|
|
|
|
// GetTokenIDByHashedToken returns the id of a personal access token by its hashed secret
|
|
func (s *FileStore) GetTokenIDByHashedToken(_ context.Context, token string) (string, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
tokenID, ok := s.HashedPAT2TokenID[token]
|
|
if !ok {
|
|
return "", status.Errorf(status.NotFound, "tokenID not found: provided token doesn't exists")
|
|
}
|
|
|
|
return tokenID, nil
|
|
}
|
|
|
|
// GetUserByTokenID returns a User object a tokenID belongs to
|
|
func (s *FileStore) GetUserByTokenID(_ context.Context, tokenID string) (*User, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
userID, ok := s.TokenID2UserID[tokenID]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "user not found: provided tokenID doesn't exists")
|
|
}
|
|
|
|
accountID, ok := s.UserID2AccountID[userID]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "accountID not found: provided userID doesn't exists")
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Users[userID].Copy(), nil
|
|
}
|
|
|
|
func (s *FileStore) GetUserByUserID(_ context.Context, _ LockingStrength, userID string) (*User, error) {
|
|
accountID, ok := s.UserID2AccountID[userID]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "accountID not found: provided userID doesn't exists")
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user := account.Users[userID].Copy()
|
|
pat := make([]PersonalAccessToken, 0, len(user.PATs))
|
|
for _, token := range user.PATs {
|
|
if token != nil {
|
|
pat = append(pat, *token)
|
|
}
|
|
}
|
|
user.PATsG = pat
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) {
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
groupsSlice := make([]*nbgroup.Group, 0, len(account.Groups))
|
|
|
|
for _, group := range account.Groups {
|
|
groupsSlice = append(groupsSlice, group)
|
|
}
|
|
|
|
return groupsSlice, nil
|
|
}
|
|
|
|
// GetAllAccounts returns all accounts
|
|
func (s *FileStore) GetAllAccounts(_ context.Context) (all []*Account) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
for _, a := range s.Accounts {
|
|
all = append(all, a.Copy())
|
|
}
|
|
|
|
return all
|
|
}
|
|
|
|
// getAccount returns a reference to the Account. Should not return a copy.
|
|
func (s *FileStore) getAccount(accountID string) (*Account, error) {
|
|
account, ok := s.Accounts[accountID]
|
|
if !ok {
|
|
return nil, status.NewAccountNotFoundError(accountID)
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
// GetAccount returns an account for ID
|
|
func (s *FileStore) GetAccount(_ context.Context, accountID string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Copy(), nil
|
|
}
|
|
|
|
// GetAccountByUser returns a user account
|
|
func (s *FileStore) GetAccountByUser(_ context.Context, userID string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.UserID2AccountID[userID]
|
|
if !ok {
|
|
return nil, status.NewUserNotFoundError(userID)
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Copy(), nil
|
|
}
|
|
|
|
// GetAccountByPeerID returns an account for a given peer ID
|
|
func (s *FileStore) GetAccountByPeerID(ctx context.Context, peerID string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.PeerID2AccountID[peerID]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "provided peer ID doesn't exists %s", peerID)
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// this protection is needed because when we delete a peer, we don't really remove index peerID -> accountID.
|
|
// check Account.Peers for a match
|
|
if _, ok := account.Peers[peerID]; !ok {
|
|
delete(s.PeerID2AccountID, peerID)
|
|
log.WithContext(ctx).Warnf("removed stale peerID %s to accountID %s index", peerID, accountID)
|
|
return nil, status.NewPeerNotFoundError(peerID)
|
|
}
|
|
|
|
return account.Copy(), nil
|
|
}
|
|
|
|
// GetAccountByPeerPubKey returns an account for a given peer WireGuard public key
|
|
func (s *FileStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.PeerKeyID2AccountID[peerKey]
|
|
if !ok {
|
|
return nil, status.NewPeerNotFoundError(peerKey)
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// this protection is needed because when we delete a peer, we don't really remove index peerKey -> accountID.
|
|
// check Account.Peers for a match
|
|
stale := true
|
|
for _, peer := range account.Peers {
|
|
if peer.Key == peerKey {
|
|
stale = false
|
|
break
|
|
}
|
|
}
|
|
if stale {
|
|
delete(s.PeerKeyID2AccountID, peerKey)
|
|
log.WithContext(ctx).Warnf("removed stale peerKey %s to accountID %s index", peerKey, accountID)
|
|
return nil, status.NewPeerNotFoundError(peerKey)
|
|
}
|
|
|
|
return account.Copy(), nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountIDByPeerPubKey(_ context.Context, peerKey string) (string, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.PeerKeyID2AccountID[peerKey]
|
|
if !ok {
|
|
return "", status.NewPeerNotFoundError(peerKey)
|
|
}
|
|
|
|
return accountID, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountIDByUserID(userID string) (string, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.UserID2AccountID[userID]
|
|
if !ok {
|
|
return "", status.NewUserNotFoundError(userID)
|
|
}
|
|
|
|
return accountID, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountIDBySetupKey(_ context.Context, setupKey string) (string, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.SetupKeyID2AccountID[strings.ToUpper(setupKey)]
|
|
if !ok {
|
|
return "", status.NewSetupKeyNotFoundError()
|
|
}
|
|
|
|
return accountID, nil
|
|
}
|
|
|
|
func (s *FileStore) GetPeerByPeerPubKey(_ context.Context, _ LockingStrength, peerKey string) (*nbpeer.Peer, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountID, ok := s.PeerKeyID2AccountID[peerKey]
|
|
if !ok {
|
|
return nil, status.NewPeerNotFoundError(peerKey)
|
|
}
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, peer := range account.Peers {
|
|
if peer.Key == peerKey {
|
|
return peer.Copy(), nil
|
|
}
|
|
}
|
|
|
|
return nil, status.NewPeerNotFoundError(peerKey)
|
|
}
|
|
|
|
func (s *FileStore) GetAccountSettings(_ context.Context, _ LockingStrength, accountID string) (*Settings, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account.Settings.Copy(), nil
|
|
}
|
|
|
|
// GetInstallationID returns the installation ID from the store
|
|
func (s *FileStore) GetInstallationID() string {
|
|
return s.InstallationID
|
|
}
|
|
|
|
// SaveInstallationID saves the installation ID
|
|
func (s *FileStore) SaveInstallationID(ctx context.Context, ID string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
s.InstallationID = ID
|
|
|
|
return s.persist(ctx, s.storeFile)
|
|
}
|
|
|
|
// SavePeer saves the peer in the account
|
|
func (s *FileStore) SavePeer(_ context.Context, accountID string, peer *nbpeer.Peer) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newPeer := peer.Copy()
|
|
|
|
account.Peers[peer.ID] = newPeer
|
|
|
|
s.PeerKeyID2AccountID[peer.Key] = accountID
|
|
s.PeerID2AccountID[peer.ID] = accountID
|
|
|
|
return nil
|
|
}
|
|
|
|
// SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
|
|
// PeerStatus will be saved eventually when some other changes occur.
|
|
func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peer := account.Peers[peerID]
|
|
if peer == nil {
|
|
return status.Errorf(status.NotFound, "peer %s not found", peerID)
|
|
}
|
|
|
|
peer.Status = &peerStatus
|
|
|
|
return nil
|
|
}
|
|
|
|
// SavePeerLocation stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
|
|
// Peer.Location will be saved eventually when some other changes occur.
|
|
func (s *FileStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.Peer) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peer := account.Peers[peerWithLocation.ID]
|
|
if peer == nil {
|
|
return status.Errorf(status.NotFound, "peer %s not found", peerWithLocation.ID)
|
|
}
|
|
|
|
peer.Location = peerWithLocation.Location
|
|
|
|
return nil
|
|
}
|
|
|
|
// SaveUserLastLogin stores the last login time for a user in memory. It doesn't attempt to persist data to speed up things.
|
|
func (s *FileStore) SaveUserLastLogin(_ context.Context, accountID, userID string, lastLogin time.Time) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peer := account.Users[userID]
|
|
if peer == nil {
|
|
return status.Errorf(status.NotFound, "user %s not found", userID)
|
|
}
|
|
|
|
peer.LastLogin = lastLogin
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *FileStore) GetPostureCheckByChecksDefinition(_ string, _ *posture.ChecksDefinition) (*posture.Checks, error) {
|
|
return nil, status.Errorf(status.Internal, "GetPostureCheckByChecksDefinition is not implemented")
|
|
}
|
|
|
|
// Close the FileStore persisting data to disk
|
|
func (s *FileStore) Close(ctx context.Context) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
log.WithContext(ctx).Infof("closing FileStore")
|
|
|
|
return s.persist(ctx, s.storeFile)
|
|
}
|
|
|
|
// GetStoreEngine returns FileStoreEngine
|
|
func (s *FileStore) GetStoreEngine() StoreEngine {
|
|
return FileStoreEngine
|
|
}
|
|
|
|
func (s *FileStore) SaveUsers(_ string, _ map[string]*User) error {
|
|
return status.Errorf(status.Internal, "SaveUsers is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) SaveGroups(_ string, _ map[string]*nbgroup.Group) error {
|
|
return status.Errorf(status.Internal, "SaveGroups is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountIDByPrivateDomain(_ context.Context, _ LockingStrength, _ string) (string, error) {
|
|
return "", status.Errorf(status.Internal, "GetAccountIDByPrivateDomain is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountDomainAndCategory(_ context.Context, _ LockingStrength, accountID string) (string, string, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.getAccount(accountID)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return account.Domain, account.DomainCategory, nil
|
|
}
|
|
|
|
// AccountExists checks whether an account exists by the given ID.
|
|
func (s *FileStore) AccountExists(_ context.Context, id string) (bool, error) {
|
|
_, exists := s.Accounts[id]
|
|
return exists, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountDNSSettings(_ context.Context, _ LockingStrength, _ string) (*DNSSettings, error) {
|
|
return nil, status.Errorf(status.Internal, "GetAccountDNSSettings is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetGroupByID(_ context.Context, _ LockingStrength, _, _ string) (*nbgroup.Group, error) {
|
|
return nil, status.Errorf(status.Internal, "GetGroupByID is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetGroupByName(_ context.Context, _ LockingStrength, _, _ string) (*nbgroup.Group, error) {
|
|
return nil, status.Errorf(status.Internal, "GetGroupByName is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountPolicies(_ context.Context, _ string) ([]*Policy, error) {
|
|
return nil, status.Errorf(status.Internal, "GetPolicyByID is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetPolicyByID(_ context.Context, _ LockingStrength, _ string, _ string) (*Policy, error) {
|
|
return nil, status.Errorf(status.Internal, "GetPolicyByID is not implemented")
|
|
|
|
}
|
|
|
|
func (s *FileStore) SavePolicy(_ context.Context, _ LockingStrength, _ *Policy) error {
|
|
return status.Errorf(status.Internal, "SavePolicy is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) DeletePolicy(_ context.Context, _ LockingStrength, _ string) error {
|
|
return status.Errorf(status.Internal, "DeletePolicy is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountPostureChecks(_ context.Context, _ string) ([]*posture.Checks, error) {
|
|
return nil, status.Errorf(status.Internal, "GetAccountPostureChecks is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetPostureChecksByID(_ context.Context, _ LockingStrength, _ string, _ string) (*posture.Checks, error) {
|
|
return nil, status.Errorf(status.Internal, "GetPostureChecksByID is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) SavePostureChecks(_ context.Context, _ LockingStrength, _ *posture.Checks) error {
|
|
return status.Errorf(status.Internal, "SavePostureChecks is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) DeletePostureChecks(_ context.Context, _ LockingStrength, _ string) error {
|
|
return status.Errorf(status.Internal, "DeletePostureChecks is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountRoutes(_ context.Context, _ string) ([]*route.Route, error) {
|
|
return nil, status.Errorf(status.Internal, "GetAccountRoutes is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetRouteByID(_ context.Context, _ LockingStrength, _ string, _ string) (*route.Route, error) {
|
|
return nil, status.Errorf(status.Internal, "GetRouteByID is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountSetupKeys(_ context.Context, _ string) ([]*SetupKey, error) {
|
|
return nil, status.Errorf(status.Internal, "GetAccountSetupKeys is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetSetupKeyByID(_ context.Context, _ LockingStrength, _ string, _ string) (*SetupKey, error) {
|
|
return nil, status.Errorf(status.Internal, "GetSetupKeyByID is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetAccountNameServerGroups(_ context.Context, _ string) ([]*dns.NameServerGroup, error) {
|
|
return nil, status.Errorf(status.Internal, "GetAccountNameServerGroups is not implemented")
|
|
}
|
|
|
|
func (s *FileStore) GetNameServerGroupByID(_ context.Context, _ LockingStrength, _ string, _ string) (*dns.NameServerGroup, error) {
|
|
return nil, status.Errorf(status.Internal, "GetNameServerGroupByID is not implemented")
|
|
}
|