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 +}