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:
parent
cfba1c1ba8
commit
4500d9f485
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
77
barbican/cmd/kek_rewrap.py
Normal file
77
barbican/cmd/kek_rewrap.py
Normal 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()
|
@ -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.'
|
||||
|
46
barbican/cmd/simple_crypto_kek_rewrap.py
Normal file
46
barbican/cmd/simple_crypto_kek_rewrap.py
Normal 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
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user