Merge "Add simple_crypto new_pkek subcommand"
This commit is contained in:
commit
da7482eab0
@ -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 pkcs11_kek_rewrap as pkcs11_rewrap
|
||||||
from barbican.cmd import simple_crypto_kek_rewrap
|
from barbican.cmd import simple_crypto_kek_rewrap
|
||||||
|
from barbican.cmd import simple_crypto_pkek
|
||||||
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
|
||||||
@ -364,6 +365,15 @@ class SimpleCryptoCommands:
|
|||||||
)
|
)
|
||||||
rewrapper.execute(dryrun)
|
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 = {
|
CATEGORIES = {
|
||||||
'db': DbCommands,
|
'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_db.sqlalchemy import enginefacade
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import sqlalchemy
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import func as sa_func
|
from sqlalchemy import func as sa_func
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
import sqlalchemy.orm as sa_orm
|
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
|
# Utilize SQLAlchemy's scoped_session to ensure that we only have one
|
||||||
# session instance per thread.
|
# session instance per thread.
|
||||||
session_maker = sa_orm.sessionmaker(bind=_ENGINE)
|
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:
|
if initialize_secret_stores:
|
||||||
_initialize_secret_stores_data()
|
_initialize_secret_stores_data()
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ def _get_engine(engine):
|
|||||||
db_connection.close()
|
db_connection.close()
|
||||||
|
|
||||||
if CONF.db_auto_create:
|
if CONF.db_auto_create:
|
||||||
meta = sqlalchemy.MetaData()
|
meta = sa.MetaData()
|
||||||
meta.reflect(bind=engine)
|
meta.reflect(bind=engine)
|
||||||
tables = meta.tables
|
tables = meta.tables
|
||||||
|
|
||||||
@ -532,7 +532,7 @@ class BaseRepo(object):
|
|||||||
for entity in query:
|
for entity in query:
|
||||||
# Its a soft delete so its more like entity update
|
# Its a soft delete so its more like entity update
|
||||||
entity.delete(session=session)
|
entity.delete(session=session)
|
||||||
except sqlalchemy.exc.SQLAlchemyError:
|
except sa.exc.SQLAlchemyError:
|
||||||
LOG.exception('Problem finding project related entity to delete')
|
LOG.exception('Problem finding project related entity to delete')
|
||||||
if not suppress_exception:
|
if not suppress_exception:
|
||||||
raise exception.BarbicanException(u._('Error deleting project '
|
raise exception.BarbicanException(u._('Error deleting project '
|
||||||
@ -929,7 +929,21 @@ class KEKDatumRepo(BaseRepo):
|
|||||||
plugin_name,
|
plugin_name,
|
||||||
suppress_exception=False,
|
suppress_exception=False,
|
||||||
session=None):
|
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:
|
if not plugin_name:
|
||||||
raise exception.BarbicanException(
|
raise exception.BarbicanException(
|
||||||
u._('Tried to register crypto plugin with null or empty '
|
u._('Tried to register crypto plugin with null or empty '
|
||||||
@ -937,48 +951,70 @@ class KEKDatumRepo(BaseRepo):
|
|||||||
|
|
||||||
kek_datum = None
|
kek_datum = None
|
||||||
|
|
||||||
session = self.get_session(session)
|
kek_datums = self._get_active_kek_datums(project.id, plugin_name)
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
if not kek_datums:
|
if not kek_datums:
|
||||||
kek_datum = models.KEKDatum()
|
kek_datum = self.create_kek_datum(project, plugin_name)
|
||||||
|
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
kek_datum = kek_datums.pop()
|
|
||||||
|
|
||||||
# (alee) There should be only one active KEKDatum.
|
# (alee) There should be only one active KEKDatum.
|
||||||
# Due to a race condition with many threads or
|
# Due to a race condition with many threads or
|
||||||
# many barbican processes, its possible to have
|
# many barbican processes, its possible to have
|
||||||
# multiple active KEKDatum. The code below makes
|
# multiple active KEKDatum. The code below makes
|
||||||
# all the extra KEKDatum inactive
|
# all the extra KEKDatum inactive
|
||||||
# See LP#1726378
|
# See LP#1726378
|
||||||
for kd in kek_datums:
|
kek_datum = kek_datums.pop()
|
||||||
LOG.debug(
|
self._deactivate_kek_datums(kek_datums)
|
||||||
"Multiple active KEKDatum found for %s."
|
|
||||||
"Setting %s to be inactive.",
|
|
||||||
project.external_id,
|
|
||||||
kd.kek_label)
|
|
||||||
kd.active = False
|
|
||||||
self.save(kd)
|
|
||||||
|
|
||||||
return kek_datum
|
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):
|
def _do_entity_name(self):
|
||||||
"""Sub-class hook: return entity name, such as for debugging."""
|
"""Sub-class hook: return entity name, such as for debugging."""
|
||||||
return "KEKDatum"
|
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