diff options
author | Douglas Mendizabal <douglas.mendizabal@rackspace.com> | 2013-09-01 01:41:32 -0500 |
---|---|---|
committer | Douglas Mendizabal <douglas.mendizabal@rackspace.com> | 2013-09-01 01:41:32 -0500 |
commit | 7c41da3a2890fa92f15447d97683b93cf3cfc853 (patch) | |
tree | 3c9b36dcac9f828e39d7ab2b7b30f7b3fc0cb466 | |
parent | bab914d1cbc656ced395b0862fbc0120f79f6f63 (diff) | |
download | python-barbicanclient-7c41da3a2890fa92f15447d97683b93cf3cfc853.tar.gz |
Moved secret create and list into secret manager
-rw-r--r-- | barbicanclient/base.py | 27 | ||||
-rw-r--r-- | barbicanclient/client.py | 163 | ||||
-rw-r--r-- | barbicanclient/common/auth.py | 3 | ||||
-rw-r--r-- | barbicanclient/secrets.py | 76 | ||||
-rw-r--r-- | barbicanclient/test/common/test_auth.py | 6 | ||||
-rw-r--r-- | barbicanclient/test/test_client.py | 22 |
6 files changed, 188 insertions, 109 deletions
diff --git a/barbicanclient/base.py b/barbicanclient/base.py new file mode 100644 index 0000000..449b07a --- /dev/null +++ b/barbicanclient/base.py @@ -0,0 +1,27 @@ +# Copyright (c) 2013 Rackspace, 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. +""" +Base utilites to build API operation managers. +""" + +class BaseEntityManager(object): + def __init__(self, api, entity): + self.api = api + self.entity = entity + + def _remove_empty_keys(self, dictionary): + for k in dictionary.keys(): + if dictionary[k] is None: + dictionary.pop(k) diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 3b08597..4955f8a 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -1,8 +1,10 @@ import json import os +import urlparse import requests +from barbicanclient import secrets from barbicanclient.secrets import Secret from barbicanclient.orders import Order from barbicanclient.common import auth @@ -20,9 +22,8 @@ class Client(object): SECRETS_PATH = 'secrets' ORDERS_PATH = 'orders' - def __init__(self, auth=True, - auth_endpoint=None, user=None, password=None, tenant=None, - key=None, token=None, **kwargs): + def __init__(self, auth_plugin=None, endpoint=None, tenant_id=None, + **kwargs): """ Authenticate and connect to the service endpoint, which can be received through authentication. @@ -30,18 +31,9 @@ class Client(object): Environment variables will be used by default when their corresponding arguments are not passed in. - :param auth: Whether the client should use keystone - authentication, defaults to True - :param auth_endpoint: The keystone URL used for authentication - required if auth=True - default: env('OS_AUTH_URL') - :param user: keystone user account, required if auth=True - default: env('OS_USERNAME') - :param password: password associated with the user - required if auth=Tru - default: env('OS_PASSWORD') - :param tenant: The tenant ID - default: env('OS_TENANT_NAME') + :param auth_plugin: Authentication backend plugin + defaults to None + :param endpoint: Barbican endpoint url :param key: The API key or password to auth with :keyword param endpoint: The barbican endpoint to connect to @@ -50,34 +42,49 @@ class Client(object): LOG.debug(_("Creating Client object")) - self.env = kwargs.get('fake_env') or env - - if auth: - LOG.debug(_('Using authentication with keystone')) - self._auth_endpoint = auth_endpoint or self.env('OS_AUTH_URL') - self._user = user or self.env('OS_USERNAME') - self._password = password or self.env('OS_PASSWORD') - self._tenant = tenant or self.env('OS_TENANT_NAME') - if not all([self._auth_endpoint, self._user, - self._password, self._tenant]): - raise ValueError('Authentication requires an endpoint, user, ' - 'password, and tenant.') - #TODO(dmend): remove these - self._auth_endpoint = auth_endpoint or self.env('OS_AUTH_URL') - self._user = user or self.env('OS_USERNAME') - self._tenant = tenant or self.env('OS_TENANT_NAME') - self._key = key or self._password - - if not all([self._auth_endpoint, self._user, self._key, self._tenant]): - raise ClientException("The authorization endpoint, username, key," - " and tenant name should either be passed i" - "n or defined as environment variables.") - self.authenticate = kwargs.get('authenticate') or auth.authenticate - self.request = kwargs.get('request') or requests.request - self._endpoint = (kwargs.get('endpoint') or - self.env('BARBICAN_ENDPOINT')) - self._cacert = kwargs.get('cacert') - self.connect(token=(token or self.env('AUTH_TOKEN'))) + self._session = requests.Session() + self.auth_plugin = auth_plugin + + if self.auth_plugin is not None: + self._barbican_url = self.auth_plugin.barbican_url + self._tenant_id = self.auth_plugin.tenant_id + self._session.headers.update( + {'X-Auth-Token': self.auth_plugin.auth_token} + ) + else: + if endpoint is None: + raise ValueError('Barbican endpoint url must be provided, or ' + 'must be available from auth_plugin') + if tenant_id is None: + raise ValueError('Tenant ID must be provided, or must be available' + ' from auth_plugin') + if endpoint.endswith('/'): + self._barbican_url = endpoint[:-1] + else: + self._barbican_url = endpoint + self._tenant_id = tenant_id + + self.base_url = '{0}/{1}'.format(self._barbican_url, self._tenant_id) + self.secrets = secrets.SecretManager(self) + + # self.env = kwargs.get('fake_env') or env + + # #TODO(dmend): remove these + # self._auth_endpoint = kwargs.get('auth_endpoint') or self.env('OS_AUTH_URL') + # self._user = kwargs.get('user') or self.env('OS_USERNAME') + # self._tenant = kwargs.get('tenant') or self.env('OS_TENANT_NAME') + # self._key = kwargs.get('key') + + # if not all([self._auth_endpoint, self._user, self._key, self._tenant]): + # raise ClientException("The authorization endpoint, username, key," + # " and tenant name should either be passed i" + # "n or defined as environment variables.") + # self.authenticate = kwargs.get('authenticate') or auth.authenticate + # self.request = kwargs.get('request') or requests.request + # self._endpoint = (kwargs.get('endpoint') or + # self.env('BARBICAN_ENDPOINT')) + # self._cacert = kwargs.get('cacert') + # self.connect(token=(kwargs.get('token') or self.env('AUTH_TOKEN'))) @property def _conn(self): @@ -194,40 +201,15 @@ class Client(object): bit_length=None, cypher_type=None, expiration=None): - """ - Creates and returns a Secret object with all of its metadata filled in. - - :param name: A friendly name for the secret - :param payload: The unencrypted secret - :param payload_content_type: The format/type of the secret - :param payload_content_encoding: The encoding of the secret - :param algorithm: The algorithm the secret is used with - :param bit_length: The bit length of the secret - :param cypher_type: The cypher type (e.g. block cipher mode) - :param expiration: The expiration time of the secret in ISO 8601 format - """ - LOG.debug(_("Creating secret of payload content type {0}").format( - payload_content_type)) - href = "{0}/{1}".format(self._tenant, self.SECRETS_PATH) - LOG.debug(_("href: {0}").format(href)) - secret_dict = {} - secret_dict['name'] = name - secret_dict['payload'] = payload - secret_dict['payload_content_type'] = payload_content_type - secret_dict['payload_content_encoding'] = payload_content_encoding - secret_dict['algorithm'] = algorithm - secret_dict['cypher_type'] = cypher_type - secret_dict['bit_length'] = bit_length - secret_dict['expiration'] = expiration - self._remove_empty_keys(secret_dict) - LOG.debug(_("Request body: {0}").format(secret_dict)) - hdrs, body = self._perform_http(href=href, - method='POST', - request_body=json.dumps(secret_dict)) - - LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) - - return self.get_secret(body['secret_ref']) + """Deprecated""" + self.secrets.create(name=name, + payload=payload, + payload_content_type=payload_content_type, + payload_content_encoding=payload_content_encoding, + algorithm=algorithm, + bit_length=bit_length, + mode=cypher_type, + expiration=expiration) def delete_secret_by_id(self, secret_id): """ @@ -411,11 +393,6 @@ class Client(object): LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) return Order(self._conn, body) - def _remove_empty_keys(self, dictionary): - for k in dictionary.keys(): - if dictionary[k] is None: - dictionary.pop(k) - def _perform_http(self, method, href, request_body='', headers={}, parse_json=True): """ @@ -457,6 +434,28 @@ class Client(object): return response.headers, resp_body + def _request(self, url, method, headers): + resp = self._session.request() + + def get(self, path, params): + url = '{0}/{1}/'.format(self.base_url, path) + headers = {'content-type': 'application/json'} + resp = self._session.get(url, params=params, headers=headers) + self._check_status_code(resp) + return resp.json() + + def post(self, path, data): + url = '{0}/{1}/'.format(self.base_url, path) + headers = {'content-type': 'application/json'} + resp = self._session.post(url, data=json.dumps(data), headers=headers) + self._check_status_code(resp) + return resp.json() + + #TODO(dmend): beef this up + def _check_status_code(self, resp): + status = resp.status_code + print('status {0}'.format(status)) + def env(*vars, **kwargs): """Search for the first defined of possibly many env vars diff --git a/barbicanclient/common/auth.py b/barbicanclient/common/auth.py index 9f6051b..205d91d 100644 --- a/barbicanclient/common/auth.py +++ b/barbicanclient/common/auth.py @@ -35,6 +35,9 @@ class KeystoneAuth(object): self._service_type = 'keystore' self._endpoint_type = 'publicURL' + self.tenant_name = self._keystone.tenant_name + self.tenant_id = self._keystone.tenant_id + @property def auth_token(self): return self._keystone.auth_token diff --git a/barbicanclient/secrets.py b/barbicanclient/secrets.py index d5ad472..c052940 100644 --- a/barbicanclient/secrets.py +++ b/barbicanclient/secrets.py @@ -1,6 +1,13 @@ from urlparse import urlparse + +from openstack.common import log as logging from openstack.common.timeutils import parse_isotime +from barbicanclient import base + + +LOG = logging.getLogger(__name__) + class Secret(object): @@ -8,12 +15,10 @@ class Secret(object): A secret is any data the user has stored in the key management system. """ - def __init__(self, connection, secret_dict): + def __init__(self, secret_dict): """ - Builds a secret object from a json representation. Includes the - connection object for subtasks. + Builds a secret object from a dictionary. """ - self.connection = connection self.secret_ref = secret_dict.get('secret_ref') self.created = parse_isotime(secret_dict.get('created')) self.status = secret_dict.get('status') @@ -60,3 +65,66 @@ class Secret(object): self.payload_content_encoding, self.bit_length, self.algorithm, self.cypher_type, self.expiration) ) + + +class SecretManager(base.BaseEntityManager): + + def __init__(self, api): + super(SecretManager, self).__init__(api, 'secrets') + + def create(self, + name=None, + payload=None, + payload_content_type=None, + payload_content_encoding=None, + algorithm=None, + bit_length=None, + mode=None, + expiration=None): + """ + Stores a new secret in Barbican + + :param name: A friendly name for the secret + :param payload: The unencrypted secret data + :param payload_content_type: The format/type of the secret data + :param payload_content_encoding: The encoding of the secret data + :param algorithm: The algorithm barbican should use to encrypt + :param bit_length: The bit length of the key used for ecnryption + :param mode: The algorithm mode (e.g. CBC or CTR mode) + :param expiration: The expiration time of the secret in ISO 8601 format + :returns: Secret ID for the stored secret + """ + LOG.debug("Creating secret of payload content type {0}".format( + payload_content_type)) + href = self.entity + LOG.debug("href: {0}".format(href)) + + secret_dict = dict() + secret_dict['name'] = name + secret_dict['payload'] = payload + secret_dict['payload_content_type'] = payload_content_type + secret_dict['payload_content_encoding'] = payload_content_encoding + secret_dict['algorithm'] = algorithm + #TODO(dmend): Change this to 'mode' + secret_dict['cypher_type'] = mode + secret_dict['bit_length'] = bit_length + secret_dict['expiration'] = expiration + self._remove_empty_keys(secret_dict) + + LOG.debug("Request body: {0}".format(secret_dict)) + + resp = self.api.post(self.entity, secret_dict) + #TODO(dmend): return secret object? + #secret = Secret(resp) + secret_id = resp['secret_ref'].split('/')[-1] + + return secret_id + + def list(self, limit=10, offset=0): + + LOG.debug('Listing secrets - offset {0} limit {1}'.format(offset, + limit)) + params = {'limit': limit, 'offset': offset} + resp = self.api.get(self.entity, params) + + return resp diff --git a/barbicanclient/test/common/test_auth.py b/barbicanclient/test/common/test_auth.py index 8376d1b..d91e970 100644 --- a/barbicanclient/test/common/test_auth.py +++ b/barbicanclient/test/common/test_auth.py @@ -18,12 +18,6 @@ from barbicanclient.common import auth class WhenTestingKeystoneAuthentication(unittest.TestCase): - def setUp(self): - self.keystone = auth.KeystoneAuth(endpoint='endpoint_url', - username='user', - password='password', - tenant_name='demo') - def test_endpoint_username_password_tenant_are_required(self): with self.assertRaises(ValueError): keystone = auth.KeystoneAuth() diff --git a/barbicanclient/test/test_client.py b/barbicanclient/test/test_client.py index 8ef1e6b..418dba9 100644 --- a/barbicanclient/test/test_client.py +++ b/barbicanclient/test/test_client.py @@ -29,10 +29,6 @@ class WhenTestingClient(unittest.TestCase): self.user = 'user' self.password = 'password' self.tenant = 'tenant' - self.keystone = auth.KeystoneAuth(endpoint=self.auth_endpoint, - username=self.user, - password=self.password, - tenant_name=self.tenant) self.key = 'key' self.endpoint = 'http://localhost:9311/v1/' @@ -66,17 +62,7 @@ class WhenTestingClient(unittest.TestCase): authenticate=self.authenticate, request=self.request, endpoint=self.endpoint, - auth=False) - - def test_authenticated_client_requires_endpoint_user_pw_tenant(self): - with self.assertRaises(ValueError): - c = client.Client(auth=True) - with self.assertRaises(ValueError): - c = client.Client() # default auth=True - c=client.Client(auth_endpoint=self.auth_endpoint, user=self.user, - password=self.password, tenant=self.tenant, - #TODO(dmend): remove authenticate below - authenticate=self.authenticate) + tenant_id='test_tenant') def test_should_connect_with_token(self): self.assertFalse(self.authenticate.called) @@ -88,7 +74,8 @@ class WhenTestingClient(unittest.TestCase): key=self.key, tenant=self.tenant, authenticate=self.authenticate, - endpoint=self.endpoint) + endpoint=self.endpoint, + tenant_id='test_tenant') self.authenticate\ .assert_called_once_with(self.auth_endpoint, self.user, @@ -116,7 +103,8 @@ class WhenTestingClient(unittest.TestCase): token=self.auth_token, authenticate=self.authenticate, request=self.request, - endpoint=self.endpoint) + endpoint=self.endpoint, + tenant_id='test_tenant') def test_should_create_secret(self): body = {'status': "ACTIVE", |