458 lines
17 KiB
Python
458 lines
17 KiB
Python
import eventlet
|
|
eventlet.monkey_patch(socket=True, select=True)
|
|
|
|
import json
|
|
import os
|
|
import requests
|
|
|
|
from barbicanclient.secrets import Secret
|
|
from barbicanclient.orders import Order
|
|
from barbicanclient.common import auth
|
|
from barbicanclient.openstack.common import log
|
|
from barbicanclient.common.exceptions import ClientException
|
|
from barbicanclient.openstack.common.gettextutils import _
|
|
from urlparse import urljoin
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
log.setup('barbicanclient')
|
|
|
|
|
|
class Connection(object):
|
|
SECRETS_PATH = 'secrets'
|
|
ORDERS_PATH = 'orders'
|
|
|
|
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.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 or self.env('AUTH_TOKEN')))
|
|
|
|
@property
|
|
def _conn(self):
|
|
"""Property to enable decorators to work properly"""
|
|
return self
|
|
|
|
@property
|
|
def auth_endpoint(self):
|
|
"""The fully-qualified URI of the auth endpoint"""
|
|
return self._auth_endpoint
|
|
|
|
@property
|
|
def endpoint(self):
|
|
"""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 or empty, it will be
|
|
used for this connection, and authentication will not take place.
|
|
|
|
:param token: An authentication token
|
|
"""
|
|
|
|
LOG.debug(_("Establishing connection"))
|
|
|
|
self._session = requests.Session()
|
|
|
|
# headers = {"Client-Id": self._client_id}
|
|
# self._session.headers.update(headers)
|
|
self._session.verify = True
|
|
|
|
if token:
|
|
self.auth_token = token
|
|
else:
|
|
LOG.debug(_("Authenticating token"))
|
|
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):
|
|
try:
|
|
return self._session.headers['X-Auth-Token']
|
|
except KeyError:
|
|
return None
|
|
|
|
@auth_token.setter
|
|
def auth_token(self, value):
|
|
self._token = value
|
|
self._session.headers['X-Auth-Token'] = value
|
|
|
|
def list_secrets(self, limit=10, offset=0):
|
|
"""
|
|
Returns a tuple containing three items: a list of secrets pertaining
|
|
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))
|
|
href = "{0}/{1}?limit={2}&offset={3}".format(self._tenant,
|
|
self.SECRETS_PATH,
|
|
limit, offset)
|
|
return self.list_secrets_by_href(href)
|
|
|
|
def list_secrets_by_href(self, href):
|
|
"""
|
|
Returns a tuple containing three items: a list of secrets pertaining
|
|
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))
|
|
if href is None:
|
|
return [], None, None
|
|
|
|
hdrs, body = self._perform_http(href=href, method='GET')
|
|
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
|
|
|
secrets_dict = body['secrets']
|
|
secrets = [Secret(self._conn, s) for s in secrets_dict]
|
|
|
|
prev_ref = body.get('previous')
|
|
|
|
next_ref = body.get('next')
|
|
|
|
return secrets, prev_ref, next_ref
|
|
|
|
def create_secret(self,
|
|
name=None,
|
|
payload=None,
|
|
payload_content_type=None,
|
|
payload_content_encoding=None,
|
|
algorithm=None,
|
|
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'])
|
|
|
|
def delete_secret_by_id(self, secret_id):
|
|
"""
|
|
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))
|
|
return self.delete_secret(href)
|
|
|
|
def delete_secret(self, href):
|
|
"""
|
|
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
|
|
|
|
: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)
|
|
return self.get_secret(href)
|
|
|
|
def get_secret(self, href):
|
|
"""
|
|
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))
|
|
return Secret(self._conn, body)
|
|
|
|
def get_raw_secret_by_id(self, secret_id, payload_content_type):
|
|
"""
|
|
Returns the raw secret
|
|
|
|
:param secret_id: The UUID of the secret
|
|
:param payload_content_type: The data 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)
|
|
return self.get_raw_secret(href, payload_content_type)
|
|
|
|
def get_raw_secret(self, href, payload_content_type):
|
|
"""
|
|
Returns the raw secret
|
|
|
|
:param href: The reference to the secret
|
|
:param payload_content_type: The data type of the secret
|
|
"""
|
|
hdrs = {"Accept": payload_content_type}
|
|
hdrs, body = self._perform_http(href=href, method='GET', headers=hdrs,
|
|
parse_json=False)
|
|
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
|
return body
|
|
|
|
def list_orders(self, limit=10, offset=0):
|
|
"""
|
|
Returns a tuple containing three items: a list of orders pertaining
|
|
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))
|
|
href = "{0}/{1}?limit={2}&offset={3}".format(self._tenant,
|
|
self.ORDERS_PATH,
|
|
limit, offset)
|
|
return self.list_orders_by_href(href)
|
|
|
|
def list_orders_by_href(self, href):
|
|
"""
|
|
Returns a tuple containing three items: a list of orders pertaining
|
|
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))
|
|
if href is None:
|
|
return [], None, None
|
|
|
|
hdrs, body = self._perform_http(href=href, method='GET')
|
|
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
|
|
|
orders_dict = body['orders']
|
|
orders = [Order(self._conn, o) for o in orders_dict]
|
|
|
|
prev_ref = body.get('previous')
|
|
|
|
next_ref = body.get('next')
|
|
|
|
return orders, prev_ref, next_ref
|
|
|
|
def create_order(self,
|
|
name=None,
|
|
payload_content_type='application/octet-stream',
|
|
algorithm='aes',
|
|
bit_length=256,
|
|
cypher_type='cbc',
|
|
expiration=None):
|
|
"""
|
|
Creates and returns an Order object with all of its metadata filled in.
|
|
|
|
: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 order"))
|
|
href = "{0}/{1}".format(self._tenant, self.ORDERS_PATH)
|
|
LOG.debug("href: {0}".format(href))
|
|
order_dict = {'secret': {}}
|
|
order_dict['secret']['name'] = name
|
|
order_dict['secret'][
|
|
'payload_content_type'] = payload_content_type
|
|
order_dict['secret']['algorithm'] = algorithm
|
|
order_dict['secret']['bit_length'] = bit_length
|
|
order_dict['secret']['cypher_type'] = cypher_type
|
|
order_dict['secret']['expiration'] = expiration
|
|
self._remove_empty_keys(order_dict['secret'])
|
|
LOG.debug(_("Request body: {0}").format(order_dict['secret']))
|
|
hdrs, body = self._perform_http(href=href,
|
|
method='POST',
|
|
request_body=json.dumps(order_dict))
|
|
|
|
LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body))
|
|
|
|
return self.get_order(body['order_ref'])
|
|
|
|
def delete_order_by_id(self, order_id):
|
|
"""
|
|
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)
|
|
return self.delete_order(href)
|
|
|
|
def delete_order(self, href):
|
|
"""
|
|
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
|
|
|
|
: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)
|
|
return self.get_order(href)
|
|
|
|
def get_order(self, href):
|
|
"""
|
|
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))
|
|
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):
|
|
"""
|
|
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
|
|
"""
|
|
if not isinstance(request_body, str):
|
|
request_body = json.dumps(request_body)
|
|
|
|
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))
|
|
raise ClientException(href=href, method=method,
|
|
http_status=response.status_code,
|
|
http_response_content=response.content)
|
|
|
|
if response.content and parse_json is True:
|
|
resp_body = json.loads(response.content)
|
|
elif response.content and parse_json is False:
|
|
resp_body = response.content
|
|
else:
|
|
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', '')
|