b05c4b6f86
Modified logic to retrieve applicable plugins when preferred project is set. This is part 2 of central logic changes needed for multiple backend support work. Change-Id: I15eb7ca88611a12677227647e3026822e67413b0 Partially-Implements: blueprint multiple-secret-backend
412 lines
16 KiB
Python
412 lines
16 KiB
Python
# 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 exception
|
|
from barbican.common import utils
|
|
from barbican.model import models
|
|
from barbican.model import repositories as repos
|
|
from barbican.plugin.interface import secret_store
|
|
from barbican.plugin import store_crypto
|
|
from barbican.plugin.util import translations as tr
|
|
|
|
|
|
def _get_transport_key_model(key_spec, transport_key_needed, project_id):
|
|
key_model = None
|
|
if transport_key_needed:
|
|
# get_plugin_store() will throw an exception if no suitable
|
|
# plugin with transport key is found
|
|
plugin_manager = secret_store.get_manager()
|
|
store_plugin = plugin_manager.get_plugin_store(
|
|
key_spec=key_spec, transport_key_needed=True,
|
|
project_id=project_id)
|
|
plugin_name = utils.generate_fullname_for(store_plugin)
|
|
|
|
key_repo = repos.get_transport_key_repository()
|
|
key_model = key_repo.get_latest_transport_key(plugin_name)
|
|
|
|
if not key_model or not store_plugin.is_transport_key_current(
|
|
key_model.transport_key):
|
|
# transport key does not exist or is not current.
|
|
# need to get a new transport key
|
|
transport_key = store_plugin.get_transport_key()
|
|
new_key_model = models.TransportKey(plugin_name, transport_key)
|
|
key_model = key_repo.create_from(new_key_model)
|
|
return key_model
|
|
|
|
|
|
def _get_plugin_name_and_transport_key(transport_key_id):
|
|
plugin_name = None
|
|
transport_key = None
|
|
if transport_key_id is not None:
|
|
transport_key_repo = repos.get_transport_key_repository()
|
|
try:
|
|
transport_key_model = transport_key_repo.get(
|
|
entity_id=transport_key_id)
|
|
except exception.NotFound:
|
|
raise exception.ProvidedTransportKeyNotFound(str(transport_key_id))
|
|
|
|
plugin_name = transport_key_model.plugin_name
|
|
if plugin_name is None:
|
|
raise ValueError("Invalid plugin name for transport key")
|
|
|
|
transport_key = transport_key_model.transport_key
|
|
|
|
return plugin_name, transport_key
|
|
|
|
|
|
def store_secret(unencrypted_raw, content_type_raw, content_encoding,
|
|
secret_model, project_model,
|
|
transport_key_needed=False,
|
|
transport_key_id=None):
|
|
"""Store a provided secret into secure backend."""
|
|
if _secret_already_has_stored_data(secret_model):
|
|
raise ValueError('Secret already has encrypted data stored for it.')
|
|
|
|
# Create a KeySpec to find a plugin that will support storing the secret
|
|
key_spec = secret_store.KeySpec(alg=secret_model.algorithm,
|
|
bit_length=secret_model.bit_length,
|
|
mode=secret_model.mode)
|
|
|
|
# If there is no secret data to store, then just create Secret entity and
|
|
# leave. A subsequent call to this method should provide both the Secret
|
|
# entity created here *and* the secret data to store into it.
|
|
if not unencrypted_raw:
|
|
key_model = _get_transport_key_model(key_spec, transport_key_needed,
|
|
project_id=project_model.id)
|
|
|
|
_save_secret_in_repo(secret_model, project_model)
|
|
return secret_model, key_model
|
|
|
|
plugin_name, transport_key = _get_plugin_name_and_transport_key(
|
|
transport_key_id)
|
|
|
|
unencrypted, content_type = tr.normalize_before_encryption(
|
|
unencrypted_raw, content_type_raw, content_encoding,
|
|
secret_model.secret_type, enforce_text_only=True)
|
|
|
|
plugin_manager = secret_store.get_manager()
|
|
store_plugin = plugin_manager.get_plugin_store(key_spec=key_spec,
|
|
plugin_name=plugin_name,
|
|
project_id=project_model.id)
|
|
|
|
secret_dto = secret_store.SecretDTO(type=secret_model.secret_type,
|
|
secret=unencrypted,
|
|
key_spec=key_spec,
|
|
content_type=content_type,
|
|
transport_key=transport_key)
|
|
|
|
secret_metadata = _store_secret_using_plugin(store_plugin, secret_dto,
|
|
secret_model, project_model)
|
|
_save_secret_in_repo(secret_model, project_model)
|
|
_save_secret_metadata_in_repo(secret_model, secret_metadata, store_plugin,
|
|
content_type)
|
|
|
|
return secret_model, None
|
|
|
|
|
|
def get_secret(requesting_content_type, secret_model, project_model,
|
|
twsk=None, transport_key=None):
|
|
secret_metadata = _get_secret_meta(secret_model)
|
|
|
|
# NOTE: */* is the pecan default meaning no content type sent in. In this
|
|
# case we should use the mime type stored in the metadata.
|
|
if requesting_content_type == '*/*':
|
|
requesting_content_type = secret_metadata['content_type']
|
|
|
|
tr.analyze_before_decryption(requesting_content_type)
|
|
|
|
if twsk is not None:
|
|
secret_metadata['trans_wrapped_session_key'] = twsk
|
|
secret_metadata['transport_key'] = transport_key
|
|
|
|
# Locate a suitable plugin to store the secret.
|
|
plugin_manager = secret_store.get_manager()
|
|
retrieve_plugin = plugin_manager.get_plugin_retrieve_delete(
|
|
secret_metadata.get('plugin_name'))
|
|
|
|
# Retrieve the secret.
|
|
secret_dto = _get_secret(
|
|
retrieve_plugin, secret_metadata, secret_model, project_model)
|
|
|
|
if twsk is not None:
|
|
del secret_metadata['transport_key']
|
|
del secret_metadata['trans_wrapped_session_key']
|
|
|
|
# Denormalize the secret.
|
|
return tr.denormalize_after_decryption(secret_dto.secret,
|
|
requesting_content_type)
|
|
|
|
|
|
def get_transport_key_id_for_retrieval(secret_model):
|
|
"""Return a transport key ID for retrieval if the plugin supports it."""
|
|
|
|
secret_metadata = _get_secret_meta(secret_model)
|
|
|
|
plugin_manager = secret_store.get_manager()
|
|
retrieve_plugin = plugin_manager.get_plugin_retrieve_delete(
|
|
secret_metadata.get('plugin_name'))
|
|
|
|
transport_key_id = retrieve_plugin.get_transport_key()
|
|
return transport_key_id
|
|
|
|
|
|
def generate_secret(spec, content_type, project_model):
|
|
"""Generate a secret and store into a secure backend."""
|
|
|
|
# Locate a suitable plugin to store the secret.
|
|
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
|
|
bit_length=spec.get('bit_length'),
|
|
mode=spec.get('mode'))
|
|
|
|
plugin_manager = secret_store.get_manager()
|
|
generate_plugin = plugin_manager.get_plugin_generate(
|
|
key_spec, project_id=project_model.id)
|
|
|
|
# Create secret model to eventually save metadata to.
|
|
secret_model = models.Secret(spec)
|
|
secret_model['secret_type'] = secret_store.SecretType.SYMMETRIC
|
|
|
|
# Generate the secret.
|
|
secret_metadata = _generate_symmetric_key(
|
|
generate_plugin, key_spec, secret_model, project_model, content_type)
|
|
|
|
# Save secret and metadata.
|
|
_save_secret_in_repo(secret_model, project_model)
|
|
_save_secret_metadata_in_repo(secret_model, secret_metadata,
|
|
generate_plugin, content_type)
|
|
|
|
return secret_model
|
|
|
|
|
|
def generate_asymmetric_secret(spec, content_type, project_model):
|
|
"""Generate an asymmetric secret and store into a secure backend."""
|
|
# Locate a suitable plugin to store the secret.
|
|
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
|
|
bit_length=spec.get('bit_length'),
|
|
passphrase=spec.get('passphrase'))
|
|
|
|
plugin_manager = secret_store.get_manager()
|
|
generate_plugin = plugin_manager.get_plugin_generate(
|
|
key_spec, project_id=project_model.id)
|
|
|
|
# Create secret models to eventually save metadata to.
|
|
private_secret_model = models.Secret(spec)
|
|
private_secret_model['secret_type'] = secret_store.SecretType.PRIVATE
|
|
public_secret_model = models.Secret(spec)
|
|
public_secret_model['secret_type'] = secret_store.SecretType.PUBLIC
|
|
passphrase_secret_model = (models.Secret(spec)
|
|
if spec.get('passphrase') else None)
|
|
if passphrase_secret_model:
|
|
passphrase_type = secret_store.SecretType.PASSPHRASE
|
|
passphrase_secret_model['secret_type'] = passphrase_type
|
|
|
|
asymmetric_meta_dto = _generate_asymmetric_key(
|
|
generate_plugin,
|
|
key_spec,
|
|
private_secret_model,
|
|
public_secret_model,
|
|
passphrase_secret_model,
|
|
project_model,
|
|
content_type
|
|
)
|
|
|
|
_save_secret_in_repo(private_secret_model, project_model)
|
|
_save_secret_metadata_in_repo(private_secret_model,
|
|
asymmetric_meta_dto.private_key_meta,
|
|
generate_plugin,
|
|
content_type)
|
|
|
|
_save_secret_in_repo(public_secret_model, project_model)
|
|
_save_secret_metadata_in_repo(public_secret_model,
|
|
asymmetric_meta_dto.public_key_meta,
|
|
generate_plugin,
|
|
content_type)
|
|
|
|
if passphrase_secret_model:
|
|
_save_secret_in_repo(passphrase_secret_model, project_model)
|
|
_save_secret_metadata_in_repo(passphrase_secret_model,
|
|
asymmetric_meta_dto.passphrase_meta,
|
|
generate_plugin,
|
|
content_type)
|
|
|
|
container_model = _create_container_for_asymmetric_secret(spec,
|
|
project_model)
|
|
_save_asymmetric_secret_in_repo(
|
|
container_model, private_secret_model, public_secret_model,
|
|
passphrase_secret_model)
|
|
|
|
return container_model
|
|
|
|
|
|
def delete_secret(secret_model, project_id):
|
|
"""Remove a secret from secure backend."""
|
|
|
|
secret_metadata = _get_secret_meta(secret_model)
|
|
|
|
# We should only try to delete a secret using the plugin interface if
|
|
# there's the metadata available. This addresses bug/1377330.
|
|
if secret_metadata:
|
|
# Locate a suitable plugin to delete the secret from.
|
|
plugin_manager = secret_store.get_manager()
|
|
delete_plugin = plugin_manager.get_plugin_retrieve_delete(
|
|
secret_metadata.get('plugin_name'))
|
|
|
|
# Delete the secret from plugin storage.
|
|
delete_plugin.delete_secret(secret_metadata)
|
|
|
|
# Delete the secret from data model.
|
|
secret_repo = repos.get_secret_repository()
|
|
secret_repo.delete_entity_by_id(entity_id=secret_model.id,
|
|
external_project_id=project_id)
|
|
|
|
|
|
def _store_secret_using_plugin(store_plugin, secret_dto, secret_model,
|
|
project_model):
|
|
if isinstance(store_plugin, store_crypto.StoreCryptoAdapterPlugin):
|
|
context = store_crypto.StoreCryptoContext(
|
|
project_model,
|
|
secret_model=secret_model)
|
|
secret_metadata = store_plugin.store_secret(secret_dto, context)
|
|
else:
|
|
secret_metadata = store_plugin.store_secret(secret_dto)
|
|
return secret_metadata
|
|
|
|
|
|
def _generate_symmetric_key(
|
|
generate_plugin, key_spec, secret_model, project_model, content_type):
|
|
if isinstance(generate_plugin, store_crypto.StoreCryptoAdapterPlugin):
|
|
context = store_crypto.StoreCryptoContext(
|
|
project_model,
|
|
secret_model=secret_model,
|
|
content_type=content_type)
|
|
secret_metadata = generate_plugin.generate_symmetric_key(
|
|
key_spec, context)
|
|
else:
|
|
secret_metadata = generate_plugin.generate_symmetric_key(key_spec)
|
|
return secret_metadata
|
|
|
|
|
|
def _generate_asymmetric_key(generate_plugin, key_spec, private_secret_model,
|
|
public_secret_model, passphrase_secret_model,
|
|
project_model, content_type):
|
|
if isinstance(generate_plugin, store_crypto.StoreCryptoAdapterPlugin):
|
|
context = store_crypto.StoreCryptoContext(
|
|
project_model,
|
|
private_secret_model=private_secret_model,
|
|
public_secret_model=public_secret_model,
|
|
passphrase_secret_model=passphrase_secret_model,
|
|
content_type=content_type)
|
|
asymmetric_meta_dto = generate_plugin.generate_asymmetric_key(
|
|
key_spec, context)
|
|
else:
|
|
asymmetric_meta_dto = generate_plugin.generate_asymmetric_key(key_spec)
|
|
return asymmetric_meta_dto
|
|
|
|
|
|
def _get_secret(retrieve_plugin, secret_metadata, secret_model, project_model):
|
|
if isinstance(retrieve_plugin, store_crypto.StoreCryptoAdapterPlugin):
|
|
context = store_crypto.StoreCryptoContext(
|
|
project_model,
|
|
secret_model=secret_model)
|
|
secret_dto = retrieve_plugin.get_secret(secret_model.secret_type,
|
|
secret_metadata,
|
|
context)
|
|
else:
|
|
secret_dto = retrieve_plugin.get_secret(secret_model.secret_type,
|
|
secret_metadata)
|
|
return secret_dto
|
|
|
|
|
|
def _get_secret_meta(secret_model):
|
|
if secret_model:
|
|
secret_meta_repo = repos.get_secret_meta_repository()
|
|
return secret_meta_repo.get_metadata_for_secret(secret_model.id)
|
|
else:
|
|
return {}
|
|
|
|
|
|
def _save_secret_metadata_in_repo(secret_model, secret_metadata,
|
|
store_plugin, content_type):
|
|
"""Add secret metadata to a secret."""
|
|
|
|
if not secret_metadata:
|
|
secret_metadata = {}
|
|
|
|
secret_metadata['plugin_name'] = utils.generate_fullname_for(store_plugin)
|
|
secret_metadata['content_type'] = content_type
|
|
|
|
secret_meta_repo = repos.get_secret_meta_repository()
|
|
secret_meta_repo.save(secret_metadata, secret_model)
|
|
|
|
|
|
def _save_secret_in_repo(secret_model, project_model):
|
|
"""Save a Secret entity."""
|
|
|
|
secret_repo = repos.get_secret_repository()
|
|
# Create Secret entities in data store.
|
|
if not secret_model.id:
|
|
secret_model.project_id = project_model.id
|
|
secret_repo.create_from(secret_model)
|
|
else:
|
|
secret_repo.save(secret_model)
|
|
|
|
|
|
def _secret_already_has_stored_data(secret_model):
|
|
if not secret_model:
|
|
return False
|
|
return secret_model.encrypted_data or secret_model.secret_store_metadata
|
|
|
|
|
|
def _create_container_for_asymmetric_secret(spec, project_model):
|
|
container_model = models.Container()
|
|
container_model.name = spec.get('name')
|
|
container_model.type = spec.get('algorithm', '').lower()
|
|
container_model.status = models.States.ACTIVE
|
|
container_model.project_id = project_model.id
|
|
container_model.creator_id = spec.get('creator_id')
|
|
return container_model
|
|
|
|
|
|
def _save_asymmetric_secret_in_repo(container_model, private_secret_model,
|
|
public_secret_model,
|
|
passphrase_secret_model):
|
|
container_repo = repos.get_container_repository()
|
|
container_repo.create_from(container_model)
|
|
|
|
# create container_secret for private_key
|
|
_create_container_secret_association('private_key',
|
|
private_secret_model,
|
|
container_model)
|
|
|
|
# create container_secret for public_key
|
|
_create_container_secret_association('public_key',
|
|
public_secret_model,
|
|
container_model)
|
|
|
|
if passphrase_secret_model:
|
|
# create container_secret for passphrase
|
|
_create_container_secret_association('private_key_passphrase',
|
|
passphrase_secret_model,
|
|
container_model)
|
|
|
|
|
|
def _create_container_secret_association(assoc_name, secret_model,
|
|
container_model):
|
|
container_secret = models.ContainerSecret()
|
|
container_secret.name = assoc_name
|
|
container_secret.container_id = container_model.id
|
|
container_secret.secret_id = secret_model.id
|
|
|
|
container_secret_repo = repos.get_container_secret_repository()
|
|
container_secret_repo.create_from(container_secret)
|