diff --git a/management/server/account.go b/management/server/account.go index c8dadb4e1..8bb6bdba1 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -40,8 +40,8 @@ func cacheEntryExpiration() time.Duration { type AccountManager interface { GetOrCreateAccountByUser(userId, domain string) (*Account, error) CreateSetupKey(accountID string, keyName string, keyType SetupKeyType, expiresIn time.Duration, - autoGroups []string, usageLimit int) (*SetupKey, error) - SaveSetupKey(accountID string, key *SetupKey) (*SetupKey, error) + autoGroups []string, usageLimit int, userID string) (*SetupKey, error) + SaveSetupKey(accountID string, key *SetupKey, userID string) (*SetupKey, error) CreateUser(accountID, userID string, key *UserInfo) (*UserInfo, error) ListSetupKeys(accountID, userID string) ([]*SetupKey, error) SaveUser(accountID string, key *User) (*UserInfo, error) diff --git a/management/server/activity/event.go b/management/server/activity/event.go index 1584abe11..14603af99 100644 --- a/management/server/activity/event.go +++ b/management/server/activity/event.go @@ -21,6 +21,14 @@ const ( RuleUpdated // RuleRemoved indicates that a user removed a rule RuleRemoved + // SetupKeyCreated indicates that a user created a new setup key + SetupKeyCreated + // SetupKeyUpdated indicates that a user updated a setup key + SetupKeyUpdated + // SetupKeyRevoked indicates that a user revoked a setup key + SetupKeyRevoked + // SetupKeyOverused indicates that setup key usage exhausted + SetupKeyOverused ) const ( @@ -42,6 +50,14 @@ const ( RuleRemovedMessage string = "Rule deleted" // RuleUpdatedMessage is a human-readable text message of the RuleRemoved activity RuleUpdatedMessage string = "Rule updated" + // SetupKeyCreatedMessage is a human-readable text message of the SetupKeyCreated activity + SetupKeyCreatedMessage string = "Setup key created" + // SetupKeyUpdatedMessage is a human-readable text message of the SetupKeyUpdated activity + SetupKeyUpdatedMessage string = "Setup key updated" + // SetupKeyRevokedMessage is a human-readable text message of the SetupKeyRevoked activity + SetupKeyRevokedMessage string = "Setup key revoked" + // SetupKeyOverusedMessage is a human-readable text message of the SetupKeyOverused activity + SetupKeyOverusedMessage string = "Setup key overused" ) // Activity that triggered an Event @@ -68,6 +84,14 @@ func (a Activity) Message() string { return RuleRemovedMessage case RuleUpdated: return RuleUpdatedMessage + case SetupKeyCreated: + return SetupKeyCreatedMessage + case SetupKeyUpdated: + return SetupKeyUpdatedMessage + case SetupKeyRevoked: + return SetupKeyRevokedMessage + case SetupKeyOverused: + return SetupKeyOverusedMessage default: return "UNKNOWN_ACTIVITY" } @@ -94,6 +118,14 @@ func (a Activity) StringCode() string { return "rule.delete" case RuleUpdated: return "rule.update" + case SetupKeyCreated: + return "setupkey.add" + case SetupKeyRevoked: + return "setupkey.revoke" + case SetupKeyOverused: + return "setupkey.overuse" + case SetupKeyUpdated: + return "setupkey.update" default: return "UNKNOWN_ACTIVITY" } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 238a74f49..71e6842d1 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -519,7 +519,11 @@ components: activity_code: description: The string code of the activity that occurred during the event type: string - enum: [ "account.create", "user.join", "user.invite", "user.peer.add", "setupkey.peer.add", "user.peer.delete", "rule.add", "rule.delete", "rule.update"] + enum: [ "user.peer.delete", "user.join", "user.invite", "user.peer.add", + "setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse", + "rule.add", "rule.delete", "rule.update", + "account.create", + ] initiator_id: description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event. type: string diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 3c99a7948..e723e37fc 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -17,7 +17,11 @@ const ( EventActivityCodeRuleAdd EventActivityCode = "rule.add" EventActivityCodeRuleDelete EventActivityCode = "rule.delete" EventActivityCodeRuleUpdate EventActivityCode = "rule.update" + EventActivityCodeSetupkeyAdd EventActivityCode = "setupkey.add" + EventActivityCodeSetupkeyOveruse EventActivityCode = "setupkey.overuse" EventActivityCodeSetupkeyPeerAdd EventActivityCode = "setupkey.peer.add" + EventActivityCodeSetupkeyRevoke EventActivityCode = "setupkey.revoke" + EventActivityCodeSetupkeyUpdate EventActivityCode = "setupkey.update" EventActivityCodeUserInvite EventActivityCode = "user.invite" EventActivityCodeUserJoin EventActivityCode = "user.join" EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add" diff --git a/management/server/http/setupkeys.go b/management/server/http/setupkeys.go index a88cba29e..cecc86036 100644 --- a/management/server/http/setupkeys.go +++ b/management/server/http/setupkeys.go @@ -30,7 +30,7 @@ func NewSetupKeysHandler(accountManager server.AccountManager, authAudience stri // CreateSetupKeyHandler is a POST requests that creates a new SetupKey func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request) { claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience) - account, _, err := h.accountManager.GetAccountFromToken(claims) + account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return @@ -61,7 +61,7 @@ func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request } setupKey, err := h.accountManager.CreateSetupKey(account.Id, req.Name, server.SetupKeyType(req.Type), expiresIn, - req.AutoGroups, req.UsageLimit) + req.AutoGroups, req.UsageLimit, user.Id) if err != nil { util.WriteError(err, w) return @@ -98,7 +98,7 @@ func (h *SetupKeys) GetSetupKeyHandler(w http.ResponseWriter, r *http.Request) { // UpdateSetupKeyHandler is a PUT request to update server.SetupKey func (h *SetupKeys) UpdateSetupKeyHandler(w http.ResponseWriter, r *http.Request) { claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience) - account, _, err := h.accountManager.GetAccountFromToken(claims) + account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return @@ -134,7 +134,7 @@ func (h *SetupKeys) UpdateSetupKeyHandler(w http.ResponseWriter, r *http.Request newKey.Name = req.Name newKey.Id = keyID - newKey, err = h.accountManager.SaveSetupKey(account.Id, newKey) + newKey, err = h.accountManager.SaveSetupKey(account.Id, newKey, user.Id) if err != nil { util.WriteError(err, w) return diff --git a/management/server/setupkey.go b/management/server/setupkey.go index 824d381a2..84fe45d71 100644 --- a/management/server/setupkey.go +++ b/management/server/setupkey.go @@ -2,6 +2,7 @@ package server import ( "github.com/google/uuid" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/status" "hash/fnv" "strconv" @@ -107,12 +108,20 @@ func (key *SetupKey) Copy() *SetupKey { } } +// EventMeta returns activity event meta related to the peer +func (key *SetupKey) EventMeta() map[string]any { + return map[string]any{"name": key.Name, "type": key.Type, "key": key.HiddenCopy(1).Key} +} + // HiddenCopy returns a copy of the key with a Key value hidden with "*" and a 5 character prefix. // E.g., "831F6*******************************" -func (key *SetupKey) HiddenCopy() *SetupKey { +func (key *SetupKey) HiddenCopy(length int) *SetupKey { k := key.Copy() prefix := k.Key[0:5] - k.Key = prefix + strings.Repeat("*", utf8.RuneCountInString(key.Key)-len(prefix)) + if length > utf8.RuneCountInString(key.Key) { + length = utf8.RuneCountInString(key.Key) - len(prefix) + } + k.Key = prefix + strings.Repeat("*", length) return k } @@ -189,7 +198,7 @@ func Hash(s string) uint32 { // 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(accountID string, keyName string, keyType SetupKeyType, - expiresIn time.Duration, autoGroups []string, usageLimit int) (*SetupKey, error) { + expiresIn time.Duration, autoGroups []string, usageLimit int, userID string) (*SetupKey, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -211,12 +220,23 @@ func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string setupKey := GenerateSetupKey(keyName, keyType, keyDuration, autoGroups, usageLimit) account.SetupKeys[setupKey.Key] = setupKey - err = am.Store.SaveAccount(account) if err != nil { return nil, status.Errorf(status.Internal, "failed adding account key") } + _, err = am.eventStore.Save(&activity.Event{ + Timestamp: time.Now(), + Activity: activity.SetupKeyCreated, + InitiatorID: userID, + TargetID: setupKey.Id, + AccountID: accountID, + Meta: setupKey.EventMeta(), + }) + if err != nil { + return nil, err + } + return setupKey, nil } @@ -224,7 +244,7 @@ func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string // 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: Name, AutoGroups, Revoked. The rest is copied from the existing key. -func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *SetupKey) (*SetupKey, error) { +func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *SetupKey, userID string) (*SetupKey, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -261,6 +281,20 @@ func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *Setup return nil, err } + if !oldKey.Revoked && newKey.Revoked { + _, err = am.eventStore.Save(&activity.Event{ + Timestamp: time.Now(), + Activity: activity.SetupKeyRevoked, + InitiatorID: userID, + TargetID: newKey.Id, + AccountID: accountID, + Meta: newKey.EventMeta(), + }) + if err != nil { + return nil, err + } + } + return newKey, am.updateAccountPeers(account) } @@ -282,7 +316,7 @@ func (am *DefaultAccountManager) ListSetupKeys(accountID, userID string) ([]*Set for _, key := range account.SetupKeys { var k *SetupKey if !user.IsAdmin() { - k = key.HiddenCopy() + k = key.HiddenCopy(999) } else { k = key.Copy() } @@ -324,7 +358,7 @@ func (am *DefaultAccountManager) GetSetupKey(accountID, userID, keyID string) (* } if !user.IsAdmin() { - foundKey = foundKey.HiddenCopy() + foundKey = foundKey.HiddenCopy(999) } return foundKey, nil