Reconciler & quota hardening for open registration (fallback, non-LFS caps, token scope, org/CI bypass) #125
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-infra#125
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "%!s()"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
Severity: HIGH — pre-onboarding infra audit (2026-07-19). Reconciler/quota hardening for open registration; groups several related findings.
All references are
ansible/roles/reconciler/templates/gitborg-reconciler.sh.j2unless noted.lfs-basic(:249-254,else tgt="$BASIC"), whose sharedbaserule grants unlimitedsize:repos:all+size:assets:all(only LFS capped). A failedtier_participantassignment at signup (but created JIT account) silently yields unlimited non-LFS storage. → Change fallback to the zero-quotaparticipantgroup; require positive tier membership beforebase.size:repos:all=-1,size:assets:all=-1(:92-105). One basic user can fill the 60 GB volume with ordinarygit push/packages → shared-volume outage. Only reactiveDiskUsageCritical(90%) exists. → Introduce bounded per-tiersize:repos:all/size:assets:all(mind Forgejo's most-permissive merge rule).set_repo_actions(:171-193) WARN-and-return 0on non-200; a token missingread:user/write:repositorymeans Actions default-deny is never enforced and the run still exits 0 → non-entitled users keep CI (real OpenStack VM cost) with no alert. → Verify live token scopes; make scope failure fatal or emit an alertable metric.DEFAULT_REPO_UNITSonly affects new repos; a user re-enabling theactionsunit gets ~15 min of runner jobs per cycle. Org repos are never reconciled → permanent CI bypass. → Reconcile org repos; consider a sticky toggle.app.ini.j2 [repository]has noMAX_CREATION_LIMIT, no[repository.upload] FILE_MAX_SIZE, no mirror caps. → Set them (compounds H2).member(ACL-filtered) read (:137-147) downgrades entitled users / drops participants to BASIC (via H1) with no signal. → Add a sanity floor (abort if a should-be-nonempty group is empty); verify the SA token's read ACL (ADR-0017 open item).H1 + H2 implemented on
feat/125-reconciler-quota-hardening(commitfce8f64; push/PR pending — SSH agent locked overnight). Rendered +bash -nverified; syntax-check + ansible-lint clean. Not applied — needs review + a dry-run pass on prod first (the timer applies within ~5 min of the role apply, so reviewreconciler_user_exemptbefore shipping).participant(waslfs-basic= unlimited non-LFS).tier_basicis now read as the positive paid-seat signal. First-party identities (forgejo_users + service accounts +reconciler_user_exempt_extra, currentlysupernaut— ⚠️ confirm that's the only out-of-band admin) are exempted into the unlimited group, since fail-closed would otherwise zero-quota the operator. A WARN is logged on every fail-closed fallback, which also makes an ACL-filtered empty tier read (M8) visible.baserule is gone (detached from every group + rule deleted). Each tier group now carries<group>-store=size:repos:all+size:assets:allcapped at 10 GiB (per the pricing decision — 10 GiB shared, no repo-count cap; LFS add-ons bump only LFS). Subjects don't overlap with the LFS rule, so the most-permissive merge can't defeat either cap.MAX_CREATION_LIMITdeliberately NOT set — the pricing decision explicitly chose "no repo-count cap, bounded by storage quota". Upload/file-size caps remain open here.Still open on this issue: H3 (token-scope no-op → fatal or alertable metric), M4 (per-cycle/org Actions bypass), M8 (sanity floor on group reads), remainder of M3 (upload caps).
H1+H2 applied to prod 2026-07-20 (PR #164, + follow-up PR sourcing the exempt list from vault). Reconciler ran clean (
APPLY=true, complete, no errors):<group>-storecap:lfs-basic/-pro/-xlallnon-LFS 10737418240(10 GiB). Legacy unlimitedbaserule deleted.supernaut+ all service accounts →org-unlimited;kofish→lfs-xl.participantwith the WARN line.⚠️ The apply surfaced a pre-existing HIGH bug → filed #166: the reconciler reads DIRECT group members, but the taxonomy nests tiers into ent_* groups (transitive memberOf). So tier_pro users don't resolve (ent_lfs.member=[tier_pro], not persons) and now fail-closed to zero-quota
participant— Pro seats get less than Basic. Observed:alexanderkjall(in tier_pro, renovate cap 100) → participant. Acute impact nil (owns no repos). Fix implemented onfeat/166-nested-group-expansion(committed; push pending on SSH agent).Remaining on this issue: H3, M4, M8 (M8 partly subsumed by #166).
Reconciled scope: H1/H2 applied (#164), M4 shipped (#169), M8 subsumed by #166. H3 (token-scope no-op) split out to #183. Remaining here: M3 — per-user upload / file-size caps (
[repository.upload] FILE_MAX_SIZE, mirror caps);MAX_CREATION_LIMITdeliberately unset per the pricing decision (10 GiB shared, no repo-count cap).Closing. Most of this was superseded by the ADR 0035 reconciler rewrite, which retired the bash script these line references point at.
fbd3bb0(org repos are reconciled).app.ini.j2. Filed as #495.