From e8112c1abe217b60c106434336aa6ae6a0dfcdbf Mon Sep 17 00:00:00 2001 From: Catherine Date: Sun, 26 Apr 2026 22:28:19 +0000 Subject: [PATCH] Add a CLI command `-audit-expire` to purge old audit records. This is particularly important with the FS backend, where there isn't necessarily native tooling capable of handling this task correctly (since not every filesystem supports file "birth times", and since restoring data from a backup will reset the "birth time" of audit records to the moment of restoration). --- src/backend.go | 3 +++ src/backend_fs.go | 4 ++++ src/backend_s3.go | 7 +++++++ src/main.go | 34 ++++++++++++++++++++++++++++++++++ src/observe.go | 7 +++++++ 5 files changed, 55 insertions(+) diff --git a/src/backend.go b/src/backend.go index 1df11b3..50d4e83 100644 --- a/src/backend.go +++ b/src/backend.go @@ -159,6 +159,9 @@ type Backend interface { // Retrieve audit record contents for given IDs. GetAuditLogRecords(ctx context.Context, ids iter.Seq2[AuditID, error]) iter.Seq2[*AuditRecord, error] + + // Delete an audit record with a given ID. + ExpireAuditRecord(ctx context.Context, id AuditID) error } func CreateBackend(ctx context.Context, config *StorageConfig) (backend Backend, err error) { diff --git a/src/backend_fs.go b/src/backend_fs.go index 6d54953..78dbe52 100644 --- a/src/backend_fs.go +++ b/src/backend_fs.go @@ -545,3 +545,7 @@ func (fs *FSBackend) GetAuditLogRecords( } } } + +func (fs *FSBackend) ExpireAuditRecord(ctx context.Context, id AuditID) error { + return fs.auditRoot.Remove(id.String()) +} diff --git a/src/backend_s3.go b/src/backend_s3.go index 830025f..dc3c125 100644 --- a/src/backend_s3.go +++ b/src/backend_s3.go @@ -924,3 +924,10 @@ func (s3 *S3Backend) GetAuditLogRecords( } } } + +func (s3 *S3Backend) ExpireAuditRecord(ctx context.Context, id AuditID) error { + logc.Printf(ctx, "s3: expire audit record %s\n", id) + + return s3.client.RemoveObject(ctx, s3.bucket, auditObjectName(id), + minio.RemoveObjectOptions{}) +} diff --git a/src/main.go b/src/main.go index 7d59408..009b18a 100644 --- a/src/main.go +++ b/src/main.go @@ -18,6 +18,7 @@ import ( "path" "runtime/debug" "slices" + "strconv" "strings" "time" @@ -193,6 +194,8 @@ func usage() { "git-pages {-freeze-domain |-unfreeze-domain }\n") fmt.Fprintf(os.Stderr, "(audit) "+ "git-pages {-audit-log|-audit-read |-audit-server [args...]}\n") + fmt.Fprintf(os.Stderr, "(audit) "+ + "git-pages {-audit-expire }\n") fmt.Fprintf(os.Stderr, "(maint) "+ "git-pages {-run-migration |-trace-garbage|-size-histogram {original|stored}}\n") flag.PrintDefaults() @@ -236,6 +239,8 @@ func Main(versionInfo string) { "restore site from contents of audit record `id`") auditServer := flag.String("audit-server", "", "listen for notifications on `endpoint` and spawn a process for each audit event") + auditExpire := flag.String("audit-expire", "", + "expire audit records older than `days` old") runMigration := flag.String("run-migration", "", "run a store `migration` (one of: create-domain-markers)") sizeHistogram := flag.String("size-histogram", "", @@ -265,6 +270,7 @@ func Main(versionInfo string) { *auditRead != "", *auditRollback != "", *auditServer != "", + *auditExpire != "", *runMigration != "", *sizeHistogram != "", *traceGarbage, @@ -559,6 +565,34 @@ func Main(versionInfo string) { serve(ctx, listen(ctx, "audit", *auditServer), ObserveHTTPHandler(processor)) + case *auditExpire != "": + days, err := strconv.ParseInt(*auditExpire, 10, 0) + if err != nil { + logc.Fatalln(ctx, err) + } + + ids := backend.SearchAuditLog(ctx, SearchAuditLogOptions{ + Until: time.Now().AddDate(0, 0, int(-days)), + }) + + count := 0 + for id, err := range ids { + if err != nil { + logc.Fatalln(ctx, err) + continue + } + + err = backend.ExpireAuditRecord(ctx, id) + if err != nil { + logc.Fatalln(ctx, err) + } else { + logc.Printf(ctx, "audit: expired record %s\n", id) + count += 1 + } + } + + logc.Printf(ctx, "audit: expired %d records\n", count) + case *runMigration != "": if err = RunMigration(ctx, *runMigration); err != nil { logc.Fatalln(ctx, err) diff --git a/src/observe.go b/src/observe.go index 074c89b..ec61c61 100644 --- a/src/observe.go +++ b/src/observe.go @@ -397,3 +397,10 @@ func (backend *observedBackend) GetAuditLogRecords( span.Finish() } } + +func (backend *observedBackend) ExpireAuditRecord(ctx context.Context, id AuditID) (err error) { + span, ctx := ObserveFunction(ctx, "ExpireAuditRecord", "audit.id", id) + err = backend.inner.ExpireAuditRecord(ctx, id) + span.Finish() + return +}