Multitenancy

This commit is contained in:
2026-03-19 18:06:44 +01:00
parent 106ed50361
commit e594defc00
15 changed files with 1090 additions and 37 deletions
@@ -0,0 +1,153 @@
"""RLS completa su tutte le tabelle applicative + ruolo pechub_app
Revision ID: 0006
Revises: 0005
Create Date: 2026-03-19 00:00:00.000000
Aggiunge:
- ENABLE ROW LEVEL SECURITY + policy USING (tenant_id = ...) su tutte le
tabelle applicative che mancavano (attachments, send_jobs, archival_batches,
archival_dips, labels, mailbox_permissions, virtual_boxes,
notification_channels, notification_rules, notification_log, tenant_settings)
- Crea il ruolo PostgreSQL applicativo 'pechub_app' (non-superuser) con
GRANT SELECT/INSERT/UPDATE/DELETE su tutte le tabelle.
Il ruolo viene usato dalla connessione applicativa (DATABASE_URL) mentre
Alembic continua ad usare l'utente superuser (DATABASE_URL_SYNC).
Nota: la migrazione usa DO $$ BEGIN ... EXCEPTION WHEN ... END $$ per
rendere idempotente la creazione del ruolo e delle policy.
"""
from alembic import op
revision = "0006"
down_revision = "0005"
branch_labels = None
depends_on = None
# Tabelle con tenant_id che devono avere RLS (escluse quelle già gestite in 0001)
_RLS_TABLES = [
"attachments",
"send_jobs",
"archival_batches",
"archival_dips",
"labels",
"mailbox_permissions",
"virtual_boxes",
"notification_channels",
"notification_rules",
"notification_log",
"tenant_settings",
]
# Tutte le tabelle su cui pechub_app ha SELECT/INSERT/UPDATE/DELETE
_ALL_APP_TABLES = [
"tenants",
"users",
"refresh_tokens",
"mailboxes",
"messages",
"attachments",
"send_jobs",
"archival_batches",
"archival_batch_messages",
"archival_dips",
"labels",
"message_labels",
"mailbox_permissions",
"virtual_boxes",
"virtual_box_rules",
"virtual_box_assignments",
"virtual_box_mailboxes",
"notification_channels",
"notification_rules",
"notification_log",
"audit_log",
"tenant_settings",
]
def upgrade() -> None:
# ── 1. Abilita RLS + policy su tabelle mancanti ───────────────────────────
for table in _RLS_TABLES:
op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY")
# Politica idempotente: elimina la policy se esiste e la ricrea
op.execute(
f"""
DO $$
BEGIN
-- Elimina policy esistente (se presente da un run precedente)
DROP POLICY IF EXISTS {table}_tenant_isolation ON {table};
-- Crea la nuova policy
CREATE POLICY {table}_tenant_isolation ON {table}
USING (
tenant_id = NULLIF(
current_setting('app.current_tenant_id', TRUE), ''
)::UUID
);
END
$$;
"""
)
# ── 2. Crea ruolo applicativo pechub_app (non-superuser) ──────────────────
op.execute(
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_roles WHERE rolname = 'pechub_app'
) THEN
CREATE USER pechub_app WITH PASSWORD 'pechub_app_password'
NOSUPERUSER NOCREATEDB NOCREATEROLE;
RAISE NOTICE 'Ruolo pechub_app creato';
ELSE
RAISE NOTICE 'Ruolo pechub_app già esistente skip';
END IF;
END
$$;
"""
)
# ── 3. Permessi al ruolo applicativo ──────────────────────────────────────
op.execute("GRANT CONNECT ON DATABASE pechub TO pechub_app")
op.execute("GRANT USAGE ON SCHEMA public TO pechub_app")
for table in _ALL_APP_TABLES:
op.execute(
f"GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE {table} TO pechub_app"
)
# Sequenze (per BIGSERIAL come audit_log.id)
op.execute("GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO pechub_app")
# Default privileges: le tabelle create nelle migrazioni future ricevono
# automaticamente i permessi per pechub_app
op.execute(
"""
ALTER DEFAULT PRIVILEGES FOR ROLE pechub IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO pechub_app;
"""
)
op.execute(
"""
ALTER DEFAULT PRIVILEGES FOR ROLE pechub IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO pechub_app;
"""
)
def downgrade() -> None:
# Rimuovi policy e disabilita RLS
for table in _RLS_TABLES:
op.execute(
f"DROP POLICY IF EXISTS {table}_tenant_isolation ON {table}"
)
op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")
# Revoca permessi e rimuovi ruolo
op.execute("REVOKE ALL ON ALL TABLES IN SCHEMA public FROM pechub_app")
op.execute("REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM pechub_app")
op.execute("REVOKE USAGE ON SCHEMA public FROM pechub_app")
op.execute("REVOKE CONNECT ON DATABASE pechub FROM pechub_app")
op.execute("DROP USER IF EXISTS pechub_app")