2021-07-30 17:46:38 +02:00
|
|
|
package server
|
2021-07-18 20:51:09 +02:00
|
|
|
|
|
|
|
import (
|
2022-11-07 17:52:23 +01:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-07-18 20:51:09 +02:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2022-11-07 17:52:23 +01:00
|
|
|
"time"
|
2021-07-18 20:51:09 +02:00
|
|
|
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
2022-03-26 12:08:54 +01:00
|
|
|
"github.com/netbirdio/netbird/util"
|
2021-07-18 20:51:09 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// storeFileName Store file name. Stored in the datadir
|
|
|
|
const storeFileName = "store.json"
|
|
|
|
|
2021-07-18 21:00:32 +02:00
|
|
|
// FileStore represents an account storage backed by a file persisted to disk
|
2021-07-18 20:51:09 +02:00
|
|
|
type FileStore struct {
|
2022-03-01 15:22:18 +01:00
|
|
|
Accounts map[string]*Account
|
2022-11-07 12:10:56 +01:00
|
|
|
SetupKeyID2AccountID map[string]string `json:"-"`
|
|
|
|
PeerKeyID2AccountID map[string]string `json:"-"`
|
|
|
|
UserID2AccountID map[string]string `json:"-"`
|
|
|
|
PrivateDomain2AccountID map[string]string `json:"-"`
|
2022-10-16 13:33:46 +02:00
|
|
|
InstallationID string
|
2021-07-18 20:51:09 +02:00
|
|
|
|
|
|
|
// mutex to synchronise Store read/write operations
|
|
|
|
mux sync.Mutex `json:"-"`
|
|
|
|
storeFile string `json:"-"`
|
2022-11-07 17:52:23 +01:00
|
|
|
|
|
|
|
// sync.Mutex indexed by accountID
|
|
|
|
accountLocks sync.Map `json:"-"`
|
|
|
|
globalAccountLock sync.Mutex `json:"-"`
|
2021-07-18 20:51:09 +02:00
|
|
|
}
|
|
|
|
|
2022-05-03 16:02:51 +02:00
|
|
|
type StoredAccount struct{}
|
2021-07-30 17:46:38 +02:00
|
|
|
|
2021-07-18 20:51:09 +02:00
|
|
|
// NewStore restores a store from the file located in the datadir
|
|
|
|
func NewStore(dataDir string) (*FileStore, error) {
|
|
|
|
return restore(filepath.Join(dataDir, storeFileName))
|
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
// restore the state of the store from the file.
|
2021-07-18 20:51:09 +02:00
|
|
|
// Creates a new empty store file if doesn't exist
|
|
|
|
func restore(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{
|
2022-03-01 15:22:18 +01:00
|
|
|
Accounts: make(map[string]*Account),
|
|
|
|
mux: sync.Mutex{},
|
2022-11-07 17:52:23 +01:00
|
|
|
globalAccountLock: sync.Mutex{},
|
2022-11-07 12:10:56 +01:00
|
|
|
SetupKeyID2AccountID: make(map[string]string),
|
|
|
|
PeerKeyID2AccountID: make(map[string]string),
|
|
|
|
UserID2AccountID: make(map[string]string),
|
|
|
|
PrivateDomain2AccountID: make(map[string]string),
|
2022-03-01 15:22:18 +01:00
|
|
|
storeFile: file,
|
2021-07-18 20:51:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err = s.persist(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
|
2022-11-07 12:10:56 +01:00
|
|
|
store.SetupKeyID2AccountID = make(map[string]string)
|
|
|
|
store.PeerKeyID2AccountID = make(map[string]string)
|
|
|
|
store.UserID2AccountID = make(map[string]string)
|
|
|
|
store.PrivateDomain2AccountID = make(map[string]string)
|
|
|
|
|
|
|
|
for accountID, account := range store.Accounts {
|
2021-07-18 20:51:09 +02:00
|
|
|
for setupKeyId := range account.SetupKeys {
|
2022-11-07 12:10:56 +01:00
|
|
|
store.SetupKeyID2AccountID[strings.ToUpper(setupKeyId)] = accountID
|
2022-05-21 15:21:39 +02:00
|
|
|
}
|
2022-11-07 12:10:56 +01:00
|
|
|
|
2021-07-30 17:46:38 +02:00
|
|
|
for _, peer := range account.Peers {
|
2022-11-07 12:10:56 +01:00
|
|
|
store.PeerKeyID2AccountID[peer.Key] = accountID
|
|
|
|
// reset all peers to status = Disconnected
|
|
|
|
if peer.Status != nil && peer.Status.Connected {
|
|
|
|
peer.Status.Connected = false
|
|
|
|
}
|
2021-07-22 10:28:00 +02:00
|
|
|
}
|
2021-12-27 13:17:15 +01:00
|
|
|
for _, user := range account.Users {
|
2022-11-07 12:10:56 +01:00
|
|
|
store.UserID2AccountID[user.Id] = accountID
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
2022-03-01 15:22:18 +01:00
|
|
|
for _, user := range account.Users {
|
2022-11-07 12:10:56 +01:00
|
|
|
store.UserID2AccountID[user.Id] = accountID
|
2022-08-18 18:22:15 +02:00
|
|
|
}
|
2022-11-07 12:10:56 +01:00
|
|
|
|
2022-05-21 15:21:39 +02:00
|
|
|
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
|
|
|
account.IsDomainPrimaryAccount {
|
2022-11-07 12:10:56 +01:00
|
|
|
store.PrivateDomain2AccountID[account.Domain] = accountID
|
2022-03-01 15:22:18 +01:00
|
|
|
}
|
2021-07-22 10:28:00 +02:00
|
|
|
}
|
2021-07-18 20:51:09 +02:00
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
// we need this persist to apply changes we made to account.Peers (we set them to Disconnected)
|
|
|
|
err = store.persist(store.storeFile)
|
2021-08-23 21:43:05 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
return store, nil
|
2021-08-23 21:43:05 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
// persist account data to a file
|
|
|
|
// It is recommended to call it with locking FileStore.mux
|
|
|
|
func (s *FileStore) persist(file string) error {
|
|
|
|
return util.WriteJson(file, s)
|
2021-07-18 20:51:09 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 17:52:23 +01:00
|
|
|
// AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock
|
|
|
|
func (s *FileStore) AcquireGlobalLock() (unlock func()) {
|
|
|
|
log.Debugf("acquiring global lock")
|
|
|
|
start := time.Now()
|
|
|
|
s.globalAccountLock.Lock()
|
|
|
|
|
|
|
|
unlock = func() {
|
|
|
|
s.globalAccountLock.Unlock()
|
|
|
|
log.Debugf("released global lock in %v", time.Since(start))
|
|
|
|
}
|
|
|
|
|
|
|
|
return unlock
|
|
|
|
}
|
|
|
|
|
|
|
|
// AcquireAccountLock acquires account lock and returns a function that releases the lock
|
|
|
|
func (s *FileStore) AcquireAccountLock(accountID string) (unlock func()) {
|
|
|
|
log.Debugf("acquiring lock for account %s", accountID)
|
|
|
|
start := time.Now()
|
|
|
|
value, _ := s.accountLocks.LoadOrStore(accountID, &sync.Mutex{})
|
|
|
|
mtx := value.(*sync.Mutex)
|
|
|
|
mtx.Lock()
|
|
|
|
|
|
|
|
unlock = func() {
|
|
|
|
mtx.Unlock()
|
|
|
|
log.Debugf("released lock for account %s in %v", accountID, time.Since(start))
|
|
|
|
}
|
|
|
|
|
|
|
|
return unlock
|
|
|
|
}
|
|
|
|
|
2021-07-30 17:46:38 +02:00
|
|
|
func (s *FileStore) SaveAccount(account *Account) error {
|
2021-07-18 20:51:09 +02:00
|
|
|
s.mux.Lock()
|
|
|
|
defer s.mux.Unlock()
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
accountCopy := account.Copy()
|
|
|
|
|
2021-07-18 20:51:09 +02:00
|
|
|
// todo will override, handle existing keys
|
2022-11-07 12:10:56 +01:00
|
|
|
s.Accounts[accountCopy.Id] = accountCopy
|
2021-07-18 20:51:09 +02:00
|
|
|
|
|
|
|
// todo check that account.Id and keyId are not exist already
|
|
|
|
// because if keyId exists for other accounts this can be bad
|
2022-11-07 12:10:56 +01:00
|
|
|
for keyID := range accountCopy.SetupKeys {
|
|
|
|
s.SetupKeyID2AccountID[strings.ToUpper(keyID)] = accountCopy.Id
|
2021-07-18 20:51:09 +02:00
|
|
|
}
|
|
|
|
|
2022-08-18 18:22:15 +02:00
|
|
|
// enforce peer to account index and delete peer to route indexes for rebuild
|
2022-11-07 12:10:56 +01:00
|
|
|
for _, peer := range accountCopy.Peers {
|
|
|
|
s.PeerKeyID2AccountID[peer.Key] = accountCopy.Id
|
2022-08-18 18:22:15 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
for _, user := range accountCopy.Users {
|
|
|
|
s.UserID2AccountID[user.Id] = accountCopy.Id
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
if accountCopy.DomainCategory == PrivateCategory && accountCopy.IsDomainPrimaryAccount {
|
|
|
|
s.PrivateDomain2AccountID[accountCopy.Domain] = accountCopy.Id
|
2022-03-01 15:22:18 +01:00
|
|
|
}
|
|
|
|
|
2022-05-03 16:02:51 +02:00
|
|
|
return s.persist(s.storeFile)
|
2021-07-18 20:51:09 +02:00
|
|
|
}
|
2021-07-22 10:28:00 +02:00
|
|
|
|
2022-08-18 18:22:15 +02:00
|
|
|
// GetAccountByPrivateDomain returns account by private domain
|
2022-03-01 15:22:18 +01:00
|
|
|
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
2022-11-07 12:10:56 +01:00
|
|
|
accountID, accountIDFound := s.PrivateDomain2AccountID[strings.ToLower(domain)]
|
|
|
|
if !accountIDFound {
|
2022-05-21 15:21:39 +02:00
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.NotFound,
|
2022-11-07 12:10:56 +01:00
|
|
|
"account not found: provided domain is not registered or is not private",
|
2022-05-21 15:21:39 +02:00
|
|
|
)
|
2022-03-01 15:22:18 +01:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
return s.GetAccount(accountID)
|
2022-03-01 15:22:18 +01:00
|
|
|
}
|
|
|
|
|
2022-08-18 18:22:15 +02:00
|
|
|
// GetAccountBySetupKey returns account by setup key id
|
2021-07-30 17:46:38 +02:00
|
|
|
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
2022-11-07 12:10:56 +01:00
|
|
|
accountID, accountIDFound := s.SetupKeyID2AccountID[strings.ToUpper(setupKey)]
|
|
|
|
if !accountIDFound {
|
|
|
|
return nil, status.Errorf(codes.NotFound, "account not found: provided setup key doesn't exists")
|
2021-07-30 17:46:38 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
return s.GetAccount(accountID)
|
2021-09-07 18:36:46 +02:00
|
|
|
}
|
2021-07-30 17:46:38 +02:00
|
|
|
|
2022-08-18 18:22:15 +02:00
|
|
|
// GetAllAccounts returns all accounts
|
2022-05-21 15:21:39 +02:00
|
|
|
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
2022-10-16 13:33:46 +02:00
|
|
|
s.mux.Lock()
|
|
|
|
defer s.mux.Unlock()
|
2022-05-21 15:21:39 +02:00
|
|
|
for _, a := range s.Accounts {
|
2022-10-16 13:33:46 +02:00
|
|
|
all = append(all, a.Copy())
|
2022-05-21 15:21:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return all
|
|
|
|
}
|
|
|
|
|
2022-08-18 18:22:15 +02:00
|
|
|
// GetAccount returns an account for id
|
2022-11-07 12:10:56 +01:00
|
|
|
func (s *FileStore) GetAccount(accountID string) (*Account, error) {
|
|
|
|
account, accountFound := s.Accounts[accountID]
|
2021-07-22 10:28:00 +02:00
|
|
|
if !accountFound {
|
2021-08-12 12:49:10 +02:00
|
|
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
2021-07-22 10:28:00 +02:00
|
|
|
}
|
2021-07-30 17:46:38 +02:00
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
return account.Copy(), nil
|
2021-07-30 17:46:38 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
// GetAccountByUser returns a user account
|
|
|
|
func (s *FileStore) GetAccountByUser(userID string) (*Account, error) {
|
2021-12-27 13:17:15 +01:00
|
|
|
s.mux.Lock()
|
|
|
|
defer s.mux.Unlock()
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
accountID, accountIDFound := s.UserID2AccountID[userID]
|
|
|
|
if !accountIDFound {
|
2021-12-27 13:17:15 +01:00
|
|
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
return s.GetAccount(accountID)
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
// GetAccountByPeerPubKey returns an account for a given peer WireGuard public key
|
|
|
|
func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
|
2022-08-18 18:22:15 +02:00
|
|
|
s.mux.Lock()
|
|
|
|
defer s.mux.Unlock()
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
accountID, accountIDFound := s.PeerKeyID2AccountID[peerKey]
|
|
|
|
if !accountIDFound {
|
|
|
|
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
2022-08-18 18:22:15 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:10:56 +01:00
|
|
|
return s.GetAccount(accountID)
|
2022-08-18 18:22:15 +02:00
|
|
|
}
|
2022-10-16 13:33:46 +02:00
|
|
|
|
|
|
|
// 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(id string) error {
|
|
|
|
s.mux.Lock()
|
|
|
|
defer s.mux.Unlock()
|
|
|
|
|
|
|
|
s.InstallationID = id
|
|
|
|
|
|
|
|
return s.persist(s.storeFile)
|
|
|
|
}
|