mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-15 11:21:04 +01:00
315 lines
9.8 KiB
Go
315 lines
9.8 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
"github.com/netbirdio/netbird/management/server/store"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
"github.com/netbirdio/netbird/management/server/util"
|
|
)
|
|
|
|
const (
|
|
// UpdateSetupKeyName indicates a setup key name update operation
|
|
UpdateSetupKeyName SetupKeyUpdateOperationType = iota
|
|
// UpdateSetupKeyRevoked indicates a setup key revoked filed update operation
|
|
UpdateSetupKeyRevoked
|
|
// UpdateSetupKeyAutoGroups indicates a setup key auto-assign groups update operation
|
|
UpdateSetupKeyAutoGroups
|
|
// UpdateSetupKeyExpiresAt indicates a setup key expiration time update operation
|
|
UpdateSetupKeyExpiresAt
|
|
)
|
|
|
|
// SetupKeyUpdateOperationType operation type
|
|
type SetupKeyUpdateOperationType int
|
|
|
|
func (t SetupKeyUpdateOperationType) String() string {
|
|
switch t {
|
|
case UpdateSetupKeyName:
|
|
return "UpdateSetupKeyName"
|
|
case UpdateSetupKeyRevoked:
|
|
return "UpdateSetupKeyRevoked"
|
|
case UpdateSetupKeyAutoGroups:
|
|
return "UpdateSetupKeyAutoGroups"
|
|
case UpdateSetupKeyExpiresAt:
|
|
return "UpdateSetupKeyExpiresAt"
|
|
default:
|
|
return "InvalidOperation"
|
|
}
|
|
}
|
|
|
|
// SetupKeyUpdateOperation operation object with type and values to be applied
|
|
type SetupKeyUpdateOperation struct {
|
|
Type SetupKeyUpdateOperationType
|
|
Values []string
|
|
}
|
|
|
|
// CreateSetupKey generates a new setup key with a given name, type, list of groups IDs to auto-assign to peers registered with this key,
|
|
// and adds it to the specified account. A list of autoGroups IDs can be empty.
|
|
func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID string, keyName string, keyType types.SetupKeyType,
|
|
expiresIn time.Duration, autoGroups []string, usageLimit int, userID string, ephemeral bool) (*types.SetupKey, error) {
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
|
defer unlock()
|
|
|
|
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.AccountID != accountID {
|
|
return nil, status.NewUserNotPartOfAccountError()
|
|
}
|
|
|
|
if user.IsRegularUser() {
|
|
return nil, status.NewAdminPermissionError()
|
|
}
|
|
|
|
var setupKey *types.SetupKey
|
|
var plainKey string
|
|
var eventsToStore []func()
|
|
|
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
|
if err = validateSetupKeyAutoGroups(ctx, transaction, accountID, autoGroups); err != nil {
|
|
return err
|
|
}
|
|
|
|
setupKey, plainKey = types.GenerateSetupKey(keyName, keyType, expiresIn, autoGroups, usageLimit, ephemeral)
|
|
setupKey.AccountID = accountID
|
|
|
|
events := am.prepareSetupKeyEvents(ctx, transaction, accountID, userID, autoGroups, nil, setupKey)
|
|
eventsToStore = append(eventsToStore, events...)
|
|
|
|
return transaction.SaveSetupKey(ctx, store.LockingStrengthUpdate, setupKey)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
am.StoreEvent(ctx, userID, setupKey.Id, accountID, activity.SetupKeyCreated, setupKey.EventMeta())
|
|
for _, storeEvent := range eventsToStore {
|
|
storeEvent()
|
|
}
|
|
|
|
// for the creation return the plain key to the caller
|
|
setupKey.Key = plainKey
|
|
|
|
return setupKey, nil
|
|
}
|
|
|
|
// SaveSetupKey saves the provided SetupKey to the database overriding the existing one.
|
|
// Due to the unique nature of a SetupKey certain properties must not be overwritten
|
|
// (e.g. the key itself, creation date, ID, etc).
|
|
// These properties are overwritten: AutoGroups, Revoked (only from false to true), and the UpdatedAt. The rest is copied from the existing key.
|
|
func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID string, keyToSave *types.SetupKey, userID string) (*types.SetupKey, error) {
|
|
if keyToSave == nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "provided setup key to update is nil")
|
|
}
|
|
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
|
defer unlock()
|
|
|
|
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.AccountID != accountID {
|
|
return nil, status.NewUserNotPartOfAccountError()
|
|
}
|
|
|
|
if user.IsRegularUser() {
|
|
return nil, status.NewAdminPermissionError()
|
|
}
|
|
|
|
var oldKey *types.SetupKey
|
|
var newKey *types.SetupKey
|
|
var eventsToStore []func()
|
|
|
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
|
if err = validateSetupKeyAutoGroups(ctx, transaction, accountID, keyToSave.AutoGroups); err != nil {
|
|
return err
|
|
}
|
|
|
|
oldKey, err = transaction.GetSetupKeyByID(ctx, store.LockingStrengthShare, accountID, keyToSave.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if oldKey.Revoked && !keyToSave.Revoked {
|
|
return status.Errorf(status.InvalidArgument, "can't un-revoke a revoked setup key")
|
|
}
|
|
|
|
// only auto groups, revoked status (from false to true) can be updated
|
|
newKey = oldKey.Copy()
|
|
newKey.AutoGroups = keyToSave.AutoGroups
|
|
newKey.Revoked = keyToSave.Revoked
|
|
newKey.UpdatedAt = time.Now().UTC()
|
|
|
|
addedGroups := util.Difference(newKey.AutoGroups, oldKey.AutoGroups)
|
|
removedGroups := util.Difference(oldKey.AutoGroups, newKey.AutoGroups)
|
|
|
|
events := am.prepareSetupKeyEvents(ctx, transaction, accountID, userID, addedGroups, removedGroups, oldKey)
|
|
eventsToStore = append(eventsToStore, events...)
|
|
|
|
return transaction.SaveSetupKey(ctx, store.LockingStrengthUpdate, newKey)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !oldKey.Revoked && newKey.Revoked {
|
|
am.StoreEvent(ctx, userID, newKey.Id, accountID, activity.SetupKeyRevoked, newKey.EventMeta())
|
|
}
|
|
|
|
for _, storeEvent := range eventsToStore {
|
|
storeEvent()
|
|
}
|
|
|
|
return newKey, nil
|
|
}
|
|
|
|
// ListSetupKeys returns a list of all setup keys of the account
|
|
func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, userID string) ([]*types.SetupKey, error) {
|
|
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.AccountID != accountID {
|
|
return nil, status.NewUserNotPartOfAccountError()
|
|
}
|
|
|
|
if user.IsRegularUser() {
|
|
return nil, status.NewAdminPermissionError()
|
|
}
|
|
|
|
return am.Store.GetAccountSetupKeys(ctx, store.LockingStrengthShare, accountID)
|
|
}
|
|
|
|
// GetSetupKey looks up a SetupKey by KeyID, returns NotFound error if not found.
|
|
func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, userID, keyID string) (*types.SetupKey, error) {
|
|
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.AccountID != accountID {
|
|
return nil, status.NewUserNotPartOfAccountError()
|
|
}
|
|
|
|
if user.IsRegularUser() {
|
|
return nil, status.NewAdminPermissionError()
|
|
}
|
|
|
|
setupKey, err := am.Store.GetSetupKeyByID(ctx, store.LockingStrengthShare, accountID, keyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// the UpdatedAt field was introduced later, so there might be that some keys have a Zero value (e.g, null in the store file)
|
|
if setupKey.UpdatedAt.IsZero() {
|
|
setupKey.UpdatedAt = setupKey.CreatedAt
|
|
}
|
|
|
|
return setupKey, nil
|
|
}
|
|
|
|
// DeleteSetupKey removes the setup key from the account
|
|
func (am *DefaultAccountManager) DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error {
|
|
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if user.AccountID != accountID {
|
|
return status.NewUserNotPartOfAccountError()
|
|
}
|
|
|
|
if user.IsRegularUser() {
|
|
return status.NewAdminPermissionError()
|
|
}
|
|
|
|
var deletedSetupKey *types.SetupKey
|
|
|
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
|
deletedSetupKey, err = transaction.GetSetupKeyByID(ctx, store.LockingStrengthShare, accountID, keyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return transaction.DeleteSetupKey(ctx, store.LockingStrengthUpdate, accountID, keyID)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
am.StoreEvent(ctx, userID, keyID, accountID, activity.SetupKeyDeleted, deletedSetupKey.EventMeta())
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateSetupKeyAutoGroups(ctx context.Context, transaction store.Store, accountID string, autoGroupIDs []string) error {
|
|
groups, err := transaction.GetGroupsByIDs(ctx, store.LockingStrengthShare, accountID, autoGroupIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, groupID := range autoGroupIDs {
|
|
group, ok := groups[groupID]
|
|
if !ok {
|
|
return status.Errorf(status.NotFound, "group not found: %s", groupID)
|
|
}
|
|
|
|
if group.IsGroupAll() {
|
|
return status.Errorf(status.InvalidArgument, "can't add 'All' group to the setup key")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// prepareSetupKeyEvents prepares a list of event functions to be stored.
|
|
func (am *DefaultAccountManager) prepareSetupKeyEvents(ctx context.Context, transaction store.Store, accountID, userID string, addedGroups, removedGroups []string, key *types.SetupKey) []func() {
|
|
var eventsToStore []func()
|
|
|
|
modifiedGroups := slices.Concat(addedGroups, removedGroups)
|
|
groups, err := transaction.GetGroupsByIDs(ctx, store.LockingStrengthShare, accountID, modifiedGroups)
|
|
if err != nil {
|
|
log.WithContext(ctx).Debugf("failed to get groups for setup key events: %v", err)
|
|
return nil
|
|
}
|
|
|
|
for _, g := range removedGroups {
|
|
group, ok := groups[g]
|
|
if !ok {
|
|
log.WithContext(ctx).Debugf("skipped adding group: %s GroupRemovedFromSetupKey activity: group not found", g)
|
|
continue
|
|
}
|
|
|
|
eventsToStore = append(eventsToStore, func() {
|
|
meta := map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": key.Name}
|
|
am.StoreEvent(ctx, userID, key.Id, accountID, activity.GroupRemovedFromSetupKey, meta)
|
|
})
|
|
}
|
|
|
|
for _, g := range addedGroups {
|
|
group, ok := groups[g]
|
|
if !ok {
|
|
log.WithContext(ctx).Debugf("skipped adding group: %s GroupAddedToSetupKey activity: group not found", g)
|
|
continue
|
|
}
|
|
|
|
eventsToStore = append(eventsToStore, func() {
|
|
meta := map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": key.Name}
|
|
am.StoreEvent(ctx, userID, key.Id, accountID, activity.GroupAddedToSetupKey, meta)
|
|
})
|
|
}
|
|
|
|
return eventsToStore
|
|
}
|