mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-23 08:33:45 +01:00
503a116f7c
Introduced an OpenAPI specification. Updated API handlers to use the specification types. Added patch operation for rules and groups and methods to the account manager. HTTP PUT operations require id, fail if not provided. Use snake_case for HTTP request and response body
446 lines
11 KiB
Go
446 lines
11 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)
|
|
delete(s.PeerKeyId2DstRulesId, peerKey)
|
|
delete(s.PeerKeyId2SrcRulesId, peerKey)
|
|
|
|
// cleanup groups
|
|
for _, g := range account.Groups {
|
|
var peers []string
|
|
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
|
|
}
|
|
|
|
// remove all peers related to account from rules indexes
|
|
cleanIDs := make([]string, 0)
|
|
for key := range s.PeerKeyId2SrcRulesId {
|
|
if accountID, ok := s.PeerKeyId2AccountId[key]; ok && accountID == account.Id {
|
|
cleanIDs = append(cleanIDs, key)
|
|
}
|
|
}
|
|
for _, key := range cleanIDs {
|
|
delete(s.PeerKeyId2SrcRulesId, key)
|
|
}
|
|
cleanIDs = cleanIDs[:0]
|
|
for key := range s.PeerKeyId2DstRulesId {
|
|
if accountID, ok := s.PeerKeyId2AccountId[key]; ok && accountID == account.Id {
|
|
cleanIDs = append(cleanIDs, key)
|
|
}
|
|
}
|
|
for _, key := range cleanIDs {
|
|
delete(s.PeerKeyId2DstRulesId, key)
|
|
}
|
|
|
|
// rebuild rule indexes
|
|
for _, rule := range account.Rules {
|
|
for _, gid := range rule.Source {
|
|
g, ok := account.Groups[gid]
|
|
if !ok {
|
|
break
|
|
}
|
|
for _, pid := range g.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 {
|
|
g, ok := account.Groups[gid]
|
|
if !ok {
|
|
break
|
|
}
|
|
for _, pid := range g.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
|
|
}
|