Merge "Adding barbican client and keymgr module"

This commit is contained in:
Jenkins 2015-03-11 19:22:36 +00:00 committed by Gerrit Code Review
commit 3814c2796e
6 changed files with 286 additions and 2 deletions

View File

@ -160,3 +160,40 @@ set the following configuration property in your sahara configuration file:
For more information on rootwrap please refer to the For more information on rootwrap please refer to the
`official Rootwrap documentation <https://wiki.openstack.org/wiki/Rootwrap>`_ `official Rootwrap documentation <https://wiki.openstack.org/wiki/Rootwrap>`_
External key manager usage
--------------------------
Sahara generates and stores several passwords during the course of operation.
To harden Sahara's usage of passwords it can be instructed to use an
external key manager for storage and retrieval of these secrets. To enable
this feature there must first be an OpenStack Key Manager service deployed
within the stack. Currently, the Barbican project is the only key manager
supported by Sahara.
With a Key Manager service deployed on the stack, Sahara must be configured
to enable the external storage of secrets. This is accomplished by editing
the configuration file as follows:
.. sourcecode:: cfg
[DEFAULT]
use_external_key_manager=True
.. TODO (mimccune)
this language should be removed once a new keystone authentication
section has been created in the configuration file.
Additionally, at this time there are two more values which must be provided
to ensure proper access for Sahara to the Key Manager service. These are
the Identity domain for the administrative user and the domain for the
administrative project. By default these values will appear as:
.. sourcecode:: cfg
[DEFAULT]
admin_user_domain_name=default
admin_project_domain_name=default
With all of these values configured and the Key Manager service deployed,
Sahara will begin storing its secrets in the external manager.

View File

@ -25,6 +25,7 @@ oslo.serialization>=1.2.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0 oslo.utils>=1.2.0 # Apache-2.0
paramiko>=1.13.0 paramiko>=1.13.0
requests>=2.2.0,!=2.4.0 requests>=2.2.0,!=2.4.0
python-barbicanclient>=3.0.1
python-cinderclient>=1.1.0 python-cinderclient>=1.1.0
python-keystoneclient>=1.1.0 python-keystoneclient>=1.1.0
python-novaclient>=2.18.0,!=2.21.0 python-novaclient>=2.18.0,!=2.21.0

View File

@ -0,0 +1,80 @@
# Copyright (c) 2015 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.
import mock
from sahara.tests.unit import base
from sahara.utils import keymgr
class TestKeymgrUtils(base.SaharaTestCase):
def setUp(self):
super(TestKeymgrUtils, self).setUp()
@mock.patch('sahara.utils.openstack.barbican.client_for_admin')
def test_keymgr_delete_with_external(self, client_for_admin):
self.override_config('use_external_key_manager', True)
keyref = 'test_key_reference'
secrets_manager = mock.Mock()
secrets_manager.delete = mock.Mock()
client = mock.Mock(secrets=secrets_manager)
client_for_admin.return_value = client
keymgr.delete(keyref)
secrets_manager.delete.assert_called_with(keyref)
def test_keymgr_get_no_external(self):
actual_key = 'test_key_super_secret'
# with no external key manager, get should return the argument
keyref = keymgr.get(actual_key)
self.assertEqual(actual_key, keyref)
@mock.patch('sahara.utils.openstack.barbican.client_for_admin')
def test_keymgr_get_with_external(self, client_for_admin):
self.override_config('use_external_key_manager', True)
actual_key = 'test_key_super_secret'
keyref = 'test_key_reference'
secret = mock.Mock(payload=actual_key)
secrets_manager = mock.Mock()
secrets_manager.get = mock.Mock(return_value=secret)
client = mock.Mock(secrets=secrets_manager)
client_for_admin.return_value = client
# with external key manager, get should return a key from a reference
key = keymgr.get(keyref)
secrets_manager.get.assert_called_with(keyref)
self.assertEqual(actual_key, key)
def test_keymgr_store_no_external(self):
actual_key = 'test_key_super_secret'
# with no external key manager, store should return the argument
keyref = keymgr.store(actual_key)
self.assertEqual(actual_key, keyref)
@mock.patch('sahara.utils.openstack.barbican.client_for_admin')
def test_keymgr_store_with_external(self, client_for_admin):
self.override_config('use_external_key_manager', True)
key = 'test_key_super_secret'
actual_keyref = 'test_key_reference'
secret = mock.Mock()
secret.store = mock.Mock(return_value=actual_keyref)
secrets_manager = mock.Mock()
secrets_manager.create = mock.Mock(return_value=secret)
client = mock.Mock(secrets=secrets_manager)
client_for_admin.return_value = client
# with external key manager, store should return a key reference
keyref = keymgr.store(key)
secrets_manager.create.assert_called_with(
payload=key, payload_content_type='text/plain')
secret.store.assert_called_once_with()
self.assertEqual(actual_keyref, keyref)

96
sahara/utils/keymgr.py Normal file
View File

@ -0,0 +1,96 @@
# Copyright (c) 2015 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 oslo_config import cfg
from oslo_log import log as logging
from sahara.utils.openstack import barbican
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
opts = [
cfg.BoolOpt('use_external_key_manager',
default=False,
help='Enable Sahara to use an external key manager service '
'provided by the identity service catalog. Sahara will '
'store all keys with the manager service.')
]
CONF.register_opts(opts)
def delete(key_ref):
'''delete a key
When this function is used without an external key manager it does
nothing to the provided reference.
:param key_ref: The reference of the key to delete
'''
if CONF.use_external_key_manager:
client = barbican.client_for_admin()
client.secrets.delete(key_ref)
LOG.debug('Deleted key {key_ref}'.format(key_ref=key_ref))
else:
LOG.debug('External key manager not enabled, key not deleted')
def get(key_ref):
'''retrieve a key
When used with an external key manager this will retrieve the key
and return it as stored.
When used without an external key manager it will return the argument
provided.
:param key_ref: The reference of the key to retrieve
:returns: The retrieved key
'''
if CONF.use_external_key_manager:
client = barbican.client_for_admin()
key = client.secrets.get(key_ref)
LOG.debug('Retrieved key for {key_ref}'.format(key_ref=key_ref))
payload = key.payload
return payload
else:
return key_ref
def store(key):
'''store a key
When used with an external key manager this function will store the key
in the manager and return a reference provided by the manager.
When used without an external manager this function will return the
argument provided.
:param key: The key to store
:returns: A reference for the stored key
'''
if CONF.use_external_key_manager:
client = barbican.client_for_admin()
secret = client.secrets.create(payload=key,
payload_content_type='text/plain')
secret_ref = secret.store()
LOG.debug('Stored key as {key_ref}'.format(key_ref=secret_ref))
return secret_ref
else:
return key

View File

@ -0,0 +1,24 @@
# Copyright (c) 2015 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.
import barbicanclient.client as barbican_client
from sahara.utils.openstack import keystone
def client_for_admin():
'''return a barbican client for the admin user.'''
session = keystone.session_for_admin()
return barbican_client.Client(session=session)

View File

@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from keystoneclient.auth import identity as keystone_identity
from keystoneclient import session as keystone_session
from keystoneclient.v2_0 import client as keystone_client from keystoneclient.v2_0 import client as keystone_client
from keystoneclient.v3 import client as keystone_client_v3 from keystoneclient.v3 import client as keystone_client_v3
from oslo_config import cfg from oslo_config import cfg
@ -21,14 +23,24 @@ from sahara import context
from sahara.utils.openstack import base from sahara.utils.openstack import base
# TODO(alazarev) Move to [keystone] section
opts = [ opts = [
# TODO(alazarev) Move to [keystone] section
cfg.BoolOpt('use_identity_api_v3', cfg.BoolOpt('use_identity_api_v3',
default=True, default=True,
help='Enables Sahara to use Keystone API v3. ' help='Enables Sahara to use Keystone API v3. '
'If that flag is disabled, ' 'If that flag is disabled, '
'per-job clusters will not be terminated ' 'per-job clusters will not be terminated '
'automatically.') 'automatically.'),
# TODO(mimccune) The following should be integrated into a custom
# auth section
cfg.StrOpt('admin_user_domain_name',
default='default',
help='The name of the domain to which the admin user '
'belongs.'),
cfg.StrOpt('admin_project_domain_name',
default='default',
help='The name of the domain for the service '
'project(ex. tenant).')
] ]
ssl_opts = [ ssl_opts = [
@ -114,3 +126,37 @@ def client_for_proxy_user(username, password, trust_id=None):
password=password, password=password,
domain_name=CONF.proxy_user_domain_name, domain_name=CONF.proxy_user_domain_name,
trust_id=trust_id) trust_id=trust_id)
def _session(username, password, project_name, user_domain_name=None,
project_domain_name=None):
passwd_kwargs = dict(
auth_url=base.retrieve_auth_url(),
username=CONF.keystone_authtoken.admin_user,
password=CONF.keystone_authtoken.admin_password
)
if CONF.use_identity_api_v3:
passwd_kwargs.update(dict(
project_name=project_name,
user_domain_name=user_domain_name,
project_domain_name=project_domain_name
))
auth = keystone_identity.v3.Password(**passwd_kwargs)
else:
passwd_kwargs.update(dict(
tenant_name=project_name
))
auth = keystone_identity.v2.Password(**passwd_kwargs)
return keystone_session.Session(auth=auth)
def session_for_admin():
'''Return a Keystone session for the admin user.'''
return _session(
username=CONF.keystone_authtoken.admin_user,
password=CONF.keystone_authtoken.admin_password,
project_name=CONF.keystone_authtoken.admin_tenant_name,
user_domain_name=CONF.admin_user_domain_name,
project_domain_name=CONF.admin_project_domain_name)