mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-27 00:58:52 +01:00
415 lines
10 KiB
Go
415 lines
10 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"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:"-"`
|
|
UserId2AccountId map[string]string `json:"-"`
|
|
PrivateDomain2AccountId map[string]string `json:"-"`
|
|
PeerKeyId2SrcRulesId map[string]map[string]struct{} `json:"-"`
|
|
PeerKeyId2DstRulesId map[string]map[string]struct{} `json:"-"`
|
|
|
|
// mutex to synchronise Store read/write operations
|
|
mux sync.Mutex `json:"-"`
|
|
storeFile string `json:"-"`
|
|
}
|
|
|
|
type StoredAccount struct{}
|
|
|
|
// NewStore restores a store from the file located in the datadir
|
|
func NewStore(dataDir string) (*FileStore, error) {
|
|
return restore(filepath.Join(dataDir, storeFileName))
|
|
}
|
|
|
|
// restore restores the state of the store from the file.
|
|
// 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{
|
|
Accounts: make(map[string]*Account),
|
|
mux: sync.Mutex{},
|
|
SetupKeyId2AccountId: make(map[string]string),
|
|
PeerKeyId2AccountId: make(map[string]string),
|
|
UserId2AccountId: make(map[string]string),
|
|
PrivateDomain2AccountId: make(map[string]string),
|
|
PeerKeyId2SrcRulesId: make(map[string]map[string]struct{}),
|
|
PeerKeyId2DstRulesId: make(map[string]map[string]struct{}),
|
|
storeFile: file,
|
|
}
|
|
|
|
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
|
|
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.PeerKeyId2SrcRulesId = map[string]map[string]struct{}{}
|
|
store.PeerKeyId2DstRulesId = map[string]map[string]struct{}{}
|
|
|
|
for accountId, account := range store.Accounts {
|
|
for setupKeyId := range account.SetupKeys {
|
|
store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId
|
|
}
|
|
for _, rule := range account.Rules {
|
|
for _, groupID := range rule.Source {
|
|
if group, ok := account.Groups[groupID]; ok {
|
|
for _, peerID := range group.Peers {
|
|
rules := store.PeerKeyId2SrcRulesId[peerID]
|
|
if rules == nil {
|
|
rules = map[string]struct{}{}
|
|
store.PeerKeyId2SrcRulesId[peerID] = rules
|
|
}
|
|
rules[rule.ID] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
for _, groupID := range rule.Destination {
|
|
if group, ok := account.Groups[groupID]; ok {
|
|
for _, peerID := range group.Peers {
|
|
rules := store.PeerKeyId2DstRulesId[peerID]
|
|
if rules == nil {
|
|
rules = map[string]struct{}{}
|
|
store.PeerKeyId2DstRulesId[peerID] = rules
|
|
}
|
|
rules[rule.ID] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, peer := range account.Peers {
|
|
store.PeerKeyId2AccountId[peer.Key] = accountId
|
|
}
|
|
for _, user := range account.Users {
|
|
store.UserId2AccountId[user.Id] = accountId
|
|
}
|
|
for _, user := range account.Users {
|
|
store.UserId2AccountId[user.Id] = accountId
|
|
}
|
|
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
|
account.IsDomainPrimaryAccount {
|
|
store.PrivateDomain2AccountId[account.Domain] = accountId
|
|
}
|
|
}
|
|
|
|
return store, nil
|
|
}
|
|
|
|
// persist persists 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)
|
|
}
|
|
|
|
// SavePeer saves updated peer
|
|
func (s *FileStore) SavePeer(accountId string, peer *Peer) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if it is new peer, add it to default 'All' group
|
|
allGroup, err := account.GetGroupAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ind := -1
|
|
for i, pid := range allGroup.Peers {
|
|
if pid == peer.Key {
|
|
ind = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if ind < 0 {
|
|
allGroup.Peers = append(allGroup.Peers, peer.Key)
|
|
}
|
|
|
|
account.Peers[peer.Key] = peer
|
|
return s.persist(s.storeFile)
|
|
}
|
|
|
|
// DeletePeer deletes peer from the Store
|
|
func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
peer := account.Peers[peerKey]
|
|
if peer == nil {
|
|
return nil, status.Errorf(codes.NotFound, "peer not found")
|
|
}
|
|
|
|
delete(account.Peers, peerKey)
|
|
delete(s.PeerKeyId2AccountId, peerKey)
|
|
|
|
// cleanup groups
|
|
var peers []string
|
|
for _, g := range account.Groups {
|
|
for _, p := range g.Peers {
|
|
if p != peerKey {
|
|
peers = append(peers, p)
|
|
}
|
|
}
|
|
g.Peers = peers
|
|
}
|
|
|
|
err = s.persist(s.storeFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return peer, nil
|
|
}
|
|
|
|
// GetPeer returns a peer from a Store
|
|
func (s *FileStore) GetPeer(peerKey string) (*Peer, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
|
if !accountIdFound {
|
|
return nil, status.Errorf(codes.NotFound, "peer not found")
|
|
}
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if peer, ok := account.Peers[peerKey]; ok {
|
|
return peer, nil
|
|
}
|
|
|
|
return nil, status.Errorf(codes.NotFound, "peer not found")
|
|
}
|
|
|
|
// SaveAccount updates an existing account or adds a new one
|
|
func (s *FileStore) SaveAccount(account *Account) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
// todo will override, handle existing keys
|
|
s.Accounts[account.Id] = account
|
|
|
|
// 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 account.SetupKeys {
|
|
s.SetupKeyId2AccountId[strings.ToUpper(keyId)] = account.Id
|
|
}
|
|
|
|
for _, peer := range account.Peers {
|
|
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
|
}
|
|
|
|
for _, rule := range account.Rules {
|
|
for _, gid := range rule.Source {
|
|
for _, pid := range account.Groups[gid].Peers {
|
|
rules := s.PeerKeyId2SrcRulesId[pid]
|
|
if rules == nil {
|
|
rules = map[string]struct{}{}
|
|
s.PeerKeyId2SrcRulesId[pid] = rules
|
|
}
|
|
rules[rule.ID] = struct{}{}
|
|
}
|
|
}
|
|
for _, gid := range rule.Destination {
|
|
for _, pid := range account.Groups[gid].Peers {
|
|
rules := s.PeerKeyId2DstRulesId[pid]
|
|
if rules == nil {
|
|
rules = map[string]struct{}{}
|
|
s.PeerKeyId2DstRulesId[pid] = rules
|
|
}
|
|
rules[rule.ID] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, user := range account.Users {
|
|
s.UserId2AccountId[user.Id] = account.Id
|
|
}
|
|
|
|
if account.DomainCategory == PrivateCategory && account.IsDomainPrimaryAccount {
|
|
s.PrivateDomain2AccountId[account.Domain] = account.Id
|
|
}
|
|
|
|
return s.persist(s.storeFile)
|
|
}
|
|
|
|
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
|
accountId, accountIdFound := s.PrivateDomain2AccountId[strings.ToLower(domain)]
|
|
if !accountIdFound {
|
|
return nil, status.Errorf(
|
|
codes.NotFound,
|
|
"provided domain is not registered or is not private",
|
|
)
|
|
}
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
|
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToUpper(setupKey)]
|
|
if !accountIdFound {
|
|
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
|
}
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var peers []*Peer
|
|
for _, peer := range account.Peers {
|
|
peers = append(peers, peer)
|
|
}
|
|
|
|
return peers, nil
|
|
}
|
|
|
|
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
|
for _, a := range s.Accounts {
|
|
all = append(all, a)
|
|
}
|
|
|
|
return all
|
|
}
|
|
|
|
func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
|
account, accountFound := s.Accounts[accountId]
|
|
if !accountFound {
|
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountId, accountIdFound := s.UserId2AccountId[userId]
|
|
if !accountIdFound {
|
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
}
|
|
|
|
return s.GetAccount(accountId)
|
|
}
|
|
|
|
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
|
if !accountIdFound {
|
|
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
|
}
|
|
|
|
return s.GetAccount(accountId)
|
|
}
|
|
|
|
func (s *FileStore) GetPeerSrcRules(accountId, peerKey string) ([]*Rule, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ruleIDs, ok := s.PeerKeyId2SrcRulesId[peerKey]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no rules for peer: %v", ruleIDs)
|
|
}
|
|
|
|
rules := []*Rule{}
|
|
for id := range ruleIDs {
|
|
rule, ok := account.Rules[id]
|
|
if ok {
|
|
rules = append(rules, rule)
|
|
}
|
|
}
|
|
|
|
return rules, nil
|
|
}
|
|
|
|
func (s *FileStore) GetPeerDstRules(accountId, peerKey string) ([]*Rule, error) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
|
|
account, err := s.GetAccount(accountId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ruleIDs, ok := s.PeerKeyId2DstRulesId[peerKey]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no rules for peer: %v", ruleIDs)
|
|
}
|
|
|
|
rules := []*Rule{}
|
|
for id := range ruleIDs {
|
|
rule, ok := account.Rules[id]
|
|
if ok {
|
|
rules = append(rules, rule)
|
|
}
|
|
}
|
|
|
|
return rules, nil
|
|
}
|