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
|
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.
|
||||||
|
@ -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
|
||||||
|
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
|
# 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)
|
||||||
|
Loading…
Reference in New Issue
Block a user