IR-20261112: feat(monitoring): ship Forgejo access logs to a 45d audit tenant (R39) #953
Open
pi-landing
wants to merge 10 commits from
plane/R39-forgejo-access-log into live
pull from: plane/R39-forgejo-access-log
merge into: Homelab:live
Homelab:main
Homelab:live
Homelab:sso/zitadel-guest-users
Homelab:sso/argocd-readonly-groups
Homelab:lr-20261111-025/amendment-4
Homelab:ld/IR-20261112-phase1guard
Homelab:ld/IR-20261112-kimipool
Homelab:lr-20261111-017-a24
Homelab:plane/IR-20261112-relayalert
Homelab:plane/IR-20261112-sbxdisk
Homelab:lr-20261111-025/seat-template
Homelab:plane/IR-20261112-mgmtdns
Homelab:plane/IR-20261112-r10-openbao-loki
Homelab:plane/f-r2-d6-sbx-reviewer-out
Homelab:plane/R39-forgejo-access-log-sink
Homelab:plane/R39-forgejo-access-log-auth
Homelab:plane/IR-20261111-extdel-v2
Homelab:plane/f-r2-d6-gits-reviewer-removal
Homelab:plane/R1-localbin
Homelab:plane/IR-20261111-r25offdisk
Homelab:plane/IR-20261112-runners8
Homelab:plane/EXTDEL-4918-forgejo-ext
Homelab:plane/IR-20261112-npmcache
Homelab:lr-20261111-016-launchcap
Homelab:plane/IR-20261111-runners10
Homelab:plane/IR-20261111-c8brief
Homelab:IR-20261111-runnercap
Homelab:plane/IR-20261111-baohelper
Homelab:plane/IR-20261111-gitsemu
Homelab:plane/IR-20261111-runnerlock
Homelab:plane/IR-20261111-c8grants
Homelab:lr-20261111-017-n9
Homelab:lr-20261111-025/seatpost
Homelab:ld/LR-20261111-024-seat-ban
Homelab:lr-20261111-017-seatlookup
Homelab:spec-014-skill-sync
Homelab:ld-spec-009/relay-fixforward
Homelab:ld/spec-016-launch-cap
Homelab:ld-spec-009/relay-a3
Homelab:plane/LR-20261111-018-seat-posting
Homelab:plane/LR-20261111-012-sbx-lab-transcripts
Homelab:spec-010-fit2
Homelab:ld-spec-009/relay
Homelab:plane/HOMELAB-C3C-epic-supervisor
Homelab:plane/dircfg-c3b-work-pointer
Homelab:plane/HOMELAB-2479-sbx-ffmpeg
Homelab:plane/dircfg-c5-epic-supervisor
Homelab:plane/dircfg-c4-opening
Homelab:plane/dircfg-c3-work-md
Homelab:plane/dircfg-c2-brief-metodo
Homelab:plane/dircfg-c6-sbx-sendrast-skills
Homelab:plane/HOMELAB-2479-zitadel-display-name
Homelab:plane/dircfg-c1-director-gitinit
Homelab:plane/HOMELAB-2479-sbx-sendrast-9gi
Homelab:plane/HOMELAB-2479-invite-list-reader
Homelab:plane/HOMELAB-2479-sbx-lab-wk02-affinity
Homelab:plane/HOMELAB-2479-wk02-sbx-lab-limit
Homelab:plane/HOMELAB-2479-zitadel-user-drift
Homelab:plane/HOMELAB-2479-zitadel-h2c
Homelab:spec-008-b-sbxlab-pin
Homelab:spec-008-a-sbxfleet-base-bump
Homelab:spec-007-ld-implement-skill
Homelab:archive/plane/HOMELAB-2658-omniroute-containment-wip-20261110
Homelab:archive/plane/HOMELAB-2446-wk01-requests-wip-20261110
Homelab:plane/HOMELAB-2479-gits-fixture-vendor
Homelab:ld-spec-004
Homelab:plane/HOMELAB-2479-plane-smtp-chart
Homelab:ld-spec-003
Homelab:plane/HOMELAB-2479-aaron-site-zitadel
Homelab:plane/HOMELAB-2479-runner-requests
Homelab:plane/ld-stopnow-gits-pin
Homelab:plane/ld-stopnow-alias-retire
Homelab:plane/HOMELAB-2479-sbx-gits-probe-tmp
Homelab:plane/HOMELAB-2479-gits-toolcache
Homelab:plane/ld-stopnow-sbxlab-pin
Homelab:plane/ld-stopnow-sbxfleet-base2
Homelab:plane/ld-stopnow-sbxfleet-base
Homelab:plane/ld-stopnow-item2-skill-rule
Homelab:plane/HOMELAB-88-cm-ssa-syncopt
Homelab:plane/HOMELAB-88-sandbox-telemetry
Homelab:plane/HOMELAB-88-sbx-gits-16g
Homelab:plane/HOMELAB-88-whisper-cuda
Homelab:plane/HOMELAB-2479-secretscan-fixture-allowlist
Homelab:plane/HOMELAB-2479-registry-backup
Homelab:plane/HOMELAB-2682-sandbox-init-tmpfix
Homelab:plane/HOMELAB-2479-fix-port-gate
Homelab:plane/HOMELAB-2479-emulator-gate
Homelab:plane/HOMELAB-2479-fixture-resync
Homelab:plane/HOMELAB-2682-forgejo-ext-pin-cd50fbc6
Homelab:plane/HOMELAB-2479-openbao-raft-verify
Homelab:plane/HOMELAB-2682-forgejo-ext-pin-96437d4a
Homelab:plane/HOMELAB-2479-moore-disk-alert
Homelab:plane/HOMELAB-2479-harbor-pin-secrets
Homelab:plane/HOMELAB-2682-relief-threshold-p1
Homelab:archive/plane/HOMELAB-2682-relief-threshold-20261109
Homelab:plane/HOMELAB-2682-relief-threshold
Homelab:plane/HOMELAB-2682-sbx-gits-seed-pin
Homelab:plane/HOMELAB-2479-ci-trim
Homelab:plane/HOMELAB-2682-ok0
Homelab:plane/HOMELAB-2479-liveness-window-120s
Homelab:plane/HOMELAB-2682-seed-bump
Homelab:plane/HOMELAB-2682-transcripts
Homelab:plane/HOMELAB-2682-pin-bump
Homelab:plane/HOMELAB-2682-declaration
Homelab:plane/HOMELAB-2682-gradle-daemon-gen
Homelab:plane/HOMELAB-2682-tools-pin
Homelab:plane/HOMELAB-2479-harbor-db-backup
Homelab:plane/HOMELAB-2682-suite-relpath
Homelab:plane/HOMELAB-2682-revert-859-delivery
Homelab:plane/HOMELAB-2479-volume-floor
Homelab:plane/HOMELAB-2682-check-anchor
Homelab:dev/cockpit
Homelab:plane/HOMELAB-2479-nic-roster
Homelab:plane/HOMELAB-2479-nic-roster-rehome
Homelab:ld-1/truth-fixes
Homelab:ld-1/remove-open-chat
Homelab:ld-1/gate-cut
Homelab:plane/HOMELAB-2479-nic-critical
Homelab:plane/HOMELAB-2479-startup-probe
Homelab:ld-1/dev-route-1
Homelab:plane/HOMELAB-2479-factory-token
Homelab:plane/HOMELAB-2682-evidence-fixes
Homelab:ld-1/gate-dev-route
Homelab:plane/HOMELAB-2479-sbx-resources
Homelab:plane/HOMELAB-2479-factory-pr-series
Homelab:plane/HOMELAB-2682-preclean-optout
Homelab:plane/HOMELAB-2479-measurement
Homelab:plane/sbx-gits-resources-20261105
Homelab:plane/sbx-lab-resources-20261105
Homelab:plane/HOMELAB-2682-emu-proof
Homelab:plane/HOMELAB-2682-sbxgits-pin
Homelab:plane/HOMELAB-2682-ssh-image
Homelab:archive/plane/HOMELAB-2694-sandbox-mem-page-20261110
Homelab:plane/HOMELAB-2694-sandbox-mem-page
Homelab:plane/HOMELAB-2537-jvm-proof-mode
Homelab:plane/HOMELAB-2694-sbx-lab-memory
Homelab:plane/HOMELAB-2694-psi-memory-alert
Homelab:plane/HOMELAB-2682-lab-env
Homelab:plane/HOMELAB-2694-sandbox-oom-alerts
Homelab:plane/HOMELAB-2682-quote-rule
Homelab:plane/HOMELAB-2481-sandbox-log-reader
Homelab:plane/HOMELAB-2682-sbx-lab-ext
Homelab:plane/HOMELAB-2688-longhorn-alerts
Homelab:plane/HOMELAB-2682-sbx-lab-idents
Homelab:plane/HOMELAB-2682-ct-skills
Homelab:plane/GITS-492-tablet-build-delivery
Homelab:epic/ld-1
Homelab:plane/HOMELAB-2680-sandbox-redact
Homelab:plane/HOMELAB-2683-sbx-lab-pin
Homelab:plane/HOMELAB-2685-outage-alerts
Homelab:plane/HOMELAB-2683-gcc-sbx-lab
Homelab:plane/HOMELAB-2658-omniroute-containment
Homelab:plane/HOMELAB-2440-sbx-lab-cpu
Homelab:plane/HOMELAB-2673-ci-pending-paging
Homelab:plane/HOMELAB-2675-sbx-gits-alerts
Homelab:plane/HOMELAB-2440-sbx-lab-request
Homelab:plane/HOMELAB-2445-sbx-lab-offpve
Homelab:plane/HOMELAB-2445-wk06-requests
Homelab:plane/HOMELAB-2658-liveness
Homelab:plane/HOMELAB-2655-sbx-sendrast-pin-54c78dd
Homelab:plane/HOMELAB-2655-sbx-sendrast-t7
Homelab:plane/HOMELAB-2626-h9-voz-sandbox
Homelab:plane/HOMELAB-2636-android-emu-b
Homelab:plane/GITS-401-drain-envelope
Homelab:plane/HOMELAB-2492-volume-attach-alerts
Homelab:plane/HOMELAB-2626-h8e-skill-hipervisor
Homelab:plane/HOMELAB-2488-io-psi-alerts
Homelab:plane/HOMELAB-2626-h8d-skills-pi
Homelab:plane/HOMELAB-2626-h8c-skills-pi-pulido
Homelab:plane/HOMELAB-2626-h8b-skills-pi-fixes
Homelab:plane/HOMELAB-2489-longhorn-tgtd-alerts
Homelab:plane/HOMELAB-2626-h8-skills-pi
Homelab:plane/HOMELAB-2618-h3-checklist
Homelab:plane/HOMELAB-2547-sbx-sendrast-pin7
Homelab:plane/HOMELAB-2618-h7-sbx-sendrast-kubectl
Homelab:plane/HOMELAB-2618-h6-sbx-lab-viewer
Homelab:plane/HOMELAB-2547-sbx-sendrast-pin6
Homelab:plane/HOMELAB-2614-etcd-leader-alert
Homelab:plane/HOMELAB-2547-sbx-sendrast-r59
Homelab:plane/HOMELAB-2618-h4-ci-reader
Homelab:plane/HOMELAB-2618-h2v3-sbx-sendrast
Homelab:plane/HOMELAB-2546-sbx-sendrast-pin5
Homelab:plane/HOMELAB-2547-sbx-sendrast-pidir
Homelab:plane/HOMELAB-2547-sbx-sendrast-pooldir
Homelab:plane/HOMELAB-2618-h5c-bump
Homelab:plane/HOMELAB-2547-sbx-sendrast-credhelper
Homelab:plane/HOMELAB-2618-h1fix-kustomize
Homelab:plane/HOMELAB-2618-h5-sbx-fleet
Homelab:plane/HOMELAB-2618-h1-fuente-unica
Homelab:plane/HOMELAB-2618-h2-sendrast
Homelab:plane/HOMELAB-2547-sbx-sendrast-rbac
Homelab:plane/HOMELAB-2618-h0-ondelete
Homelab:plane/HOMELAB-2446-wk01-requests
Homelab:plane/HOMELAB-2570-idle-liveness
Homelab:plane/HOMELAB-2511-step-alert
Homelab:plane/HOMELAB-2546-sbx-sendrast-pin4
Homelab:plane/HOMELAB-2546-sbx-sendrast-health2
Homelab:plane/HOMELAB-2546-sbx-sendrast-pin3
Homelab:plane/HOMELAB-2546-sbx-sendrast-health
Homelab:plane/HOMELAB-2546-sbx-sendrast-gotoolchain
Homelab:plane/HOMELAB-2546-sbx-sendrast-pin2
Homelab:plane/HOMELAB-2546-sbx-sendrast-r43
Homelab:plane/HOMELAB-2445-descheduler-repause
Homelab:plane/HOMELAB-2546-sbx-sendrast-psa
Homelab:plane/HOMELAB-2537-sbx-gits-post-humo
Homelab:plane/HOMELAB-2546-sbx-sendrast-wk03
Homelab:plane/HOMELAB-2537-sbx-gits-fix-perm
Homelab:plane/HOMELAB-2546-sbx-sendrast-pin
Homelab:plane/HOMELAB-2546-sbx-sendrast-platform
Homelab:plane/HOMELAB-2546-sbx-sendrast
Homelab:plane/HOMELAB-2537-sbx-gits
Homelab:plane/HOMELAB-2481-dind-gc-tightening
Homelab:archive/plane/HOMELAB-2481-node-local-dind-20261110
Homelab:plane/HOMELAB-2481-node-local-dind
Homelab:plane/HOMELAB-2535-sbx-lab-gitops
Homelab:plane/HOMELAB-2483-volume-usage-alert
Homelab:plane/HOMELAB-2482-slice-B-prepare
Homelab:plane/HOMELAB-2482-slice-C-ephemeral-ceilings
Homelab:plane/HOMELAB-2483-retention
Homelab:plane/HOMELAB-2483-alerts
Homelab:plane/GITS-395-emulator-adb-v2
Homelab:plane/GITS-395-emulator-adb
Homelab:plane/HOMELAB-2472-dor-newtest
Homelab:plane/GITS-403-emu-env-drift
Homelab:plane/HOMELAB-2467-gitleaks-scan
Homelab:plane/GITS-401-dind-digest
Homelab:plane/GITS-371-android-emu-prestop-command
Homelab:plane/GITS-371-android-emu-dind-drain
Homelab:plane/GITS-371-android-emu-shutdown-grace
Homelab:plane/HOMELAB-2196-speed-batch
Homelab:plane/HOMELAB-2442-android-ci-raise
Homelab:plane/HOMELAB-2442-runner-pool
Homelab:plane/HOMELAB-2442-mem-rightsize
Homelab:plane/HOMELAB-2443-ldpush-memory
Homelab:plane/HOMELAB-2444-fpr
Homelab:plane/GITS-364-emu-drift-fix
Homelab:plane/GITS-362-release-upload-mount
Homelab:plane/HOMELAB-2432-alerts
Homelab:plane/HOMELAB-1945-landing-step
Homelab:plane/HOMELAB-2421-dashboard
Homelab:plane/HOMELAB-2417-baseline
Homelab:plane/HOMELAB-2418-runners
Homelab:plane/HOMELAB-2415-budget-kill-scope
Homelab:plane/GITS-33-e0r-t3-android-release-lane
Homelab:plane/HOMELAB-2412-pernode-cap
Homelab:plane/GITS-281-android-ci-pin
Homelab:plane/HOMELAB-2407-gate-creation-exempt
Homelab:plane/GITS-281-android-ci-image-emulator
Homelab:plane/HOMELAB-2406-panel-artifact
Homelab:plane/GITS-22-e0-t13
Homelab:plane/HOMELAB-2402-node-keys
Homelab:plane/GITS-21-e0-t12
Homelab:plane/GITS-19-e0-t10
Homelab:plane/GITS-18-e0-t9
Homelab:plane/HOMELAB-1941-b13-slice2
Homelab:plane/HOMELAB-2400-active-rows
Homelab:plane/HOMELAB-2395-trim-manage-key
Homelab:plane/HOMELAB-1944-panel-gateway
Homelab:plane/HOMELAB-2392-diff-fetch
Homelab:plane/HOMELAB-1940-caps-accounting
Homelab:plane/HOMELAB-2390-diff-git
Homelab:plane/HOMELAB-1944-panel-runtime
Homelab:plane/HOMELAB-2329-gate-decision
Homelab:plane/HOMELAB-2381-claim-retry
Homelab:plane/record-sql-test-fix
Homelab:plane/HOMELAB-2373-tailscale-1.104.4
Homelab:plane/HOMELAB-2357-resume-inflight
Homelab:plane/HOMELAB-2372-fail-closed
Homelab:plane/HOMELAB-2278-freshness
Homelab:plane/HOMELAB-2356-runtime-verdicts
Homelab:plane/HOMELAB-2281-objectlock
Homelab:plane/HOMELAB-2340-pane-loss
Homelab:plane/HOMELAB-1943-frozen-charters
Homelab:plane/HOMELAB-2348-replay-refusal
Homelab:plane/HOMELAB-1944-review-panel
Homelab:plane/HOMELAB-2341-reauthorize-anchor
Homelab:plane/HOMELAB-1942-workflow-knobs
Homelab:plane/HOMELAB-2327-signing-dispatch
Homelab:plane/HOMELAB-2280-pacing
Homelab:plane/HOMELAB-2195-f9-dor
Homelab:plane/HOMELAB-2249-orphan-alert
Homelab:plane/HOMELAB-2311-runbook
Homelab:plane/HOMELAB-2302-argocd-reader
Homelab:plane/HOMELAB-2273-etcd-snapshot-b2
Homelab:plane/HOMELAB-2298-eso-repo-password
Homelab:plane/HOMELAB-2274-raft-b2
Homelab:plane/HOMELAB-2249-alerts
Homelab:plane/HOMELAB-2258-xhost-sc
Homelab:plane/HOMELAB-2259-strict-local
Homelab:plane/HOMELAB-2195-f4-workflow-id
Homelab:plane/HOMELAB-2260-node-labels
Homelab:plane/HOMELAB-2257-zone-labels
Homelab:plane/HOMELAB-2195-b2-drain-rule
Homelab:plane/HOMELAB-2195-b3-records
Homelab:plane/HOMELAB-2195-b5-fragility
Homelab:plane/HOMELAB-2195-b1-dor-pickup
Homelab:plane/HOMELAB-1939-bumpupdate
Homelab:plane/HOMELAB-1939-mergedecode
Homelab:plane/HOMELAB-1939-agentwait
Homelab:plane/HOMELAB-2240-lane
Homelab:plane/HOMELAB-2246-runner-1rep
Homelab:plane/HOMELAB-1980-b11-pane-hold
Homelab:plane/HOMELAB-2235-argocd-memory
Homelab:plane/HOMELAB-2232-proxmox-module
Homelab:plane/HOMELAB-gitops-atlantis
Homelab:plane/HOMELAB-2210-docker-config-seed2
Homelab:plane/HOMELAB-2210-runner-pin
Homelab:plane/HOMELAB-2210-runner-buildx
Homelab:plane/HOMELAB-1932-ld-runners
Homelab:plane/HOMELAB-2200-snapshot-retain
Homelab:plane/HOMELAB-2199-cnpg-backup-stale
Homelab:plane/HOMELAB-2201-runner-gc
Homelab:plane/HOMELAB-1926-plane-rabbitmq-resources
Homelab:plane/HOMELAB-1905-cnpg-ha-docs
Homelab:plane/HOMELAB-1906-cronjob-ttl-rules
Homelab:plane/HOMELAB-1904-longhorn-fixes
Homelab:plane/HOMELAB-1908-annotation-fix
Homelab:plane/HOMELAB-1904-cnpg-ha-rules
Homelab:plane/HOMELAB-1904-recyclarr-netpol
Homelab:plane/HOMELAB-1904-cpu-rightsize
Homelab:plane/HOMELAB-1893-alertintake-plane-storm
Homelab:plane/HOMELAB-1878-retention
Homelab:epic/LD4
Homelab:plane/HOMELAB-1885-cockpit-e2e
Homelab:plane/HOMELAB-1727-skillhealth
Homelab:plane/HOMELAB-1728-signals
Homelab:plane/HOMELAB-1731-fitness
Homelab:plane/HOMELAB-1859-gateway
Homelab:plane/HOMELAB-1847-oncall
Homelab:plane/HOMELAB-1690-l7
Homelab:plane/HOMELAB-1850-tempo
Homelab:plane/HOMELAB-1850-attribution
Homelab:plane/HOMELAB-1686-meetings
Homelab:epic/LD3
Homelab:plane/HOMELAB-1684-derived-state
Homelab:plane/HOMELAB-1871-oidc-entrypoint-fix
Homelab:plane/HOMELAB-1871-oidc-sync
Homelab:plane/HOMELAB-1865-atlantis-leg
Homelab:plane/HOMELAB-1730-readiness
Homelab:plane/HOMELAB-1681b-image-dispatch
Homelab:plane/HOMELAB-1682-gates-backend
Homelab:plane/HOMELAB-1848-alert-audit
Homelab:plane/HOMELAB-1729-planfresh
Homelab:plane/HOMELAB-1848-contract-demo
Homelab:plane/HOMELAB-1854-longhorn-guard
Homelab:plane/HOMELAB-1685-intake
Homelab:plane/HOMELAB-1688-routines
Homelab:plane/HOMELAB-1687-el-director
Homelab:plane/HOMELAB-1827-prepull
Homelab:plane/HOMELAB-1827-livez
Homelab:plane/HOMELAB-1835-alert-rule
Homelab:plane/HOMELAB-1831-plane-vendor
Homelab:plane/HOMELAB-1746-es-newt-next
Homelab:plane/HOMELAB-1841-kubeprism
Homelab:plane/HOMELAB-1825-sandbox-pullsecrets
Homelab:plane/HOMELAB-1828-outline-minio
Homelab:plane/HOMELAB-1828-cnpg-alert
Homelab:plane/HOMELAB-1711-compile-before-claim
Homelab:plane/HOMELAB-1711-image-smoke
Homelab:epic/ld2
Homelab:plane/HOMELAB-1711-dispatcher-deploy
Homelab:plane/HOMELAB-1711-sweep-heart
Homelab:plane/HOMELAB-1824-omni-liveness
Homelab:plane/HOMELAB-1746-idp-redirect
Homelab:plane/HOMELAB-1711-sweep-control
Homelab:plane/HOMELAB-1711-raceprobe
Homelab:plane/HOMELAB-1711-dispatcher-image
Homelab:plane/HOMELAB-1720-identity
Homelab:plane/HOMELAB-1678-bashguard-fix
Homelab:plane/HOMELAB-1746-pangolin-oidc
Homelab:plane/HOMELAB-1680-custodian
Homelab:plane/HOMELAB-1678-scheduler
Homelab:plane/HOMELAB-1676-sentinel
Homelab:plane/HOMELAB-1677-rcbridge
Homelab:plane/HOMELAB-1796-teardown
Homelab:plane/HOMELAB-1794-spawn
Homelab:plane/HOMELAB-1797-claims
Homelab:plane/HOMELAB-1808-1809-fixes
Homelab:plane/HOMELAB-1791-sandbox-init
Homelab:plane/HOMELAB-1795-watch
Homelab:plane/HOMELAB-1798-compiler
Homelab:plane/HOMELAB-1674-claude-image
Homelab:plane/HOMELAB-1672-job-contract
Homelab:plane/HOMELAB-1698-meet-ai-namespace
Homelab:plane/HOMELAB-1635-cp01-pins
Homelab:plane/HOMELAB-1585-argocd-repo-creds
Homelab:plane/HOMELAB-1766-gateway-alerts
Homelab:epic/LD1
Homelab:plane/HOMELAB-1735-env-fix
Homelab:plane/HOMELAB-1735-harbor-robot
Homelab:plane/HOMELAB-1666-agent-sandbox
Homelab:plane/HOMELAB-1665-pi-image
Homelab:archive/epic/LD0-20261109
Homelab:plane/HOMELAB-1634-dind-harbor-auth
Homelab:plane/HOMELAB-1634-runner-cleanup
Homelab:plane/HOMELAB-1634-review-followups
Homelab:homelab/HOMELAB-1630-runner-resources
Homelab:plane/HOMELAB-1621-cpu-barchart
Homelab:plane/HOMELAB-1619-mgmt-vm-dashboard
Homelab:plane/HOMELAB-1598-oidc
Homelab:plane/HOMELAB-1574-plane-image-bump
Homelab:plane/HOMELAB-1535-outline-prestop
Homelab:plane/HOMELAB-1535-outline-ha
Homelab:plane/HOMELAB-1534-reviewer-fetch-auth
Homelab:plane/HOMELAB-1518-longhorn-rule-fix
Homelab:plane/HOMELAB-1527-tempo-pipeline
Homelab:plane/HOMELAB-1526-mqttx-web
Homelab:plane/HOMELAB-1511-prowlarr-qbit
No reviewers
Labels
No labels
operator-review
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
Homelab/infra-core!953
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/R39-forgejo-access-log"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What / why
Forgejo's container log is the only record of who called what, and it answers neither "who" nor "from where": no identity, no User-Agent,
RequestURIverbatim, and only ~70 min reachable through the pod-log API (kubeletcontainerLogMaxSize 10Mi × containerLogMaxFiles 5; measured: current file 3.44 MiB / 14,787 lines / 77 min). R37 could not recover the source of 7 comments because of it. Design note:working-files/infra-stability/r39-forgejo-access-log-20261112/design.md(option (a), approved by 3c).This PR is the shipper + tenancy half. The Forgejo half (access logger ON, router log OFF, escaped
ACCESS_LOG_TEMPLATE) is a separate prod PR; it lands FIRST (order flipped in fix round 1 — see "Landing order" below).Changes
core/manifests/monitoring/log-metrics/vector-longhorn.yaml— extends the EXISTING Vector DaemonSet (+0 pods):forgejo_pod_logsfile source, scoped by GLOB/var/log/pods/forgejo_*/forgejo/*.log*(the shipper never opens a non-Forgejo file),read_from: end— no backfill (3c B3): until the Forgejo PR lands, the router log writesRequestURIverbatim, so a first pass over the existing files would carry OAuth?code=and/user/recover_account?code=straight into a 45-day store.ignore_older_secsis deliberately not set — Vector reads it literally, so the design'signore_older_secs: 0would ignore every file present at startup.forgejo_annotatederives pod/ns/node/container from the log PATH (no API, no RBAC,automountServiceAccountToken: falseunchanged) and runs before any drop, so a dropped event still says where it came from.forgejo_parse— PARSE FIRST, and the parse is the ALLOWLIST (3c B2 + B3). Only a line matching the anchored access shape ships; everything else (the main log's panic and webhook-delivery dumps — a Discord/Slack hook URL keeps its secret in the PATH, where no credential regex can see it — xorm, the SSH logger) is dropped underreason="unparsed". Every later decision reads a parsed field, never the raw line, so the client-controlled User-Agent and X-Forwarded-For cannot steer a drop.bot token=1, or one merely ENDING intoken, used to erase the audit record of the request it belonged to; nowua/xffbecome[redacted](and a userinfo URL or bare Basic credential in the path becomes[redacted-path]), counted asforgejo_audit_log_redacted_total{node,pod,reason="credential"}. Defence in depth per requirement 1d:scheme://user:…@hostand bareBasic <b64>are matched explicitly.registration-token(user/org/repo/admin variants),/login/oauth/access_token,forgot_password/reset_password,/user/recover_account,/user/activate, the OAuth authorize/callback paths and/api/v1/users/*/tokens. These are exactly the requests a forensic review wants, and the old substring rule swallowed them because their paths end intokenorpassword./api/healthz,/metrics,/assets/,RunnerService/(FetchTask|UpdateLog|UpdateTask). A UA containing/assets/no longer deletes its own line. Drops are ROUTED (not aborted) and counted by reason:forgejo_audit_log_dropped_total{reason="unparsed"|"noise"}— an uncounted filter is an unwatched filter.loki_forgejo_auditsink —tenant_id: audit-forgejo(Vector's sink sends it asX-Scope-OrgID),compression: gzip,encoding.codec: text(the audit datum is the access line; pod/ns/node ride in the stream labels), labelsjob/ns/node/container. Unlimited retries (theretry_attempts: 5override is gone: five tries at a 1 s backoff gave up in ~30 s, so the Loki restart that performs the tenancy flip itself would have dropped every line in flight) andacknowledgements.enabled: true, so the file checkpoint only advances once Loki has acknowledged the push (3c N5).data_dirmoves from an emptyDir to a hostPath (/var/lib/vector-longhorn-log-metrics,DirectoryOrCreate, the pod already runs as uid 0), so a pod recreation resumes instead of re-reading ~8 h of lines with new timestamps (3c N6).checksum/configrebumped.auth_enabled: true+runtimeConfig.overrides.audit-forgejo.retention_period: 1080h(45 d, 3c's requirement; the global 30 d is untouched). Verified byhelm template grafana/loki 6.29.0→loki-runtimeConfigMap renders the override; Loki 3.4.2 accepts per-tenant retention as a runtime override.Lokidatasource (X-Scope-OrgID: main),otel-collectorotlphttp/loki.headers. The flip and its headers must be atomic:auth_enabled401s an unlabeled writer the moment Loki restarts.uid: loki-audit,orgId: 2,editable: false) — the only datasource that can nameaudit-forgejo, so read access is an org-membership (Server-Admin) decision, not an org-Viewer default. It is NOT in the provisioned set (3c B4): it lives atcore/manifests/monitoring/log-metrics/org2-bootstrap/grafana-datasource-loki-audit.yaml, a SUBDIRECTORY of the ArgoCD-synced directory, which directory-mode syncs do not apply. The gated ORG2 bootstrap job POSTs that file'sdatasources:entry verbatim toPOST /api/datasourceswithX-Grafana-Org-Id: 2after it creates org 2, after checking the file's sha256 against the review.core/manifests/monitoring/log-metrics/rule-tests/check-grafana-datasources.py(run in CI) proves the main set references no org 2 — a singleorgId: 2in the provisioned set fails provisioning for the WHOLE directory (CheckOrgExists), which would stripX-Scope-OrgID: mainfrom the chart's Loki datasource and then crash-loop Grafana on its next restart.loki-ingress-networkpolicy.yaml— the tenancy flip is a Grafana boundary, not a network one; this closes the "curl the ClusterIP withX-Scope-OrgID: audit-forgejo" read path to the enumerated writers/readers. Narrowed after review (3c N4):fromEntitiesis now[health, host]—remote-nodeandunmanagedadmitted every hostNetwork pod and the API-serverservices/proxypath for a reason ("the node's volume path") that is not Loki pod ingress at all — and every rule is port-scoped to 3100/9095/7946. The remainingservices/proxyholders are recorded in the file's header (names only).forgejo-audit-alerts.yaml—ForgejoAuditLogIngestMissing(0 bytes accepted for the tenant for 15 m while Forgejo is Ready — silence is the failure mode that matters) andForgejoAuditCredentialRedacted(a client-controlled field carried a credential: the line is KEPT with[redacted]in that field, so a live token was in a URL and the request it belonged to is on record).rule-tests/run-vector-tests.sh— new R39 cases against a local Loki capture stub (below), and a CI job that actually runs this harness, which no workflow did before (the pre-existing Longhorn cases were hand-run only).Test result (offline, fail-capable, in CI)
The stub gzips nothing — it DECODES the sink's real gzip and asserts on the bytes Loki would receive, including the
X-Scope-OrgIDheader. The three legs that keep it honest: the drop counter must be exactly 6 (a rule that dropped everything would fail), a credential-free query string must survive (the rule drops credentials, not query strings), and the hostile-UA line must still parse to ONE identity and ONE path (a UA ofcurl/8.5" user="ld-reviewer"<newline>GET /api/v1/admin/evilsurvives only as escaped text insideua="…"). The escaping itself is the prod PR's leg, proven by a real Gotext/templaterender of the shipped template.Verified locally with the SAME binary the DaemonSet runs (vector 0.61.0).
Landing order — which goes first, and why
The Forgejo values PR (prod) lands FIRST; this PR second. (flipped in fix round 1 — 3c B3)
auth_enabled: truebreaks any writer withoutX-Scope-OrgIDat the instant Loki restarts, so the flip and every header must be in the same change. They are: canary (sendsfakeitself), otel-collector, Grafana both datasources, and the new Vector sink.Tenancy flip: enumeration (before) and re-check (after)
Live read-only reads, before the flip:
loki-canary(6 pods)self-monitoringvia-tenant-id(pinned explicitly inloki/values.yaml; it wasfakebefore the flip)loki_distributor_bytes_received_total{tenant="fake"}= 12,967,905 B / 129,679 lines over 6 h ≈ 600 B/s — the ONLY series presentvector-longhorn-log-metricsaudit-forgejo(sinktenant_id)opentelemetry-collectormain(header added here)otelcol_receiver_accepted_log_records_total= 0 over 6 h: configured, idleLokidatasourcemain(header added here)kube-prometheus-stack-grafana-datasource, no header todayLoki (audit)audit-forgejoloki:3100/metrics/metricsis served outside the tenant middleware;up{job="loki"}must stay 1agent-tools/lab-director-dispatcher(Loki reader, cut by this change)signalintake/calls/loki/api/v1/serieswith no tenant header (3c N1). After the flip it gets Loki's missing-tenant error AND is refused by the CNP. It fails LOUDLY (an error plus a ledger refusal every 5 min), not silently. Two ways out, neither in this PR: give the dispatcher a tenant header (it only ever needsmain) plus a CNP allow, or turn itslogCoverageoff.After the flip each must still write/read with its header: canary bytes still rising under
fake; otel accepting a log record undermain; both Grafana datasources returning data (not 401); the Vector stream landing underaudit-forgejo;up{job="loki"}green; and{tenant="audit-forgejo"}not readable through the main-org datasource. The rollout proof re-checks exactly this list.Grafana auth config (read from values + live
grafana.ini; no credential used)role_attribute_path = contains(groups[], 'admins') && 'Admin' || 'Viewer'·auto_assign_org_role = Viewer·viewers_can_edit = false·disable_login_formnot set (absent from the live ConfigMap, so the local admin login form is enabled) · OAuth via Zitadel,allow_sign_up = true.The Grafana admin census (every main-org user/service account with Admin or
isGrafanaAdmin) is a SEPARATE credential job gated on the overseer's start message: grafana-admins=SEPARATE-JOB.Residual risks
Loki (audit)only appears after the gated ORG2 bootstrap job creates orgauditand POSTs the reviewed datasource. Nothing is provisioned from it in the meantime, so the main set cannot fail.audit-forgejoas readable by any org-1 Admin and by any Admin-role service-account token.token "cannot delete its own audit record any more, and the redaction is counted and alerted.DISABLE_ROUTER_LOG/LOGGER_*_MODEare v14 names; the prod PR's CI guard fails loudly if an upgrade stops rendering them.Class
dangerous. The diff touches
**/.forgejo/**(a CI workflow — an oversight path; the NPMCACHE PR #952 precedent),**/*networkpolic*.yaml(the CiliumNetworkPolicy) and a**/*-config.yaml-adjacentvalues.yamlundercore/charts/platform/. Perlanding-8c666fbe.tsdangerousPathsthat is two independent dangerous legs (.forgejo/**,*networkpolic*), so it needs the two-reviewer panel and a post-merge live proof. (The prod PR is routine:values/forgejo/values.yaml, atests/script and.forgejo/workflows/ci.yaml— the last of which also makes it dangerous by the same rule, so treat prod as dangerous too.)Head commit carries
Lab-Run: IR-20261112-r39as its last trailer; authorpi-landing; branch cut fromorigin/live.FIX ROUND 1 — 3c B2, B3, B4 (+ N1, N2, N5, N6)
Review:
working-files/security-hypervisor/pr-r39/review-3c.md. One new commit, no amend.B2 — client text decided what was dropped. Both rules matched the RAW line, which contains the User-Agent and the X-Forwarded-For: anyone could delete their own audit line with
User-Agent: x /assets/, uncounted. The credential rule dropped whole lines, so a UA merely ENDING intokenerased the request's record, and the security endpoints a review needs most (runnerregistration-token,/login/oauth/access_token,forgot_password,/api/v1/users/*/tokens) were dropped as false positives. Now:parse_regex!first; noise decided on the parsed.pathonly; security endpoints exempt and parameter-masked; a credential hit REDACTES the field (ua/xff→[redacted]) and KEEPS the line; every drop is counted by reason.B3 — the shipper forwarded every stdout line, and the userinfo/Basic rules were missing. The source is now an ALLOWLIST (access-anchor lines only; the rest counted as
unparsed),read_from: end(no backfill of pre-PR query strings), plus defence-in-depth rules forscheme://user:pass@hostand bareBasic <b64>. Landing order is now prod FIRST — the anchor filter stays as well; the order is the cheap guarantee, the allowlist is the structural one.B4 —
orgId: 2broke Grafana provisioning. Split: the audit datasource is noworg2-bootstrap/grafana-datasource-loki-audit.yaml, which no sync applies, and the gated ORG2 job POSTs it after creating org 2.rule-tests/check-grafana-datasources.pyproves the main set has no org 2 (fails if one is added back, including inside a block scalar).Non-blocking, same commit: N1 (the LD dispatcher is listed in the table as a reader this change cuts, with its two ways out); N2 (ONE tenant scheme, written down below); N4 (CNP narrowed to
health+host, port-scoped,services/proxyholders recorded by name); N5 (unlimited retries + end-to-end acknowledgements); N6 (checkpointdata_diron a hostPath).Tenant scheme — one name per purpose (3c N2)
mainopentelemetry-collector(otlphttp/loki.headers)Lokidatasourceaudit-forgejovector-longhorn-log-metrics(sinktenant_id)Loki (audit)in org 2 onlyself-monitoringloki-canary(-tenant-id, now pinned explicitly inloki/values.yaml)auth_enabledis truefakeTest result (offline, fail-capable, in CI)
Both scripts run in the
vector-access-log-testCI job, against the SHIPPED config.FIX ROUND 2 — 3c r2: B5, B6 (+ N9, N11, N12, N1)
Review:
working-files/security-hypervisor/pr-r39/review-3c-r2.md. ONE new commit on the handover head; no amend.B5 — an XFF redaction erased the gateway-appended client IP. The rule replaced the WHOLE field when any part looked like a credential, so
X-Forwarded-For: tokenarrived astoken, 198.51.100.7and shipped as[redacted]— withremoteonly the cilium-envoy node IP for external traffic, that is a request with no recoverable source, and the ATTACKER chose when. Redaction is now ELEMENT BY ELEMENT on the comma-separated field: an IP literal (v4/v6, with or without port/brackets) is KEPT, a non-IP element becomes[redacted], and the LAST hop — the one the gateway appended — is kept unless it is itself credential-shaped (a single element is not a gateway hop, and a credential in it is not an address).token=FAKE, 203.0.113.9shipsxff="[redacted], 203.0.113.9". Both assertions run on the captured Loki push body: the marker is ABSENT, the address is present.Live confirmation owed, and it is the leg that makes B5 measurable: the gateway must set XFF for external traffic. Rollout proof: send ONE request through the public route carrying
X-Forwarded-For: token=…and read the storedaudit-forgejoline back — it must showxff="[redacted], <real client>". If the gateway does NOT set XFF, the client address is still unrecoverable and that is a GATEWAY finding to report BEFORE the audit trail is relied on, not something this shipper can fix.B6 — length, not content. containerd splits any stdout line over
max_container_log_line_size(16 KiB) into CRIPrecords; none match the anchor, so an oversized header turned a real request into dropped "unparsed" fragments with no alert. Fixes:reason="partial"— a zero baseline, instead of being buried among Forgejo's routine non-access lines (the SSH logger writes one per git-over-SSH operation);unparsednow excludes partials explicitly (.cri_partial != true), so a fragment is counted under exactly one reason and the alert cannot double-report it;ForgejoAuditLogDropped:sum by (node, reason) (increase(forgejo_audit_log_dropped_total{reason=~"unparsed|partial"}[15m])) > 0,for: 0m, severity warning, full disposition contract.partialis the regression detector (the prod template caps UA/XFF at 512 B);unparsedcarries the git-over-SSH baseline in its own annotation so the routine case is named and not silenced;Pfragments →reason="partial"=2and neither fragment reaches Loki; a capped 512-byte User-Agent (what%.512qemits for a 20 KiB input) ships as ONE line, under 4 KiB, that parses.printf "%.512q"on UA and XFF), asserted there by rendering a 20 KiB User-Agent through real Gotext/template: one line, both fields at 512 bytes, truncation markers gone, and an anti-vacuity leg that re-renders the UNCAPPED template and requires it to fail.N9 —
GET /. The anchor path is*, not+, in all three anchors (VRL match, VRL parse, harness anchor). Forgejo's sanitiser trims the trailing slash, soGET /renders an EMPTY path ("GET "); with+every dashboard home request wasunparsedand dropped. New case asserts the empty-path line reaches Loki.N11 — the canary scrape port. The chart's ServiceMonitor scrapes the canary Service on
http-metrics= 3500 (proved by rendering:canary Service ports: {3500: 'http-metrics'}), and this policy'sendpointSelectoralso selects the canary pods, so the port-scoped rule denied the scrape and kube-prometheus-stack TargetDown would page. The CNP now admitsmonitoring/prometheus → 3500andhealth/host → 3500(the kubelet readiness probe). How it is tested:rule-tests/check-loki-tenant-pin.shrenders the chart and asserts BOTH halves — the rendered canary Service exposes 3500 AND the shipped CNP admits 3500 toprometheus(and that the dispatcher rule is 3100-only). Live leg for the rollout proof:up{job=~".*loki-canary.*"}must stay 1 after the CNP applies.N12 — the tenant pin was inert. Moved to the root
monitoring.selfMonitoring.tenant.name, the pathtemplates/loki-canary/daemonset.yamlreads.rule-tests/check-loki-tenant-pin.shproves it three ways:-user=self-monitoring -tenant-id=self-monitoringon the rendered loki-canary DaemonSet;loki.monitoring.selfMonitoring.tenant.namepath is shown to be INERT — a sentinel set there never appears.The stale "
mainis where every pre-existing stream lives" claim is corrected in the kube-prometheus-stack and otel-collector values:mainis the HUMAN-FACING tenant; the pre-existing 26.7 MB of canary history stays underfakeand is not migrated.N1 — the LD dispatcher: option 1 (header support + CNP allow for
mainonly), not "turn logCoverage off".logCoverageis the arm's designed "the coverage gap is VISIBLE in production" leg; turning it off would hide a gap to make a 401 disappear. Implemented in this PR:signalintake.LokiSourcesendsX-Scope-OrgID; the tenant issignals.lokiOrgID/SIGNALS_LOKI_ORG_ID, defaultmain(DefaultLokiOrgID). A test asserts the header is sent and that the default is neveraudit-forgejo;agent-tools/lab-director-dispatcher → 3100only, and the render check asserts that rule admits 3100 AND NOTHING ELSE — the probe may never reach the audit tenant, 9095 or 7946;dispatcher-config.yamlneeds nothing), thoughsignals.lokiOrgIDis available if the tenant ever moves.No code change, body notes:
sizeLimit. The manifest now says this in place.audit-forgejoas readable by any org-1 Admin until then.Test result (offline, this round, vector 0.61.0 — the DaemonSet's own binary)
The LD side:
go build ./... && go test ./signalintake/ ./config/— ok. Class: this commit also edits.forgejo/workflows/ci.yaml(adds the tenant-pin render step), so the CI oversight leg applies.Round 3 fix (3c N18): the drop alert is split, and the partial leg can never be silenced
3c r3 CLEARED infra-core #953 at
d624b5fcand raised N18 as non-blocking — awarning worth folding in before the alert exists, so this round does it.
The problem. One rule,
ForgejoAuditLogDropped, matchedreason=~"unparsed|partial"withincrease(...[15m]) > 0, for: 0m. Butunparsedis not exotic: it is every line of Forgejo's own main log that is notan access line. The design's own measurement (14,787 stdout lines in ~77 min,
13,310 of them router lines) leaves ≈1,477 main-log lines — about 19 lines/min.
So that leg fired continuously, and the first silence anyone wrote on
alertname=ForgejoAuditLogDroppedwould have muted the partial detector, theonly mechanical evidence that an audit record was lost.
The split (two rules, two names, so silencing one can never mute the other):
ForgejoAuditLogPartialsum by (node) (increase(forgejo_audit_log_dropped_total{reason="partial"}[15m])) > 00mForgejoAuditLogUnparsedBaselinesum(rate(forgejo_audit_log_dropped_total{reason="unparsed"}[15m])) * 60 > 6015mThe threshold, stated: 60 unparsed lines/min — 3.1× the measured 19.2
lines/min normal baseline (baseline ≈288 lines per 15 m window, threshold ≈900),
evaluated as a 15 m average and required to hold for 15 m. The margin absorbs the
routine git-over-SSH console lines the old text wrongly called "a handful per
day"; a template change, an anchor regression, a debug-level change or an
injection attempt moves the rate well past it.
The partial rule keeps
for: 0m(one fragment is already a lost record), stays atany count > 0, and is additionally the backstop for the prod PR's deliberately
uncapped X-Forwarded-For (3c B7): an oversized XFF loses its record as counted
partials rather than silently losing its source.
Body notes: N17 and N19 (deliberately not changed here)
N17 — the dispatcher's "
mainonly" is a default, not a control. The tenantcomes from
SIGNALS_LOKI_ORG_ID/signals.lokiOrgIDwith no allowlist, and theCNP admits
agent-tools/lab-director-dispatcherto port 3100 header-agnostically.Not fixed in this PR, because the LD code is another lane's. The follow-up is
filed separately: either hard-code
maininNewLokiSource(4a's lane,defence in depth) or — stronger, because then code review is not the only control
— a Cilium L7 HTTP rule on that egress, allowing only the read paths the
dispatcher actually uses with the header
X-Scope-OrgID: main, proven by a deniedrequest carrying
audit-forgejo. Comments claiming "mainis the ONLY tenant itmay name" are wrong until that lands and should not be read as a control.
N19 — the cap is per field; the line is still unbounded through the path.
EscapedPathhas no cap and 512 non-printable runes per capped field render ≈5 KB,so a write action with a user-chosen path segment can still cross 16 KiB. Not
fixed here: this PR's partial alert is the backstop — that loss is counted and
alerts on any count > 0. Lowering the UA cap or bounding the path (
%.4096sonEscapedPath, safe because it is ASCII) is a cheap follow-up.CI note
This round's single commit also serves as the CI re-run for
build-and-push, which flaked on a Go proxy error atd624b5fc(job 3656). Thereis no code change behind that rerun.
FIX ROUND 5 (overseer 10:20:12Z): the promtool docker fallback never validated anything in CI
Disclosure of the empty-commit history first. Head
a02fe825was an EMPTYCI re-run commit (its tree is byte-identical to
f474c999) pushed only becausethe 06:52Z Forgejo restart killed jobs 3675 (
alert-contract) and 3676(
helm-validate) at ~12 min, and Forgejo 14 has no rerun API. That re-run iswhat exposed a real bug, not an external event: run #3679 failed the
alert-contractjob at the promtool step withThe fault.
rule-tests/promtool-test.sh's docker fallback didmktemp -d(in/tmp) anddocker run -v $TMP:/w:ro. On the host-mode runneronly the checkout (
$GITHUB_WORKSPACE == $PWD == /workspace) is shared with thedind sidecar (HOMELAB-1634; the dispatcher smoke step in
dispatcher-image-build.yamlhit the identical fault in #1608), so the daemonbinds an EMPTY directory at
/wand promtool reads no config at all. Thepromtool step has therefore never validated anything in CI. The FIX4 local
pass used promtool from PATH, so the docker branch was never exercised.
The fix (option (a) — the smaller of the two): create the temp dir INSIDE
the shared workspace (
${GITHUB_WORKSPACE:-$PWD}), matching thedispatcher-image-build.yamlsmoke pattern; keep the digest-pinned clusterpromtool so the unit test still cannot speak a different dialect from the
evaluator. Two supporting changes the local docker-branch proof surfaced:
(1)
chmod -R a+rXon the temp tree — the extracted rule files were 0600 undera strict umask, so the container's uid 65534 got
permission deniedonce themount worked; (2) a per-file summary of the unit-test case names, because
promtool only prints
SUCCESSper FILE on success — the sixforgejo-audit-alertscases T1/T1b/T2/T3/T4/T5 are now named in thealert-contractlog.rule-tests/alertmanager-routing.sh— the very next step in the samealert-contractjob — carried the identicalmktemp -d+-v $TMP:/w:rofault and would have failed the same way; it gets the same workspace-mount +
readability fix.
Local proof (docker branch; PROMTOOL/AMTOOL unset, neither binary on PATH):
a deliberately broken rule makes
promtool check rulesfail with a real parseerror (exit 1), all rule files and all six forgejo-audit cases pass on the clean
tree, the routing harness stays 9/9 green with A-I red, and the trap removes the
workspace temp dir on both the success and failure exit paths.
FIX ROUND 6 — 3c r5: B10 (delete API), B9 (flip order), N22 (increase-leg case)
Review:
working-files/security-hypervisor/pr-r39/review-3c-r5.md. ONE new commit on the FIX5 head, no amend.B10 — the Loki delete API was open on
audit-forgejo.compactor.delete_request_store: filesystem+retention_enabled: truewithdeletion_modeset nowhere defaults tofilter-and-deletein Loki 3.x, so any party that can name the tenant by header (CNP-admitted pods, port-forward/exec holders, the dispatcher) couldPOST /loki/api/v1/deleteand silently erase the forensic log. Fix:deletion_mode: disabledunderruntimeConfig.overrides.audit-forgejoincore/charts/platform/loki/values.yaml, asserted incheck-loki-tenant-pin.sh(renders the chart, proves the value reaches theloki-runtimeConfigMap, plus a sentinel override proving the path is read). Runbook §6 gained the effective read-back:/runtime_config(and theloki-runtimeConfigMap) must showoverrides.audit-forgejo.deletion_mode: disabled, and a delete POST must be refused.B9 — the flip is NOT atomic, and the old order leaked. Four ArgoCD Applications sync separately (
monitoring-log-metricswave 5;loki/kube-prometheus-stack/opentelemetry-collectorwave 10), so with auth off, Vector could ship the audit trail intofake(readable by every Grafana Viewer) before the flip. The runbook order is rewritten: (1) pausemonitoring-log-metricsauto-sync, (2) synclokifirst (auth on) and prove an unlabeled read returns 401, (3) sync kps + otel, (4) only then log-metrics/Vector. Chosen "order the sync" over "gate the Vector sink" because Vector's loki sink gets HTTP 200 whether auth is on (audit-forgejo) or off (fake), so it cannot gate on its own responses — the only reliable boundary is the ORDER. The rollback is also ordered: stop the Vector sink (scale to zero) BEFORE auth goes off. The §5 claim that audit data "mixes intofake" is corrected: already-writtenaudit-forgejodata stays put, is unaddressable while auth is off, and ages out at global 30 d once the override reverts. A permanent sync-wave reorder is a prodapps/*.yamlfollow-up (below).N22 — T2 now isolates the
increaseleg. The old T2 (0+1x20, eval 10 m) had no sample 15 m back, so the birth leg fired with the same value and deleting the increase leg would NOT have gone red. New T2 (0+1x16, eval 15 m) is born before eval−15 m, sounless offset 15mis empty and only the increase leg can fire (value 15). Fail-capable proof: with the increase leg removed from the rule the case reportsgot: []and promtool exits 1; with it present the full suite is green (158 cases SUCCESS, T1/T1b/T2/T3/T4/T5 named in the alert-contract log).Follow-ups (listed, NOT implemented in this round):
alertclassunique label cannot do what its comment claims: a silence whose matchers all match (e.g.component=forgejoorseverity=warning) still mutesForgejoAuditLogPartial. Fix: drop/renamecomponenton the Partial rule (e.g.component: forgejo-audit-loss), correct the rule comment and thealertmanager-routing.md"R39" text, and record theseverity=warningresidual under the ruling.gateway(gateway.enabled: false), so any CNP-admitted pod, and anypods/portforward/pods/execholder inmonitoringoragent-tools, can read OR write the audit tenant by settingX-Scope-OrgID: audit-forgejo. The CNP is a network boundary, not a tenant boundary. Longer term: a per-tenant auth gateway that maps each client to one tenant.monitoring-log-metricsto a wave afterloki/kube-prometheus-stack/opentelemetry-collectorin prodapps/*.yaml, so the flip order above is enforced by ArgoCD rather than by hand.CI at this head (all four runs green):
shellcheck,terraform-validate,yaml-lint,build-and-push,preflight,alert-receiver-test,cockpit-e2e,cockpit-test,dispatcher-test,helm-validate,receiver-test,record-sql-test,runner-manifest-test,vector-access-log-test,alert-contract.Four blocking findings from review-3c, plus the non-blocking items that belong in the same change. B2 — CLIENT TEXT DECIDED WHAT WAS DROPPED. Both the credential rule and the noise rule matched the RAW line, which contains the User-Agent and the X-Forwarded-For. Anyone could delete their own audit line with `User-Agent: x /assets/`, uncounted. And the credential rule dropped the whole line, so a UA merely ENDING in `token` erased the record of the request, while security-relevant paths (runner `registration-token`, `/login/oauth/access_token`, `forgot_password`, `/api/v1/users/*/tokens`) were dropped as false positives — exactly the events a forensic review needs. - `parse_regex!` the anchored access shape FIRST; every later decision reads a PARSED FIELD, and noise is decided on the parsed `.path` only (exact/prefix), never on a substring of the line. - security endpoints are explicitly exempt from noise, params masked. - a credential hit now REDACTES the offending field (ua/xff -> [redacted], userinfo/Basic in the path -> [redacted-path]) and KEEPS the line. - drops are ROUTED, not aborted, and counted by reason: forgejo_audit_log_dropped_total{reason="unparsed"|"noise"} plus forgejo_audit_log_redacted_total{reason="credential"}. The alert that watched the old counter is renamed/moved with it. B3 — the shipper forwarded every stdout line, and the userinfo/Basic rules were missing. The source is now an ALLOWLIST (only lines matching the anchor ship; the rest are counted as `unparsed`), `read_from: end` so nothing written before the shipper existed is backfilled (the router log's `?code=`/`recover_account` query strings are the reason), and the sink gains defence-in-depth rules for `scheme://user:pass@host` and bare `Basic <b64>`, with fixtures. B4 — `orgId: 2` in the provisioned set fails provisioning for the WHOLE Grafana datasource directory before org 2 exists, which would strip `X-Scope-OrgID: main` from the chart's Loki datasource and then crash-loop Grafana. The org-2 datasource moves to `log-metrics/org2-bootstrap/` (a subdirectory ArgoCD directory mode does not apply) and is applied only by the gated ORG2 job, which POSTs that file's body verbatim. `rule-tests/check-grafana-datasources.py` (now run in CI) proves the main set references no org 2 and that the reviewed file is present, unapplied, and complete. Non-blocking in the same commit: - N2: ONE tenant scheme, written down in loki/values.yaml: `main` (Grafana + otel), `audit-forgejo` (one writer, one reader), `self-monitoring` (canary, now pinned explicitly); `fake` is legacy history that nothing reads or writes after the flip. - N4: the CNP drops `remote-node`/`unmanaged` (every hostNetwork pod and the API-server proxy path) for `health`+`host`, port-scopes every rule to 3100/9095/7946, fixes the canary selector note, and records the `services/proxy` census (names only) as remaining audit readers. - N5: `retry_attempts: 5` is gone (unlimited retries, capped backoff) and `acknowledgements.enabled: true`, so a Loki restart cannot silently drop the lines in flight. - N6: the checkpoint `data_dir` moves from emptyDir to a hostPath, so a pod recreation does not replay 8 h of lines with new timestamps. Tests: `rule-tests/run-vector-tests.sh` grows cases for no-backfill, allowlist (webhook panic with the hook secret in its path), redaction-not-drop, the two evasion UAs (` /assets/`, ending in `token`), four security endpoints, the noise count by reason, and the injection shape; the Longhorn cases still pass. Both scripts are offline and fail-capable, and both run in CI. Lab-Run: IR-20261112-r39The single ForgejoAuditLogDropped rule put reason="unparsed" and reason="partial" under one alertname with `> 0, for: 0m`. `unparsed` is not exotic: it is every line of Forgejo's own main log that is not an access line. The design's own measurement (14,787 stdout lines in ~77 min, 13,310 of them router lines) leaves ~1,477 main-log lines, about 19 lines/min — so that leg fired continuously, and the first silence anyone wrote on alertname=ForgejoAuditLogDropped would also have muted the B6 partial detector. 3c N18. Split into two rules with two names: ForgejoAuditLogPartial reason="partial", > 0, for: 0m — silence policy "never": it is the B6 regression detector AND the only mechanical evidence of the deliberate loud loss from the prod template's uncapped XFF (3c B7). ForgejoAuditLogUnparsedBaseline a baseline-deviation rule, not an any-count rule. Threshold chosen for the baseline rule: 60 unparsed lines/min, i.e. 3.1x the measured 19.2/min normal rate (baseline ~288 lines per 15 m, threshold ~900 per 15 m), evaluated as `sum(rate(...[15m])) * 60 > 60` and required to hold for 15 m. The margin absorbs routine git-over-SSH console lines; a template or anchor change, a debug-level change or an injection attempt moves the rate well past it. Its ld_silence_policy silences THIS alertname only and says so. Because silencing (b) must never mute (a), the two never share an alertname and the baseline rule's action text points any operator who wants quiet at its own name only. The comment blocks and the shipper's partial-fragment note are corrected to match: the prod half caps the User-Agent only, X-Forwarded-For is uncapped on purpose, so a partial is either that XFF bound or a regression. This commit also serves as the CI re-run for the build-and-push job that flaked on a Go proxy error atd624b5fc; there is no code change behind that rerun. Lab-Run: IR-20261112-r39View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.