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).
This commit is contained in:
Catherine
2026-04-26 22:28:19 +00:00
parent b0a674abf4
commit e8112c1abe
5 changed files with 55 additions and 0 deletions

View File

@@ -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) {

View File

@@ -545,3 +545,7 @@ func (fs *FSBackend) GetAuditLogRecords(
}
}
}
func (fs *FSBackend) ExpireAuditRecord(ctx context.Context, id AuditID) error {
return fs.auditRoot.Remove(id.String())
}

View File

@@ -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{})
}

View File

@@ -18,6 +18,7 @@ import (
"path"
"runtime/debug"
"slices"
"strconv"
"strings"
"time"
@@ -193,6 +194,8 @@ func usage() {
"git-pages {-freeze-domain <domain>|-unfreeze-domain <domain>}\n")
fmt.Fprintf(os.Stderr, "(audit) "+
"git-pages {-audit-log|-audit-read <id>|-audit-server <endpoint> <program> [args...]}\n")
fmt.Fprintf(os.Stderr, "(audit) "+
"git-pages {-audit-expire <days>}\n")
fmt.Fprintf(os.Stderr, "(maint) "+
"git-pages {-run-migration <name>|-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)

View File

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