diff --git a/src/audit.go b/src/audit.go index 63a8ffe..b170e8a 100644 --- a/src/audit.go +++ b/src/audit.go @@ -13,6 +13,7 @@ import ( "github.com/kankanreno/go-snowflake" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) @@ -72,20 +73,49 @@ func (id AuditID) CompareTime(when time.Time) int { return cmp.Compare(idMillis, whenMillis) } -func EncodeAuditRecord(auditRecord *AuditRecord) (data []byte) { - data, err := proto.MarshalOptions{Deterministic: true}.Marshal(auditRecord) +func EncodeAuditRecord(record *AuditRecord) (data []byte) { + data, err := proto.MarshalOptions{Deterministic: true}.Marshal(record) if err != nil { panic(err) } return } -func DecodeAuditRecord(data []byte) (auditRecord *AuditRecord, err error) { - auditRecord = &AuditRecord{} - err = proto.Unmarshal(data, auditRecord) +func DecodeAuditRecord(data []byte) (record *AuditRecord, err error) { + record = &AuditRecord{} + err = proto.Unmarshal(data, record) return } +type AuditRecordScope int + +const ( + AuditRecordComplete AuditRecordScope = iota + AuditRecordNoManifest +) + +func AuditRecordJSON(record *AuditRecord, scope AuditRecordScope) []byte { + switch scope { + case AuditRecordComplete: + // as-is + case AuditRecordNoManifest: + // trim the manifest + newRecord := &AuditRecord{} + proto.Merge(newRecord, record) + newRecord.Manifest = nil + record = newRecord + } + + json, err := protojson.MarshalOptions{ + Multiline: true, + EmitDefaultValues: true, + }.Marshal(record) + if err != nil { + panic(err) + } + return json +} + type auditedBackend struct { Backend } diff --git a/src/main.go b/src/main.go index 5c304a7..71ffa36 100644 --- a/src/main.go +++ b/src/main.go @@ -174,6 +174,8 @@ func usage() { "git-pages [-config |-no-config]\n") fmt.Fprintf(os.Stderr, "(admin) "+ "git-pages {-run-migration |-freeze-domain |-unfreeze-domain }\n") + fmt.Fprintf(os.Stderr, "(audit) "+ + "git-pages {-audit-read }\n") fmt.Fprintf(os.Stderr, "(info) "+ "git-pages {-print-config-env-vars|-print-config}\n") fmt.Fprintf(os.Stderr, "(cli) "+ @@ -207,32 +209,28 @@ func Main() { "prevent any site uploads to a given `domain`") unfreezeDomain := flag.String("unfreeze-domain", "", "allow site uploads to a `domain` again after it has been frozen") + auditRead := flag.String("audit-read", "", + "extract contents of audit record `id` to files '-*'") flag.Parse() var cliOperations int - if *runMigration != "" { - cliOperations += 1 - } - if *getBlob != "" { - cliOperations += 1 - } - if *getManifest != "" { - cliOperations += 1 - } - if *getArchive != "" { - cliOperations += 1 - } - if *updateSite != "" { - cliOperations += 1 - } - if *freezeDomain != "" { - cliOperations += 1 - } - if *unfreezeDomain != "" { - cliOperations += 1 + for _, selected := range []bool{ + *runMigration != "", + *getBlob != "", + *getManifest != "", + *getArchive != "", + *updateSite != "", + *freezeDomain != "", + *unfreezeDomain != "", + *auditRead != "", + } { + if selected { + cliOperations++ + } } if cliOperations > 1 { - logc.Fatalln(ctx, "-get-blob, -get-manifest, -get-archive, -update-site, -freeze, and -unfreeze are mutually exclusive") + logc.Fatalln(ctx, "-get-blob, -get-manifest, -get-archive, -update-site, "+ + "-freeze, -unfreeze, -audit-log, and -audit-read are mutually exclusive") } if *configTomlPath != "" && *noConfig { @@ -301,7 +299,7 @@ func Main() { if err != nil { logc.Fatalln(ctx, err) } - fmt.Fprintln(fileOutputArg(), ManifestDebugJSON(manifest)) + fmt.Fprintln(fileOutputArg(), string(ManifestJSON(manifest))) case *getArchive != "": if backend, err = CreateBackend(&config.Storage); err != nil { @@ -402,9 +400,37 @@ func Main() { logc.Fatalln(ctx, err) } if freeze { - log.Println("frozen") + logc.Println(ctx, "frozen") } else { - log.Println("thawed") + logc.Println(ctx, "thawed") + } + + case *auditRead != "": + if backend, err = CreateBackend(&config.Storage); err != nil { + logc.Fatalln(ctx, err) + } + + id, err := ParseAuditID(*auditRead) + if err != nil { + logc.Fatalln(ctx, err) + } + + record, err := backend.QueryAuditLog(ctx, id) + if err != nil { + logc.Fatalln(ctx, err) + } + + errEvent := os.WriteFile(fmt.Sprintf("%s-event.json", id), + AuditRecordJSON(record, AuditRecordNoManifest), 0o400) + errManifest := os.WriteFile(fmt.Sprintf("%s-manifest.json", id), + ManifestJSON(record.Manifest), 0o400) + fileArchive, errArchive := os.OpenFile(fmt.Sprintf("%s-archive.tar", id), + os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o400) + if err = errors.Join(errEvent, errManifest, errArchive); err != nil { + logc.Fatalln(ctx, err) + } + if err = CollectTar(ctx, fileArchive, record.Manifest, ManifestMetadata{}); err != nil { + logc.Fatalln(ctx, err) } default: diff --git a/src/manifest.go b/src/manifest.go index ee9d543..ea1ae2f 100644 --- a/src/manifest.go +++ b/src/manifest.go @@ -139,15 +139,15 @@ func GetProblemReport(manifest *Manifest) []string { return report } -func ManifestDebugJSON(manifest *Manifest) string { - result, err := protojson.MarshalOptions{ +func ManifestJSON(manifest *Manifest) []byte { + json, err := protojson.MarshalOptions{ Multiline: true, EmitDefaultValues: true, }.Marshal(manifest) if err != nil { panic(err) } - return string(result) + return json } var ErrSymlinkLoop = errors.New("symbolic link loop") diff --git a/src/pages.go b/src/pages.go index 216f322..4278ac2 100644 --- a/src/pages.go +++ b/src/pages.go @@ -188,7 +188,7 @@ func getPage(w http.ResponseWriter, r *http.Request) error { w.Header().Add("Last-Modified", lastModified) w.Header().Add("ETag", fmt.Sprintf("\"%s-manifest\"", metadata.ETag)) w.WriteHeader(http.StatusOK) - w.Write([]byte(ManifestDebugJSON(manifest))) + w.Write(ManifestJSON(manifest)) return nil case metadataPath == "archive.tar" && config.Feature("archive-site"): diff --git a/src/update.go b/src/update.go index 3e1f454..4aa8276 100644 --- a/src/update.go +++ b/src/update.go @@ -74,7 +74,7 @@ func Update( status = "unchanged" } if storedManifest.Commit != nil { - logc.Printf(ctx, "update %s ok: %s %s", webRoot, status, *storedManifest.Commit) + logc.Printf(ctx, "update %s ok: %s %s", webRoot, *storedManifest.Commit, status) } else { logc.Printf(ctx, "update %s ok: %s", webRoot, status) }