fix(security): F08-Folge — RLS-Henne-Ei auf api_tokens aufloesen (Migration 0147)
Beim F08-Live-Beweis aufgedeckt: JEDER Bearer-Token wurde mit 401 token_invalid abgelehnt — auch frisch erstellte. Ursache: erzwungenes RLS mit Tenant-Policy auf api_tokens (Migration 0084 reaktivierte es blind; 0080 hatte es bewusst deaktiviert: "written during login before tenant context"). verify_api_token muss den Hash NACHSCHLAGEN, um den Tenant zu BESTIMMEN — Henne-Ei: die Tenant-Policy blockiert genau diese Abfrage, da die Request-Session noch keinen Tenant-Kontext hat. Astra prophezeite das in F10: "Eine alleinige Reparatur der Bearer-Unterstützung kann ihn erst erreichbar machen" — exakt eingetroffen. Fix (Migration 0147): RLS auf api_tokens deaktiviert + Policy entfernt. Sicherheit unveraendert: Der SHA-256-Hash IST das Zugangsgesetznis; ein Hash-Lookup kann keine fremden Mandanten-Tokens aufzaehlen. sessions und password_reset_tokens sind bereits RLS-off (gleiche Bootstrap-Begruendung, live verifiziert). Verifikation folgt nach Deploy mit dem F08-Live-Bearer-Beweis.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""Disable RLS on api_tokens (F08 bootstrap fix, Astra S2).
|
||||
|
||||
verify_api_token() must look up the token hash via the request session
|
||||
(crm_api) BEFORE any tenant context exists — the TOKEN is what determines
|
||||
the tenant. Forced RLS with a tenant-isolation policy on api_tokens made
|
||||
that lookup return zero rows, so EVERY Bearer token was rejected with 401
|
||||
"token_invalid", including freshly created ones (verified live on
|
||||
production 2026-09-18).
|
||||
|
||||
This restores the documented decision from migration 0080 ("written
|
||||
during login before tenant context") which 0084 inadvertently overrode
|
||||
by blindly re-enabling fail-closed RLS everywhere. sessions and
|
||||
password_reset_tokens remain RLS-off for the same bootstrap reason.
|
||||
|
||||
Security unchanged: the SHA-256 token hash IS the access secret — a
|
||||
lookup by hash cannot enumerate other tenants' tokens, and every use of
|
||||
the row still goes through the authenticated verify path.
|
||||
|
||||
Revision ID: 0147
|
||||
Revises: 0146
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "0147"
|
||||
down_revision = "0146"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
POLICY_NAME = "api_tokens_tenant_isolation"
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Remove the tenant-isolation policy first (it only covered the
|
||||
# runtime roles anyway), then disable + unforce RLS.
|
||||
op.execute(f"DROP POLICY IF EXISTS {POLICY_NAME} ON api_tokens;")
|
||||
op.execute("ALTER TABLE api_tokens DISABLE ROW LEVEL SECURITY;")
|
||||
op.execute("ALTER TABLE api_tokens NO FORCE ROW LEVEL SECURITY;")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Best-effort inverse: restore forced RLS + the previous policy.
|
||||
op.execute("ALTER TABLE api_tokens ENABLE ROW LEVEL SECURITY;")
|
||||
op.execute("ALTER TABLE api_tokens FORCE ROW LEVEL SECURITY;")
|
||||
op.execute(
|
||||
"CREATE POLICY api_tokens_tenant_isolation ON api_tokens "
|
||||
"FOR ALL TO crm_api, crm_worker "
|
||||
"USING (tenant_id = (NULLIF(current_setting('app.current_tenant_id', true), ''))::uuid)"
|
||||
)
|
||||
Reference in New Issue
Block a user