91 lines
2.5 KiB
Python
91 lines
2.5 KiB
Python
"""Encrypt module trust credentials at rest.
|
|
|
|
`module_environments.trust_credentials` was a plain JSON column holding the
|
|
`hmac_secret` / `secret_key` values every module integration is authenticated
|
|
with. This adds an encrypted column, moves each row's secrets into it, and blanks
|
|
the plaintext.
|
|
|
|
Requires ENCRYPTION_KEY to be set, and it must be the same value the application
|
|
runs with — otherwise the application cannot read back what this wrote.
|
|
|
|
Revision ID: b2e1d4f5a602
|
|
Revises: a1f0c2d3e401
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
from sqlalchemy.sql import table, column
|
|
|
|
revision: str = "b2e1d4f5a602"
|
|
down_revision: Union[str, Sequence[str], None] = "a1f0c2d3e401"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
from app.core.crypto import encrypt_json
|
|
|
|
op.add_column(
|
|
"module_environments",
|
|
sa.Column("trust_credentials_enc", sa.Text(), nullable=True),
|
|
)
|
|
|
|
envs = table(
|
|
"module_environments",
|
|
column("id", sa.String),
|
|
column("trust_credentials", sa.JSON),
|
|
column("trust_credentials_enc", sa.Text),
|
|
)
|
|
|
|
conn = op.get_bind()
|
|
rows = conn.execute(
|
|
sa.text("SELECT id, trust_credentials FROM module_environments")
|
|
).fetchall()
|
|
|
|
migrated = 0
|
|
for row_id, creds in rows:
|
|
if not creds:
|
|
continue
|
|
if isinstance(creds, str):
|
|
import json
|
|
|
|
creds = json.loads(creds)
|
|
conn.execute(
|
|
sa.text(
|
|
"UPDATE module_environments "
|
|
"SET trust_credentials_enc = :enc, trust_credentials = '{}'::json "
|
|
"WHERE id = :id"
|
|
),
|
|
{"enc": encrypt_json(creds), "id": row_id},
|
|
)
|
|
migrated += 1
|
|
|
|
print(f" encrypted trust credentials for {migrated} module environment(s)")
|
|
|
|
|
|
def downgrade() -> None:
|
|
from app.core.crypto import decrypt_json
|
|
|
|
conn = op.get_bind()
|
|
rows = conn.execute(
|
|
sa.text(
|
|
"SELECT id, trust_credentials_enc FROM module_environments "
|
|
"WHERE trust_credentials_enc IS NOT NULL"
|
|
)
|
|
).fetchall()
|
|
|
|
import json
|
|
|
|
for row_id, enc in rows:
|
|
conn.execute(
|
|
sa.text(
|
|
"UPDATE module_environments SET trust_credentials = CAST(:c AS json) "
|
|
"WHERE id = :id"
|
|
),
|
|
{"c": json.dumps(decrypt_json(enc)), "id": row_id},
|
|
)
|
|
|
|
op.drop_column("module_environments", "trust_credentials_enc")
|