mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-22 21:21:23 +01:00
Add system activity tracking and event store (#636)
This PR adds system activity tracking. The management service records events like add/remove peer, group, rule, route, etc. The activity events are stored in the SQLite event store and can be queried by the HTTP API.
This commit is contained in:
parent
50caacff69
commit
5c0b8a46f0
4
.github/workflows/golang-test-linux.yml
vendored
4
.github/workflows/golang-test-linux.yml
vendored
@ -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
|
||||
|
@ -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.InMemoryEventStore{}
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -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.InMemoryEventStore{}
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@ -55,7 +56,9 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
}
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
@ -251,6 +257,7 @@ var (
|
||||
}
|
||||
gRPCAPIHandler.Stop()
|
||||
_ = store.Close()
|
||||
_ = eventStore.Close()
|
||||
log.Infof("stopped Management Service")
|
||||
|
||||
return nil
|
||||
|
@ -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,11 +40,11 @@ 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)
|
||||
SaveUser(accountID, userID string, update *User) (*UserInfo, error)
|
||||
GetSetupKey(accountID, userID, keyID string) (*SetupKey, error)
|
||||
GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error)
|
||||
GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, *User, 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.
|
||||
@ -251,6 +254,11 @@ func (a *Account) GetPeerRules(peerPubKey string) (srcRules []*Rule, dstRules []
|
||||
return srcRules, dstRules
|
||||
}
|
||||
|
||||
// GetGroup returns a group by ID if exists, nil otherwise
|
||||
func (a *Account) GetGroup(groupID string) *Group {
|
||||
return a.Groups[groupID]
|
||||
}
|
||||
|
||||
// GetPeers returns a list of all Account peers
|
||||
func (a *Account) GetPeers() []*Peer {
|
||||
var peers []*Peer
|
||||
@ -435,7 +443,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 +452,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 +519,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 +817,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 +861,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
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"net"
|
||||
"reflect"
|
||||
@ -112,23 +113,43 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
userId := "test_user"
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, "")
|
||||
account, err := manager.GetOrCreateAccountByUser(userID, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if account == nil {
|
||||
t.Fatalf("expected to create an account for a user %s", userId)
|
||||
t.Fatalf("expected to create an account for a user %s", userID)
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccountByUser(userId)
|
||||
account, err = manager.Store.GetAccountByUser(userID)
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userID)
|
||||
return
|
||||
}
|
||||
|
||||
if account != nil && account.Users[userId] == nil {
|
||||
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userId, account.Id)
|
||||
if account != nil && account.Users[userID] == nil {
|
||||
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userID, account.Id)
|
||||
return
|
||||
}
|
||||
|
||||
// check the corresponding events that should have been generated
|
||||
events, err := manager.GetEvents(account.Id, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ev *activity.Event
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.AccountCreated {
|
||||
ev = event
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, userID, ev.InitiatorID)
|
||||
assert.Equal(t, account.Id, ev.TargetID)
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
|
||||
@ -500,7 +521,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := createAccount(manager, "test_account", "account_creator", "")
|
||||
account, err := createAccount(manager, "test_account", "account_creator", "netbird.cloud")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -561,6 +582,27 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
if account.Network.CurrentSerial() != 1 {
|
||||
t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial())
|
||||
}
|
||||
|
||||
// check the corresponding events that should have been generated
|
||||
events, err := manager.GetEvents(account.Id, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ev *activity.Event
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.PeerAddedWithSetupKey {
|
||||
ev = event
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, peer.Name, ev.Meta["name"])
|
||||
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
||||
assert.Equal(t, setupKey.Id, ev.InitiatorID)
|
||||
assert.Equal(t, peer.IP.String(), ev.TargetID)
|
||||
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
||||
}
|
||||
|
||||
func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
@ -570,9 +612,7 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
userId := "account_creator"
|
||||
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, "")
|
||||
account, err := manager.GetOrCreateAccountByUser(userID, "netbird.cloud")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -590,9 +630,9 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
return
|
||||
}
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedUserId := userId
|
||||
expectedUserID := userID
|
||||
|
||||
peer, err := manager.AddPeer("", userId, &Peer{
|
||||
peer, err := manager.AddPeer("", userID, &Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: expectedPeerKey,
|
||||
@ -616,13 +656,34 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String())
|
||||
}
|
||||
|
||||
if peer.UserID != expectedUserId {
|
||||
t.Errorf("expecting just added peer to have UserID = %s, got %s", expectedUserId, peer.UserID)
|
||||
if peer.UserID != expectedUserID {
|
||||
t.Errorf("expecting just added peer to have UserID = %s, got %s", expectedUserID, peer.UserID)
|
||||
}
|
||||
|
||||
if account.Network.CurrentSerial() != 1 {
|
||||
t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial())
|
||||
}
|
||||
|
||||
// check the corresponding events that should have been generated
|
||||
events, err := manager.GetEvents(account.Id, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ev *activity.Event
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.PeerAddedByUser {
|
||||
ev = event
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, peer.Name, ev.Meta["name"])
|
||||
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
||||
assert.Equal(t, userID, ev.InitiatorID)
|
||||
assert.Equal(t, peer.IP.String(), ev.TargetID)
|
||||
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
||||
}
|
||||
|
||||
func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
@ -632,7 +693,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 +777,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 +802,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 +822,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 +842,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 +877,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, "netbird.cloud")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -833,7 +896,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
|
||||
peerKey := key.PublicKey().String()
|
||||
|
||||
_, err = manager.AddPeer(setupKey.Key, "", &Peer{
|
||||
peer, err := manager.AddPeer(setupKey.Key, "", &Peer{
|
||||
Key: peerKey,
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: peerKey,
|
||||
@ -843,7 +906,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
|
||||
}
|
||||
@ -857,6 +920,27 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
if account.Network.CurrentSerial() != 2 {
|
||||
t.Errorf("expecting Network Serial=%d to be incremented and be equal to 2 after adding and deleteing a peer", account.Network.CurrentSerial())
|
||||
}
|
||||
|
||||
// check the corresponding events that should have been generated
|
||||
events, err := manager.GetEvents(account.Id, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ev *activity.Event
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.PeerRemovedByUser {
|
||||
ev = event
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, peer.Name, ev.Meta["name"])
|
||||
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
||||
assert.Equal(t, userID, ev.InitiatorID)
|
||||
assert.Equal(t, peer.IP.String(), ev.TargetID)
|
||||
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
||||
}
|
||||
|
||||
func TestGetUsersFromAccount(t *testing.T) {
|
||||
@ -1228,7 +1312,8 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.cloud", eventStore)
|
||||
}
|
||||
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
|
202
management/server/activity/codes.go
Normal file
202
management/server/activity/codes.go
Normal file
@ -0,0 +1,202 @@
|
||||
package activity
|
||||
|
||||
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
|
||||
// GroupAddedToPeer indicates that a user added group to a peer
|
||||
GroupAddedToPeer
|
||||
// GroupRemovedFromPeer indicates that a user removed peer group
|
||||
GroupRemovedFromPeer
|
||||
// GroupAddedToUser indicates that a user added group to a user
|
||||
GroupAddedToUser
|
||||
// GroupRemovedFromUser indicates that a user removed a group from a user
|
||||
GroupRemovedFromUser
|
||||
// UserRoleUpdated indicates that a user changed the role of a user
|
||||
UserRoleUpdated
|
||||
// GroupAddedToSetupKey indicates that a user added group to a setup key
|
||||
GroupAddedToSetupKey
|
||||
// GroupRemovedFromSetupKey indicates that a user removed a group from a setup key
|
||||
GroupRemovedFromSetupKey
|
||||
)
|
||||
|
||||
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"
|
||||
// GroupAddedToPeerMessage is a human-readable text message of the GroupAddedToPeer activity
|
||||
GroupAddedToPeerMessage string = "Group added to peer"
|
||||
// GroupRemovedFromPeerMessage is a human-readable text message of the GroupRemovedFromPeer activity
|
||||
GroupRemovedFromPeerMessage string = "Group removed from peer"
|
||||
// GroupAddedToUserMessage is a human-readable text message of the GroupAddedToUser activity
|
||||
GroupAddedToUserMessage string = "Group added to user"
|
||||
// GroupRemovedFromUserMessage is a human-readable text message of the GroupRemovedFromUser activity
|
||||
GroupRemovedFromUserMessage string = "Group removed from user"
|
||||
// UserRoleUpdatedMessage is a human-readable text message of the UserRoleUpdatedMessage activity
|
||||
UserRoleUpdatedMessage string = "User role updated"
|
||||
// GroupAddedToSetupKeyMessage is a human-readable text message of the GroupAddedToSetupKey activity
|
||||
GroupAddedToSetupKeyMessage string = "Group added to setup key"
|
||||
// GroupRemovedFromSetupKeyMessage is a human-readable text message of the GroupRemovedFromSetupKey activity
|
||||
GroupRemovedFromSetupKeyMessage string = "Group removed from user setup key"
|
||||
)
|
||||
|
||||
// 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 GroupAddedToPeer:
|
||||
return GroupAddedToPeerMessage
|
||||
case GroupRemovedFromPeer:
|
||||
return GroupRemovedFromPeerMessage
|
||||
case GroupRemovedFromUser:
|
||||
return GroupRemovedFromUserMessage
|
||||
case GroupAddedToUser:
|
||||
return GroupAddedToUserMessage
|
||||
case UserRoleUpdated:
|
||||
return UserRoleUpdatedMessage
|
||||
case GroupAddedToSetupKey:
|
||||
return GroupAddedToSetupKeyMessage
|
||||
case GroupRemovedFromSetupKey:
|
||||
return GroupRemovedFromSetupKeyMessage
|
||||
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 GroupRemovedFromPeer:
|
||||
return "peer.group.delete"
|
||||
case GroupAddedToPeer:
|
||||
return "peer.group.add"
|
||||
case GroupAddedToUser:
|
||||
return "user.group.add"
|
||||
case GroupRemovedFromUser:
|
||||
return "user.group.delete"
|
||||
case UserRoleUpdated:
|
||||
return "user.role.update"
|
||||
case GroupAddedToSetupKey:
|
||||
return "setupkey.group.add"
|
||||
case GroupRemovedFromSetupKey:
|
||||
return "setupkey.group.delete"
|
||||
default:
|
||||
return "UNKNOWN_ACTIVITY"
|
||||
}
|
||||
}
|
42
management/server/activity/event.go
Normal file
42
management/server/activity/event.go
Normal file
@ -0,0 +1,42 @@
|
||||
package activity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
149
management/server/activity/sqlite/sqlite.go
Normal file
149
management/server/activity/sqlite/sqlite.go
Normal file
@ -0,0 +1,149 @@
|
||||
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);"
|
||||
|
||||
selectStatement = "SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta" +
|
||||
" FROM events WHERE account_id = ? ORDER BY timestamp %s LIMIT ? OFFSET ?;"
|
||||
insertStatement = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " +
|
||||
"VALUES(?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
|
||||
// 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, ×tamp, &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(selectStatement, 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(insertStatement)
|
||||
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
|
||||
}
|
53
management/server/activity/sqlite/sqlite_test.go
Normal file
53
management/server/activity/sqlite/sqlite_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
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
|
||||
}
|
||||
defer store.Close() //nolint
|
||||
|
||||
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))
|
||||
}
|
54
management/server/activity/store.go
Normal file
54
management/server/activity/store.go
Normal file
@ -0,0 +1,54 @@
|
||||
package activity
|
||||
|
||||
import "sync"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// InMemoryEventStore implements the Store interface storing data in-memory
|
||||
type InMemoryEventStore struct {
|
||||
mu sync.Mutex
|
||||
nextID uint64
|
||||
events []*Event
|
||||
}
|
||||
|
||||
// Save sets the Event.ID to 1
|
||||
func (store *InMemoryEventStore) Save(event *Event) (*Event, error) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
if store.events == nil {
|
||||
store.events = make([]*Event, 0)
|
||||
}
|
||||
event.ID = store.nextID
|
||||
store.nextID++
|
||||
store.events = append(store.events, event)
|
||||
return event, nil
|
||||
}
|
||||
|
||||
// Get returns a list of ALL events that belong to the given accountID without taking offset, limit and order into consideration
|
||||
func (store *InMemoryEventStore) Get(accountID string, offset, limit int, descending bool) ([]*Event, error) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
events := make([]*Event, 0)
|
||||
for _, event := range store.events {
|
||||
if event.AccountID == accountID {
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Close cleans up the event list
|
||||
func (store *InMemoryEventStore) Close() error {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
store.events = make([]*Event, 0)
|
||||
return nil
|
||||
}
|
33
management/server/event.go
Normal file
33
management/server/event.go
Normal 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
|
||||
}
|
63
management/server/event_test.go
Normal file
63
management/server/event_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateAndStoreEvents(t *testing.T, manager *DefaultAccountManager, typ activity.Activity, initiatorID, targetID,
|
||||
accountID string, count int) {
|
||||
for i := 0; i < count; i++ {
|
||||
_, err := manager.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: typ,
|
||||
InitiatorID: initiatorID,
|
||||
TargetID: targetID,
|
||||
AccountID: accountID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_GetEvents(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
accountID := "accountID"
|
||||
|
||||
t.Run("get empty events list", func(t *testing.T) {
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Len(t, events, 0)
|
||||
_ = manager.eventStore.Close() //nolint
|
||||
})
|
||||
|
||||
t.Run("get events", func(t *testing.T) {
|
||||
generateAndStoreEvents(t, manager, activity.PeerAddedByUser, userID, "peer", accountID, 10)
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(t, events, 10)
|
||||
_ = manager.eventStore.Close() //nolint
|
||||
})
|
||||
|
||||
t.Run("get events without duplicates", func(t *testing.T) {
|
||||
generateAndStoreEvents(t, manager, activity.UserJoined, userID, "", accountID, 10)
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Len(t, events, 1)
|
||||
_ = manager.eventStore.Close() //nolint
|
||||
})
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
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"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Group of the peers for ACL
|
||||
type Group struct {
|
||||
@ -34,6 +39,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 +72,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, newGroup *Group) error {
|
||||
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
@ -71,17 +81,95 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Groups[group.ID] = group
|
||||
oldGroup, exists := account.Groups[newGroup.ID]
|
||||
account.Groups[newGroup.ID] = newGroup
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupCreated,
|
||||
InitiatorID: userID,
|
||||
TargetID: newGroup.ID,
|
||||
AccountID: accountID,
|
||||
Meta: newGroup.EventMeta(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
addedPeers := make([]string, 0)
|
||||
removedPeers := make([]string, 0)
|
||||
if !exists {
|
||||
addedPeers = append(addedPeers, newGroup.Peers...)
|
||||
} else {
|
||||
addedPeers = difference(newGroup.Peers, oldGroup.Peers)
|
||||
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
|
||||
}
|
||||
|
||||
for _, p := range addedPeers {
|
||||
peer := account.Peers[p]
|
||||
if peer == nil {
|
||||
log.Errorf("peer %s not found under account %s while saving group", p, accountID)
|
||||
continue
|
||||
}
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupAddedToPeer,
|
||||
InitiatorID: userID,
|
||||
TargetID: peer.IP.String(),
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
|
||||
"peer_fqdn": peer.FQDN(am.GetDNSDomain())},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range removedPeers {
|
||||
peer := account.Peers[p]
|
||||
if peer == nil {
|
||||
log.Errorf("peer %s not found under account %s while saving group", p, accountID)
|
||||
continue
|
||||
}
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupRemovedFromPeer,
|
||||
InitiatorID: userID,
|
||||
TargetID: peer.IP.String(),
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
|
||||
"peer_fqdn": peer.FQDN(am.GetDNSDomain())},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
// difference returns the elements in `a` that aren't in `b`.
|
||||
func difference(a, b []string) []string {
|
||||
mb := make(map[string]struct{}, len(b))
|
||||
for _, x := range b {
|
||||
mb[x] = struct{}{}
|
||||
}
|
||||
var diff []string
|
||||
for _, x := range a {
|
||||
if _, found := mb[x]; !found {
|
||||
diff = append(diff, x)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// UpdateGroup updates a group using a list of operations
|
||||
func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
||||
groupID string, operations []GroupUpdateOperation) (*Group, error) {
|
||||
|
@ -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)},
|
||||
|
@ -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,74 @@ 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", "user.group.add", "user.group.delete",
|
||||
"user.role.update",
|
||||
"setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse",
|
||||
"setupkey.group.delete", "setupkey.group.add"
|
||||
"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 +579,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 +602,7 @@ paths:
|
||||
/api/users/:
|
||||
post:
|
||||
summary: Create a User (invite)
|
||||
tags: [ Users]
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@ -585,7 +629,7 @@ paths:
|
||||
/api/users/{id}:
|
||||
put:
|
||||
summary: Update information about a User
|
||||
tags: [ Users]
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -619,9 +663,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 +686,7 @@ paths:
|
||||
/api/peers/{id}:
|
||||
get:
|
||||
summary: Get information about a peer
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -669,7 +713,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update information about a peer
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -710,7 +754,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a peer
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -723,7 +767,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@ -735,7 +779,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 +801,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Setup Key
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@ -784,7 +828,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 +855,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 +888,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Setup Key
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -857,7 +901,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@ -869,7 +913,7 @@ paths:
|
||||
/api/groups:
|
||||
get:
|
||||
summary: Returns a list of all Groups
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
@ -891,7 +935,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@ -927,7 +971,7 @@ paths:
|
||||
/api/groups/{id}:
|
||||
get:
|
||||
summary: Get information about a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -954,7 +998,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update/Replace a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -1029,7 +1073,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -1042,7 +1086,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@ -1054,7 +1098,7 @@ paths:
|
||||
/api/rules:
|
||||
get:
|
||||
summary: Returns a list of all Rules
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
@ -1076,7 +1120,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Rule
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@ -1106,7 +1150,7 @@ paths:
|
||||
/api/rules/{id}:
|
||||
get:
|
||||
summary: Get information about a Rules
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -1133,7 +1177,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update/Replace a Rule
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -1212,7 +1256,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Rule
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@ -1225,7 +1269,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@ -1573,5 +1617,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"
|
@ -11,6 +11,28 @@ 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"
|
||||
EventActivityCodeUserGroupAdd EventActivityCode = "user.group.add"
|
||||
EventActivityCodeUserGroupDelete EventActivityCode = "user.group.delete"
|
||||
EventActivityCodeUserInvite EventActivityCode = "user.invite"
|
||||
EventActivityCodeUserJoin EventActivityCode = "user.join"
|
||||
EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add"
|
||||
EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete"
|
||||
EventActivityCodeUserRoleUpdate EventActivityCode = "user.role.update"
|
||||
)
|
||||
|
||||
// Defines values for GroupPatchOperationOp.
|
||||
const (
|
||||
GroupPatchOperationOpAdd GroupPatchOperationOp = "add"
|
||||
@ -97,6 +119,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
|
||||
|
69
management/server/http/events.go
Normal file
69
management/server/http/events.go
Normal 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,
|
||||
}
|
||||
}
|
250
management/server/http/events_test.go
Normal file
250
management/server/http/events_test.go
Normal file
@ -0,0 +1,250 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"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/jwtclaims"
|
||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func initEventsTestData(account string, user *server.User, events ...*activity.Event) *Events {
|
||||
return &Events{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetEventsFunc: func(accountID, userID string) ([]*activity.Event, error) {
|
||||
if accountID == account {
|
||||
return events, nil
|
||||
}
|
||||
return []*activity.Event{}, nil
|
||||
},
|
||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
Domain: "hotmail.com",
|
||||
Users: map[string]*server.User{
|
||||
user.Id: user,
|
||||
},
|
||||
}, user, nil
|
||||
},
|
||||
},
|
||||
authAudience: "",
|
||||
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||
return jwtclaims.AuthorizationClaims{
|
||||
UserId: "test_user",
|
||||
Domain: "hotmail.com",
|
||||
AccountId: "test_account",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateEvents(accountID, userID string) []*activity.Event {
|
||||
ID := uint64(1)
|
||||
events := make([]*activity.Event, 0)
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.PeerAddedByUser,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "100.64.0.2",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.UserJoined,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupCreated,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "group-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyUpdated,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "setup-key-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyUpdated,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "setup-key-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyRevoked,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "setup-key-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyOverused,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "setup-key-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyCreated,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "setup-key-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.RuleAdded,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "some-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.RuleRemoved,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "some-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.RuleUpdated,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "some-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
ID++
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.PeerAddedWithSetupKey,
|
||||
ID: ID,
|
||||
InitiatorID: userID,
|
||||
TargetID: "some-id",
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"some": "meta"},
|
||||
})
|
||||
return events
|
||||
}
|
||||
|
||||
func TestEvents_GetEvents(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
expectedBody bool
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
}{
|
||||
{
|
||||
name: "GetEvents OK",
|
||||
expectedBody: true,
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/events/",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
}
|
||||
accountID := "test_account"
|
||||
adminUser := server.NewAdminUser("test_user")
|
||||
events := generateEvents(accountID, adminUser.Id)
|
||||
handler := initEventsTestData(accountID, adminUser, events...)
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/events/", handler.GetEvents).Methods("GET")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
res := recorder.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
if status := recorder.Code; status != tc.expectedStatus {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
status, tc.expectedStatus)
|
||||
return
|
||||
}
|
||||
|
||||
if !tc.expectedBody {
|
||||
return
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("I don't know what I expected; %v", err)
|
||||
}
|
||||
|
||||
var got []*api.Event
|
||||
if err = json.Unmarshal(content, &got); err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
assert.Len(t, got, len(events))
|
||||
actual := map[string]*api.Event{}
|
||||
for _, event := range got {
|
||||
actual[event.Id] = event
|
||||
}
|
||||
|
||||
for _, expected := range events {
|
||||
event, ok := actual[strconv.FormatUint(expected.ID, 10)]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, expected.InitiatorID, event.InitiatorId)
|
||||
assert.Equal(t, expected.TargetID, event.TargetId)
|
||||
assert.Equal(t, expected.Activity.Message(), event.Activity)
|
||||
assert.Equal(t, expected.Activity.StringCode(), string(event.ActivityCode))
|
||||
assert.Equal(t, expected.Meta["some"], event.Meta["some"])
|
||||
assert.True(t, expected.Timestamp.Equal(event.Timestamp))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func (h *UserHandler) UpdateUser(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
|
||||
@ -60,7 +60,7 @@ func (h *UserHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
newUser, err := h.accountManager.SaveUser(account.Id, &server.User{
|
||||
newUser, err := h.accountManager.SaveUser(account.Id, user.Id, &server.User{
|
||||
Id: userID,
|
||||
Role: userRole,
|
||||
AutoGroups: req.AutoGroups,
|
||||
@ -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,
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -403,7 +404,9 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro
|
||||
return nil, err
|
||||
}
|
||||
peersUpdateManager := NewPeersUpdateManager()
|
||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
@ -493,7 +494,9 @@ 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 := &activity.InMemoryEventStore{}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a manager: %v", err)
|
||||
}
|
||||
|
@ -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,18 +51,19 @@ 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)
|
||||
SaveUserFunc func(accountID, userID string, user *server.User) (*server.UserInfo, error)
|
||||
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||
CreateNameServerGroupFunc func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool) (*nbdns.NameServerGroup, error)
|
||||
SaveNameServerGroupFunc func(accountID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||
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")
|
||||
@ -400,9 +404,9 @@ func (am *MockAccountManager) ListSetupKeys(accountID, userID string) ([]*server
|
||||
}
|
||||
|
||||
// SaveUser mocks SaveUser of the AccountManager interface
|
||||
func (am *MockAccountManager) SaveUser(accountID string, user *server.User) (*server.UserInfo, error) {
|
||||
func (am *MockAccountManager) SaveUser(accountID, userID string, user *server.User) (*server.UserInfo, error) {
|
||||
if am.SaveUserFunc != nil {
|
||||
return am.SaveUserFunc(accountID, user)
|
||||
return am.SaveUserFunc(accountID, userID, user)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SaveUser 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")
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/netip"
|
||||
"testing"
|
||||
@ -1056,7 +1057,8 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
}
|
||||
|
||||
func createNSStore(t *testing.T) (Store, error) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"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,8 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
}
|
||||
|
||||
func createRouterStore(t *testing.T) (Store, error) {
|
||||
@ -980,7 +982,6 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
}
|
||||
|
||||
accountID := "testingAcc"
|
||||
userID := "testingUser"
|
||||
domain := "example.com"
|
||||
|
||||
account := newAccountWithId(accountID, userID, domain)
|
||||
@ -1002,7 +1003,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 +1014,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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,9 @@ package server
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hash/fnv"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -107,12 +109,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 +199,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 +221,44 @@ 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
|
||||
}
|
||||
|
||||
for _, g := range setupKey.AutoGroups {
|
||||
group := account.GetGroup(g)
|
||||
if group != nil {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupAddedToSetupKey,
|
||||
InitiatorID: userID,
|
||||
TargetID: setupKey.Id,
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": setupKey.Name},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed saving setup key activity event %s: %v",
|
||||
activity.GroupAddedToSetupKey.StringCode(), err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Errorf("group %s not found while saving setup key activity event of account %s", g, account.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return setupKey, nil
|
||||
}
|
||||
|
||||
@ -224,7 +266,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 +303,67 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
addedGroups := difference(newKey.AutoGroups, oldKey.AutoGroups)
|
||||
removedGroups := difference(oldKey.AutoGroups, newKey.AutoGroups)
|
||||
for _, g := range removedGroups {
|
||||
group := account.GetGroup(g)
|
||||
if group != nil {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupRemovedFromSetupKey,
|
||||
InitiatorID: userID,
|
||||
TargetID: oldKey.Id,
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": newKey.Name},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed saving setup key activity event %s: %v",
|
||||
activity.GroupRemovedFromSetupKey.StringCode(), err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Errorf("group %s not found while saving setup key activity event of account %s", g, account.Id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, g := range addedGroups {
|
||||
group := account.GetGroup(g)
|
||||
if group != nil {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupAddedToSetupKey,
|
||||
InitiatorID: userID,
|
||||
TargetID: oldKey.Id,
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": newKey.Name},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed saving setup key activity event %s: %v",
|
||||
activity.GroupAddedToSetupKey.StringCode(), err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Errorf("group %s not found while saving setup key activity event of account %s", g, account.Id)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return newKey, am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
@ -282,7 +385,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 +427,7 @@ func (am *DefaultAccountManager) GetSetupKey(accountID, userID, keyID string) (*
|
||||
}
|
||||
|
||||
if !user.IsAdmin() {
|
||||
foundKey = foundKey.HiddenCopy()
|
||||
foundKey = foundKey.HiddenCopy(999)
|
||||
}
|
||||
|
||||
return foundKey, nil
|
||||
|
@ -1,7 +1,9 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"strconv"
|
||||
"testing"
|
||||
@ -20,7 +22,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 +35,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,13 +48,33 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
||||
Name: newKeyName,
|
||||
Revoked: revoked,
|
||||
AutoGroups: autoGroups,
|
||||
})
|
||||
}, userID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertKey(t, newKey, newKeyName, revoked, "reusable", 0, key.CreatedAt, key.ExpiresAt,
|
||||
key.Id, time.Now(), autoGroups)
|
||||
|
||||
events, err := manager.GetEvents(account.Id, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ev *activity.Event
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.SetupKeyRevoked {
|
||||
ev = event
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, newKeyName, ev.Meta["name"])
|
||||
assert.Equal(t, fmt.Sprint(key.Type), fmt.Sprint(ev.Meta["type"]))
|
||||
assert.NotEmpty(t, ev.Meta["key"])
|
||||
assert.Equal(t, userID, ev.InitiatorID)
|
||||
assert.Equal(t, key.Id, ev.TargetID)
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
|
||||
@ -67,7 +89,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 +98,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 +143,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 {
|
||||
@ -137,6 +159,24 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
|
||||
assertKey(t, key, tCase.expectedKeyName, false, tCase.expectedType, tCase.expectedUsedTimes,
|
||||
tCase.expectedCreatedAt, tCase.expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))),
|
||||
tCase.expectedUpdatedAt, tCase.expectedGroups)
|
||||
|
||||
events, err := manager.GetEvents(account.Id, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ev *activity.Event
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.SetupKeyCreated {
|
||||
ev = event
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, tCase.expectedKeyName, ev.Meta["name"])
|
||||
assert.Equal(t, tCase.expectedType, fmt.Sprint(ev.Meta["type"]))
|
||||
assert.NotEmpty(t, ev.Meta["key"])
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,13 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"strings"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -117,7 +119,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,13 +178,26 @@ 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)
|
||||
|
||||
}
|
||||
|
||||
// SaveUser saves updates a given user. If the user doesn't exit it will throw status.NotFound error.
|
||||
// Only User.AutoGroups field is allowed to be updated for now.
|
||||
func (am *DefaultAccountManager) SaveUser(accountID string, update *User) (*UserInfo, error) {
|
||||
func (am *DefaultAccountManager) SaveUser(accountID, userID string, update *User) (*UserInfo, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@ -218,6 +233,68 @@ func (am *DefaultAccountManager) SaveUser(accountID string, update *User) (*User
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if oldUser.Role != newUser.Role {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.UserRoleUpdated,
|
||||
InitiatorID: userID,
|
||||
TargetID: oldUser.Id,
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"role": newUser.Role},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed saving user activity event %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
|
||||
addedGroups := difference(newUser.AutoGroups, oldUser.AutoGroups)
|
||||
for _, g := range removedGroups {
|
||||
group := account.GetGroup(g)
|
||||
if group != nil {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupRemovedFromUser,
|
||||
InitiatorID: userID,
|
||||
TargetID: oldUser.Id,
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": group.Name, "group_id": group.ID},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed saving user activity event %s %v",
|
||||
activity.GroupRemovedFromUser.StringCode(), err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Errorf("group %s not found while saving user activity event of account %s", g, account.Id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, g := range addedGroups {
|
||||
group := account.GetGroup(g)
|
||||
if group != nil {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupAddedToUser,
|
||||
InitiatorID: userID,
|
||||
TargetID: oldUser.Id,
|
||||
AccountID: accountID,
|
||||
Meta: map[string]any{"group": group.Name, "group_id": group.ID},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed saving user activity event %s: %v",
|
||||
activity.GroupAddedToUser.StringCode(), err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Errorf("group %s not found while saving user activity event of account %s", g, account.Id)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if !isNil(am.idpManager) {
|
||||
userData, err := am.lookupUserInCache(newUser.Id, account)
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user