Enable pKEK rewrap with SimpleCrypto

This patch adds a new sub-command to the `barbican-manage` command line
tool to rewrap all pKEKs using the currently active KEK from the config
file.

Change-Id: I1e67d9a1aadbe08ddc04854eccaf195c7c6c12b1
This commit is contained in:
Douglas Mendizábal 2025-02-26 16:15:50 -05:00 committed by Douglas Mendizabal
parent cfba1c1ba8
commit 4500d9f485
5 changed files with 167 additions and 54 deletions

View File

@ -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,
}

View File

@ -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()

View File

@ -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.'

View File

@ -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

View File

@ -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.