20260718_50_datasource_credentials.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. """Add encrypted, versioned external data-source credentials."""
  2. from alembic import op
  3. revision = "20260718_50"
  4. down_revision = "20260716_40"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. """
  10. CREATE TABLE IF NOT EXISTS public.datasource_credentials (
  11. id UUID PRIMARY KEY,
  12. data_source_uid UUID NOT NULL,
  13. credential_version INTEGER NOT NULL
  14. CHECK (credential_version > 0),
  15. encrypted_payload BYTEA NOT NULL,
  16. nonce BYTEA NOT NULL CHECK (octet_length(nonce) = 12),
  17. key_version VARCHAR(40) NOT NULL,
  18. status VARCHAR(20) NOT NULL DEFAULT 'active'
  19. CHECK (status IN ('active', 'retired', 'revoked')),
  20. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  21. retired_at TIMESTAMPTZ,
  22. UNIQUE (data_source_uid, credential_version)
  23. );
  24. CREATE UNIQUE INDEX IF NOT EXISTS uq_datasource_credential_active
  25. ON public.datasource_credentials(data_source_uid)
  26. WHERE status = 'active';
  27. CREATE INDEX IF NOT EXISTS idx_datasource_credential_lookup
  28. ON public.datasource_credentials(
  29. data_source_uid, credential_version, status
  30. );
  31. CREATE TABLE IF NOT EXISTS public.datasource_credential_audit_events (
  32. id BIGSERIAL PRIMARY KEY,
  33. data_source_uid UUID NOT NULL,
  34. credential_version INTEGER,
  35. actor_uid UUID REFERENCES public.users(id) ON DELETE SET NULL,
  36. event_type VARCHAR(60) NOT NULL,
  37. success BOOLEAN NOT NULL,
  38. safe_detail VARCHAR(500),
  39. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  40. );
  41. CREATE INDEX IF NOT EXISTS idx_datasource_credential_audit_source
  42. ON public.datasource_credential_audit_events(
  43. data_source_uid, created_at DESC
  44. );
  45. CREATE INDEX IF NOT EXISTS idx_datasource_credential_audit_actor
  46. ON public.datasource_credential_audit_events(
  47. actor_uid, created_at DESC
  48. );
  49. """
  50. )
  51. def downgrade() -> None:
  52. # Credential and audit history remain available during application rollback.
  53. pass