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 oslo_log import log as logging
|
||||||
|
|
||||||
from barbican.cmd import pkcs11_kek_rewrap as pkcs11_rewrap
|
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.common import config
|
||||||
from barbican.model import clean
|
from barbican.model import clean
|
||||||
from barbican.model.migration import commands
|
from barbican.model.migration import commands
|
||||||
@ -348,9 +349,26 @@ class HSMCommands(object):
|
|||||||
sys.exit(1)
|
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 = {
|
CATEGORIES = {
|
||||||
'db': DbCommands,
|
'db': DbCommands,
|
||||||
'hsm': HSMCommands,
|
'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 argparse
|
||||||
import base64
|
import base64
|
||||||
import traceback
|
|
||||||
|
|
||||||
from oslo_db.sqlalchemy import session
|
|
||||||
from oslo_serialization import jsonutils as json
|
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.common import utils
|
||||||
from barbican.model import models
|
|
||||||
from barbican.plugin.crypto import p11_crypto
|
from barbican.plugin.crypto import p11_crypto
|
||||||
|
|
||||||
|
|
||||||
# Use config values from p11_crypto
|
# Use config values from p11_crypto
|
||||||
CONF = p11_crypto.CONF
|
CONF = p11_crypto.CONF
|
||||||
|
|
||||||
|
|
||||||
class KekRewrap(object):
|
class KekRewrap(kek_rewrap.BaseKEKRewrap):
|
||||||
|
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
|
super().__init__(conf)
|
||||||
self.dry_run = False
|
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.crypto_plugin = p11_crypto.P11CryptoPlugin(conf)
|
||||||
self.pkcs11 = self.crypto_plugin.pkcs11
|
self.pkcs11 = self.crypto_plugin.pkcs11
|
||||||
self.plugin_name = utils.generate_fullname_for(self.crypto_plugin)
|
self.plugin_name = utils.generate_fullname_for(self.crypto_plugin)
|
||||||
@ -128,47 +119,6 @@ class KekRewrap(object):
|
|||||||
# Update KEK metadata in DB
|
# Update KEK metadata in DB
|
||||||
kek.plugin_meta = p11_crypto.json_dumps_compact(updated_meta)
|
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():
|
def main():
|
||||||
script_desc = 'Utility to re-wrap project KEKs after rotating an MKEK.'
|
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