Merge "Adding barbican client and keymgr module"
This commit is contained in:
commit
3814c2796e
@ -160,3 +160,40 @@ set the following configuration property in your sahara configuration file:
|
||||
|
||||
For more information on rootwrap please refer to the
|
||||
`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.
|
||||
|
@ -25,6 +25,7 @@ oslo.serialization>=1.2.0 # Apache-2.0
|
||||
oslo.utils>=1.2.0 # Apache-2.0
|
||||
paramiko>=1.13.0
|
||||
requests>=2.2.0,!=2.4.0
|
||||
python-barbicanclient>=3.0.1
|
||||
python-cinderclient>=1.1.0
|
||||
python-keystoneclient>=1.1.0
|
||||
python-novaclient>=2.18.0,!=2.21.0
|
||||
|
80
sahara/tests/unit/utils/test_keymgr.py
Normal file
80
sahara/tests/unit/utils/test_keymgr.py
Normal 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
96
sahara/utils/keymgr.py
Normal 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
|
24
sahara/utils/openstack/barbican.py
Normal file
24
sahara/utils/openstack/barbican.py
Normal 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)
|
@ -13,6 +13,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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.v3 import client as keystone_client_v3
|
||||
from oslo_config import cfg
|
||||
@ -21,14 +23,24 @@ from sahara import context
|
||||
from sahara.utils.openstack import base
|
||||
|
||||
|
||||
# TODO(alazarev) Move to [keystone] section
|
||||
opts = [
|
||||
# TODO(alazarev) Move to [keystone] section
|
||||
cfg.BoolOpt('use_identity_api_v3',
|
||||
default=True,
|
||||
help='Enables Sahara to use Keystone API v3. '
|
||||
'If that flag is disabled, '
|
||||
'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 = [
|
||||
@ -114,3 +126,37 @@ def client_for_proxy_user(username, password, trust_id=None):
|
||||
password=password,
|
||||
domain_name=CONF.proxy_user_domain_name,
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user