From 6c2f34c54d9084fd29935b8c43e59fec9af439b4 Mon Sep 17 00:00:00 2001 From: miyuko Date: Sun, 20 Sep 2026 09:52:50 +0100 Subject: [PATCH] Allow specifying a reason for a mutation done via the CLI. The reason is appended to the audit record and is surfaced in the log. --- src/audit.go | 19 +++++++++++++++++++ src/main.go | 31 +++++++++++++------------------ src/schema.pb.go | 13 +++++++++++-- src/schema.proto | 1 + 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/audit.go b/src/audit.go index 4dc75fe..3a63e7f 100644 --- a/src/audit.go +++ b/src/audit.go @@ -50,6 +50,22 @@ func GetPrincipal(ctx context.Context) *Principal { return nil } +type reasonKey struct{} + +var ReasonKey = reasonKey{} + +func WithReason(ctx context.Context) context.Context { + reason := new(string) + return context.WithValue(ctx, ReasonKey, reason) +} + +func GetReason(ctx context.Context) *string { + if reason, ok := ctx.Value(ReasonKey).(*string); ok { + return reason + } + return nil +} + var AuditSnowflakeStartTime = time.Date(2025, 12, 1, 0, 0, 0, 0, time.UTC) type AuditID int64 @@ -300,6 +316,9 @@ func (audited *auditedBackend) appendNewAuditRecord(ctx context.Context, record record.Id = proto.Int64(int64(id)) record.Timestamp = timestamppb.Now() record.Principal = GetPrincipal(ctx) + if reason := GetReason(ctx); reason != nil && *reason != "" { + record.Reason = GetReason(ctx) + } err = audited.Backend.AppendAuditLog(ctx, id, record) if err != nil { diff --git a/src/main.go b/src/main.go index 12bde6c..c278d18 100644 --- a/src/main.go +++ b/src/main.go @@ -280,6 +280,8 @@ func Main(versionInfo string) { "display aggregate storage used per domain (argument is output mode and one of: text, json)") traceGarbage := flag.Bool("trace-garbage", false, "estimate total size of unreachable blobs") + reason := flag.String("reason", "", + "specify a reason for administrative operations in the audit log") dryRun := flag.Bool("dry-run", false, "print what would be performed instead of executing it") version := flag.Bool("version", false, @@ -386,6 +388,12 @@ func Main(versionInfo string) { if existenceCache, err = CreateExistenceCache(ctx); err != nil { logc.Fatalln(ctx, err) } + + ctx = WithPrincipal(ctx) + GetPrincipal(ctx).CliAdmin = proto.Bool(true) + + ctx = WithReason(ctx) + *GetReason(ctx) = *reason } switch { @@ -451,9 +459,6 @@ func Main(versionInfo string) { } case *updateSite != "": - ctx = WithPrincipal(ctx) - GetPrincipal(ctx).CliAdmin = proto.Bool(true) - if flag.NArg() != 1 { logc.Fatalln(ctx, "update source must be provided as the argument") } @@ -515,9 +520,6 @@ func Main(versionInfo string) { } case *deleteSite != "": - ctx = WithPrincipal(ctx) - GetPrincipal(ctx).CliAdmin = proto.Bool(true) - webRoot := webRootArg(*deleteSite) err := backend.DeleteManifest(ctx, webRoot, ModifyManifestOptions{}) if err != nil { @@ -527,9 +529,6 @@ func Main(versionInfo string) { logc.Println(ctx, "deleted") case *freezeDomain != "" || *unfreezeDomain != "": - ctx = WithPrincipal(ctx) - GetPrincipal(ctx).CliAdmin = proto.Bool(true) - var domain string var freeze bool if *freezeDomain != "" { @@ -553,9 +552,6 @@ func Main(versionInfo string) { } case *purgeDomain != "": - ctx = WithPrincipal(ctx) - GetPrincipal(ctx).CliAdmin = proto.Bool(true) - purgeCount := 0 for metadata, err := range backend.EnumerateManifests(ctx) { if err != nil { @@ -606,6 +602,11 @@ func Main(versionInfo string) { parts = append(parts, color.HiGreenString("%s", record.DescribePrincipal()), ) + if record.GetReason() != "" { + parts = append(parts, + color.HiCyanString("%q", record.GetReason()), + ) + } if record.IsDetached() { parts = append(parts, color.HiYellowString("(detached)"), @@ -630,9 +631,6 @@ func Main(versionInfo string) { } case *auditRollback != "": - ctx = WithPrincipal(ctx) - GetPrincipal(ctx).CliAdmin = proto.Bool(true) - id, err := ParseAuditID(*auditRollback) if err != nil { logc.Fatalln(ctx, err) @@ -736,9 +734,6 @@ func Main(versionInfo string) { logc.Printf(ctx, "audit: expired %d records\n", count) case *expireSites: - ctx = WithPrincipal(ctx) - GetPrincipal(ctx).CliAdmin = proto.Bool(true) - if !config.Feature("expiration") { logc.Fatalf(ctx, "expire: feature disabled") } diff --git a/src/schema.pb.go b/src/schema.pb.go index 5b79494..d245b07 100644 --- a/src/schema.pb.go +++ b/src/schema.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 +// protoc-gen-go v1.36.9 // protoc v6.30.2 // source: schema.proto @@ -783,6 +783,7 @@ type AuditRecord struct { Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp" json:"timestamp,omitempty"` Event *AuditEvent `protobuf:"varint,3,opt,name=event,enum=AuditEvent" json:"event,omitempty"` Principal *Principal `protobuf:"bytes,4,opt,name=principal" json:"principal,omitempty"` + Reason *string `protobuf:"bytes,13,opt,name=reason" json:"reason,omitempty"` // Affected resource. Domain *string `protobuf:"bytes,10,opt,name=domain" json:"domain,omitempty"` Project *string `protobuf:"bytes,11,opt,name=project" json:"project,omitempty"` // only for `*Manifest` events @@ -850,6 +851,13 @@ func (x *AuditRecord) GetPrincipal() *Principal { return nil } +func (x *AuditRecord) GetReason() string { + if x != nil && x.Reason != nil { + return *x.Reason + } + return "" +} + func (x *AuditRecord) GetDomain() string { if x != nil && x.Domain != nil { return *x.Domain @@ -1054,13 +1062,14 @@ const file_schema_proto_rawDesc = "" + "\bproblems\x18\a \x03(\v2\b.ProblemR\bproblems\x1aC\n" + "\rContentsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x1c\n" + - "\x05value\x18\x02 \x01(\v2\x06.EntryR\x05value:\x028\x01\"\xfd\x01\n" + + "\x05value\x18\x02 \x01(\v2\x06.EntryR\x05value:\x028\x01\"\x95\x02\n" + "\vAuditRecord\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x128\n" + "\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12!\n" + "\x05event\x18\x03 \x01(\x0e2\v.AuditEventR\x05event\x12(\n" + "\tprincipal\x18\x04 \x01(\v2\n" + ".PrincipalR\tprincipal\x12\x16\n" + + "\x06reason\x18\r \x01(\tR\x06reason\x12\x16\n" + "\x06domain\x18\n" + " \x01(\tR\x06domain\x12\x18\n" + "\aproject\x18\v \x01(\tR\aproject\x12%\n" + diff --git a/src/schema.proto b/src/schema.proto index 379b4b3..0b909d9 100644 --- a/src/schema.proto +++ b/src/schema.proto @@ -136,6 +136,7 @@ message AuditRecord { google.protobuf.Timestamp timestamp = 2; AuditEvent event = 3; Principal principal = 4; + string reason = 13; // Affected resource. string domain = 10;