Moved secret create and list into secret manager

This commit is contained in:
Douglas Mendizabal
2013-09-01 01:41:32 -05:00
parent bab914d1cb
commit 7c41da3a28
6 changed files with 186 additions and 107 deletions

27
barbicanclient/base.py Normal file
View File

@@ -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)

View File

@@ -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
self._session = requests.Session()
self.auth_plugin = auth_plugin
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 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
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.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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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",