Skip to content

Commit

Permalink
CBG-4188: Add audit logging metrics (#7083)
Browse files Browse the repository at this point in the history
* Add audit stats placeholders

* Add stat incrs

* Add stat assertions to existing TestAuditLoggingFields

* Stat descriptions
  • Loading branch information
bbrks authored Aug 21, 2024
1 parent 07f9552 commit 5adf0d6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 1 deletion.
3 changes: 3 additions & 0 deletions base/logger_audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func Audit(ctx context.Context, id AuditID, additionalData AuditFields) {
}

logger.logf(fieldsJSON)
SyncGatewayStats.GlobalStats.AuditStat.NumAuditsLogged.Add(1)
}

// IsAuditEnabled checks if auditing is enabled for the SG node
Expand Down Expand Up @@ -285,6 +286,7 @@ func shouldLogAuditEventForUserAndRole(logCtx *LogContext) bool {
Domain: string(logCtx.UserDomain),
Name: logCtx.Username,
}]; isDisabled {
SyncGatewayStats.GlobalStats.AuditStat.NumAuditsFilteredByUser.Add(1)
return false
}
}
Expand All @@ -295,6 +297,7 @@ func shouldLogAuditEventForUserAndRole(logCtx *LogContext) bool {
Domain: string(logCtx.UserDomain),
Name: role,
}]; isDisabled {
SyncGatewayStats.GlobalStats.AuditStat.NumAuditsFilteredByRole.Add(1)
return false
}
}
Expand Down
34 changes: 34 additions & 0 deletions base/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
NamespaceKey = "sgw"
ResourceUtilizationSubsystem = "resource_utilization"
ConfigSubsystem = "config"
AuditSubsystem = "audit"

SubsystemCacheKey = "cache"
SubsystemDatabaseKey = "database"
Expand Down Expand Up @@ -168,6 +169,7 @@ func (s *SgwStats) String() string {
type GlobalStat struct {
ResourceUtilization *ResourceUtilization `json:"resource_utilization"`
ConfigStat *ConfigStat `json:"config"`
AuditStat *AuditStat `json:"audit"`
}

func newGlobalStat() (*GlobalStat, error) {
Expand All @@ -180,6 +182,10 @@ func newGlobalStat() (*GlobalStat, error) {
if err != nil {
return nil, err
}
err = g.initAuditStats()
if err != nil {
return nil, err
}
return g, nil
}

Expand All @@ -198,6 +204,25 @@ func (g *GlobalStat) initConfigStats() error {
return nil
}

func (g *GlobalStat) initAuditStats() error {
auditStat := &AuditStat{}
var err error
auditStat.NumAuditsLogged, err = NewIntStat(AuditSubsystem, "num_audits_logged", StatUnitNoUnits, NumAuditsLoggedDesc, StatAddedVersion3dot2dot1, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, nil, nil, prometheus.CounterValue, 0)
if err != nil {
return err
}
auditStat.NumAuditsFilteredByUser, err = NewIntStat(AuditSubsystem, "num_audits_filtered_by_user", StatUnitNoUnits, NumAuditsFilteredByUserDesc, StatAddedVersion3dot2dot1, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, nil, nil, prometheus.CounterValue, 0)
if err != nil {
return err
}
auditStat.NumAuditsFilteredByRole, err = NewIntStat(AuditSubsystem, "num_audits_filtered_by_role", StatUnitNoUnits, NumAuditsFilteredByRoleDesc, StatAddedVersion3dot2dot1, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, nil, nil, prometheus.CounterValue, 0)
if err != nil {
return err
}
g.AuditStat = auditStat
return nil
}

func (g *GlobalStat) initResourceUtilizationStats() error {
var err error
resUtil := &ResourceUtilization{}
Expand Down Expand Up @@ -365,6 +390,15 @@ type ConfigStat struct {
DatabaseRollbackCollectionCollisions *SgwIntStat `json:"database_config_rollback_collection_collisions"`
}

type AuditStat struct {
// The number of times an audit event was created/emitted/logged.
NumAuditsLogged *SgwIntStat `json:"num_audits_logged"`
// The number of times an audit event was filtered by username.
NumAuditsFilteredByUser *SgwIntStat `json:"num_audits_filtered_by_user"`
// The number of times an audit event was filtered by role.
NumAuditsFilteredByRole *SgwIntStat `json:"num_audits_filtered_by_role"`
}

type DbStats struct {
dbName string
CacheStats *CacheStats `json:"cache,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions base/stats_descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ const (
DatabaseCollectionConflictDesc = "The total number of times a database config is rolled back to an invalid state (collection conflicts)."
)

// audit stat
const (
NumAuditsLoggedDesc = "The total number of audit events logged."
NumAuditsFilteredByUserDesc = "The total number of audit events filtered by user."
NumAuditsFilteredByRoleDesc = "The total number of audit events filtered by role."
)

// cache stats descriptions
const (
AbandonedSequencesDesc = "The total number of skipped sequences that were not found after 60 minutes and were abandoned."
Expand Down
24 changes: 23 additions & 1 deletion rest/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ func TestAuditLoggingFields(t *testing.T) {
// auditableAction is a function that performs an action that should've been audited
auditableAction func(t testing.TB)
// expectedAuditEvents is a list of expected audit events and their fields for the given action... can be more than one event produced for a given action
expectedAuditEventFields map[base.AuditID]base.AuditFields
expectedAuditEventFields map[base.AuditID]base.AuditFields
expectedStatNumAuditEventsFilteredByUser int64
expectedStatNumAuditEventsFilteredByRole int64
}{

{
Expand Down Expand Up @@ -377,12 +379,14 @@ func TestAuditLoggingFields(t *testing.T) {
auditableAction: func(t testing.TB) {
RequireStatus(t, rt.SendUserRequest(http.MethodGet, "/db/", "", filteredPublicUsername), http.StatusOK)
},
expectedStatNumAuditEventsFilteredByUser: 3, // http, auth, read db
},
{
name: "filtered public role request",
auditableAction: func(t testing.TB) {
RequireStatus(t, rt.SendUserRequest(http.MethodGet, "/db/", "", filteredPublicRoleUsername), http.StatusOK)
},
expectedStatNumAuditEventsFilteredByRole: 3, // http, auth, read db
},
{
name: "filtered admin request",
Expand All @@ -395,6 +399,7 @@ func TestAuditLoggingFields(t *testing.T) {
}
RequireStatus(t, rt.SendAdminRequestWithAuth(http.MethodGet, "/db/", "", filteredAdminUsername, RestTesterDefaultUserPassword), http.StatusOK)
},
expectedStatNumAuditEventsFilteredByUser: 3, // http, auth, read db
},
{
name: "filtered admin role request",
Expand All @@ -407,6 +412,7 @@ func TestAuditLoggingFields(t *testing.T) {
}
RequireStatus(t, rt.SendAdminRequestWithAuth(http.MethodGet, "/db/", "", filteredAdminRoleUsername, RestTesterDefaultUserPassword), http.StatusOK)
},
expectedStatNumAuditEventsFilteredByRole: 3, // http, auth, read db
},
{
name: "authed admin request role filtered on different bucket",
Expand Down Expand Up @@ -480,7 +486,23 @@ func TestAuditLoggingFields(t *testing.T) {
}
for _, testCase := range testCases {
rt.Run(testCase.name, func(t *testing.T) {
numAuditEventsStatBefore := base.SyncGatewayStats.GlobalStats.AuditStat.NumAuditsLogged.Value()
numAuditEventsFilteredByUserStatBefore := base.SyncGatewayStats.GlobalStats.AuditStat.NumAuditsFilteredByUser.Value()
numAuditEventsFilteredByRoleStatBefore := base.SyncGatewayStats.GlobalStats.AuditStat.NumAuditsFilteredByRole.Value()

output := base.AuditLogContents(t, testCase.auditableAction)

numAuditEventsStatAfter := base.SyncGatewayStats.GlobalStats.AuditStat.NumAuditsLogged.Value()
numAuditEventsFilteredByUserStatAfter := base.SyncGatewayStats.GlobalStats.AuditStat.NumAuditsFilteredByUser.Value()
numAuditEventsFilteredByRoleStatAfter := base.SyncGatewayStats.GlobalStats.AuditStat.NumAuditsFilteredByRole.Value()

numAuditEventsStat := numAuditEventsStatAfter - numAuditEventsStatBefore
assert.Equal(t, int64(len(testCase.expectedAuditEventFields)), numAuditEventsStat)
numAuditEventsFilteredByUserStat := numAuditEventsFilteredByUserStatAfter - numAuditEventsFilteredByUserStatBefore
assert.Equal(t, testCase.expectedStatNumAuditEventsFilteredByUser, numAuditEventsFilteredByUserStat)
numAuditEventsFilteredByRoleStat := numAuditEventsFilteredByRoleStatAfter - numAuditEventsFilteredByRoleStatBefore
assert.Equal(t, testCase.expectedStatNumAuditEventsFilteredByRole, numAuditEventsFilteredByRoleStat)

events := jsonLines(t, output)

assert.Equalf(t, len(testCase.expectedAuditEventFields), len(events), "expected exactly %d audit events, got %d", len(testCase.expectedAuditEventFields), len(events))
Expand Down

0 comments on commit 5adf0d6

Please sign in to comment.