From 7058571d2f8816be42b1d008ba70e1813a6dc98d Mon Sep 17 00:00:00 2001 From: Ben Brooks Date: Tue, 25 Jun 2024 19:31:05 +0100 Subject: [PATCH 1/3] Wire up config for audit logging. Per-database audit log settings can be read/written through /db/_config api --- base/audit_events.go | 13 +++++++++++++ base/logger_audit.go | 22 +++++++++++++++++++++- base/logging_config.go | 9 ++++----- base/logging_context.go | 1 + rest/admin_api.go | 3 ++- rest/config.go | 2 +- rest/config_database.go | 17 +++++++++++------ 7 files changed, 53 insertions(+), 14 deletions(-) diff --git a/base/audit_events.go b/base/audit_events.go index cb52e5d06a..b542f2ba23 100644 --- a/base/audit_events.go +++ b/base/audit_events.go @@ -73,3 +73,16 @@ var AuditEvents = events{ EventType: eventTypeUser, }, } + +// DefaultAuditEventIDs is a list of audit event IDs that are enabled by default. +var DefaultAuditEventIDs = buildDefaultAuditIDList(AuditEvents) + +func buildDefaultAuditIDList(e events) []uint { + var ids []uint + for id, event := range e { + if event.EnabledByDefault { + ids = append(ids, uint(id)) + } + } + return ids +} diff --git a/base/logger_audit.go b/base/logger_audit.go index 29212a4b92..540f8199fe 100644 --- a/base/logger_audit.go +++ b/base/logger_audit.go @@ -16,7 +16,8 @@ import ( ) const ( - auditLogName = "audit" + auditLogName = "audit" + defaultAuditEnabled = false ) // commonly used fields for audit events @@ -131,12 +132,28 @@ type AuditLogger struct { config AuditLoggerConfig } +func (l *AuditLogger) getAuditLoggerConfig() *AuditLoggerConfig { + c := AuditLoggerConfig{} + if l != nil { + // Copy config struct to avoid mutating running config + c = l.config + } + + c.FileLoggerConfig = *l.getFileLoggerConfig() + + return &c +} + // NewAuditLogger returns a new AuditLogger from a config. func NewAuditLogger(ctx context.Context, config *AuditLoggerConfig, logFilePath string, minAge int, buffer *strings.Builder) (*AuditLogger, error) { if config == nil { config = &AuditLoggerConfig{} } + if config.FileLoggerConfig.Enabled == nil { + config.FileLoggerConfig.Enabled = BoolPtr(defaultAuditEnabled) + } + fl, err := NewFileLogger(ctx, &config.FileLoggerConfig, LevelNone, auditLogName, logFilePath, minAge, buffer) if err != nil { return nil, err @@ -156,6 +173,9 @@ func (al *AuditLogger) shouldLog(id AuditID, ctx context.Context) bool { } logCtx := getLogCtx(ctx) if logCtx.DbLogConfig != nil && logCtx.DbLogConfig.Audit != nil { + if !logCtx.DbLogConfig.Audit.Enabled { + return false + } if _, ok := logCtx.DbLogConfig.Audit.EnabledEvents[id]; !ok { return false } diff --git a/base/logging_config.go b/base/logging_config.go index 9880bd0fc1..1a5ec2a5b2 100644 --- a/base/logging_config.go +++ b/base/logging_config.go @@ -267,10 +267,10 @@ type AuditLoggerConfig struct { AuditLogFilePath *string `json:"audit_log_file_path,omitempty"` // If set, overrides the output path for the audit log files } -func BuildLoggingConfigFromLoggers(redactionLevel RedactionLevel, LogFilePath string) *LoggingConfig { +func BuildLoggingConfigFromLoggers(originalConfig LoggingConfig) *LoggingConfig { config := LoggingConfig{ - RedactionLevel: redactionLevel, - LogFilePath: LogFilePath, + RedactionLevel: originalConfig.RedactionLevel, + LogFilePath: originalConfig.LogFilePath, } config.Console = consoleLogger.getConsoleLoggerConfig() @@ -280,8 +280,7 @@ func BuildLoggingConfigFromLoggers(redactionLevel RedactionLevel, LogFilePath st config.Debug = debugLogger.getFileLoggerConfig() config.Trace = traceLogger.getFileLoggerConfig() config.Stats = statsLogger.getFileLoggerConfig() - // FIXME(bbrks): Once AuditLogger is implemented - config.Audit = &AuditLoggerConfig{} + config.Audit = auditLogger.getAuditLoggerConfig() return &config } diff --git a/base/logging_context.go b/base/logging_context.go index 12ff9603f5..b4b27895b5 100644 --- a/base/logging_context.go +++ b/base/logging_context.go @@ -69,6 +69,7 @@ type DbConsoleLogConfig struct { // DbAuditLogConfig can be used to customise the audit logging for events associated with this database. type DbAuditLogConfig struct { + Enabled bool EnabledEvents map[AuditID]struct{} } diff --git a/rest/admin_api.go b/rest/admin_api.go index 2bca0afa2e..f492294f27 100644 --- a/rest/admin_api.go +++ b/rest/admin_api.go @@ -394,7 +394,8 @@ func (h *handler) handleGetConfig() error { } } - cfg.Logging = *base.BuildLoggingConfigFromLoggers(h.server.Config.Logging.RedactionLevel, h.server.Config.Logging.LogFilePath) + // because loggers can be changed at runtime, we need to work backwards to get the config that would've created the actually running instances + cfg.Logging = *base.BuildLoggingConfigFromLoggers(h.server.Config.Logging) cfg.Databases = databaseMap h.writeJSON(cfg) diff --git a/rest/config.go b/rest/config.go index 64e237d60d..522de15926 100644 --- a/rest/config.go +++ b/rest/config.go @@ -2174,7 +2174,7 @@ func RegisterSignalHandler(ctx context.Context) { }() } -// toDbLogConfig converts the console logging from a DbConfig to a DbLogConfig +// toDbLogConfig converts the logging from a DbConfig to a DbLogConfig func (c *DbConfig) toDbLogConfig(ctx context.Context) *base.DbLogConfig { l := c.Logging if l == nil || (l.Console == nil && l.Audit == nil) { diff --git a/rest/config_database.go b/rest/config_database.go index 7002e498cc..3eb995dc9a 100644 --- a/rest/config_database.go +++ b/rest/config_database.go @@ -90,17 +90,22 @@ func GenerateDatabaseConfigVersionID(ctx context.Context, previousRevID string, } func DefaultPerDBLogging(bootstrapLoggingCnf base.LoggingConfig) *DbLoggingConfig { + dblc := &DbLoggingConfig{} if bootstrapLoggingCnf.Console != nil { if *bootstrapLoggingCnf.Console.Enabled { - return &DbLoggingConfig{ - Console: &DbConsoleLoggingConfig{ - LogLevel: bootstrapLoggingCnf.Console.LogLevel, - LogKeys: bootstrapLoggingCnf.Console.LogKeys, - }, + dblc.Console = &DbConsoleLoggingConfig{ + LogLevel: bootstrapLoggingCnf.Console.LogLevel, + LogKeys: bootstrapLoggingCnf.Console.LogKeys, } } } - return &DbLoggingConfig{} + if bootstrapLoggingCnf.Audit != nil { + dblc.Audit = &DbAuditLoggingConfig{ + Enabled: base.BoolPtr(false), + EnabledEvents: base.DefaultAuditEventIDs, + } + } + return dblc } // MergeDatabaseConfigWithDefaults merges the passed in config onto a DefaultDbConfig which results in returned value From 0054a9e3f520ce1109c6942a0b5ecf7ac454acec Mon Sep 17 00:00:00 2001 From: Ben Brooks Date: Tue, 25 Jun 2024 20:27:06 +0100 Subject: [PATCH 2/3] Show correct 'enabled' state for event in /db/_config/audit --- rest/admin_api.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rest/admin_api.go b/rest/admin_api.go index f492294f27..8b3a7fe457 100644 --- a/rest/admin_api.go +++ b/rest/admin_api.go @@ -673,6 +673,11 @@ func (h *handler) handleGetDbAuditConfig() error { showOnlyFilterable := h.getBoolQuery("filterable") verbose := h.getBoolQuery("verbose") + isEnabledFn := func(id base.AuditID) bool { + _, ok := h.db.Options.LoggingConfig.Audit.EnabledEvents[id] + return ok + } + // TODO: Move to structs events := make(map[string]interface{}, len(base.AuditEvents)) for id, descriptor := range base.AuditEvents { @@ -684,11 +689,11 @@ func (h *handler) handleGetDbAuditConfig() error { events[idStr] = map[string]interface{}{ "name": descriptor.Name, "description": descriptor.Description, - "enabled": descriptor.EnabledByDefault, // TODO: Switch to actual configuration + "enabled": isEnabledFn(id), "filterable": descriptor.FilteringPermitted, } } else { - events[idStr] = descriptor.EnabledByDefault // TODO: Switch to actual configuration + events[idStr] = isEnabledFn(id) } } From f7c0070d578dea44d0b3c042799867bfec7b4475 Mon Sep 17 00:00:00 2001 From: Ben Brooks Date: Wed, 26 Jun 2024 13:06:04 +0100 Subject: [PATCH 3/3] Allow rutime setting of bootstrap logging.audit.enabled from root /_config like the other loggers --- base/logging_config.go | 6 ++++++ rest/admin_api.go | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/base/logging_config.go b/base/logging_config.go index 1a5ec2a5b2..8f2def2606 100644 --- a/base/logging_config.go +++ b/base/logging_config.go @@ -224,6 +224,12 @@ func EnableStatsLogger(enabled bool) { } } +func EnableAuditLogger(enabled bool) { + if auditLogger != nil { + auditLogger.Enabled.Set(enabled) + } +} + // === Used by tests only === func ErrorLoggerIsEnabled() bool { return errorLogger.Enabled.IsTrue() diff --git a/rest/admin_api.go b/rest/admin_api.go index 8b3a7fe457..1459d41b4a 100644 --- a/rest/admin_api.go +++ b/rest/admin_api.go @@ -431,6 +431,7 @@ func (h *handler) handlePutConfig() error { Debug FileLoggerPutConfig `json:"debug,omitempty"` Trace FileLoggerPutConfig `json:"trace,omitempty"` Stats FileLoggerPutConfig `json:"stats,omitempty"` + Audit FileLoggerPutConfig `json:"audit,omitempty"` } `json:"logging"` ReplicationLimit *int `json:"max_concurrent_replications,omitempty"` } @@ -484,6 +485,10 @@ func (h *handler) handlePutConfig() error { base.EnableStatsLogger(*config.Logging.Stats.Enabled) } + if config.Logging.Audit.Enabled != nil { + base.EnableAuditLogger(*config.Logging.Audit.Enabled) + } + if config.ReplicationLimit != nil { if *config.ReplicationLimit < 0 { return base.HTTPErrorf(http.StatusBadRequest, "replication limit cannot be less than 0")