Files
JQC_multi_tenant/control/migrations/versions/control0006_fernet_key_version.py
T
2026-08-21 10:18:22 -04:00

52 lines
1.7 KiB
Python

"""control0006_fernet_key_version
Adds the Fernet key-version stamp to the `tenants` table:
- key_version INT NOT NULL DEFAULT 1
MT-23: credentials are encrypted under a single control-plane Fernet key, and
before this there was no way to tell which rows had been re-encrypted after a
key change — rotation was all-or-nothing and unverifiable. Stamping the version
each time a row is written makes rotation progress a plain query:
SELECT COUNT(*) FROM tenants WHERE key_version < <current>;
Existing rows default to 1, which matches the single key in use before this
migration, so nothing needs re-encrypting to adopt it.
Guarded by an INFORMATION_SCHEMA existence check, so re-running is safe.
Revision ID: control0006_fernet_key_ver
Revises: control0005_superadmin_mfa
Create Date: 2026-08-21
"""
from alembic import op
import sqlalchemy as sa
# NOTE: revision ids must fit VARCHAR(32) — 'control0006_fernet_key_ver' is 26.
revision = 'control0006_fernet_key_ver'
down_revision = 'control0005_superadmin_mfa'
branch_labels = None
depends_on = None
def _column_exists(table, column):
result = op.get_bind().execute(sa.text(
"SELECT COUNT(*) FROM information_schema.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() "
" AND TABLE_NAME = :tbl "
" AND COLUMN_NAME = :col"
), {'tbl': table, 'col': column})
return result.scalar() > 0
def upgrade():
if not _column_exists('tenants', 'key_version'):
op.add_column('tenants', sa.Column('key_version', sa.Integer(),
nullable=False, server_default='1'))
def downgrade():
if _column_exists('tenants', 'key_version'):
op.drop_column('tenants', 'key_version')