Skip to content

Commit

Permalink
feat(cli): add new migrate-secret-key command (reanahub#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonadoni committed Nov 19, 2024
1 parent e0cba7f commit efcbe72
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 2 deletions.
20 changes: 20 additions & 0 deletions reana_db/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from reana_db.database import init_db
from reana_db.models import Resource, ResourceType
from reana_db.utils import (
change_key_encrypted_columns,
update_users_cpu_quota,
update_users_disk_quota,
update_workflows_cpu_quota,
Expand All @@ -43,6 +44,25 @@ def init():
click.secho("Database initialised.", fg="green")


@cli.command()
@click.option(
"--old-key",
required=True,
help="Previous key used to encrypt database columns.",
)
def migrate_secret_key(old_key):
"""Change the secret key used to encrypt database columns."""
click.echo("Migrating secret key...")

try:
change_key_encrypted_columns(old_key)
except Exception:
logging.exception("Failed to migrate secret key")
sys.exit(1)

click.echo("Successfully migrated secret key")


@cli.group("alembic")
@click.pass_context
def alembic_group(ctx):
Expand Down
13 changes: 11 additions & 2 deletions reana_db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine
from sqlalchemy.dialects.postgresql import ARRAY

import reana_db.config
from reana_db.config import (
DB_SECRET_KEY,
DEFAULT_QUOTA_LIMITS,
DEFAULT_QUOTA_RESOURCES,
WORKFLOW_TERMINATION_QUOTA_UPDATE_POLICY,
Expand Down Expand Up @@ -87,6 +87,15 @@ def generate_uuid():
return str(uuid.uuid4())


def _secret_key():
"""Secret key used to encrypt databse columns.
Do not use `DB_SECRET_KEY` directly, as that does not let us change the key
at runtime, which is needed when migrating between different keys.
"""
return reana_db.config.DB_SECRET_KEY


class QuotaBase:
"""Quota base functionality."""

Expand Down Expand Up @@ -326,7 +335,7 @@ class UserToken(Base, Timestamp):

id_ = Column(UUIDType, primary_key=True, default=generate_uuid)
token = Column(
EncryptedType(String(length=255), DB_SECRET_KEY, AesEngine, "pkcs5"),
EncryptedType(String(length=255), _secret_key, AesEngine, "pkcs5"),
unique=True,
)
status = Column(Enum(UserTokenStatus))
Expand Down
30 changes: 30 additions & 0 deletions reana_db/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,33 @@ def update_workflows_disk_quota() -> None:
for workflow in workflows:
store_workflow_disk_quota(workflow)
timer.count_event()


def change_key_encrypted_columns(old_key):
"""Re-encrypt database columns with new secret key.
REANA should be already deployed with the new secret key in `REANA_SECRET_KEY`.
The old key is needed to decrypt the database and is passed as parameter.
"""
from reana_db.database import Session
from reana_db.models import UserToken
from reana_db import config

new_key = config.DB_SECRET_KEY

# set old key to be able to decrypt columns in database
config.DB_SECRET_KEY = old_key

# read the columns from the database
user_tokens = Session.query(UserToken.id_, UserToken.token).all()
Session.expunge_all()

# revert to new key
config.DB_SECRET_KEY = new_key

# write columns to the database to encrypt them with new key
for user_token in user_tokens:
UserToken.query.filter_by(id_=user_token.id_).update(
{"token": user_token.token}
)
Session.commit()

0 comments on commit efcbe72

Please sign in to comment.