Merge "Code to pass through transport_key_id when storing secret"

This commit is contained in:
Jenkins authored and Gerrit Code Review committed 2014-08-06 19:05:55 +00:00
commit 1926fb13d5
8 files changed
+228 -52

No files matched your search

+6 -2
View File
@@ -113,6 +113,8 @@ class SecretController(object):
)
)
transport_key_id = kwargs.get('transport_key_id')
payload = pecan.request.body
if not payload:
raise exception.NoDataToProcess()
@@ -135,7 +137,8 @@ class SecretController(object):
plugin.store_secret(payload, content_type,
content_encoding, secret_model.to_dict_fields(),
secret_model, tenant_model, self.repos)
secret_model, tenant_model, self.repos,
transport_key_id=transport_key_id)
@index.when(method='DELETE')
@controllers.handle_exceptions(u._('Secret deletion'))
@@ -248,7 +251,8 @@ class SecretsController(object):
data.get('payload_content_encoding'),
data, None, tenant,
self.repos,
transport_key_needed=transport_key_needed)
transport_key_needed=transport_key_needed,
transport_key_id=data.get('transport_key_id'))
pecan.response.status = 201
pecan.response.headers['Location'] = '/{0}/secrets/{1}'.format(
+1
View File
@@ -129,6 +129,7 @@ class NewSecretValidator(ValidatorBase):
"type": "string",
"enum": ["true", "false"]
},
"transport_key_id": {"type": "string"},
},
}
+31 -13
View File
@@ -128,10 +128,14 @@ class DogtagPlugin(sstore.SecretStoreBase):
def store_secret(self, secret_dto, context):
"""Store a secret in the DRM
This will likely require another parameter which includes the wrapped
session key to be passed. Until that is added, we will call
archive_key() which relies on the DRM python client to create the
session keys.
If secret_dto.transport_key is not None, then we expect
secret_dto.secret to include a base64 encoded PKIArchiveOptions
structure as defined in section 6.4 of RFC 2511. This package contains
a transport key wrapped session key, the session key wrapped secret
and parameters to specify the symmetric key wrapping.
Otherwise, the data is unencrypted and we use a call to archive_key()
to have the Dogtag DRM client generate the relevant session keys.
The secret_dto contains additional information on the type of secret
that is being stored. We will use that shortly. For, now, lets just
@@ -142,11 +146,23 @@ class DogtagPlugin(sstore.SecretStoreBase):
"""
data_type = key.KeyClient.PASS_PHRASE_TYPE
client_key_id = uuid.uuid4().hex
response = self.keyclient.archive_key(client_key_id,
data_type,
secret_dto.secret,
key_algorithm=None,
key_size=None)
if secret_dto.transport_key is not None:
# TODO(alee-3) send the transport key with the archival request
# once the Dogtag Client API changes.
response = self.keyclient.archive_pki_options(
client_key_id,
data_type,
secret_dto.secret,
key_algorithm=None,
key_size=None)
else:
response = self.keyclient.archive_key(
client_key_id,
data_type,
secret_dto.secret,
key_algorithm=None,
key_size=None)
return {DogtagPlugin.SECRET_TYPE: secret_dto.type,
DogtagPlugin.SECRET_KEYSPEC: secret_dto.key_spec,
DogtagPlugin.KEY_ID: response.get_key_id()}
@@ -176,10 +192,12 @@ class DogtagPlugin(sstore.SecretStoreBase):
# TODO(alee) remove final field when content_type is removed
# from secret_dto
ret = sstore.SecretDTO(secret_metadata[DogtagPlugin.SECRET_TYPE],
recovered_key,
secret_metadata[DogtagPlugin.SECRET_KEYSPEC],
None)
ret = sstore.SecretDTO(
type=secret_metadata[DogtagPlugin.SECRET_TYPE],
secret=recovered_key,
key_spec=secret_metadata[DogtagPlugin.SECRET_KEYSPEC],
content_type=None,
transport_key=None)
return ret
+13 -2
View File
@@ -196,7 +196,7 @@ class SecretDTO(object):
#TODO(john-wood-w) Remove 'content_type' once secret normalization work is
# completed.
def __init__(self, type, secret, key_spec, content_type):
def __init__(self, type, secret, key_spec, content_type, transport_key):
"""Creates a new SecretDTO.
The secret is stored in the secret parameter. In the future this
@@ -207,11 +207,15 @@ class SecretDTO(object):
:param key_spec: KeySpec key specifications
:param content_type: Content type of the secret, one of MIME
types such as 'text/plain' or 'application/octet-stream'
:param transport_key: presence of this parameter indicates that the
secret has been encrypted using a transport key. The transport
key is a base64 encoded x509 transport certificate.
"""
self.type = type
self.secret = secret
self.key_spec = key_spec
self.content_type = content_type
self.transport_key = transport_key
#TODO(john-wood-w) Remove this class once repository factory work is
@@ -378,8 +382,9 @@ class SecretStorePluginManager(named.NamedExtensionManager):
invoke_kwds=invoke_kwargs
)
def get_plugin_store(self, transport_key_needed=False):
def get_plugin_store(self, plugin_name=None, transport_key_needed=False):
"""Gets a secret store plugin.
:param: plugin_name: set to plugin_name to get specific plugin
:param: transport_key_needed: set to True if a transport
key is required.
:returns: SecretStoreBase plugin implementation
@@ -388,6 +393,12 @@ class SecretStorePluginManager(named.NamedExtensionManager):
if len(self.extensions) < 1:
raise SecretStorePluginNotFound()
if plugin_name is not None:
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
return ext.obj
raise SecretStoreSupportedPluginNotFound()
if not transport_key_needed:
return self.extensions[0].obj
+53 -22
View File
@@ -17,9 +17,50 @@ from barbican.plugin.interface import secret_store
from barbican.plugin.util import translations as tr
def get_transport_key_model(repos, transport_key_needed):
key_model = None
if transport_key_needed:
# get_plugin_store() will throw an exception if no suitable
# plugin with transport key is found
store_plugin = secret_store.SecretStorePluginManager(). \
get_plugin_store(transport_key_needed=True)
plugin_name = utils.generate_fullname_for(store_plugin)
key_repo = repos.transport_key_repo
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(repos, transport_key_id):
plugin_name = None
transport_key = None
if transport_key_id is not None:
transport_key_model = repos.transport_key_repo.get(
entity_id=transport_key_id)
if transport_key_model is None:
raise ValueError("Invalid transport key ID provided")
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,
spec, secret_model, tenant_model, repos,
transport_key_needed=False):
transport_key_needed=False,
transport_key_id=None):
"""Store a provided secret into secure backend."""
# Create a secret model is one isn't provided.
@@ -35,30 +76,17 @@ def store_secret(unencrypted_raw, content_type_raw, content_encoding,
# 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 = None
if transport_key_needed:
# get_plugin_store() will throw an exception if no suitable
# plugin with transport key is found
store_plugin = secret_store.SecretStorePluginManager().\
get_plugin_store(True)
plugin_name = utils.generate_fullname_for(store_plugin)
key_repo = repos.transport_key_repo
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)
key_model = get_transport_key_model(repos, transport_key_needed)
_save_secret(secret_model, tenant_model, repos)
return secret_model, key_model
plugin_name, transport_key = get_plugin_name_and_transport_key(
repos, transport_key_id)
# Locate a suitable plugin to store the secret.
store_plugin = secret_store.SecretStorePluginManager().get_plugin_store()
store_plugin = secret_store.SecretStorePluginManager().\
get_plugin_store(plugin_name=plugin_name)
# Normalize inputs prior to storage.
#TODO(john-wood-w) Normalize all secrets to base64, so we don't have to
@@ -76,8 +104,11 @@ def store_secret(unencrypted_raw, content_type_raw, content_encoding,
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
bit_length=spec.get('bit_length'),
mode=spec.get('mode'))
secret_dto = secret_store.SecretDTO(None, unencrypted, key_spec,
content_type)
secret_dto = secret_store.SecretDTO(type=None,
secret=unencrypted,
key_spec=key_spec,
content_type=content_type,
transport_key=transport_key)
secret_metadata = store_plugin.store_secret(secret_dto, context)
# Save secret and metadata.
+96 -9
View File
@@ -232,7 +232,7 @@ class BaseSecretsResource(FunctionalTest):
self.transport_key = models.TransportKey(
'default_plugin_name', 'XXXABCDEF')
self.transport_key.id = 'tkey12345'
self.transport_key_id = 'tkey12345'
self.tkey_url = hrefs.convert_transport_key_to_href(
self.keystone_id,
self.transport_key.id)
@@ -265,7 +265,8 @@ class BaseSecretsResource(FunctionalTest):
'application/octet-stream'),
self.secret_req.get('payload_content_encoding'),
expected, None, self.tenant, mock.ANY,
transport_key_needed=False
transport_key_needed=False,
transport_key_id=None
)
@mock.patch('barbican.plugin.resources.store_secret')
@@ -295,7 +296,40 @@ class BaseSecretsResource(FunctionalTest):
expected, None,
self.tenant if check_tenant_id else mock.ANY,
mock.ANY,
transport_key_needed=False
transport_key_needed=False,
transport_key_id=None
)
@mock.patch('barbican.plugin.resources.store_secret')
def _test_should_add_new_secret_one_step_with_tkey_id(
self, mock_store_secret, check_tenant_id=True):
"""Test the one-step secret creation with transport_key_id set
:param check_tenant_id: True if the retrieved Tenant id needs to be
verified, False to skip this check (necessary for new-Tenant flows).
"""
mock_store_secret.return_value = self.secret, None
self.secret_req['transport_key_id'] = self.transport_key_id
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
expected = dict(self.secret_req)
expected['expiration'] = None
mock_store_secret\
.assert_called_once_with(
self.secret_req.get('payload'),
self.secret_req.get('payload_content_type',
'application/octet-stream'),
self.secret_req.get('payload_content_encoding'),
expected, None,
self.tenant if check_tenant_id else mock.ANY,
mock.ANY,
transport_key_needed=False,
transport_key_id=self.transport_key_id
)
def _test_should_add_new_secret_if_tenant_does_not_exist(self):
@@ -415,6 +449,9 @@ class WhenCreatingPlainTextSecretsUsingSecretsResource(BaseSecretsResource):
def test_should_add_new_secret_one_step(self):
self._test_should_add_new_secret_one_step()
def test_should_add_new_secret_one_step_with_tkey_id(self):
self._test_should_add_new_secret_one_step_with_tkey_id()
def test_should_add_new_secret_with_expiration(self):
self._test_should_add_new_secret_with_expiration()
@@ -547,6 +584,9 @@ class WhenCreatingBinarySecretsUsingSecretsResource(BaseSecretsResource):
def test_should_add_new_secret_one_step(self):
self._test_should_add_new_secret_one_step()
def test_should_add_new_secret_one_step_with_tkey_id(self):
self._test_should_add_new_secret_one_step_with_tkey_id()
def test_should_add_new_secret_with_expiration(self):
self._test_should_add_new_secret_with_expiration()
@@ -875,6 +915,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.secret_meta_repo = mock.MagicMock()
self.transport_key_repo = mock.MagicMock()
self.transport_key_id = 'tkey12345'
def test_should_get_secret_as_json(self):
resp = self.app.get(
@@ -990,9 +1031,29 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.assertEqual(resp.status_int, 204)
mock_store_secret\
.assert_called_once_with('plain text', 'text/plain',
None, self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY)
.assert_called_once_with('plain text', 'text/plain', None,
self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY,
transport_key_id=None)
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_secret_as_plain_with_tkey_id(self, mock_store_secret):
self.secret.encrypted_data = []
resp = self.app.put(
'/%s/secrets/%s/?transport_key_id=%s' %
(self.keystone_id, self.secret.id, self.transport_key_id),
'plain text',
headers={'Accept': 'text/plain', 'Content-Type': 'text/plain'},
)
self.assertEqual(resp.status_int, 204)
mock_store_secret\
.assert_called_once_with('plain text', 'text/plain', None,
self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY,
transport_key_id=self.transport_key_id)
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_secret_as_binary(self, mock_store_secret):
@@ -1011,8 +1072,33 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
mock_store_secret\
.assert_called_once_with('plain text', 'application/octet-stream',
None, self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY)
None,
self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY,
transport_key_id=None)
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_secret_as_binary_with_tkey_id(self, mock_store_secret):
self.secret.encrypted_data = []
resp = self.app.put(
'/%s/secrets/%s/?transport_key_id=%s' %
(self.keystone_id, self.secret.id, self.transport_key_id),
'plain text',
headers={
'Accept': 'text/plain',
'Content-Type': 'application/octet-stream'
},
)
self.assertEqual(resp.status_int, 204)
mock_store_secret\
.assert_called_once_with('plain text', 'application/octet-stream',
None,
self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY,
transport_key_id=self.transport_key_id)
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_encoded_secret_as_binary(self, mock_store_secret):
@@ -1033,7 +1119,8 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
mock_store_secret\
.assert_called_once_with(payload, 'application/octet-stream',
'base64', self.secret.to_dict_fields(),
self.secret, self.tenant, mock.ANY)
self.secret, self.tenant, mock.ANY,
transport_key_id=None)
def test_should_raise_to_put_secret_with_unsupported_encoding(self):
self.secret.encrypted_data = []
@@ -137,7 +137,7 @@ class WhenTestingSecretStorePluginManager(testtools.TestCase):
self.assertRaises(
str.SecretStoreSupportedPluginNotFound,
self.manager.get_plugin_store,
True,
transport_key_needed=True,
)
def test_get_store_with_tkey(self):
@@ -146,5 +146,6 @@ class WhenTestingSecretStorePluginManager(testtools.TestCase):
plugin2 = TestSecretStoreWithTransportKey(False)
plugin2_mock = mock.MagicMock(obj=plugin2)
self.manager.extensions = [plugin1_mock, plugin2_mock]
self.assertEqual(plugin2,
self.manager.get_plugin_store(True))
self.assertEqual(
plugin2,
self.manager.get_plugin_store(transport_key_needed=True))
+24 -1
View File
@@ -119,10 +119,12 @@ class WhenTestingDogtagPlugin(testtools.TestCase):
key_spec = mock.MagicMock()
content_type = mock.MagicMock()
context = mock.MagicMock()
transport_key = None
secret_dto = sstore.SecretDTO(sstore.SecretType.SYMMETRIC,
payload,
key_spec,
content_type)
content_type,
transport_key)
self.plugin.store_secret(secret_dto, context)
self.keyclient_mock.archive_key.assert_called_once_with(
mock.ANY,
@@ -131,6 +133,27 @@ class WhenTestingDogtagPlugin(testtools.TestCase):
key_algorithm=None,
key_size=None)
def test_store_secret_with_tkey_id(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
payload = 'data wrapped in PKIArchiveOptions object'
key_spec = mock.MagicMock()
content_type = mock.MagicMock()
context = mock.MagicMock()
transport_key = mock.MagicMock()
secret_dto = sstore.SecretDTO(sstore.SecretType.SYMMETRIC,
payload,
key_spec,
content_type,
transport_key)
self.plugin.store_secret(secret_dto, context)
self.keyclient_mock.archive_pki_options.assert_called_once_with(
mock.ANY,
"passPhrase",
payload,
key_algorithm=None,
key_size=None)
def test_get_secret(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")