feat(PROJ-48): Audit-Log Unveränderbarkeit (Trigger, append-only Logfile, Healthcheck)

DB-Trigger audit_log_immutable verhindert UPDATE/DELETE auf audit_log,
zusätzliches append-only JSON-Lines-Logfile (audit.log_path) als
tamper-evident Backup, neuer Healthcheck-Prüfpunkt in archivmail status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-06-13 19:44:07 +02:00
parent cca27c663a
commit 7e4175923f
7 changed files with 325 additions and 12 deletions
+23
View File
@@ -45,6 +45,7 @@ func runStatus(args []string) {
checkPostgres(cfg),
checkManticore(cfg),
checkStorage(cfg),
checkAuditLog(cfg),
}
allOK := true
@@ -172,6 +173,28 @@ func checkStorage(cfg *config.Config) checkResult {
return checkResult{Name: "Storage", OK: ok, Detail: detail}
}
// checkAuditLog verifies that the append-only audit log file (PROJ-48) exists
// and is writable. It performs a non-destructive O_APPEND open without writing
// any bytes, so the immutability of existing content is not affected.
func checkAuditLog(cfg *config.Config) checkResult {
path := cfg.Audit.ResolvedLogPath()
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0o640)
if err != nil {
if os.IsNotExist(err) {
return checkResult{Name: "Audit-Log", OK: false, Detail: fmt.Sprintf("%s existiert nicht", path)}
}
return checkResult{Name: "Audit-Log", OK: false, Detail: fmt.Sprintf("%s nicht beschreibbar: %v", path, err)}
}
defer f.Close()
detail := path + ", beschreibbar"
if fi, statErr := f.Stat(); statErr == nil {
detail = fmt.Sprintf("%s, beschreibbar, %s", path, formatBytes(uint64(fi.Size())))
}
return checkResult{Name: "Audit-Log", OK: true, Detail: detail}
}
func formatBytes(b uint64) string {
const unit = 1024
if b < unit {
+1 -1
View File
@@ -248,7 +248,7 @@ func main() {
defer users.Close()
// Audit log
audlog, err := audit.New(cfg.Database.DSN(), cfg.Audit.LogPath, logger)
audlog, err := audit.New(cfg.Database.DSN(), cfg.Audit.ResolvedLogPath(), logger)
if err != nil {
logger.Error("audit init failed", "err", err)
os.Exit(1)