Add simple_crypto new_pkek subcommand
This patch adds a new subcommand to `barbican-manage`: barbican-manage simple_crypto new_pkek --project $PROJECT_ID The subcommand adds a new Project-specific Key-encryption-key (pKEK) for the specified project. Previous pKEKs are marked as inactive, so all new secrets uploaded for that project will be encrypted using the new pKEK. Change-Id: Ica7ab4889ffb656d026095db8b43590000ceb546
This commit is contained in:
parent
4500d9f485
commit
b07d8e6fbd
@ -27,6 +27,7 @@ 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.cmd import simple_crypto_pkek
|
||||
from barbican.common import config
|
||||
from barbican.model import clean
|
||||
from barbican.model.migration import commands
|
||||
@ -364,6 +365,15 @@ class SimpleCryptoCommands:
|
||||
)
|
||||
rewrapper.execute(dryrun)
|
||||
|
||||
new_pkek_description = ("Create a new Project-specific Key-Encryption-Key "
|
||||
"(pKEK) for the given project-id.")
|
||||
|
||||
@args('--project', dest='project_id', metavar='<project_id>',
|
||||
help="External Project ID e.g. Keystone Project ID.")
|
||||
def new_pkek(self, conf, project_id):
|
||||
pkek_cmd = simple_crypto_pkek.SimpleCryptoPKEK(simple_crypto_pkek.CONF)
|
||||
pkek_cmd.new_pkek(project_id)
|
||||
|
||||
|
||||
CATEGORIES = {
|
||||
'db': DbCommands,
|
||||
|
42
barbican/cmd/simple_crypto_pkek.py
Normal file
42
barbican/cmd/simple_crypto_pkek.py
Normal file
@ -0,0 +1,42 @@
|
||||
# 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 barbican.common import resources
|
||||
from barbican.common import utils
|
||||
from barbican.model import repositories
|
||||
from barbican.plugin.crypto import simple_crypto
|
||||
|
||||
|
||||
CONF = simple_crypto.CONF
|
||||
|
||||
|
||||
class SimpleCryptoPKEK:
|
||||
|
||||
def __init__(self, conf):
|
||||
self.crypto_plugin = simple_crypto.SimpleCryptoPlugin(conf)
|
||||
self.crypto_plugin_name = utils.generate_fullname_for(
|
||||
self.crypto_plugin
|
||||
)
|
||||
repositories.setup_database_engine_and_factory()
|
||||
|
||||
def new_pkek(self, external_id):
|
||||
"""Creates a new Project-specific KEK
|
||||
|
||||
:param str external_id: Project ID as defined by the external identity
|
||||
system. e.g. Keystone Project ID.
|
||||
"""
|
||||
print(f"Generating new pKEK for {external_id}")
|
||||
project = resources.get_or_create_project(external_id)
|
||||
kek_repo = repositories.get_kek_datum_repository()
|
||||
_ = kek_repo.create_kek_datum(project, self.crypto_plugin_name)
|
||||
repositories.commit()
|
@ -29,7 +29,7 @@ from oslo_db import exception as db_exc
|
||||
from oslo_db.sqlalchemy import enginefacade
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
import sqlalchemy
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import func as sa_func
|
||||
from sqlalchemy import or_
|
||||
import sqlalchemy.orm as sa_orm
|
||||
@ -111,7 +111,7 @@ def setup_database_engine_and_factory(initialize_secret_stores=False):
|
||||
# Utilize SQLAlchemy's scoped_session to ensure that we only have one
|
||||
# session instance per thread.
|
||||
session_maker = sa_orm.sessionmaker(bind=_ENGINE)
|
||||
_SESSION_FACTORY = sqlalchemy.orm.scoped_session(session_maker)
|
||||
_SESSION_FACTORY = sa.orm.scoped_session(session_maker)
|
||||
if initialize_secret_stores:
|
||||
_initialize_secret_stores_data()
|
||||
|
||||
@ -182,7 +182,7 @@ def _get_engine(engine):
|
||||
db_connection.close()
|
||||
|
||||
if CONF.db_auto_create:
|
||||
meta = sqlalchemy.MetaData()
|
||||
meta = sa.MetaData()
|
||||
meta.reflect(bind=engine)
|
||||
tables = meta.tables
|
||||
|
||||
@ -532,7 +532,7 @@ class BaseRepo(object):
|
||||
for entity in query:
|
||||
# Its a soft delete so its more like entity update
|
||||
entity.delete(session=session)
|
||||
except sqlalchemy.exc.SQLAlchemyError:
|
||||
except sa.exc.SQLAlchemyError:
|
||||
LOG.exception('Problem finding project related entity to delete')
|
||||
if not suppress_exception:
|
||||
raise exception.BarbicanException(u._('Error deleting project '
|
||||
@ -929,7 +929,21 @@ class KEKDatumRepo(BaseRepo):
|
||||
plugin_name,
|
||||
suppress_exception=False,
|
||||
session=None):
|
||||
"""Find or create a KEK datum instance."""
|
||||
"""Find or create a KEK datum instance
|
||||
|
||||
Returns the active KEK datum for the given project. This method also
|
||||
ensures there is only one active KEK. If more than one active KEK
|
||||
is found, the newest KEK will be used and all others will be
|
||||
deactivated.
|
||||
|
||||
:param project: Project instance to be associated with the new KEK
|
||||
:type project: :py:class:`barbican.model.models.Project`
|
||||
:param str plugin_name: Fully qualified class name to identify
|
||||
the encryption plugin.
|
||||
:param bool supress_exception: ? - not used
|
||||
:param session:
|
||||
:type session: :py:class:`sqlalchemy.orm.Session` or None
|
||||
"""
|
||||
if not plugin_name:
|
||||
raise exception.BarbicanException(
|
||||
u._('Tried to register crypto plugin with null or empty '
|
||||
@ -937,48 +951,70 @@ class KEKDatumRepo(BaseRepo):
|
||||
|
||||
kek_datum = None
|
||||
|
||||
session = self.get_session(session)
|
||||
|
||||
query = session.query(models.KEKDatum)
|
||||
query = query.filter_by(project_id=project.id,
|
||||
plugin_name=plugin_name,
|
||||
active=True,
|
||||
deleted=False)
|
||||
|
||||
query = query.order_by(models.KEKDatum.created_at)
|
||||
|
||||
kek_datums = query.all()
|
||||
kek_datums = self._get_active_kek_datums(project.id, plugin_name)
|
||||
|
||||
if not kek_datums:
|
||||
kek_datum = models.KEKDatum()
|
||||
|
||||
kek_datum.kek_label = "project-{0}-key-{1}".format(
|
||||
project.external_id, uuidutils.generate_uuid())
|
||||
kek_datum.project_id = project.id
|
||||
kek_datum.plugin_name = plugin_name
|
||||
kek_datum.status = models.States.ACTIVE
|
||||
|
||||
self.save(kek_datum)
|
||||
kek_datum = self.create_kek_datum(project, plugin_name)
|
||||
else:
|
||||
kek_datum = kek_datums.pop()
|
||||
|
||||
# (alee) There should be only one active KEKDatum.
|
||||
# Due to a race condition with many threads or
|
||||
# many barbican processes, its possible to have
|
||||
# multiple active KEKDatum. The code below makes
|
||||
# all the extra KEKDatum inactive
|
||||
# See LP#1726378
|
||||
for kd in kek_datums:
|
||||
LOG.debug(
|
||||
"Multiple active KEKDatum found for %s."
|
||||
"Setting %s to be inactive.",
|
||||
project.external_id,
|
||||
kd.kek_label)
|
||||
kd.active = False
|
||||
self.save(kd)
|
||||
kek_datum = kek_datums.pop()
|
||||
self._deactivate_kek_datums(kek_datums)
|
||||
|
||||
return kek_datum
|
||||
|
||||
def create_kek_datum(self, project, plugin_name):
|
||||
"""Create a new KEK instance
|
||||
|
||||
Creates a new KEK instance associated with the given project and
|
||||
plugin, and also deactivates any existing KEKs.
|
||||
|
||||
:param project: Project instance to be associated with the new KEK
|
||||
:type project: :py:class:`barbican.model.models.Project`
|
||||
:param str plugin_name: Fully qualified class name to identify
|
||||
the encryption plugin.
|
||||
"""
|
||||
if not plugin_name:
|
||||
raise exception.BarbicanException(
|
||||
u._('Tried to register crypto plugin with null or empty '
|
||||
'name.'))
|
||||
# Deactivate any existing KEKs
|
||||
kek_datums = self._get_active_kek_datums(project.id, plugin_name)
|
||||
self._deactivate_kek_datums(kek_datums)
|
||||
|
||||
# Create new unbound KEK
|
||||
new_kek = models.KEKDatum()
|
||||
new_kek_id = uuidutils.generate_uuid()
|
||||
new_kek.kek_label = f"project-{project.external_id}-key-{new_kek_id}"
|
||||
new_kek.project_id = project.id
|
||||
new_kek.plugin_name = plugin_name
|
||||
new_kek.status = models.States.ACTIVE
|
||||
LOG.info(f"Created new KEK {new_kek.kek_label}")
|
||||
self.save(new_kek)
|
||||
return new_kek
|
||||
|
||||
def _get_active_kek_datums(self, project_id, plugin_name):
|
||||
session = self.get_session()
|
||||
stmt = sa.select(models.KEKDatum).where(
|
||||
sa.and_(
|
||||
models.KEKDatum.project_id == project_id,
|
||||
models.KEKDatum.plugin_name == plugin_name,
|
||||
models.KEKDatum.active.is_(True),
|
||||
models.KEKDatum.deleted.is_(False)
|
||||
)
|
||||
).order_by(models.KEKDatum.created_at)
|
||||
return session.execute(stmt).scalars().all()
|
||||
|
||||
def _deactivate_kek_datums(self, kek_datums):
|
||||
for kek in kek_datums:
|
||||
LOG.info(f"Deactivating KEK {kek.kek_label}")
|
||||
kek.active = False
|
||||
self.save(kek)
|
||||
|
||||
def _do_entity_name(self):
|
||||
"""Sub-class hook: return entity name, such as for debugging."""
|
||||
return "KEKDatum"
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
security:
|
||||
- |
|
||||
Added a new subcommand to `barbican-manage`:
|
||||
|
||||
barbican-manage simple_crypto new_pkek --project $PROJECT_ID
|
||||
|
||||
This new command creates a new Project-specific Key-encryption-key (pKEK)
|
||||
for the specified project. New secrets created in that project will be
|
||||
encrypted with this new pKEK. Existing secrets are not modified by this
|
||||
command.
|
Loading…
x
Reference in New Issue
Block a user