diff --git a/barbican/cmd/barbican_manage.py b/barbican/cmd/barbican_manage.py index bbb17ade6..6a7db1089 100644 --- a/barbican/cmd/barbican_manage.py +++ b/barbican/cmd/barbican_manage.py @@ -26,6 +26,7 @@ from oslo_config import cfg from oslo_log import log as logging from barbican.cmd import pkcs11_kek_rewrap as pkcs11_rewrap +from barbican.cmd import simple_crypto_kek_rewrap from barbican.common import config from barbican.model import clean from barbican.model.migration import commands @@ -348,9 +349,26 @@ class HSMCommands(object): sys.exit(1) +class SimpleCryptoCommands: + """Class for mananging SimpleCryptoPlugin backend""" + + description = "Subcommands for managing SimpleCryptoPlugin backend" + + rewrap_pkek_description = "Re-wrap project KEKs" + + @args('--dry-run', action='store_true', dest='dryrun', default=False, + help="Displays changes that will be made (non-destructive)") + def rewrap_pkek(self, conf, dryrun=True): + rewrapper = simple_crypto_kek_rewrap.SimpleCryptoKEKRewrap( + simple_crypto_kek_rewrap.CONF + ) + rewrapper.execute(dryrun) + + CATEGORIES = { 'db': DbCommands, 'hsm': HSMCommands, + 'simple_crypto': SimpleCryptoCommands, } diff --git a/barbican/cmd/kek_rewrap.py b/barbican/cmd/kek_rewrap.py new file mode 100644 index 000000000..9dcb55738 --- /dev/null +++ b/barbican/cmd/kek_rewrap.py @@ -0,0 +1,77 @@ +# Copyright 2025 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import abc +import traceback + +from oslo_db.sqlalchemy import session +from sqlalchemy import orm +from sqlalchemy.orm import scoping + +from barbican.model import models + + +class BaseKEKRewrap(metaclass=abc.ABCMeta): + + def __init__(self, conf): + self.db_engine = session.create_engine(conf.database.connection) + self._session_creator = scoping.scoped_session( + orm.sessionmaker( + bind=self.db_engine, + ) + ) + + @abc.abstractmethod + def rewrap_kek(self, project, kek): + raise NotImplementedError + + @property + def db_session(self): + return self._session_creator() + + def get_keks_for_project(self, project): + keks = [] + with self.db_session.begin() as transaction: + print('Retrieving KEKs for Project {}'.format(project.id)) + query = transaction.session.query(models.KEKDatum) + query = query.filter_by(project_id=project.id) + query = query.filter_by(plugin_name=self.plugin_name) + + keks = query.all() + + return keks + + def get_projects(self): + print('Retrieving all available projects') + + projects = [] + with self.db_session.begin() as transaction: + projects = transaction.session.query(models.Project).all() + + return projects + + def execute(self, dry_run=True): + self.dry_run = dry_run + if self.dry_run: + print('-- Running in dry-run mode --') + + projects = self.get_projects() + for project in projects: + keks = self.get_keks_for_project(project) + for kek in keks: + try: + self.rewrap_kek(project, kek) + except Exception: + print('Error occurred! SQLAlchemy automatically rolled-' + 'back the transaction') + traceback.print_exc() diff --git a/barbican/cmd/pkcs11_kek_rewrap.py b/barbican/cmd/pkcs11_kek_rewrap.py index ea447136c..0bdc3c375 100644 --- a/barbican/cmd/pkcs11_kek_rewrap.py +++ b/barbican/cmd/pkcs11_kek_rewrap.py @@ -14,32 +14,23 @@ import argparse import base64 -import traceback -from oslo_db.sqlalchemy import session from oslo_serialization import jsonutils as json -from sqlalchemy import orm -from sqlalchemy.orm import scoping +from barbican.cmd import kek_rewrap from barbican.common import utils -from barbican.model import models from barbican.plugin.crypto import p11_crypto + # Use config values from p11_crypto CONF = p11_crypto.CONF -class KekRewrap(object): +class KekRewrap(kek_rewrap.BaseKEKRewrap): def __init__(self, conf): + super().__init__(conf) self.dry_run = False - self.db_engine = session.create_engine(conf.database.connection) - self._session_creator = scoping.scoped_session( - orm.sessionmaker( - bind=self.db_engine, - autocommit=True - ) - ) self.crypto_plugin = p11_crypto.P11CryptoPlugin(conf) self.pkcs11 = self.crypto_plugin.pkcs11 self.plugin_name = utils.generate_fullname_for(self.crypto_plugin) @@ -128,47 +119,6 @@ class KekRewrap(object): # Update KEK metadata in DB kek.plugin_meta = p11_crypto.json_dumps_compact(updated_meta) - def get_keks_for_project(self, project): - keks = [] - with self.db_session.begin() as transaction: - print('Retrieving KEKs for Project {}'.format(project.id)) - query = transaction.session.query(models.KEKDatum) - query = query.filter_by(project_id=project.id) - query = query.filter_by(plugin_name=self.plugin_name) - - keks = query.all() - - return keks - - def get_projects(self): - print('Retrieving all available projects') - - projects = [] - with self.db_session.begin() as transaction: - projects = transaction.session.query(models.Project).all() - - return projects - - @property - def db_session(self): - return self._session_creator() - - def execute(self, dry_run=True): - self.dry_run = dry_run - if self.dry_run: - print('-- Running in dry-run mode --') - - projects = self.get_projects() - for project in projects: - keks = self.get_keks_for_project(project) - for kek in keks: - try: - self.rewrap_kek(project, kek) - except Exception: - print('Error occurred! SQLAlchemy automatically rolled-' - 'back the transaction') - traceback.print_exc() - def main(): script_desc = 'Utility to re-wrap project KEKs after rotating an MKEK.' diff --git a/barbican/cmd/simple_crypto_kek_rewrap.py b/barbican/cmd/simple_crypto_kek_rewrap.py new file mode 100644 index 000000000..1effd1aa0 --- /dev/null +++ b/barbican/cmd/simple_crypto_kek_rewrap.py @@ -0,0 +1,46 @@ +# Copyright 2025 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cryptography import fernet + +from barbican.cmd import kek_rewrap +from barbican.common import utils +from barbican.plugin.crypto import simple_crypto + + +CONF = simple_crypto.CONF + + +class SimpleCryptoKEKRewrap(kek_rewrap.BaseKEKRewrap): + + def __init__(self, conf): + super().__init__(conf) + + self.crypto_plugin = simple_crypto.SimpleCryptoPlugin(conf) + self.plugin_name = utils.generate_fullname_for(self.crypto_plugin) + self.master_keys = conf.simple_crypto_plugin.kek + + def rewrap_kek(self, project, kek): + with self.db_session.begin(): + encrypted_pkek = kek.plugin_meta + + if self.dry_run: + print("Would have rotated PKEK {}".format(kek.kek_label)) + return + + encryptor = fernet.MultiFernet( + [fernet.Fernet(mkek) for mkek in self.master_keys] + ) + rotated_pkek = encryptor.rotate(encrypted_pkek) + kek.plugin_meta = rotated_pkek diff --git a/releasenotes/notes/simple-crypto-kek-rotation-b8fe76b32aa76190.yaml b/releasenotes/notes/simple-crypto-kek-rotation-b8fe76b32aa76190.yaml new file mode 100644 index 000000000..f7a2f6ff8 --- /dev/null +++ b/releasenotes/notes/simple-crypto-kek-rotation-b8fe76b32aa76190.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + Key-encryption-key rotation has been implemented for for the Simple Crypto + plugin backend. A new symmetric Fernet key can be created and added to + the configuration file at any time. The `kek` option in the + `[simple_crypto_plugin]` section can now be specified multiple times. + When more than one KEK is configured, the first key is used to encrypt + new project-specific keys (pKEKs) and the rest of the keys are only used + to decrypt existing data. + + A new sub-command has been added to `barbican-manage` to re-encrypt + existing pKEKs using the first `kek` in the config file. This command + can be executed to ensure that all pKEKs in the database are re-encrypted + with a specific key. + + To fully rotate an existing KEK, you can now generate a new KEK to replace + ane existing key. You can add the new key as the first `kek` in the + configuration file, and keep the existing key as the second `kek`. Then + you can execute `barbican-manage simple_crypto rewrap_pkek` to re-encrypt + all existing pKEKs with the new key. After the command executes, you can + remove any previous keys from the config file.