Implement -audit-read option.

This commit is contained in:
Catherine
2025-12-04 15:25:28 +00:00
parent 8f0712b3ad
commit 4161013fc0
5 changed files with 90 additions and 34 deletions

View File

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

View File

@@ -174,6 +174,8 @@ func usage() {
"git-pages [-config <file>|-no-config]\n")
fmt.Fprintf(os.Stderr, "(admin) "+
"git-pages {-run-migration <name>|-freeze-domain <domain>|-unfreeze-domain <domain>}\n")
fmt.Fprintf(os.Stderr, "(audit) "+
"git-pages {-audit-read <id>}\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 '<id>-*'")
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:

View File

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

View File

@@ -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"):

View File

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