Compare commits

...

74 Commits

Author SHA1 Message Date
braginini
213ab7d43e Fix sqlite codacy 2022-12-27 18:00:40 +01:00
braginini
b1cc1b880e Fix tests 2022-12-27 17:52:55 +01:00
braginini
7cf9ab71f2 Fix tests 2022-12-27 17:48:00 +01:00
braginini
6143aeef35 Move sqlite activity 2022-12-27 17:39:19 +01:00
braginini
afacac174c Fix test 2022-12-27 17:29:14 +01:00
braginini
fea4336bca Fix uint :) 2022-12-27 17:23:33 +01:00
braginini
0e5cfcf9ce Try without cgo 2022-12-27 17:17:25 +01:00
braginini
37b22de660 Try without cgo 2022-12-27 17:13:45 +01:00
braginini
0600df1217 Fix docker test 2022-12-27 16:35:36 +01:00
braginini
9e95c82a98 Fix docker test 2022-12-27 16:31:59 +01:00
braginini
22934ac640 Fix base.setup.env 2022-12-27 14:16:43 +01:00
braginini
ad2ac7a383 Fix sqlite test 2022-12-27 13:54:55 +01:00
braginini
86f731a76b fix linux test run 2022-12-27 13:44:26 +01:00
braginini
7e0237d2cb CGO_ENABLED=1 in tests 2022-12-27 12:38:02 +01:00
braginini
6bc2fb2d74 CGO_ENABLED=1 2022-12-27 12:18:36 +01:00
braginini
81ad6db431 Attempt to fix sqlite 2022-12-27 12:12:37 +01:00
braginini
59eac3cb63 Rollback an attempt to fix sqlite 2022-12-27 12:11:21 +01:00
braginini
fa9007bc04 Fix tests 2022-12-27 11:57:50 +01:00
braginini
fbd03e8ec2 Fix test methods calls 2022-12-27 11:52:15 +01:00
braginini
1a0e2c6d30 Attempt to fix sqlite cgo 2022-12-22 13:27:08 +01:00
braginini
4f9e047e7e Fix codacy and lint issues 2022-12-22 12:24:02 +01:00
braginini
e750a1a40e Merge branch 'feature/event-storage' of github.com:wiretrustee/wiretrustee into feature/event-storage
# Conflicts:
#	infrastructure_files/base.setup.env
2022-12-22 12:08:48 +01:00
braginini
4105a1a24b Make test compile 2022-12-22 12:06:15 +01:00
braginini
bfe84fd6e8 Add group events 2022-12-22 12:06:15 +01:00
braginini
91b61efe60 Add setup key events 2022-12-22 12:06:15 +01:00
braginini
747192c9f8 Add more events 2022-12-22 12:06:15 +01:00
braginini
dbda9087f5 Remove duplicate UserJoined events from the response 2022-12-22 12:06:15 +01:00
braginini
599c823750 Add user invite event 2022-12-22 12:06:15 +01:00
braginini
5cb2dc676b Finalize events API 2022-12-22 12:06:15 +01:00
braginini
cc12aff3a4 Fetch Activity events 2022-12-22 12:06:15 +01:00
braginini
647f726737 Add Events HTTP API 2022-12-22 12:06:15 +01:00
braginini
1174141abc Add join user operation 2022-12-22 12:06:15 +01:00
braginini
26f721179b Add peer operation 2022-12-22 12:06:15 +01:00
braginini
a74b4c4b42 Test event ordering in SQLite storage 2022-12-22 12:06:15 +01:00
braginini
6733e9d51e Remove unused event store methods 2022-12-22 12:06:15 +01:00
braginini
1cf041fe07 Remove unused event store methods 2022-12-22 12:06:15 +01:00
braginini
94d5d499ac Fetch events with offset limit 2022-12-22 12:06:15 +01:00
braginini
1a7f441652 Fetch events with offset limit 2022-12-22 12:06:15 +01:00
braginini
908eb4d051 Fix SQLiteStore test 2022-12-22 12:06:15 +01:00
braginini
e12b74760c Fix lint issue 2022-12-22 12:06:15 +01:00
braginini
31fda2b062 Add basic Save and Get operations to the event store 2022-12-22 12:06:15 +01:00
braginini
7a3c6b576c Add basic Save and Get operations to the event store 2022-12-22 12:06:15 +01:00
braginini
b7f4a6f098 Rename audit to event 2022-12-22 12:06:15 +01:00
braginini
cafd10b3a3 Initial event store draft 2022-12-22 12:06:15 +01:00
braginini
359b2a13a7 Make test compile 2022-12-22 11:54:26 +01:00
braginini
a32bc441d2 Add group events 2022-12-21 18:55:14 +01:00
braginini
ef171ebdd4 Add setup key events 2022-12-21 18:43:10 +01:00
braginini
f485b654b4 Add more events 2022-12-20 18:52:18 +01:00
braginini
ca291a0fd6 Remove duplicate UserJoined events from the response 2022-12-13 15:50:45 +01:00
braginini
3d6b0e3638 Add user invite event 2022-12-12 14:05:11 +01:00
braginini
c50d07b83f Finalize events API 2022-12-12 09:10:55 +01:00
braginini
4440ad1271 Fetch Activity events 2022-12-11 21:16:47 +01:00
braginini
f684baa665 Add Events HTTP API 2022-12-11 20:01:17 +01:00
Maycon Santos
cf27bd8951 Export single account domain variable 2022-12-11 19:35:43 +01:00
Maycon Santos
739afea4b4 Filter routes to sync from same HA group (#618)
An additional check and filter for routes that are part
 of the same HA group where the peer is a routing peer
2022-12-11 19:35:43 +01:00
Maycon Santos
2dec9989c6 Use latest tag for dashboard (#617)
Using the latest tag will align with the
dashboard's new release cycle that relies on tags
2022-12-11 19:35:43 +01:00
Krzysztof Nazarewski
5d544ae55e HA Network Routes: prevent routing directly-accessible networks through VPN interface (#612)
Prevent routing peer to add routes from the same HA group as client routes
2022-12-11 19:35:43 +01:00
Maycon Santos
94803417cf Generate validation certificate from mandatory JWK fields (#614)
When there is no X5c we will use N and E fields of 
a JWK to generate the public RSA and a Pem certificate
2022-12-11 19:35:43 +01:00
Maycon Santos
1835dd3e3c Remove wiretrustee conflict checks (#615) 2022-12-11 19:35:43 +01:00
Maycon Santos
cfed36ff8d Handle peer deletion and state update (#611)
If peer is deleted in the console,
we set its state as needs login

On Down command we clean any previous state errors
this prevents need for daemon restart

Removed state error wrapping when engine exits, log is enough
2022-12-11 19:35:43 +01:00
Maycon Santos
a212686193 Add network routes distribution groups (#606)
Updated tests, API, and account manager methods

Sync routes to peers in the distribution groups

Added store upgrade by adding the All group to routes that don't have them
2022-12-11 19:35:43 +01:00
braginini
e741775d4d Add join user operation 2022-12-07 12:04:43 +01:00
braginini
04ea300043 Add peer operation 2022-12-07 11:06:46 +01:00
braginini
e75c25b10d Test event ordering in SQLite storage 2022-12-07 10:37:49 +01:00
braginini
e81f4af3e8 Remove unused event store methods 2022-12-07 10:33:32 +01:00
braginini
c09c3a70ab Remove unused event store methods 2022-12-07 10:32:38 +01:00
braginini
0e3bbf0e55 Fetch events with offset limit 2022-12-07 10:31:05 +01:00
braginini
0f4d132b93 Fetch events with offset limit 2022-12-07 10:30:59 +01:00
braginini
ef97e0840c Fix SQLiteStore test 2022-12-06 16:59:35 +01:00
braginini
d98a8e0645 Fix lint issue 2022-12-06 16:58:08 +01:00
braginini
1c1e639445 Add basic Save and Get operations to the event store 2022-12-06 16:17:01 +01:00
braginini
65afc9a884 Add basic Save and Get operations to the event store 2022-12-06 16:08:21 +01:00
braginini
16e4d109ff Rename audit to event 2022-12-06 13:33:10 +01:00
braginini
0f68edbefb Initial event store draft 2022-12-05 15:42:23 +01:00
39 changed files with 1099 additions and 202 deletions

View File

@@ -31,13 +31,13 @@ jobs:
uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib
- name: Install modules
run: go mod tidy
- name: Test
run: GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
test_client_on_docker:
runs-on: ubuntu-latest

View File

@@ -2,6 +2,7 @@ package cmd
import (
"context"
"github.com/netbirdio/netbird/management/server/activity"
"net"
"path/filepath"
"testing"
@@ -68,7 +69,12 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
}
peersUpdateManager := mgmt.NewPeersUpdateManager()
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
eventStore := &activity.NoopEventStore{}
if err != nil {
return nil, nil
}
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore)
if err != nil {
t.Fatal(err)
}

View File

@@ -9,6 +9,7 @@ import (
nbstatus "github.com/netbirdio/netbird/client/status"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/route"
"github.com/stretchr/testify/assert"
"net"
@@ -953,7 +954,12 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
}
peersUpdateManager := server.NewPeersUpdateManager()
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "")
eventStore := &activity.NoopEventStore{}
if err != nil {
return nil, nil
}
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore)
if err != nil {
return nil, err
}

1
go.mod
View File

@@ -40,6 +40,7 @@ require (
github.com/hashicorp/go-version v1.6.0
github.com/libp2p/go-netroute v0.2.0
github.com/magiconair/properties v1.8.5
github.com/mattn/go-sqlite3 v1.14.16
github.com/miekg/dns v1.1.41
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/patrickmn/go-cache v2.1.0+incompatible

2
go.sum
View File

@@ -432,6 +432,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=

View File

@@ -2,6 +2,7 @@ package client
import (
"context"
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
"net"
"path/filepath"
"sync"
@@ -55,7 +56,12 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
}
peersUpdateManager := mgmt.NewPeersUpdateManager()
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
eventStore, err := activity.NewSQLiteStore(t.TempDir())
if err != nil {
return nil, nil
}
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore)
if err != nil {
t.Fatal(err)
}

View File

@@ -9,6 +9,7 @@ import (
"fmt"
"github.com/google/uuid"
"github.com/miekg/dns"
"github.com/netbirdio/netbird/management/server/activity/sqlite"
httpapi "github.com/netbirdio/netbird/management/server/http"
"github.com/netbirdio/netbird/management/server/metrics"
"github.com/netbirdio/netbird/management/server/telemetry"
@@ -142,7 +143,12 @@ var (
if disableSingleAccMode {
mgmtSingleAccModeDomain = ""
}
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, dnsDomain)
eventStore, err := sqlite.NewSQLiteStore(config.Datadir)
if err != nil {
return err
}
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
dnsDomain, eventStore)
if err != nil {
return fmt.Errorf("failed to build default manager: %v", err)
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/eko/gocache/v3/cache"
cacheStore "github.com/eko/gocache/v3/store"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/status"
@@ -39,9 +40,9 @@ 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)
CreateUser(accountID string, key *UserInfo) (*UserInfo, 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)
GetSetupKey(accountID, userID, keyID string) (*SetupKey, error)
@@ -52,7 +53,7 @@ type AccountManager interface {
GetPeer(peerKey string) (*Peer, error)
GetPeers(accountID, userID string) ([]*Peer, error)
MarkPeerConnected(peerKey string, connected bool) error
DeletePeer(accountId string, peerKey string) (*Peer, error)
DeletePeer(accountID, peerKey, userID string) (*Peer, error)
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
UpdatePeer(accountID string, peer *Peer) (*Peer, error)
GetNetworkMap(peerKey string) (*NetworkMap, error)
@@ -62,7 +63,7 @@ type AccountManager interface {
UpdatePeerSSHKey(peerKey string, sshKey string) error
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
GetGroup(accountId, groupID string) (*Group, error)
SaveGroup(accountId string, group *Group) error
SaveGroup(accountID, userID string, group *Group) error
UpdateGroup(accountID string, groupID string, operations []GroupUpdateOperation) (*Group, error)
DeleteGroup(accountId, groupID string) error
ListGroups(accountId string) ([]*Group, error)
@@ -70,9 +71,9 @@ type AccountManager interface {
GroupDeletePeer(accountId, groupID, peerKey string) error
GroupListPeers(accountId, groupID string) ([]*Peer, error)
GetRule(accountID, ruleID, userID string) (*Rule, error)
SaveRule(accountID string, rule *Rule) error
SaveRule(accountID, userID string, rule *Rule) error
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
DeleteRule(accountId, ruleID string) error
DeleteRule(accountID, ruleID, userID string) error
ListRules(accountID, userID string) ([]*Rule, error)
GetRoute(accountID, routeID, userID string) (*route.Route, error)
CreateRoute(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool) (*route.Route, error)
@@ -87,6 +88,7 @@ type AccountManager interface {
DeleteNameServerGroup(accountID, nsGroupID string) error
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
GetDNSDomain() string
GetEvents(accountID, userID string) ([]*activity.Event, error)
}
type DefaultAccountManager struct {
@@ -99,6 +101,7 @@ type DefaultAccountManager struct {
idpManager idp.Manager
cacheManager cache.CacheInterface[[]*idp.UserData]
ctx context.Context
eventStore activity.Store
// singleAccountMode indicates whether the instance has a single account.
// If true, then every new user will end up under the same account.
@@ -435,7 +438,7 @@ func (a *Account) GetGroupAll() (*Group, error) {
// BuildManager creates a new DefaultAccountManager with a provided Store
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
singleAccountModeDomain string, dnsDomain string) (*DefaultAccountManager, error) {
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store) (*DefaultAccountManager, error) {
am := &DefaultAccountManager{
Store: store,
peersUpdateManager: peersUpdateManager,
@@ -444,6 +447,7 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
cacheMux: sync.Mutex{},
cacheLoading: map[string]chan struct{}{},
dnsDomain: dnsDomain,
eventStore: eventStore,
}
allAccounts := store.GetAllAccounts()
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
@@ -510,7 +514,18 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er
log.Warnf("an account with ID already exists, retrying...")
continue
} else if statusErr.Type() == status.NotFound {
return newAccountWithId(accountId, userID, domain), nil
newAccount := newAccountWithId(accountId, userID, domain)
_, err = am.eventStore.Save(&activity.Event{
Timestamp: time.Now(),
Activity: activity.AccountCreated,
AccountID: newAccount.Id,
TargetID: newAccount.Id,
InitiatorID: userID,
})
if err != nil {
return nil, err
}
return newAccount, nil
} else {
return nil, err
}
@@ -797,6 +812,19 @@ func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims
return nil, err
}
event := &activity.Event{
Timestamp: time.Now(),
Activity: activity.UserJoined,
AccountID: account.Id,
TargetID: claims.UserId,
InitiatorID: claims.UserId,
}
_, err = am.eventStore.Save(event)
if err != nil {
return nil, err
}
return account, nil
}
@@ -828,6 +856,17 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e
return
}
log.Debugf("user %s of account %s redeemed invite", user.ID, account.Id)
_, err = am.eventStore.Save(&activity.Event{
Timestamp: time.Now(),
Activity: activity.UserJoined,
AccountID: account.Id,
TargetID: userID,
InitiatorID: userID,
})
if err != nil {
log.Warnf("failed saving activity event %v", err)
return
}
}()
}

View File

@@ -3,6 +3,7 @@ package server
import (
"fmt"
nbdns "github.com/netbirdio/netbird/dns"
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
"github.com/netbirdio/netbird/route"
"net"
"reflect"
@@ -632,7 +633,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
return
}
account, err := createAccount(manager, "test_account", "account_creator", "")
userID := "account_creator"
account, err := createAccount(manager, "test_account", userID, "")
if err != nil {
t.Fatal(err)
}
@@ -714,7 +717,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
}
}()
if err := manager.SaveGroup(account.Id, &group); err != nil {
if err := manager.SaveGroup(account.Id, userID, &group); err != nil {
t.Errorf("save group: %v", err)
return
}
@@ -739,7 +742,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
defaultRule = r
}
if err := manager.DeleteRule(account.Id, defaultRule.ID); err != nil {
if err := manager.DeleteRule(account.Id, defaultRule.ID, userID); err != nil {
t.Errorf("delete default rule: %v", err)
return
}
@@ -759,7 +762,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
}
}()
if err := manager.SaveRule(account.Id, &rule); err != nil {
if err := manager.SaveRule(account.Id, userID, &rule); err != nil {
t.Errorf("delete default rule: %v", err)
return
}
@@ -779,7 +782,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
}
}()
if _, err := manager.DeletePeer(account.Id, peer3.Key); err != nil {
if _, err := manager.DeletePeer(account.Id, peer3.Key, userID); err != nil {
t.Errorf("delete peer: %v", err)
return
}
@@ -814,8 +817,8 @@ func TestAccountManager_DeletePeer(t *testing.T) {
t.Fatal(err)
return
}
account, err := createAccount(manager, "test_account", "account_creator", "")
userID := "account_creator"
account, err := createAccount(manager, "test_account", userID, "")
if err != nil {
t.Fatal(err)
}
@@ -843,7 +846,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
return
}
_, err = manager.DeletePeer(account.Id, peerKey)
_, err = manager.DeletePeer(account.Id, peerKey, userID)
if err != nil {
return
}
@@ -1228,7 +1231,11 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
eventStore, err := activity.NewSQLiteStore(t.TempDir())
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
}
func createStore(t *testing.T) (Store, error) {

View File

@@ -0,0 +1,223 @@
package activity
import "time"
const (
// PeerAddedByUser indicates that a user added a new peer to the system
PeerAddedByUser Activity = iota
// PeerAddedWithSetupKey indicates that a new peer joined the system using a setup key
PeerAddedWithSetupKey
// UserJoined indicates that a new user joined the account
UserJoined
// UserInvited indicates that a new user was invited to join the account
UserInvited
// AccountCreated indicates that a new account has been created
AccountCreated
// PeerRemovedByUser indicates that a user removed a peer from the system
PeerRemovedByUser
// RuleAdded indicates that a user added a new rule
RuleAdded
// RuleUpdated indicates that a user updated a rule
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
// GroupCreated indicates that a user created a group
GroupCreated
// GroupUpdated indicates that a user updated a group
GroupUpdated
// PeerGroupsUpdated indicates that a user updated groups of a peer
PeerGroupsUpdated
)
const (
// PeerAddedByUserMessage is a human-readable text message of the PeerAddedByUser activity
PeerAddedByUserMessage string = "Peer added"
// PeerAddedWithSetupKeyMessage is a human-readable text message of the PeerAddedWithSetupKey activity
PeerAddedWithSetupKeyMessage = PeerAddedByUserMessage
//UserJoinedMessage is a human-readable text message of the UserJoined activity
UserJoinedMessage string = "User joined"
//UserInvitedMessage is a human-readable text message of the UserInvited activity
UserInvitedMessage string = "User invited"
//AccountCreatedMessage is a human-readable text message of the AccountCreated activity
AccountCreatedMessage string = "Account created"
// PeerRemovedByUserMessage is a human-readable text message of the PeerRemovedByUser activity
PeerRemovedByUserMessage string = "Peer deleted"
// RuleAddedMessage is a human-readable text message of the RuleAdded activity
RuleAddedMessage string = "Rule added"
// RuleRemovedMessage is a human-readable text message of the RuleRemoved activity
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"
// GroupCreatedMessage is a human-readable text message of the GroupCreated activity
GroupCreatedMessage string = "Group created"
// GroupUpdatedMessage is a human-readable text message of the GroupUpdated activity
GroupUpdatedMessage string = "Group updated"
// PeerGroupsUpdatedMessage is a human-readable text message of the PeerGroupsUpdated activity
PeerGroupsUpdatedMessage string = "Peer groups updated"
)
// Activity that triggered an Event
type Activity int
// Message returns a string representation of an activity
func (a Activity) Message() string {
switch a {
case PeerAddedByUser:
return PeerAddedByUserMessage
case PeerRemovedByUser:
return PeerRemovedByUserMessage
case PeerAddedWithSetupKey:
return PeerAddedWithSetupKeyMessage
case UserJoined:
return UserJoinedMessage
case UserInvited:
return UserInvitedMessage
case AccountCreated:
return AccountCreatedMessage
case RuleAdded:
return RuleAddedMessage
case RuleRemoved:
return RuleRemovedMessage
case RuleUpdated:
return RuleUpdatedMessage
case SetupKeyCreated:
return SetupKeyCreatedMessage
case SetupKeyUpdated:
return SetupKeyUpdatedMessage
case SetupKeyRevoked:
return SetupKeyRevokedMessage
case SetupKeyOverused:
return SetupKeyOverusedMessage
case GroupCreated:
return GroupCreatedMessage
case GroupUpdated:
return GroupUpdatedMessage
case PeerGroupsUpdated:
return PeerGroupsUpdatedMessage
default:
return "UNKNOWN_ACTIVITY"
}
}
// StringCode returns a string code of the activity
func (a Activity) StringCode() string {
switch a {
case PeerAddedByUser:
return "user.peer.add"
case PeerRemovedByUser:
return "user.peer.delete"
case PeerAddedWithSetupKey:
return "setupkey.peer.add"
case UserJoined:
return "user.join"
case UserInvited:
return "user.invite"
case AccountCreated:
return "account.create"
case RuleAdded:
return "rule.add"
case RuleRemoved:
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"
case GroupCreated:
return "group.add"
case GroupUpdated:
return "group.update"
case PeerGroupsUpdated:
return "peer.groups.update"
default:
return "UNKNOWN_ACTIVITY"
}
}
// Store provides an interface to store or stream events.
type Store interface {
// Save an event in the store
Save(event *Event) (*Event, error)
// Get returns "limit" number of events from the "offset" index ordered descending or ascending by a timestamp
Get(accountID string, offset, limit int, descending bool) ([]*Event, error)
// Close the sink flushing events if necessary
Close() error
}
// NoopEventStore implements the Store interface without doing any operations
type NoopEventStore struct {
}
// Save sets the Event.ID to 1
func (store *NoopEventStore) Save(event *Event) (*Event, error) {
event.ID = 1
return event, nil
}
// Get returns an empty list of events
func (store *NoopEventStore) Get(accountID string, offset, limit int, descending bool) ([]*Event, error) {
return []*Event{}, nil
}
// Close doesn't close anything
func (store *NoopEventStore) Close() error {
return nil
}
// Event represents a network/system activity event.
type Event struct {
// Timestamp of the event
Timestamp time.Time
// Activity that was performed during the event
Activity Activity
// ID of the event (can be empty, meaning that it wasn't yet generated)
ID uint64
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
InitiatorID string
// TargetID is the ID of an object that was effected by the event (e.g., a peer)
TargetID string
// AccountID is the ID of an account where the event happened
AccountID string
// Meta of the event, e.g. deleted peer information like name, IP, etc
Meta map[string]any
}
// Copy the event
func (e *Event) Copy() *Event {
meta := make(map[string]any, len(e.Meta))
for key, value := range e.Meta {
meta[key] = value
}
return &Event{
Timestamp: e.Timestamp,
Activity: e.Activity,
ID: e.ID,
InitiatorID: e.InitiatorID,
TargetID: e.TargetID,
AccountID: e.AccountID,
Meta: meta,
}
}

View File

@@ -0,0 +1 @@
package activity

View File

@@ -0,0 +1,145 @@
package sqlite
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/netbirdio/netbird/management/server/activity"
// sqlite driver
_ "github.com/mattn/go-sqlite3"
"path/filepath"
"time"
)
const (
//eventSinkDB is the default name of the events database
eventSinkDB = "events.db"
createTableQuery = "CREATE TABLE IF NOT EXISTS events " +
"(id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"activity INTEGER, " +
"timestamp DATETIME, " +
"initiator_id TEXT," +
"account_id TEXT," +
"meta TEXT," +
" target_id TEXT);"
)
// Store is the implementation of the activity.Store interface backed by SQLite
type Store struct {
db *sql.DB
}
// NewSQLiteStore creates a new Store with an event table if not exists.
func NewSQLiteStore(dataDir string) (*Store, error) {
dbFile := filepath.Join(dataDir, eventSinkDB)
db, err := sql.Open("sqlite3", dbFile)
if err != nil {
return nil, err
}
_, err = db.Exec(createTableQuery)
if err != nil {
return nil, err
}
return &Store{db: db}, nil
}
func processResult(result *sql.Rows) ([]*activity.Event, error) {
events := make([]*activity.Event, 0)
for result.Next() {
var id int64
var operation activity.Activity
var timestamp time.Time
var initiator string
var target string
var account string
var jsonMeta string
err := result.Scan(&id, &operation, &timestamp, &initiator, &target, &account, &jsonMeta)
if err != nil {
return nil, err
}
meta := make(map[string]any)
if jsonMeta != "" {
err = json.Unmarshal([]byte(jsonMeta), &meta)
if err != nil {
return nil, err
}
}
events = append(events, &activity.Event{
Timestamp: timestamp,
Activity: operation,
ID: uint64(id),
InitiatorID: initiator,
TargetID: target,
AccountID: account,
Meta: meta,
})
}
return events, nil
}
// Get returns "limit" number of events from index ordered descending or ascending by a timestamp
func (store *Store) Get(accountID string, offset, limit int, descending bool) ([]*activity.Event, error) {
order := "DESC"
if !descending {
order = "ASC"
}
stmt, err := store.db.Prepare(fmt.Sprintf("SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta"+
" FROM events WHERE account_id = ? ORDER BY timestamp %s LIMIT ? OFFSET ?;", order))
if err != nil {
return nil, err
}
result, err := stmt.Query(accountID, limit, offset)
if err != nil {
return nil, err
}
defer result.Close() //nolint
return processResult(result)
}
// Save an event in the SQLite events table
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
stmt, err := store.db.Prepare("INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) VALUES(?, ?, ?, ?, ?, ?)")
if err != nil {
return nil, err
}
var jsonMeta string
if event.Meta != nil {
metaBytes, err := json.Marshal(event.Meta)
if err != nil {
return nil, err
}
jsonMeta = string(metaBytes)
}
result, err := stmt.Exec(event.Activity, event.Timestamp, event.InitiatorID, event.TargetID, event.AccountID, jsonMeta)
if err != nil {
return nil, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, err
}
eventCopy := event.Copy()
eventCopy.ID = uint64(id)
return eventCopy, nil
}
// Close the Store
func (store *Store) Close() error {
if store.db != nil {
return store.db.Close()
}
return nil
}

View File

@@ -0,0 +1,52 @@
package sqlite
import (
"fmt"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestNewSQLiteStore(t *testing.T) {
dataDir := t.TempDir()
store, err := NewSQLiteStore(dataDir)
if err != nil {
t.Fatal(err)
return
}
accountID := "account_1"
for i := 0; i < 10; i++ {
_, err = store.Save(&activity.Event{
Timestamp: time.Now(),
Activity: activity.PeerAddedByUser,
InitiatorID: "user_" + fmt.Sprint(i),
TargetID: "peer_" + fmt.Sprint(i),
AccountID: accountID,
})
if err != nil {
t.Fatal(err)
return
}
}
result, err := store.Get(accountID, 0, 10, false)
if err != nil {
t.Fatal(err)
return
}
assert.Len(t, result, 10)
assert.True(t, result[0].Timestamp.Before(result[len(result)-1].Timestamp))
result, err = store.Get(accountID, 0, 5, true)
if err != nil {
t.Fatal(err)
return
}
assert.Len(t, result, 5)
assert.True(t, result[0].Timestamp.After(result[len(result)-1].Timestamp))
}

View File

@@ -0,0 +1,33 @@
package server
import (
"fmt"
"github.com/netbirdio/netbird/management/server/activity"
)
// GetEvents returns a list of activity events of an account
func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activity.Event, error) {
events, err := am.eventStore.Get(accountID, 0, 10000, true)
if err != nil {
return nil, err
}
// this is a workaround for duplicate activity.UserJoined events that might occur when a user redeems invite.
// we will need to find a better way to handle this.
filtered := make([]*activity.Event, 0)
dups := make(map[string]struct{})
for _, event := range events {
if event.Activity == activity.UserJoined {
key := event.TargetID + event.InitiatorID + event.AccountID + fmt.Sprint(event.Activity)
_, duplicate := dups[key]
if duplicate {
continue
} else {
dups[key] = struct{}{}
}
}
filtered = append(filtered, event)
}
return filtered, nil
}

View File

@@ -1,6 +1,10 @@
package server
import "github.com/netbirdio/netbird/management/server/status"
import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"time"
)
// Group of the peers for ACL
type Group struct {
@@ -34,6 +38,11 @@ type GroupUpdateOperation struct {
Values []string
}
// EventMeta returns activity event meta related to the group
func (g *Group) EventMeta() map[string]any {
return map[string]any{"name": g.Name}
}
func (g *Group) Copy() *Group {
return &Group{
ID: g.ID,
@@ -62,7 +71,7 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, er
}
// SaveGroup object of the peers
func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error {
func (am *DefaultAccountManager) SaveGroup(accountID, userID string, group *Group) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@@ -71,7 +80,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
if err != nil {
return err
}
_, exists := account.Groups[group.ID]
account.Groups[group.ID] = group
account.Network.IncSerial()
@@ -79,6 +88,20 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
return err
}
if !exists {
_, err = am.eventStore.Save(&activity.Event{
Timestamp: time.Now(),
Activity: activity.GroupCreated,
InitiatorID: userID,
TargetID: group.ID,
AccountID: accountID,
Meta: group.EventMeta(),
})
if err != nil {
return err
}
}
return am.updateAccountPeers(account)
}

View File

@@ -435,10 +435,7 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig {
netmask, _ := network.Net.Mask.Size()
fqdn := ""
if dnsName != "" {
fqdn = peer.DNSLabel + "." + dnsName
}
fqdn := peer.FQDN(dnsName)
return &proto.PeerConfig{
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
@@ -449,10 +446,7 @@ func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfi
func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig {
remotePeers := []*proto.RemotePeerConfig{}
for _, rPeer := range peers {
fqdn := ""
if dnsName != "" {
fqdn = rPeer.DNSLabel + "." + dnsName
}
fqdn := rPeer.FQDN(dnsName)
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
WgPubKey: rPeer.Key,
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},

View File

@@ -18,6 +18,8 @@ tags:
description: Interact with and view information about routes.
- name: DNS
description: Interact with and view information about DNS configuration.
- name: Events
description: View information about the account and network events.
components:
schemas:
User:
@@ -45,12 +47,12 @@ components:
items:
type: string
required:
- id
- email
- name
- role
- auto_groups
- status
- id
- email
- name
- role
- auto_groups
- status
UserRequest:
type: object
properties:
@@ -96,8 +98,8 @@ components:
description: Peer's hostname
type: string
required:
- id
- name
- id
- name
Peer:
allOf:
- $ref: '#/components/schemas/PeerMinimum'
@@ -140,15 +142,15 @@ components:
description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
type: string
required:
- ip
- connected
- last_seen
- os
- version
- groups
- ssh_enabled
- hostname
- dns_label
- ip
- connected
- last_seen
- os
- version
- groups
- ssh_enabled
- hostname
- dns_label
SetupKey:
type: object
properties:
@@ -197,19 +199,19 @@ components:
description: A number of times this key can be used. The value of 0 indicates the unlimited usage.
type: integer
required:
- id
- key
- name
- expires
- type
- valid
- revoked
- used_times
- last_used
- state
- auto_groups
- updated_at
- usage_limit
- id
- key
- name
- expires
- type
- valid
- revoked
- used_times
- last_used
- state
- auto_groups
- updated_at
- usage_limit
SetupKeyRequest:
type: object
properties:
@@ -253,9 +255,9 @@ components:
description: Count of peers associated to the group
type: integer
required:
- id
- name
- peers_count
- id
- name
- peers_count
Group:
allOf:
- $ref: '#/components/schemas/GroupMinimum'
@@ -267,7 +269,7 @@ components:
items:
$ref: '#/components/schemas/PeerMinimum'
required:
- peers
- peers
PatchMinimum:
type: object
properties:
@@ -311,10 +313,10 @@ components:
description: Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
type: string
required:
- name
- description
- disabled
- flow
- name
- description
- disabled
- flow
Rule:
allOf:
- type: object
@@ -323,7 +325,7 @@ components:
description: Rule ID
type: string
required:
- id
- id
- $ref: '#/components/schemas/RuleMinimum'
- type: object
properties:
@@ -338,8 +340,8 @@ components:
items:
$ref: '#/components/schemas/GroupMinimum'
required:
- sources
- destinations
- sources
- destinations
RulePatchOperation:
allOf:
- $ref: '#/components/schemas/PatchMinimum'
@@ -428,7 +430,7 @@ components:
ns_type:
description: Nameserver Type
type: string
enum: ["udp"]
enum: [ "udp" ]
port:
description: Nameserver Port
type: integer
@@ -498,32 +500,72 @@ components:
path:
description: Nameserver group field to update in form /<field>
type: string
enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ]
enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ]
required:
- path
Event:
type: object
properties:
id:
description: Event unique identifier
type: string
timestamp:
description: The date and time when the event occurred
type: string
format: date-time
activity:
description: The activity that occurred during the event
type: string
activity_code:
description: The string code of the activity that occurred during the event
type: string
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",
"group.add", "group.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
target_id:
description: The ID of the target of the event. E.g., an ID of the peer that a user removed.
type: string
meta:
description: The metadata of the event
type: object
additionalProperties:
type: string
required:
- id
- timestamp
- activity
- activity_code
- initiator_id
- target_id
- meta
responses:
not_found:
description: Resource not found
content: {}
content: { }
validation_failed_simple:
description: Validation failed
content: {}
content: { }
bad_request:
description: Bad Request
content: {}
content: { }
internal_error:
description: Internal Server Error
content: { }
validation_failed:
description: Validation failed
content: {}
content: { }
forbidden:
description: Forbidden
content: {}
content: { }
requires_authentication:
description: Requires authentication
content: {}
content: { }
securitySchemes:
BearerAuth:
type: http
@@ -535,9 +577,9 @@ paths:
/api/users:
get:
summary: Returns a list of all users
tags: [Users]
tags: [ Users ]
security:
- BearerAuth: []
- BearerAuth: [ ]
responses:
'200':
description: A JSON array of Users
@@ -558,7 +600,7 @@ paths:
/api/users/:
post:
summary: Create a User (invite)
tags: [ Users]
tags: [ Users ]
security:
- BearerAuth: [ ]
requestBody:
@@ -585,7 +627,7 @@ paths:
/api/users/{id}:
put:
summary: Update information about a User
tags: [ Users]
tags: [ Users ]
security:
- BearerAuth: [ ]
parameters:
@@ -619,9 +661,9 @@ paths:
/api/peers:
get:
summary: Returns a list of all peers
tags: [Peers]
tags: [ Peers ]
security:
- BearerAuth: []
- BearerAuth: [ ]
responses:
'200':
description: A JSON Array of Peers
@@ -642,7 +684,7 @@ paths:
/api/peers/{id}:
get:
summary: Get information about a peer
tags: [Peers]
tags: [ Peers ]
security:
- BearerAuth: [ ]
parameters:
@@ -669,7 +711,7 @@ paths:
"$ref": "#/components/responses/internal_error"
put:
summary: Update information about a peer
tags: [Peers]
tags: [ Peers ]
security:
- BearerAuth: [ ]
parameters:
@@ -710,7 +752,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a peer
tags: [Peers]
tags: [ Peers ]
security:
- BearerAuth: [ ]
parameters:
@@ -723,7 +765,7 @@ paths:
responses:
'200':
description: Delete status code
content: {}
content: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
@@ -735,7 +777,7 @@ paths:
/api/setup-keys:
get:
summary: Returns a list of all Setup Keys
tags: [Setup Keys]
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
responses:
@@ -757,7 +799,7 @@ paths:
"$ref": "#/components/responses/internal_error"
post:
summary: Creates a Setup Key
tags: [Setup Keys]
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
requestBody:
@@ -784,7 +826,7 @@ paths:
/api/setup-keys/{id}:
get:
summary: Get information about a Setup Key
tags: [Setup Keys]
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
parameters:
@@ -811,7 +853,7 @@ paths:
"$ref": "#/components/responses/internal_error"
put:
summary: Update information about a Setup Key
tags: [Setup Keys]
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
parameters:
@@ -844,7 +886,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Setup Key
tags: [Setup Keys]
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
parameters:
@@ -857,7 +899,7 @@ paths:
responses:
'200':
description: Delete status code
content: {}
content: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
@@ -869,7 +911,7 @@ paths:
/api/groups:
get:
summary: Returns a list of all Groups
tags: [Groups]
tags: [ Groups ]
security:
- BearerAuth: [ ]
responses:
@@ -891,7 +933,7 @@ paths:
"$ref": "#/components/responses/internal_error"
post:
summary: Creates a Group
tags: [Groups]
tags: [ Groups ]
security:
- BearerAuth: [ ]
requestBody:
@@ -927,7 +969,7 @@ paths:
/api/groups/{id}:
get:
summary: Get information about a Group
tags: [Groups]
tags: [ Groups ]
security:
- BearerAuth: [ ]
parameters:
@@ -954,7 +996,7 @@ paths:
"$ref": "#/components/responses/internal_error"
put:
summary: Update/Replace a Group
tags: [Groups]
tags: [ Groups ]
security:
- BearerAuth: [ ]
parameters:
@@ -1029,7 +1071,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Group
tags: [Groups]
tags: [ Groups ]
security:
- BearerAuth: [ ]
parameters:
@@ -1042,7 +1084,7 @@ paths:
responses:
'200':
description: Delete status code
content: {}
content: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
@@ -1054,7 +1096,7 @@ paths:
/api/rules:
get:
summary: Returns a list of all Rules
tags: [Rules]
tags: [ Rules ]
security:
- BearerAuth: [ ]
responses:
@@ -1076,7 +1118,7 @@ paths:
"$ref": "#/components/responses/internal_error"
post:
summary: Creates a Rule
tags: [Rules]
tags: [ Rules ]
security:
- BearerAuth: [ ]
requestBody:
@@ -1106,7 +1148,7 @@ paths:
/api/rules/{id}:
get:
summary: Get information about a Rules
tags: [Rules]
tags: [ Rules ]
security:
- BearerAuth: [ ]
parameters:
@@ -1133,7 +1175,7 @@ paths:
"$ref": "#/components/responses/internal_error"
put:
summary: Update/Replace a Rule
tags: [Rules]
tags: [ Rules ]
security:
- BearerAuth: [ ]
parameters:
@@ -1212,7 +1254,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Rule
tags: [Rules]
tags: [ Rules ]
security:
- BearerAuth: [ ]
parameters:
@@ -1225,7 +1267,7 @@ paths:
responses:
'200':
description: Delete status code
content: {}
content: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
@@ -1573,5 +1615,28 @@ paths:
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/events:
get:
summary: Returns a list of all events
tags: [ Events ]
security:
- BearerAuth: [ ]
responses:
'200':
description: A JSON Array of Events
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Event'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"

View File

@@ -11,6 +11,25 @@ const (
BearerAuthScopes = "BearerAuth.Scopes"
)
// Defines values for EventActivityCode.
const (
EventActivityCodeAccountCreate EventActivityCode = "account.create"
EventActivityCodeGroupAdd EventActivityCode = "group.add"
EventActivityCodeGroupUpdate EventActivityCode = "group.update"
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"
EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete"
)
// Defines values for GroupPatchOperationOp.
const (
GroupPatchOperationOpAdd GroupPatchOperationOp = "add"
@@ -97,6 +116,33 @@ const (
UserStatusInvited UserStatus = "invited"
)
// Event defines model for Event.
type Event struct {
// Activity The activity that occurred during the event
Activity string `json:"activity"`
// ActivityCode The string code of the activity that occurred during the event
ActivityCode EventActivityCode `json:"activity_code"`
// Id Event unique identifier
Id string `json:"id"`
// InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
InitiatorId string `json:"initiator_id"`
// Meta The metadata of the event
Meta map[string]string `json:"meta"`
// TargetId The ID of the target of the event. E.g., an ID of the peer that a user removed.
TargetId string `json:"target_id"`
// Timestamp The date and time when the event occurred
Timestamp time.Time `json:"timestamp"`
}
// EventActivityCode The string code of the activity that occurred during the event
type EventActivityCode string
// Group defines model for Group.
type Group struct {
// Id Group ID

View File

@@ -0,0 +1,69 @@
package http
import (
"fmt"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims"
log "github.com/sirupsen/logrus"
"net/http"
)
// Events HTTP handler
type Events struct {
accountManager server.AccountManager
authAudience string
jwtExtractor jwtclaims.ClaimsExtractor
}
// NewEvents creates a new Events HTTP handler
func NewEvents(accountManager server.AccountManager, authAudience string) *Events {
return &Events{
accountManager: accountManager,
authAudience: authAudience,
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
}
}
// GetEvents list of the given account
func (h *Events) GetEvents(w http.ResponseWriter, r *http.Request) {
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
accountEvents, err := h.accountManager.GetEvents(account.Id, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
events := make([]*api.Event, 0)
for _, e := range accountEvents {
events = append(events, toEventResponse(e))
}
util.WriteJSONObject(w, events)
}
func toEventResponse(event *activity.Event) *api.Event {
meta := make(map[string]string)
if event.Meta != nil {
for s, a := range event.Meta {
meta[s] = fmt.Sprintf("%v", a)
}
}
return &api.Event{
Id: fmt.Sprint(event.ID),
InitiatorId: event.InitiatorID,
Activity: event.Activity.Message(),
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
TargetId: event.TargetID,
Timestamp: event.Timestamp,
Meta: meta,
}
}

View File

@@ -51,7 +51,7 @@ func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
// UpdateGroupHandler handles update to a group identified by a given ID
func (h *Groups) UpdateGroupHandler(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
@@ -102,7 +102,7 @@ func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
Peers: peerIPsToKeys(account, req.Peers),
}
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
util.WriteError(err, w)
return
@@ -219,7 +219,7 @@ func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
// CreateGroupHandler handles group creation request
func (h *Groups) CreateGroupHandler(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
@@ -243,7 +243,7 @@ func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
Peers: peerIPsToKeys(account, req.Peers),
}
err = h.accountManager.SaveGroup(account.Id, &group)
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
if err != nil {
util.WriteError(err, w)
return

View File

@@ -29,7 +29,7 @@ var TestPeers = map[string]*server.Peer{
func initGroupTestData(user *server.User, groups ...*server.Group) *Groups {
return &Groups{
accountManager: &mock_server.MockAccountManager{
SaveGroupFunc: func(accountID string, group *server.Group) error {
SaveGroupFunc: func(accountID, userID string, group *server.Group) error {
if !strings.HasPrefix(group.ID, "id-") {
group.ID = "id-was-set"
}

View File

@@ -40,6 +40,7 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
userHandler := NewUserHandler(accountManager, authAudience)
routesHandler := NewRoutes(accountManager, authAudience)
nameserversHandler := NewNameservers(accountManager, authAudience)
eventsHandler := NewEvents(accountManager, authAudience)
apiHandler.HandleFunc("/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/peers/{id}", peersHandler.HandlePeer).
@@ -81,6 +82,8 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.GetNameserverGroupHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.DeleteNameserverGroupHandler).Methods("DELETE", "OPTIONS")
apiHandler.HandleFunc("/events", eventsHandler.GetEvents).Methods("GET", "OPTIONS")
err = apiHandler.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
methods, err := route.GetMethods()
if err != nil {

View File

@@ -45,8 +45,8 @@ func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.Re
util.WriteJSONObject(w, toPeerResponse(peer, account, dnsDomain))
}
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
func (h *Peers) deletePeer(accountID, userID string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
_, err := h.accountManager.DeletePeer(accountID, peer.Key, userID)
if err != nil {
util.WriteError(err, w)
return
@@ -56,7 +56,7 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
func (h *Peers) HandlePeer(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
@@ -78,7 +78,7 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodDelete:
h.deletePeer(account.Id, peer, w, r)
h.deletePeer(account.Id, user.Id, peer, w, r)
return
case http.MethodPut:
h.updatePeer(account, peer, w, r)
@@ -143,9 +143,10 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string
}
}
}
fqdn := peer.DNSLabel
if dnsDomain != "" {
fqdn = peer.DNSLabel + "." + dnsDomain
fqdn := peer.FQDN(dnsDomain)
if fqdn == "" {
fqdn = peer.DNSLabel
}
return &api.Peer{
Id: peer.IP.String(),

View File

@@ -52,7 +52,7 @@ func (h *Rules) GetAllRulesHandler(w http.ResponseWriter, r *http.Request) {
// UpdateRuleHandler handles update to a rule identified by a given ID
func (h *Rules) UpdateRuleHandler(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
@@ -109,7 +109,7 @@ func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
return
}
err = h.accountManager.SaveRule(account.Id, &rule)
err = h.accountManager.SaveRule(account.Id, user.Id, &rule)
if err != nil {
util.WriteError(err, w)
return
@@ -267,7 +267,7 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
// CreateRuleHandler handles rule creation request
func (h *Rules) CreateRuleHandler(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
@@ -312,7 +312,7 @@ func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
return
}
err = h.accountManager.SaveRule(account.Id, &rule)
err = h.accountManager.SaveRule(account.Id, user.Id, &rule)
if err != nil {
util.WriteError(err, w)
return
@@ -326,7 +326,7 @@ func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
// DeleteRuleHandler handles rule deletion request
func (h *Rules) DeleteRuleHandler(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
@@ -339,7 +339,7 @@ func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
return
}
err = h.accountManager.DeleteRule(aID, rID)
err = h.accountManager.DeleteRule(aID, rID, user.Id)
if err != nil {
util.WriteError(err, w)
return

View File

@@ -22,7 +22,7 @@ import (
func initRulesTestData(rules ...*server.Rule) *Rules {
return &Rules{
accountManager: &mock_server.MockAccountManager{
SaveRuleFunc: func(_ string, rule *server.Rule) error {
SaveRuleFunc: func(_, _ string, rule *server.Rule) error {
if !strings.HasPrefix(rule.ID, "id-") {
rule.ID = "id-was-set"
}

View File

@@ -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

View File

@@ -47,7 +47,7 @@ func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.Setup
}, user, nil
},
CreateSetupKeyFunc: func(_ string, keyName string, typ server.SetupKeyType, _ time.Duration, _ []string,
_ int) (*server.SetupKey, error) {
_ int, _ string) (*server.SetupKey, error) {
if keyName == newKey.Name || typ != newKey.Type {
return newKey, nil
}
@@ -64,7 +64,7 @@ func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.Setup
}
},
SaveSetupKeyFunc: func(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
SaveSetupKeyFunc: func(accountID string, key *server.SetupKey, _ string) (*server.SetupKey, error) {
if key.Id == updatedSetupKey.Id {
return updatedSetupKey, nil
}

View File

@@ -81,7 +81,7 @@ func (h *UserHandler) CreateUserHandler(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
@@ -99,7 +99,7 @@ func (h *UserHandler) CreateUserHandler(w http.ResponseWriter, r *http.Request)
return
}
newUser, err := h.accountManager.CreateUser(account.Id, &server.UserInfo{
newUser, err := h.accountManager.CreateUser(account.Id, user.Id, &server.UserInfo{
Email: req.Email,
Name: *req.Name,
Role: req.Role,

View File

@@ -3,6 +3,7 @@ package server
import (
"context"
"fmt"
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
"net"
"os"
"path/filepath"
@@ -403,7 +404,12 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro
return nil, err
}
peersUpdateManager := NewPeersUpdateManager()
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "")
eventStore, err := activity.NewSQLiteStore(t.TempDir())
if err != nil {
return nil, err
}
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "",
eventStore)
if err != nil {
return nil, err
}

View File

@@ -2,6 +2,7 @@ package server_test
import (
"context"
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
"math/rand"
"net"
"os"
@@ -493,7 +494,12 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
}
peersUpdateManager := server.NewPeersUpdateManager()
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "")
eventStore, err := activity.NewSQLiteStore(config.Datadir)
if err != nil {
log.Fatalf("failed creating a event store: %s: %v", config.Datadir, err)
}
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore)
if err != nil {
log.Fatalf("failed creating a manager: %v", err)
}

View File

@@ -3,6 +3,7 @@ package mock_server
import (
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/route"
"google.golang.org/grpc/codes"
@@ -11,9 +12,10 @@ import (
)
type MockAccountManager struct {
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
GetAccountByUserFunc func(userId string) (*server.Account, error)
CreateSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration, autoGroups []string, usageLimit int) (*server.SetupKey, error)
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
GetAccountByUserFunc func(userId string) (*server.Account, error)
CreateSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType,
expiresIn time.Duration, autoGroups []string, usageLimit int, userID string) (*server.SetupKey, error)
GetSetupKeyFunc func(accountID, userID, keyID string) (*server.SetupKey, error)
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
IsUserAdminFunc func(claims jwtclaims.AuthorizationClaims) (bool, error)
@@ -21,13 +23,13 @@ type MockAccountManager struct {
GetPeerFunc func(peerKey string) (*server.Peer, error)
GetPeersFunc func(accountID, userID string) ([]*server.Peer, error)
MarkPeerConnectedFunc func(peerKey string, connected bool) error
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
DeletePeerFunc func(accountID, peerKey, userID string) (*server.Peer, error)
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
SaveGroupFunc func(accountID string, group *server.Group) error
SaveGroupFunc func(accountID, userID string, group *server.Group) error
UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error)
DeleteGroupFunc func(accountID, groupID string) error
ListGroupsFunc func(accountID string) ([]*server.Group, error)
@@ -35,9 +37,9 @@ type MockAccountManager struct {
GroupDeletePeerFunc func(accountID, groupID, peerKey string) error
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
SaveRuleFunc func(accountID string, rule *server.Rule) error
SaveRuleFunc func(accountID, userID string, rule *server.Rule) error
UpdateRuleFunc func(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error)
DeleteRuleFunc func(accountID, ruleID string) error
DeleteRuleFunc func(accountID, ruleID, userID string) error
ListRulesFunc func(accountID, userID string) ([]*server.Rule, error)
GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error)
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
@@ -49,7 +51,7 @@ type MockAccountManager struct {
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
DeleteRouteFunc func(accountID, routeID string) error
ListRoutesFunc func(accountID, userID string) ([]*route.Route, error)
SaveSetupKeyFunc func(accountID string, key *server.SetupKey) (*server.SetupKey, error)
SaveSetupKeyFunc func(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error)
ListSetupKeysFunc func(accountID, userID string) ([]*server.SetupKey, error)
SaveUserFunc func(accountID string, user *server.User) (*server.UserInfo, error)
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
@@ -58,9 +60,10 @@ type MockAccountManager struct {
UpdateNameServerGroupFunc func(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
DeleteNameServerGroupFunc func(accountID, nsGroupID string) error
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
CreateUserFunc func(accountID string, key *server.UserInfo) (*server.UserInfo, error)
CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error)
GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error)
GetDNSDomainFunc func() string
GetEventsFunc func(accountID, userID string) ([]*activity.Event, error)
}
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
@@ -72,9 +75,9 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string, userID strin
}
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*server.Peer, error) {
func (am *MockAccountManager) DeletePeer(accountID, peerKey, userID string) (*server.Peer, error) {
if am.DeletePeerFunc != nil {
return am.DeletePeerFunc(accountId, peerKey)
return am.DeletePeerFunc(accountID, peerKey, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
}
@@ -108,9 +111,10 @@ func (am *MockAccountManager) CreateSetupKey(
expiresIn time.Duration,
autoGroups []string,
usageLimit int,
userID string,
) (*server.SetupKey, error) {
if am.CreateSetupKeyFunc != nil {
return am.CreateSetupKeyFunc(accountID, keyName, keyType, expiresIn, autoGroups, usageLimit)
return am.CreateSetupKeyFunc(accountID, keyName, keyType, expiresIn, autoGroups, usageLimit, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method CreateSetupKey is not implemented")
}
@@ -197,9 +201,9 @@ func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group
}
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) error {
func (am *MockAccountManager) SaveGroup(accountID, userID string, group *server.Group) error {
if am.SaveGroupFunc != nil {
return am.SaveGroupFunc(accountID, group)
return am.SaveGroupFunc(accountID, userID, group)
}
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
}
@@ -261,9 +265,9 @@ func (am *MockAccountManager) GetRule(accountID, ruleID, userID string) (*server
}
// SaveRule mock implementation of SaveRule from server.AccountManager interface
func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) error {
func (am *MockAccountManager) SaveRule(accountID, userID string, rule *server.Rule) error {
if am.SaveRuleFunc != nil {
return am.SaveRuleFunc(accountID, rule)
return am.SaveRuleFunc(accountID, userID, rule)
}
return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented")
}
@@ -277,9 +281,9 @@ func (am *MockAccountManager) UpdateRule(accountID string, ruleID string, operat
}
// DeleteRule mock implementation of DeleteRule from server.AccountManager interface
func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
func (am *MockAccountManager) DeleteRule(accountID, ruleID, userID string) error {
if am.DeleteRuleFunc != nil {
return am.DeleteRuleFunc(accountID, ruleID)
return am.DeleteRuleFunc(accountID, ruleID, userID)
}
return status.Errorf(codes.Unimplemented, "method DeleteRule is not implemented")
}
@@ -373,9 +377,9 @@ func (am *MockAccountManager) ListRoutes(accountID, userID string) ([]*route.Rou
}
// SaveSetupKey mocks SaveSetupKey of the AccountManager interface
func (am *MockAccountManager) SaveSetupKey(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
func (am *MockAccountManager) SaveSetupKey(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error) {
if am.SaveSetupKeyFunc != nil {
return am.SaveSetupKeyFunc(accountID, key)
return am.SaveSetupKeyFunc(accountID, key, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method SaveSetupKey is not implemented")
@@ -456,9 +460,9 @@ func (am *MockAccountManager) ListNameServerGroups(accountID string) ([]*nbdns.N
}
// CreateUser mocks CreateUser of the AccountManager interface
func (am *MockAccountManager) CreateUser(accountID string, invite *server.UserInfo) (*server.UserInfo, error) {
func (am *MockAccountManager) CreateUser(accountID, userID string, invite *server.UserInfo) (*server.UserInfo, error) {
if am.CreateUserFunc != nil {
return am.CreateUserFunc(accountID, invite)
return am.CreateUserFunc(accountID, userID, invite)
}
return nil, status.Errorf(codes.Unimplemented, "method CreateUser is not implemented")
}
@@ -477,7 +481,7 @@ func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*server.Peer
if am.GetAccountFromTokenFunc != nil {
return am.GetPeersFunc(accountID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetPeersFunc is not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetPeers is not implemented")
}
// GetDNSDomain mocks GetDNSDomain of the AccountManager interface
@@ -487,3 +491,11 @@ func (am *MockAccountManager) GetDNSDomain() string {
}
return ""
}
// GetEvents mocks GetEvents of the AccountManager interface
func (am *MockAccountManager) GetEvents(accountID, userID string) ([]*activity.Event, error) {
if am.GetEventsFunc != nil {
return am.GetEventsFunc(accountID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetEvents is not implemented")
}

View File

@@ -2,6 +2,7 @@ package server
import (
nbdns "github.com/netbirdio/netbird/dns"
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
"github.com/stretchr/testify/require"
"net/netip"
"testing"
@@ -1056,7 +1057,11 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
eventStore, err := activity.NewSQLiteStore(t.TempDir())
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
}
func createNSStore(t *testing.T) (Store, error) {

View File

@@ -1,7 +1,9 @@
package server
import (
"fmt"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"net"
"strings"
@@ -73,6 +75,19 @@ func (p *Peer) Copy() *Peer {
}
}
// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain
func (p *Peer) FQDN(dnsDomain string) string {
if dnsDomain == "" {
return ""
}
return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain)
}
// EventMeta returns activity event meta related to the peer
func (p *Peer) EventMeta(dnsDomain string) map[string]any {
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP}
}
// Copy PeerStatus
func (p *PeerStatus) Copy() *PeerStatus {
return &PeerStatus{
@@ -216,7 +231,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID string, update *Peer) (*Pe
}
// DeletePeer removes peer from the account by its IP
func (am *DefaultAccountManager) DeletePeer(accountID string, peerPubKey string) (*Peer, error) {
func (am *DefaultAccountManager) DeletePeer(accountID, peerPubKey, userID string) (*Peer, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@@ -262,6 +277,18 @@ func (am *DefaultAccountManager) DeletePeer(accountID string, peerPubKey string)
}
am.peersUpdateManager.CloseChannel(peerPubKey)
event := &activity.Event{
Timestamp: time.Now(),
AccountID: account.Id,
InitiatorID: userID,
TargetID: peer.IP.String(),
Activity: activity.PeerRemovedByUser,
Meta: peer.EventMeta(am.GetDNSDomain()),
}
_, err = am.eventStore.Save(event)
if err != nil {
return nil, err
}
return peer, nil
}
@@ -359,6 +386,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
return nil, err
}
opEvent := &activity.Event{
Timestamp: time.Now(),
AccountID: account.Id,
}
if !addedByUser {
// validate the setup key if adding with a key
sk, err := account.FindSetupKey(upperKey)
@@ -371,6 +403,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
}
account.SetupKeys[sk.Key] = sk.IncrementUsage()
opEvent.InitiatorID = sk.Id
opEvent.Activity = activity.PeerAddedWithSetupKey
} else {
opEvent.InitiatorID = userID
opEvent.Activity = activity.PeerAddedByUser
}
takenIps := account.getTakenIPs()
@@ -436,6 +473,13 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
return nil, err
}
opEvent.TargetID = newPeer.IP.String()
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
_, err = am.eventStore.Save(opEvent)
if err != nil {
return nil, err
}
return newPeer, nil
}

View File

@@ -87,9 +87,9 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
return
}
expectedId := "test_account"
userId := "account_creator"
account, err := createAccount(manager, expectedId, userId, "")
expectedID := "test_account"
userID := "account_creator"
account, err := createAccount(manager, expectedID, userID, "")
if err != nil {
t.Fatal(err)
}
@@ -134,13 +134,13 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
return
}
rules, err := manager.ListRules(account.Id, userId)
rules, err := manager.ListRules(account.Id, userID)
if err != nil {
t.Errorf("expecting to get a list of rules, got failure %v", err)
return
}
err = manager.DeleteRule(account.Id, rules[0].ID)
err = manager.DeleteRule(account.Id, rules[0].ID, userID)
if err != nil {
t.Errorf("expecting to delete 1 group, got failure %v", err)
return
@@ -159,12 +159,12 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
group1.Peers = append(group1.Peers, peerKey1.PublicKey().String())
group2.Peers = append(group2.Peers, peerKey2.PublicKey().String())
err = manager.SaveGroup(account.Id, &group1)
err = manager.SaveGroup(account.Id, userID, &group1)
if err != nil {
t.Errorf("expecting group1 to be added, got failure %v", err)
return
}
err = manager.SaveGroup(account.Id, &group2)
err = manager.SaveGroup(account.Id, userID, &group2)
if err != nil {
t.Errorf("expecting group2 to be added, got failure %v", err)
return
@@ -174,7 +174,7 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
rule.Source = append(rule.Source, group1.ID)
rule.Destination = append(rule.Destination, group2.ID)
rule.Flow = TrafficFlowBidirect
err = manager.SaveRule(account.Id, &rule)
err = manager.SaveRule(account.Id, userID, &rule)
if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err)
return
@@ -222,7 +222,7 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
}
rule.Disabled = true
err = manager.SaveRule(account.Id, &rule)
err = manager.SaveRule(account.Id, userID, &rule)
if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err)
return

View File

@@ -1,6 +1,7 @@
package server
import (
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
"github.com/netbirdio/netbird/route"
"github.com/rs/xid"
"github.com/stretchr/testify/require"
@@ -14,6 +15,7 @@ const (
routeGroup1 = "routeGroup1"
routeGroup2 = "routeGroup2"
routeInvalidGroup1 = "routeInvalidGroup1"
userID = "testingUser"
)
func TestCreateRoute(t *testing.T) {
@@ -831,7 +833,6 @@ func TestDeleteRoute(t *testing.T) {
func TestGetNetworkMap_RouteSync(t *testing.T) {
// no routes for peer in different groups
// no routes when route is deleted
baseRoute := &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
@@ -895,7 +896,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
Name: "peer1 group",
Peers: []string{peer1Key},
}
err = am.SaveGroup(account.Id, newGroup)
err = am.SaveGroup(account.Id, userID, newGroup)
require.NoError(t, err)
rules, err := am.ListRules(account.Id, "testingUser")
@@ -908,10 +909,10 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
newRule.Source = []string{newGroup.ID}
newRule.Destination = []string{newGroup.ID}
err = am.SaveRule(account.Id, newRule)
err = am.SaveRule(account.Id, userID, newRule)
require.NoError(t, err)
err = am.DeleteRule(account.Id, defaultRule.ID)
err = am.DeleteRule(account.Id, defaultRule.ID, userID)
require.NoError(t, err)
peer1GroupRoutes, err := am.GetNetworkMap(peer1Key)
@@ -936,7 +937,11 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
eventStore, err := activity.NewSQLiteStore(t.TempDir())
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
}
func createRouterStore(t *testing.T) (Store, error) {
@@ -980,7 +985,6 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
}
accountID := "testingAcc"
userID := "testingUser"
domain := "example.com"
account := newAccountWithId(accountID, userID, domain)
@@ -1002,7 +1006,7 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
Name: routeGroup1,
Peers: []string{peer1Key},
}
err = am.SaveGroup(accountID, newGroup)
err = am.SaveGroup(accountID, userID, newGroup)
if err != nil {
return nil, err
}
@@ -1013,7 +1017,7 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
Peers: []string{peer1Key},
}
err = am.SaveGroup(accountID, newGroup)
err = am.SaveGroup(accountID, userID, newGroup)
if err != nil {
return nil, err
}

View File

@@ -1,8 +1,10 @@
package server
import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"strings"
"time"
)
// TrafficFlowType defines allowed direction of the traffic in the rule
@@ -87,6 +89,11 @@ func (r *Rule) Copy() *Rule {
}
}
// EventMeta returns activity event meta related to this rule
func (r *Rule) EventMeta() map[string]any {
return map[string]any{"name": r.Name}
}
// GetRule of ACL from the store
func (am *DefaultAccountManager) GetRule(accountID, ruleID, userID string) (*Rule, error) {
unlock := am.Store.AcquireAccountLock(accountID)
@@ -115,7 +122,7 @@ func (am *DefaultAccountManager) GetRule(accountID, ruleID, userID string) (*Rul
}
// SaveRule of ACL in the store
func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
func (am *DefaultAccountManager) SaveRule(accountID, userID string, rule *Rule) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@@ -124,6 +131,8 @@ func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
return err
}
_, exists := account.Rules[rule.ID]
account.Rules[rule.ID] = rule
account.Network.IncSerial()
@@ -131,6 +140,24 @@ func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
return err
}
action := activity.RuleAdded
if exists {
action = activity.RuleUpdated
}
_, err = am.eventStore.Save(&activity.Event{
Timestamp: time.Now(),
Activity: action,
InitiatorID: userID,
TargetID: rule.ID,
AccountID: accountID,
Meta: rule.EventMeta(),
})
if err != nil {
return err
}
return am.updateAccountPeers(account)
}
@@ -210,7 +237,7 @@ func (am *DefaultAccountManager) UpdateRule(accountID string, ruleID string,
}
// DeleteRule of ACL from the store
func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
func (am *DefaultAccountManager) DeleteRule(accountID, ruleID, userID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@@ -219,6 +246,10 @@ func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
return err
}
rule := account.Rules[ruleID]
if rule == nil {
return status.Errorf(status.NotFound, "rule with ID %s doesn't exist", ruleID)
}
delete(account.Rules, ruleID)
account.Network.IncSerial()
@@ -226,6 +257,19 @@ func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
return err
}
_, err = am.eventStore.Save(&activity.Event{
Timestamp: time.Now(),
Activity: activity.RuleRemoved,
InitiatorID: userID,
TargetID: ruleID,
AccountID: accountID,
Meta: rule.EventMeta(),
})
if err != nil {
return err
}
return am.updateAccountPeers(account)
}

View File

@@ -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 setup key
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

View File

@@ -20,7 +20,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
t.Fatal(err)
}
err = manager.SaveGroup(account.Id, &Group{
err = manager.SaveGroup(account.Id, userID, &Group{
ID: "group_1",
Name: "group_name_1",
Peers: []string{},
@@ -33,7 +33,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
keyName := "my-test-key"
key, err := manager.CreateSetupKey(account.Id, keyName, SetupKeyReusable, expiresIn, []string{},
SetupKeyUnlimitedUsage)
SetupKeyUnlimitedUsage, userID)
if err != nil {
t.Fatal(err)
}
@@ -46,7 +46,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
Name: newKeyName,
Revoked: revoked,
AutoGroups: autoGroups,
})
}, userID)
if err != nil {
t.Fatal(err)
}
@@ -67,7 +67,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
t.Fatal(err)
}
err = manager.SaveGroup(account.Id, &Group{
err = manager.SaveGroup(account.Id, userID, &Group{
ID: "group_1",
Name: "group_name_1",
Peers: []string{},
@@ -76,7 +76,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
t.Fatal(err)
}
err = manager.SaveGroup(account.Id, &Group{
err = manager.SaveGroup(account.Id, userID, &Group{
ID: "group_2",
Name: "group_name_2",
Peers: []string{},
@@ -121,7 +121,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
for _, tCase := range []testCase{testCase1, testCase2} {
t.Run(tCase.name, func(t *testing.T) {
key, err := manager.CreateSetupKey(account.Id, tCase.expectedKeyName, SetupKeyReusable, expiresIn,
tCase.expectedGroups, SetupKeyUnlimitedUsage)
tCase.expectedGroups, SetupKeyUnlimitedUsage, userID)
if tCase.expectedFailure {
if err == nil {

View File

@@ -2,11 +2,12 @@ package server
import (
"fmt"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/status"
"strings"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"time"
)
const (
@@ -117,7 +118,7 @@ func NewAdminUser(id string) *User {
}
// CreateUser creates a new user under the given account. Effectively this is a user invite.
func (am *DefaultAccountManager) CreateUser(accountID string, invite *UserInfo) (*UserInfo, error) {
func (am *DefaultAccountManager) CreateUser(accountID, userID string, invite *UserInfo) (*UserInfo, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@@ -176,6 +177,19 @@ func (am *DefaultAccountManager) CreateUser(accountID string, invite *UserInfo)
return nil, err
}
event := &activity.Event{
Timestamp: time.Now(),
Activity: activity.UserInvited,
AccountID: account.Id,
TargetID: newUser.Id,
InitiatorID: userID,
}
_, err = am.eventStore.Save(event)
if err != nil {
return nil, err
}
return newUser.toUserInfo(idpUser)
}