Merge "Add simple_crypto new_pkek subcommand"

This commit is contained in:
Zuul 2025-03-25 11:01:44 +00:00 committed by Gerrit Code Review
commit da7482eab0
4 changed files with 134 additions and 35 deletions

View File

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

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

View File

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

View File

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