Moved secret create and list into secret manager
This commit is contained in:
27
barbicanclient/base.py
Normal file
27
barbicanclient/base.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user