feat(rls): Postgres row-level-tenant-isolation prototype + Postgres 18 bump #138
No reviewers
Labels
No labels
architecture
backend
bug
ci
config
database
docker
documentation
duplicate
enhancement
frontend
help wanted
invalid
issue-25
issue-26
issue-42
issue-65
issue-67
issue-82
migrations
p0-critical
p1-high
p2-medium
p3-low
performance
quality
question
security
state-management
testing
timezone
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
fastie81/honbu-manager!138
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/rls-tenant-isolation"
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
Replaces the abandoned database-per-club split with Postgres Row-Level Security for tenant isolation, plus a fully-automated, CI-driven Postgres 15 → 18 upgrade run through the app startup entrypoint.
RLS (single-DB, database-enforced)
app/infrastructure/database/rls.py— opt-in bootstrap: creates a non-superuserhonbu_approle, transfers ownership of the 13 club-scoped tables, enablesFORCE ROW LEVEL SECURITYwith tenant policies keyed on theapp.club_idGUC.club_id.SET LOCAL app.club_id— transaction-scoped, pool-safe.HONBU_ENABLE_RLS=1+ runtime engine connects ashonbu_app. Default (superuser) behaviour byte-for-byte unchanged.Automated Postgres 15 → 18 upgrade (CI-driven, zero operator DB touch)
Triggered by a Forgejo Actions variable,
POSTGRES_MAJOR. Routine deploys are unchanged.POSTGRES_MAJORpostgres:15-alpinepostgres_data(stable, = current prod)18postgres:18-alpinepostgres_data_18(fresh)postgres_datapreserved as rollback/safety15postgres:15-alpinepostgres_data_15(fresh)Sequence on upgrade:
POSTGRES_MAJOR='${{ vars.POSTGRES_MAJOR || '' }}'into the test+proddocker compose pull/upcommands (read from the Forgejo Actions variable).postgres_data_<major>volume +postgres:<major>image (old volume intact).pg_dumponce per major → auto-restores the prior-major dump into the fresh DB → applies migrations → RLS bootstrap → starts server.CI safety — the hard-won workflow is untouched
Only additive change to
.forgejo/workflows/ci.yml: 4POSTGRES_MAJORinjections (pull+up × test+prod). Gating,needs,if:conditions, secrets, env-file handling, SSH/scp flow all unchanged. Empty var → compose:-15fallback → identical behavior. Verified: YAML parses, all 4 jobs intact.Verified (real, isolated, not production)
postgres_data, 18→postgres_data_18, 15→postgres_data_15.bash -nOK; existing suite 196 pass / 2 pre-existing fail (no regression).Enable RLS in prod (separate from merge)
HONBU_ENABLE_RLS=1on the superuser/migration boot. 2. Point runtimeDATABASE_URLathonbu_app. Safe to leave off — ships disabled (legacy parity).- Compose: versioned postgres volume + image driven by POSTGRES_MAJOR (postgres_data_${POSTGRES_MAJOR}). Deploying 15->18 creates a FRESH data volume automatically while the prior major volume is preserved as a safety copy. - Entrypoint (maintenance mode): on boot, auto-restores the newest prior-major pg_dump into the fresh/empty DB, then runs migrations + RLS bootstrap, then starts the web server LAST. Server is down only for the maintenance window covering the upgrade; no operator DB touch. - Idempotency: dump taken once per major; restore gated on DB being empty, so a restart never double-restores, and a populated DB never clobbered. - .env.example documents POSTGRES_MAJOR, HONBU_UPGRADE_SAFETY_DUMP, HONBU_AUTO_RESTORE.- CRITICAL FIX: revert the versioned postgres_data_${POSTGRES_MAJOR} volume. It would have orphanned prod data on the first routine deploy (prod runs PG15 on a volume literally named postgres_data). Data volume is now stable so a code deploy can never orphan live data. - postgres image default back to postgres:${POSTGRES_MAJOR:-15}-alpine (matches current prod/test) so nothing changes until POSTGRES_MAJOR is explicitly set. - CI deploy job: inject POSTGRES_MAJOR="${{ vars.POSTGRES_MAJOR || "" }}" into both test and prod docker compose pull + up commands, read from a Forgejo Actions variable. Empty (unset) -> compose falls back to :15 -> no behavior change. CI structure, gating, needs, secrets, env-file handling untouched. - test job service bumped to postgres:18-alpine so tests exercise the target.