Add more events

This commit is contained in:
braginini
2022-12-20 18:52:18 +01:00
parent dbda9087f5
commit 747192c9f8
12 changed files with 194 additions and 49 deletions

View File

@@ -53,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)
@@ -71,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)

View File

@@ -13,19 +13,35 @@ const (
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
)
const (
// PeerAddedByUserMessage is a human-readable text message of the PeerAddedByUser activity
PeerAddedByUserMessage string = "User added a new peer"
PeerAddedByUserMessage string = "Peer added"
// PeerAddedWithSetupKeyMessage is a human-readable text message of the PeerAddedWithSetupKey activity
PeerAddedWithSetupKeyMessage = "New peer added with a setup key"
PeerAddedWithSetupKeyMessage = PeerAddedByUserMessage
//UserJoinedMessage is a human-readable text message of the UserJoined activity
UserJoinedMessage string = "New user joined"
UserJoinedMessage string = "User joined"
//UserInvitedMessage is a human-readable text message of the UserInvited activity
UserInvitedMessage string = "New user invited"
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"
)
// Activity that triggered an Event
@@ -36,6 +52,8 @@ func (a Activity) Message() string {
switch a {
case PeerAddedByUser:
return PeerAddedByUserMessage
case PeerRemovedByUser:
return PeerRemovedByUserMessage
case PeerAddedWithSetupKey:
return PeerAddedWithSetupKeyMessage
case UserJoined:
@@ -44,6 +62,12 @@ func (a Activity) Message() string {
return UserInvitedMessage
case AccountCreated:
return AccountCreatedMessage
case RuleAdded:
return RuleAddedMessage
case RuleRemoved:
return RuleRemovedMessage
case RuleUpdated:
return RuleUpdatedMessage
default:
return "UNKNOWN_ACTIVITY"
}
@@ -54,6 +78,8 @@ 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:
@@ -62,6 +88,12 @@ func (a Activity) StringCode() string {
return "user.invite"
case AccountCreated:
return "account.create"
case RuleAdded:
return "rule.add"
case RuleRemoved:
return "rule.delete"
case RuleUpdated:
return "rule.update"
default:
return "UNKNOWN_ACTIVITY"
}
@@ -91,10 +123,18 @@ type Event struct {
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,
@@ -102,5 +142,6 @@ func (e *Event) Copy() *Event {
InitiatorID: e.InitiatorID,
TargetID: e.TargetID,
AccountID: e.AccountID,
Meta: meta,
}
}

View File

@@ -2,6 +2,7 @@ package activity
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/mattn/go-sqlite3"
"path/filepath"
@@ -16,6 +17,7 @@ const (
"timestamp DATETIME, " +
"initiator_id TEXT," +
"account_id TEXT," +
"meta TEXT," +
" target_id TEXT);"
)
@@ -49,11 +51,20 @@ func processResult(result *sql.Rows) ([]*Event, error) {
var initiator string
var target string
var account string
err := result.Scan(&id, &operation, &timestamp, &initiator, &target, &account)
var jsonMeta string
err := result.Scan(&id, &operation, &timestamp, &initiator, &target, &account, &jsonMeta)
if err != nil {
return nil, err
}
meta := make(map[string]any)
if jsonMeta != "" {
err = json.Unmarshal([]byte(jsonMeta), &meta)
if err != nil {
return nil, err
}
}
events = append(events, &Event{
Timestamp: timestamp,
Activity: operation,
@@ -61,6 +72,7 @@ func processResult(result *sql.Rows) ([]*Event, error) {
InitiatorID: initiator,
TargetID: target,
AccountID: account,
Meta: meta,
})
}
@@ -73,7 +85,7 @@ func (store *SQLiteStore) Get(accountID string, offset, limit int, descending bo
if !descending {
order = "ASC"
}
stmt, err := store.db.Prepare(fmt.Sprintf("SELECT id, activity, timestamp, initiator_id, target_id, account_id"+
stmt, err := store.db.Prepare(fmt.Sprintf("SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta"+
" FROM events WHERE account_id = ? ORDER BY timestamp %s LIMIT ? OFFSET ?;", order))
if err != nil {
return nil, err
@@ -91,12 +103,21 @@ func (store *SQLiteStore) Get(accountID string, offset, limit int, descending bo
// Save an event in the SQLite events table
func (store *SQLiteStore) Save(event *Event) (*Event, error) {
stmt, err := store.db.Prepare("INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id) VALUES(?, ?, ?, ?, ?)")
stmt, err := store.db.Prepare("INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) VALUES(?, ?, ?, ?, ?, ?)")
if err != nil {
return nil, err
}
result, err := stmt.Exec(event.Activity, event.Timestamp, event.InitiatorID, event.TargetID, event.AccountID)
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
}

View File

@@ -1,29 +1,27 @@
package activity
import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestNewSQLiteStore(t *testing.T) {
dataDir := t.TempDir()
store, err := NewSQLiteStore(dataDir)
//dataDir := t.TempDir()
store, err := NewSQLiteStore("/home/braginini/wiretrustee/test/")
if err != nil {
t.Fatal(err)
return
}
accountID := "account_1"
//accountID := "account_1"
for i := 0; i < 10; i++ {
for i := 0; i < 10000; i++ {
_, err = store.Save(&Event{
Timestamp: time.Now(),
Timestamp: time.Now().Add(-1 * time.Minute),
Activity: PeerAddedByUser,
InitiatorID: "user_" + fmt.Sprint(i),
TargetID: "peer_" + fmt.Sprint(i),
AccountID: accountID,
InitiatorID: "google-oauth2|110866222733584764488",
TargetID: "100.101.249.29",
AccountID: "cebi9h3lo1hkhn1qc7cg",
})
if err != nil {
t.Fatal(err)
@@ -31,7 +29,7 @@ func TestNewSQLiteStore(t *testing.T) {
}
}
result, err := store.Get(accountID, 0, 10, false)
/*result, err := store.Get(accountID, 0, 10, false)
if err != nil {
t.Fatal(err)
return
@@ -47,5 +45,5 @@ func TestNewSQLiteStore(t *testing.T) {
}
assert.Len(t, result, 5)
assert.True(t, result[0].Timestamp.After(result[len(result)-1].Timestamp))
assert.True(t, result[0].Timestamp.After(result[len(result)-1].Timestamp))*/
}

View File

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

View File

@@ -519,13 +519,18 @@ components:
activity_code:
description: The string code of the activity that occurred during the event
type: string
enum: [ "account.create", "user.join", "user.invite", "user.peer.add", "setupkey.peer.add" ]
enum: [ "account.create", "user.join", "user.invite", "user.peer.add", "setupkey.peer.add", "user.peer.delete", "rule.add", "rule.delete", "rule.update"]
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
@@ -533,6 +538,7 @@ components:
- activity_code
- initiator_id
- target_id
- meta
responses:
not_found:
description: Resource not found

View File

@@ -14,10 +14,14 @@ const (
// Defines values for EventActivityCode.
const (
EventActivityCodeAccountCreate EventActivityCode = "account.create"
EventActivityCodeRuleAdd EventActivityCode = "rule.add"
EventActivityCodeRuleDelete EventActivityCode = "rule.delete"
EventActivityCodeRuleUpdate EventActivityCode = "rule.update"
EventActivityCodeSetupkeyPeerAdd EventActivityCode = "setupkey.peer.add"
EventActivityCodeUserInvite EventActivityCode = "user.invite"
EventActivityCodeUserJoin EventActivityCode = "user.join"
EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add"
EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete"
)
// Defines values for GroupPatchOperationOp.
@@ -120,6 +124,9 @@ type Event struct {
// 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"`

View File

@@ -51,7 +51,12 @@ func (h *Events) GetEvents(w http.ResponseWriter, r *http.Request) {
}
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,
@@ -59,5 +64,6 @@ func toEventResponse(event *activity.Event) *api.Event {
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
TargetId: event.TargetID,
Timestamp: event.Timestamp,
Meta: meta,
}
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package server
import (
"fmt"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
@@ -74,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, "dns": p.FQDN(dnsDomain), "ip": p.IP}
}
// Copy PeerStatus
func (p *PeerStatus) Copy() *PeerStatus {
return &PeerStatus{
@@ -217,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()
@@ -263,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
}
@@ -449,6 +475,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
}
opEvent.TargetID = newPeer.IP.String()
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
_, err = am.eventStore.Save(opEvent)
if err != nil {
return nil, err

View File

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