mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-16 18:11:58 +02:00
[management] Setup key improvements (#2775)
This commit is contained in:
@ -2,6 +2,9 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -73,6 +76,7 @@ type SetupKey struct {
|
||||
// AccountID is a reference to Account that this object belongs
|
||||
AccountID string `json:"-" gorm:"index"`
|
||||
Key string
|
||||
KeySecret string
|
||||
Name string
|
||||
Type SetupKeyType
|
||||
CreatedAt time.Time
|
||||
@ -104,6 +108,7 @@ func (key *SetupKey) Copy() *SetupKey {
|
||||
Id: key.Id,
|
||||
AccountID: key.AccountID,
|
||||
Key: key.Key,
|
||||
KeySecret: key.KeySecret,
|
||||
Name: key.Name,
|
||||
Type: key.Type,
|
||||
CreatedAt: key.CreatedAt,
|
||||
@ -120,19 +125,17 @@ func (key *SetupKey) Copy() *SetupKey {
|
||||
|
||||
// EventMeta returns activity event meta related to the setup key
|
||||
func (key *SetupKey) EventMeta() map[string]any {
|
||||
return map[string]any{"name": key.Name, "type": key.Type, "key": key.HiddenCopy(1).Key}
|
||||
return map[string]any{"name": key.Name, "type": key.Type, "key": key.KeySecret}
|
||||
}
|
||||
|
||||
// HiddenCopy returns a copy of the key with a Key value hidden with "*" and a 5 character prefix.
|
||||
// hiddenKey returns the Key value hidden with "*" and a 5 character prefix.
|
||||
// E.g., "831F6*******************************"
|
||||
func (key *SetupKey) HiddenCopy(length int) *SetupKey {
|
||||
k := key.Copy()
|
||||
prefix := k.Key[0:5]
|
||||
if length > utf8.RuneCountInString(key.Key) {
|
||||
length = utf8.RuneCountInString(key.Key) - len(prefix)
|
||||
func hiddenKey(key string, length int) string {
|
||||
prefix := key[0:5]
|
||||
if length > utf8.RuneCountInString(key) {
|
||||
length = utf8.RuneCountInString(key) - len(prefix)
|
||||
}
|
||||
k.Key = prefix + strings.Repeat("*", length)
|
||||
return k
|
||||
return prefix + strings.Repeat("*", length)
|
||||
}
|
||||
|
||||
// IncrementUsage makes a copy of a key, increments the UsedTimes by 1 and sets LastUsed to now
|
||||
@ -155,6 +158,9 @@ func (key *SetupKey) IsRevoked() bool {
|
||||
|
||||
// IsExpired if key was expired
|
||||
func (key *SetupKey) IsExpired() bool {
|
||||
if key.ExpiresAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
return time.Now().After(key.ExpiresAt)
|
||||
}
|
||||
|
||||
@ -169,30 +175,40 @@ func (key *SetupKey) IsOverUsed() bool {
|
||||
|
||||
// GenerateSetupKey generates a new setup key
|
||||
func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration, autoGroups []string,
|
||||
usageLimit int, ephemeral bool) *SetupKey {
|
||||
usageLimit int, ephemeral bool) (*SetupKey, string) {
|
||||
key := strings.ToUpper(uuid.New().String())
|
||||
limit := usageLimit
|
||||
if t == SetupKeyOneOff {
|
||||
limit = 1
|
||||
}
|
||||
|
||||
expiresAt := time.Time{}
|
||||
if validFor != 0 {
|
||||
expiresAt = time.Now().UTC().Add(validFor)
|
||||
}
|
||||
|
||||
hashedKey := sha256.Sum256([]byte(key))
|
||||
encodedHashedKey := b64.StdEncoding.EncodeToString(hashedKey[:])
|
||||
|
||||
return &SetupKey{
|
||||
Id: strconv.Itoa(int(Hash(key))),
|
||||
Key: key,
|
||||
Key: encodedHashedKey,
|
||||
KeySecret: hiddenKey(key, 4),
|
||||
Name: name,
|
||||
Type: t,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
ExpiresAt: time.Now().UTC().Add(validFor),
|
||||
ExpiresAt: expiresAt,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Revoked: false,
|
||||
UsedTimes: 0,
|
||||
AutoGroups: autoGroups,
|
||||
UsageLimit: limit,
|
||||
Ephemeral: ephemeral,
|
||||
}
|
||||
}, key
|
||||
}
|
||||
|
||||
// GenerateDefaultSetupKey generates a default reusable setup key with an unlimited usage and 30 days expiration
|
||||
func GenerateDefaultSetupKey() *SetupKey {
|
||||
func GenerateDefaultSetupKey() (*SetupKey, string) {
|
||||
return GenerateSetupKey(DefaultSetupKeyName, SetupKeyReusable, DefaultSetupKeyDuration, []string{},
|
||||
SetupKeyUnlimitedUsage, false)
|
||||
}
|
||||
@ -213,11 +229,6 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
|
||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
keyDuration := DefaultSetupKeyDuration
|
||||
if expiresIn != 0 {
|
||||
keyDuration = expiresIn
|
||||
}
|
||||
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -227,7 +238,7 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setupKey := GenerateSetupKey(keyName, keyType, keyDuration, autoGroups, usageLimit, ephemeral)
|
||||
setupKey, plainKey := GenerateSetupKey(keyName, keyType, expiresIn, autoGroups, usageLimit, ephemeral)
|
||||
account.SetupKeys[setupKey.Key] = setupKey
|
||||
err = am.Store.SaveAccount(ctx, account)
|
||||
if err != nil {
|
||||
@ -246,6 +257,9 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
|
||||
}
|
||||
}
|
||||
|
||||
// for the creation return the plain key to the caller
|
||||
setupKey.Key = plainKey
|
||||
|
||||
return setupKey, nil
|
||||
}
|
||||
|
||||
@ -334,7 +348,7 @@ func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, u
|
||||
}
|
||||
|
||||
if !user.IsAdminOrServiceUser() || user.AccountID != accountID {
|
||||
return nil, status.Errorf(status.Unauthorized, "only users with admin power can view setup keys")
|
||||
return nil, status.NewUnauthorizedToViewSetupKeysError()
|
||||
}
|
||||
|
||||
setupKeys, err := am.Store.GetAccountSetupKeys(ctx, LockingStrengthShare, accountID)
|
||||
@ -342,18 +356,7 @@ func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, u
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]*SetupKey, 0, len(setupKeys))
|
||||
for _, key := range setupKeys {
|
||||
var k *SetupKey
|
||||
if !user.IsAdminOrServiceUser() {
|
||||
k = key.HiddenCopy(999)
|
||||
} else {
|
||||
k = key.Copy()
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
return setupKeys, nil
|
||||
}
|
||||
|
||||
// GetSetupKey looks up a SetupKey by KeyID, returns NotFound error if not found.
|
||||
@ -364,7 +367,7 @@ func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, use
|
||||
}
|
||||
|
||||
if !user.IsAdminOrServiceUser() || user.AccountID != accountID {
|
||||
return nil, status.Errorf(status.Unauthorized, "only users with admin power can view setup keys")
|
||||
return nil, status.NewUnauthorizedToViewSetupKeysError()
|
||||
}
|
||||
|
||||
setupKey, err := am.Store.GetSetupKeyByID(ctx, LockingStrengthShare, keyID, accountID)
|
||||
@ -377,11 +380,33 @@ func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, use
|
||||
setupKey.UpdatedAt = setupKey.CreatedAt
|
||||
}
|
||||
|
||||
if !user.IsAdminOrServiceUser() {
|
||||
setupKey = setupKey.HiddenCopy(999)
|
||||
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, LockingStrengthShare, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
return setupKey, nil
|
||||
if !user.IsAdminOrServiceUser() || user.AccountID != accountID {
|
||||
return status.NewUnauthorizedToViewSetupKeysError()
|
||||
}
|
||||
|
||||
deletedSetupKey, err := am.Store.GetSetupKeyByID(ctx, LockingStrengthShare, keyID, accountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get setup key: %w", err)
|
||||
}
|
||||
|
||||
err = am.Store.DeleteSetupKey(ctx, accountID, keyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete setup key: %w", err)
|
||||
}
|
||||
|
||||
am.StoreEvent(ctx, userID, keyID, accountID, activity.SetupKeyDeleted, deletedSetupKey.EventMeta())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSetupKeyAutoGroups(account *Account, autoGroups []string) error {
|
||||
|
Reference in New Issue
Block a user