Merge pull request #10 from Aghoreyshi/master
Authentication and Service Endpoint Retrieval; Addition of Environment Variable Defaults to Client Library
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -36,6 +36,10 @@ nosetests.xml
|
||||
|
||||
# Idea
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# generic venvs
|
||||
.venv
|
||||
|
||||
# OSX metadata
|
||||
.DS_Store
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
python-barbicanclient
|
||||
=====================
|
||||
|
||||
A python library for accessing the Barbican key management service.
|
||||
This is a client for the [Barbican](https://github.com/cloudkeep/barbican)
|
||||
Key Management API. There is a Python library for accessing the API
|
||||
(`barbicanclient` module), and a command-line script (`keep`).
|
||||
|
||||
@@ -2,6 +2,7 @@ import eventlet
|
||||
eventlet.monkey_patch(socket=True, select=True)
|
||||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
|
||||
from barbicanclient.secrets import Secret
|
||||
@@ -22,34 +23,50 @@ class Connection(object):
|
||||
SECRETS_PATH = 'secrets'
|
||||
ORDERS_PATH = 'orders'
|
||||
|
||||
def __init__(self, auth_endpoint, user, key, tenant,
|
||||
token=None, authenticate=None, request=None, **kwargs):
|
||||
def __init__(self, auth_endpoint=None, user=None, key=None, tenant=None,
|
||||
token=None, **kwargs):
|
||||
"""
|
||||
Authenticate and connect to the service endpoint, which can be
|
||||
received through authentication.
|
||||
|
||||
Environment variables will be used by default when their corresponding
|
||||
arguments are not passed in.
|
||||
|
||||
:param auth_endpoint: The auth URL to authenticate against
|
||||
default: env('OS_AUTH_URL')
|
||||
:param user: The user to authenticate as
|
||||
default: env('OS_USERNAME')
|
||||
:param key: The API key or password to auth with
|
||||
default: env('OS_PASSWORD')
|
||||
:param tenant: The tenant ID
|
||||
default: env('OS_TENANT_NAME')
|
||||
:keyword param endpoint: The barbican endpoint to connect to
|
||||
default: env('BARBICAN_ENDPOINT')
|
||||
|
||||
If a token is provided, an endpoint should be as well.
|
||||
"""
|
||||
|
||||
LOG.debug(_("Creating Connection object"))
|
||||
|
||||
self._auth_endpoint = auth_endpoint
|
||||
self.authenticate = authenticate or auth.authenticate
|
||||
self.request = request or requests.request
|
||||
self._user = user
|
||||
self._key = key
|
||||
self._tenant = tenant
|
||||
self._endpoint = (kwargs.get('endpoint')
|
||||
or 'https://barbican.api.rackspacecloud.com/v1/')
|
||||
self.env = kwargs.get('fake_env') or env
|
||||
self._auth_endpoint = auth_endpoint or self.env('OS_AUTH_URL')
|
||||
self._user = user or self.env('OS_USERNAME')
|
||||
self._key = key or self.env('OS_PASSWORD')
|
||||
self._tenant = tenant or self.env('OS_TENANT_NAME')
|
||||
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)
|
||||
self.connect(token=(token or self.env('AUTH_TOKEN')))
|
||||
|
||||
@property
|
||||
def _conn(self):
|
||||
"""
|
||||
Property to enable decorators to work
|
||||
properly
|
||||
"""
|
||||
"""Property to enable decorators to work properly"""
|
||||
return self
|
||||
|
||||
@property
|
||||
@@ -62,11 +79,16 @@ class Connection(object):
|
||||
"""The fully-qualified URI of the endpoint"""
|
||||
return self._endpoint
|
||||
|
||||
@endpoint.setter
|
||||
def endpoint(self, value):
|
||||
self._endpoint = value
|
||||
|
||||
def connect(self, token=None):
|
||||
"""
|
||||
Establishes a connection. If token is not None the
|
||||
token will be used for this connection and auth will
|
||||
not happen.
|
||||
Establishes a connection. If token is not None or empty, it will be
|
||||
used for this connection, and authentication will not take place.
|
||||
|
||||
:param token: An authentication token
|
||||
"""
|
||||
|
||||
LOG.debug(_("Establishing connection"))
|
||||
@@ -81,14 +103,17 @@ class Connection(object):
|
||||
self.auth_token = token
|
||||
else:
|
||||
LOG.debug(_("Authenticating token"))
|
||||
self._endpoint, self.auth_token = self.authenticate(
|
||||
endpoint, self.auth_token = self.authenticate(
|
||||
self._auth_endpoint,
|
||||
self._user,
|
||||
self._key,
|
||||
self._tenant,
|
||||
service_type='key-store',
|
||||
endpoint=self._endpoint,
|
||||
cacert=self._cacert
|
||||
)
|
||||
if self.endpoint is None:
|
||||
self.endpoint = endpoint
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
@@ -108,6 +133,9 @@ class Connection(object):
|
||||
to the given offset and limit, a reference to the previous set of
|
||||
secrets, and a reference to the next set of secrets. Either of the
|
||||
references may be None.
|
||||
|
||||
:param limit: The limit to the number of secrets to list
|
||||
:param offset: The offset from the beginning to start listing
|
||||
"""
|
||||
LOG.debug(_("Listing secrets - offset: {0}, limit: {1}").format(offset,
|
||||
limit))
|
||||
@@ -122,6 +150,8 @@ class Connection(object):
|
||||
to the offset and limit within href, a reference to the previous set
|
||||
of secrets, and a reference to the next set of secrets. Either of the
|
||||
references may be None.
|
||||
|
||||
:param href: The full secrets URI
|
||||
"""
|
||||
LOG.debug(_("Listing secrets by href"))
|
||||
LOG.debug("href: {0}".format(href))
|
||||
@@ -151,14 +181,13 @@ class Connection(object):
|
||||
"""
|
||||
Creates and returns a Secret object with all of its metadata filled in.
|
||||
|
||||
arguments:
|
||||
mime_type - The MIME type of the secret
|
||||
plain_text - The unencrypted secret
|
||||
name - A friendly name for the secret
|
||||
algorithm - The algorithm the secret is used with
|
||||
bit_length - The bit length of the secret
|
||||
cypher_type - The cypher type (e.g. block cipher mode of operation)
|
||||
expiration - The expiration time for the secret in ISO 8601 format
|
||||
:param mime_type: The MIME type of the secret
|
||||
:param plain_text: The unencrypted secret
|
||||
:param name: A friendly name for 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 mime_type {0}").format(mime_type))
|
||||
href = "{0}/{1}".format(self._tenant, self.SECRETS_PATH)
|
||||
@@ -185,7 +214,9 @@ class Connection(object):
|
||||
|
||||
def delete_secret_by_id(self, secret_id):
|
||||
"""
|
||||
Deletes a secret using its UUID
|
||||
Deletes a secret
|
||||
|
||||
:param secret_id: The UUID of the secret
|
||||
"""
|
||||
href = "{0}/{1}/{2}".format(self._tenant, self.SECRETS_PATH, secret_id)
|
||||
LOG.info(_("Deleting secret - Secret ID: {0}").format(secret_id))
|
||||
@@ -193,14 +224,18 @@ class Connection(object):
|
||||
|
||||
def delete_secret(self, href):
|
||||
"""
|
||||
Deletes a secret using its full reference
|
||||
Deletes a secret
|
||||
|
||||
:param href: The full URI of the secret
|
||||
"""
|
||||
hdrs, body = self._perform_http(href=href, method='DELETE')
|
||||
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
||||
|
||||
def get_secret_by_id(self, secret_id):
|
||||
"""
|
||||
Returns a Secret object using the secret's UUID
|
||||
Returns a Secret object
|
||||
|
||||
:param secret_id: The UUID of the secret
|
||||
"""
|
||||
LOG.debug(_("Getting secret - Secret ID: {0}").format(secret_id))
|
||||
href = "{0}/{1}/{2}".format(self._tenant, self.SECRETS_PATH, secret_id)
|
||||
@@ -208,7 +243,9 @@ class Connection(object):
|
||||
|
||||
def get_secret(self, href):
|
||||
"""
|
||||
Returns a Secret object using the secret's full reference
|
||||
Returns a Secret object
|
||||
|
||||
:param href: The full URI of the secret
|
||||
"""
|
||||
hdrs, body = self._perform_http(href=href, method='GET')
|
||||
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
||||
@@ -216,7 +253,10 @@ class Connection(object):
|
||||
|
||||
def get_raw_secret_by_id(self, secret_id, mime_type):
|
||||
"""
|
||||
Returns the raw secret using the secret's UUID and MIME type
|
||||
Returns the raw secret
|
||||
|
||||
:param secret_id: The UUID of the secret
|
||||
:param mime_type: The MIME type of the secret
|
||||
"""
|
||||
LOG.debug(_("Getting raw secret - Secret ID: {0}").format(secret_id))
|
||||
href = "{0}/{1}/{2}".format(self._tenant, self.SECRETS_PATH, secret_id)
|
||||
@@ -224,7 +264,10 @@ class Connection(object):
|
||||
|
||||
def get_raw_secret(self, href, mime_type):
|
||||
"""
|
||||
Returns the raw secret using the secret's UUID and MIME type
|
||||
Returns the raw secret
|
||||
|
||||
:param href: The reference to the secret
|
||||
:param mime_type: The MIME type of the secret
|
||||
"""
|
||||
hdrs = {"Accept": mime_type}
|
||||
hdrs, body = self._perform_http(href=href, method='GET', headers=hdrs,
|
||||
@@ -238,6 +281,9 @@ class Connection(object):
|
||||
to the given offset and limit, a reference to the previous set of
|
||||
orders, and a reference to the next set of orders. Either of the
|
||||
references may be None.
|
||||
|
||||
:param limit: The limit to the number of orders to list
|
||||
:param offset: The offset from the beginning to start listing
|
||||
"""
|
||||
LOG.debug(_("Listing orders - offset: {0}, limit: {1}").format(offset,
|
||||
limit))
|
||||
@@ -252,6 +298,8 @@ class Connection(object):
|
||||
to the offset and limit within href, a reference to the previous set
|
||||
of orders, and a reference to the next set of orders. Either of the
|
||||
references may be None.
|
||||
|
||||
:param href: The full orders URI
|
||||
"""
|
||||
LOG.debug(_("Listing orders by href"))
|
||||
LOG.debug("href: {0}".format(href))
|
||||
@@ -279,12 +327,11 @@ class Connection(object):
|
||||
"""
|
||||
Creates and returns an Order object with all of its metadata filled in.
|
||||
|
||||
arguments:
|
||||
mime_type - The MIME type of the secret
|
||||
name - A friendly name for the secret
|
||||
algorithm - The algorithm the secret is used with
|
||||
bit_length - The bit length of the secret
|
||||
cypher_type - The cypher type (e.g. block cipher mode of operation)
|
||||
:param mime_type: The MIME type of the secret
|
||||
:param name: A friendly name for 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)
|
||||
"""
|
||||
LOG.debug(_("Creating order of mime_type {0}").format(mime_type))
|
||||
href = "{0}/{1}".format(self._tenant, self.ORDERS_PATH)
|
||||
@@ -307,7 +354,9 @@ class Connection(object):
|
||||
|
||||
def delete_order_by_id(self, order_id):
|
||||
"""
|
||||
Deletes an order using its UUID
|
||||
Deletes an order
|
||||
|
||||
:param order_id: The UUID of the order
|
||||
"""
|
||||
LOG.info(_("Deleting order - Order ID: {0}").format(order_id))
|
||||
href = "{0}/{1}/{2}".format(self._tenant, self.ORDERS_PATH, order_id)
|
||||
@@ -315,14 +364,18 @@ class Connection(object):
|
||||
|
||||
def delete_order(self, href):
|
||||
"""
|
||||
Deletes an order using its full reference
|
||||
Deletes an order
|
||||
|
||||
:param href: The full URI of the order
|
||||
"""
|
||||
hdrs, body = self._perform_http(href=href, method='DELETE')
|
||||
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
||||
|
||||
def get_order_by_id(self, order_id):
|
||||
"""
|
||||
Returns an Order object using the order's UUID
|
||||
Returns an Order object
|
||||
|
||||
:param order_id: The UUID of the order
|
||||
"""
|
||||
LOG.debug(_("Getting order - Order ID: {0}").format(order_id))
|
||||
href = "{0}/{1}/{2}".format(self._tenant, self.ORDERS_PATH, order_id)
|
||||
@@ -330,7 +383,9 @@ class Connection(object):
|
||||
|
||||
def get_order(self, href):
|
||||
"""
|
||||
Returns an Order object using the order's full reference
|
||||
Returns an Order object
|
||||
|
||||
:param href: The full URI of the order
|
||||
"""
|
||||
hdrs, body = self._perform_http(href=href, method='GET')
|
||||
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
||||
@@ -347,20 +402,25 @@ class Connection(object):
|
||||
Perform an HTTP operation, checking for appropriate
|
||||
errors, etc. and returns the response
|
||||
|
||||
Returns the headers and body as a tuple.
|
||||
|
||||
:param method: The http method to use (GET, PUT, etc)
|
||||
:param body: The optional body to submit
|
||||
:param headers: Any additional headers to submit
|
||||
:param parse_json: Whether the response body should be parsed as json
|
||||
:return: (headers, body)
|
||||
"""
|
||||
if not isinstance(request_body, str):
|
||||
request_body = json.dumps(request_body)
|
||||
|
||||
url = urljoin(self._endpoint, href)
|
||||
if not self.endpoint.endswith('/'):
|
||||
self.endpoint += '/'
|
||||
|
||||
url = urljoin(self.endpoint, href)
|
||||
|
||||
headers['X-Auth-Token'] = self.auth_token
|
||||
|
||||
response = self.request(method=method, url=url, data=request_body,
|
||||
headers=headers)
|
||||
|
||||
# Check if the status code is 2xx class
|
||||
if not response.ok:
|
||||
LOG.error('Bad response: {0}'.format(response.status_code))
|
||||
@@ -376,3 +436,18 @@ class Connection(object):
|
||||
resp_body = ''
|
||||
|
||||
return response.headers, resp_body
|
||||
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
"""Search for the first defined of possibly many env vars
|
||||
|
||||
Returns the first environment variable defined in vars, or
|
||||
returns the default defined in kwargs.
|
||||
|
||||
Source: Keystone's shell.py
|
||||
"""
|
||||
for v in vars:
|
||||
value = os.environ.get(v, None)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
46
keep → barbicanclient/keep.py
Executable file → Normal file
46
keep → barbicanclient/keep.py
Executable file → Normal file
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from barbicanclient import client
|
||||
|
||||
@@ -24,23 +23,26 @@ class Keep:
|
||||
choices=["order", "secret"],
|
||||
help="type to operate on")
|
||||
parser.add_argument('--auth_endpoint', '-A',
|
||||
default=env('OS_AUTH_URL'),
|
||||
default=client.env('OS_AUTH_URL'),
|
||||
help='the URL to authenticate against (default: '
|
||||
'%(default)s)')
|
||||
parser.add_argument('--user', '-U', default=env('OS_USERNAME'),
|
||||
parser.add_argument('--user', '-U', default=client.env('OS_USERNAME'),
|
||||
help='the user to authenticate as (default: %(de'
|
||||
'fault)s)')
|
||||
parser.add_argument('--password', '-P', default=env('OS_PASSWORD'),
|
||||
parser.add_argument('--password', '-P',
|
||||
default=client.env('OS_PASSWORD'),
|
||||
help='the API key or password to authenticate with'
|
||||
' (default: %(default)s)')
|
||||
parser.add_argument('--tenant', '-T', default=env('OS_TENANT_NAME'),
|
||||
parser.add_argument('--tenant', '-T',
|
||||
default=client.env('OS_TENANT_NAME'),
|
||||
help='the tenant ID (default: %(default)s)')
|
||||
parser.add_argument('--endpoint', '-E', default=env('SERVICE_ENDPOINT')
|
||||
, help='the URL of the barbican server (default: %'
|
||||
parser.add_argument('--endpoint', '-E',
|
||||
default=client.env('BARBICAN_ENDPOINT'),
|
||||
help='the URL of the barbican server (default: %'
|
||||
'(default)s)')
|
||||
parser.add_argument('--token', '-K', default=env('SERVICE_TOKEN'),
|
||||
help='the authentication token (default: %(default'
|
||||
')s)')
|
||||
parser.add_argument('--token', '-K',
|
||||
default=client.env('AUTH_TOKEN'), help='the au'
|
||||
'thentication token (default: %(default)s)')
|
||||
return parser
|
||||
|
||||
def add_create_args(self):
|
||||
@@ -149,33 +151,19 @@ class Keep:
|
||||
l = self.conn.list_orders(args.limit, args.offset)
|
||||
for i in l[0]:
|
||||
print i
|
||||
print 'Displayed {0} {1}s - offset: {2}'.format(len(l[0]), args.type,
|
||||
args.offset)
|
||||
print '{0}s displayed: {1} - offset: {2}'.format(args.type, len(l[0]),
|
||||
args.offset)
|
||||
|
||||
def execute(self):
|
||||
args = self.parser.parse_args()
|
||||
def execute(self, **kwargs):
|
||||
args = self.parser.parse_args(kwargs.get('argv'))
|
||||
self.conn = client.Connection(args.auth_endpoint, args.user,
|
||||
args.password, args.tenant,
|
||||
args.token,
|
||||
endpoint=args.endpoint)
|
||||
|
||||
args.func(args)
|
||||
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
"""Search for the first defined of possibly many env vars
|
||||
|
||||
Returns the first environment variable defined in vars, or
|
||||
returns the default defined in kwargs.
|
||||
|
||||
Source: Keystone's shell.py
|
||||
"""
|
||||
for v in vars:
|
||||
value = os.environ.get(v, None)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def main():
|
||||
k = Keep()
|
||||
k.execute()
|
||||
BIN
barbicanclient/openstack/.DS_Store
vendored
BIN
barbicanclient/openstack/.DS_Store
vendored
Binary file not shown.
6
clientrc
Normal file
6
clientrc
Normal file
@@ -0,0 +1,6 @@
|
||||
export OS_TENANT_NAME=demo
|
||||
export OS_USERNAME=demo
|
||||
export OS_PASSWORD=password
|
||||
export OS_AUTH_URL="http://keystone-int.cloudkeep.io:5000/v2.0/"
|
||||
export BARBICAN_ENDPOINT="http://api-01-int.cloudkeep.io:9311/v1/"
|
||||
export AUTH_TOKEN=''
|
||||
5
setup.py
5
setup.py
@@ -55,5 +55,8 @@ setuptools.setup(
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Environment :: No Input/Output (Daemon)',
|
||||
],
|
||||
scripts = ['keep']
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
keep = barbicanclient.keep:main
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -40,6 +40,8 @@ class WhenTestingConnection(unittest.TestCase):
|
||||
self.auth_token = 'token'
|
||||
self.href = 'http://localhost:9311/v1/12345/orders'
|
||||
|
||||
self.fake_env = MagicMock()
|
||||
self.fake_env.return_value = None
|
||||
self.authenticate = MagicMock()
|
||||
self.authenticate.return_value = (self.endpoint, self.auth_token)
|
||||
self.request = MagicMock()
|
||||
@@ -62,7 +64,8 @@ class WhenTestingConnection(unittest.TestCase):
|
||||
self.key, self.tenant,
|
||||
token=self.auth_token,
|
||||
authenticate=self.authenticate,
|
||||
request=self.request)
|
||||
request=self.request,
|
||||
endpoint=self.endpoint)
|
||||
|
||||
def test_should_connect_with_token(self):
|
||||
self.assertFalse(self.authenticate.called)
|
||||
@@ -79,6 +82,7 @@ class WhenTestingConnection(unittest.TestCase):
|
||||
self.user,
|
||||
self.key,
|
||||
self.tenant,
|
||||
service_type='key-store',
|
||||
endpoint=self.endpoint,
|
||||
cacert=None
|
||||
)
|
||||
@@ -89,6 +93,16 @@ class WhenTestingConnection(unittest.TestCase):
|
||||
self.assertEqual(self.tenant, self.connection._tenant)
|
||||
self.assertEqual(self.endpoint, self.connection._endpoint)
|
||||
|
||||
def test_should_raise_for_bad_args(self):
|
||||
with self.assertRaises(ClientException):
|
||||
self.connection = client.Connection(None, self.user,
|
||||
self.key, self.tenant,
|
||||
fake_env=self.fake_env,
|
||||
token=self.auth_token,
|
||||
authenticate=self.authenticate,
|
||||
request=self.request,
|
||||
endpoint=self.endpoint)
|
||||
|
||||
def test_should_create_secret(self):
|
||||
body = {'status': 'ACTIVE',
|
||||
'content_types': {'default': 'text/plain'},
|
||||
@@ -318,7 +332,7 @@ class WhenTestingConnection(unittest.TestCase):
|
||||
parse_json=False)
|
||||
self.assertEqual(self.request.return_value.content, body)
|
||||
|
||||
def test_should_raise_exception(self):
|
||||
def test_should_raise_for_bad_response(self):
|
||||
self._setup_request()
|
||||
self.request.return_value.ok = False
|
||||
self.request.return_value.status_code = 404
|
||||
|
||||
Reference in New Issue
Block a user