diff options
author | Ade Lee <alee@redhat.com> | 2015-07-30 00:47:58 -0400 |
---|---|---|
committer | Ade Lee <alee@redhat.com> | 2015-08-03 12:47:53 -0400 |
commit | b3be322d9de4876c4ca323097a251a98891ef0be (patch) | |
tree | 8cfdde244b39cb83eddc2973702b8aeca5138f06 | |
parent | 072744565418a3cff8702d0961efc8029f5b3a02 (diff) | |
download | python-barbicanclient-b3be322d9de4876c4ca323097a251a98891ef0be.tar.gz |
Add ability to add and list CAs
Change-Id: Ice07a597658facc5d774d4c4eff9e3093121c851
Implements: blueprint identify-cas
-rw-r--r-- | barbicanclient/barbican_cli/cas.py | 56 | ||||
-rw-r--r-- | barbicanclient/cas.py | 236 | ||||
-rw-r--r-- | barbicanclient/client.py | 2 | ||||
-rw-r--r-- | barbicanclient/tests/test_cas.py | 113 | ||||
-rw-r--r-- | doc/source/reference.rst | 9 | ||||
-rw-r--r-- | setup.cfg | 3 |
6 files changed, 419 insertions, 0 deletions
diff --git a/barbicanclient/barbican_cli/cas.py b/barbicanclient/barbican_cli/cas.py new file mode 100644 index 0000000..1aa12a3 --- /dev/null +++ b/barbicanclient/barbican_cli/cas.py @@ -0,0 +1,56 @@ +# 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. +""" +Command-line interface sub-commands related to secrets. +""" +from cliff import lister +from cliff import show + +from barbicanclient import cas + + +class GetCA(show.ShowOne): + """Retrieve a CA by providing its URI.""" + + def get_parser(self, prog_name): + parser = super(GetCA, self).get_parser(prog_name) + parser.add_argument('URI', help='The URI reference for the CA.') + return parser + + def take_action(self, args): + entity = self.app.client.cas.get(ca_ref=args.URI) + return entity._get_formatted_entity() + + +class ListCA(lister.Lister): + """List cas.""" + + def get_parser(self, prog_name): + parser = super(ListCA, self).get_parser(prog_name) + parser.add_argument('--limit', '-l', default=10, + help='specify the limit to the number of items ' + 'to list per page (default: %(default)s; ' + 'maximum: 100)', + type=int) + parser.add_argument('--offset', '-o', default=0, + help='specify the page offset ' + '(default: %(default)s)', + type=int) + parser.add_argument('--name', '-n', default=None, + help='specify the secret name ' + '(default: %(default)s)') + return parser + + def take_action(self, args): + obj_list = self.app.client.cas.list(args.limit, args.offset, args.name) + return cas.CA._list_objects(obj_list) diff --git a/barbicanclient/cas.py b/barbicanclient/cas.py new file mode 100644 index 0000000..b4a66d5 --- /dev/null +++ b/barbicanclient/cas.py @@ -0,0 +1,236 @@ +# Copyright (c) 2015 Red Hat 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. +import functools +import logging +import traceback + +from oslo_utils.timeutils import parse_isotime + +from barbicanclient import base +from barbicanclient import formatter + + +LOG = logging.getLogger(__name__) + + +def lazy(func): + @functools.wraps(func) + def wrapper(self, *args): + self._fill_lazy_properties() + return func(self, *args) + return wrapper + + +class CAFormatter(formatter.EntityFormatter): + + columns = ("CA href", + "Name", + "Description", + "Created", + "Updated", + "Status", + "Plugin Name", + "Plugin CA ID", + "Expiration" + ) + + def _get_formatted_data(self): + data = (self.ca_ref, + self.name, + self.description, + self.created, + self.updated, + self.status, + self.plugin_name, + self.plugin_ca_id, + self.expiration + ) + return data + + +class CA(CAFormatter): + """ + CAs represent certificate authorities or subCAs with which the Barbican + service is configured to interact. + """ + _entity = 'cas' + + def __init__(self, api, meta=None, expiration=None, + plugin_name=None, plugin_ca_id=None, + ca_ref=None, created=None, updated=None, + status=None, creator_id=None): + """ + CA objects should not be instantiated directly. You should use + the `create` or `get` methods of the + :class:`barbicanclient.cas.CAManager` instead. + """ + self._api = api + self._ca_ref = ca_ref + self._fill_from_data( + meta=meta, + expiration=expiration, + plugin_name=plugin_name, + plugin_ca_id=plugin_ca_id, + created=created, + updated=updated, + status=status, + creator_id=creator_id + ) + + @property + def ca_ref(self): + return self._ca_ref + + @property + @lazy + def name(self): + return self._name + + @property + @lazy + def expiration(self): + return self._expiration + + @property + @lazy + def description(self): + return self._description + + @property + @lazy + def plugin_name(self): + return self._plugin_name + + @property + @lazy + def plugin_ca_id(self): + return self._plugin_ca_id + + @property + @lazy + def created(self): + return self._created + + @property + @lazy + def updated(self): + return self._updated + + @property + @lazy + def status(self): + return self._status + + def _fill_from_data(self, meta=None, expiration=None, + plugin_name=None, plugin_ca_id=None, created=None, + updated=None, status=None, creator_id=None): + if meta: + for s in meta: + key = list(s.keys())[0] + value = list(s.values())[0] + if key == 'name': + self._name = value + if key == 'description': + self._description = value + self._plugin_name = plugin_name + self._plugin_ca_id = plugin_ca_id + self._expiration = expiration + self._creator_id = creator_id + if self._expiration: + self._expiration = parse_isotime(self._expiration) + if self._ca_ref: + self._status = status + self._created = created + self._updated = updated + if self._created: + self._created = parse_isotime(self._created) + if self._updated: + self._updated = parse_isotime(self._updated) + else: + self._status = None + self._created = None + self._updated = None + + def _fill_lazy_properties(self): + if self._ca_ref and not self._plugin_name: + result = self._api.get(self._ca_ref) + self._fill_from_data( + meta=result.get('meta'), + expiration=result.get('expiration'), + plugin_name=result.get('plugin_name'), + plugin_ca_id=result.get('plugin_ca_id'), + created=result.get('created'), + updated=result.get('updated'), + status=result.get('status') + ) + + def __repr__(self): + if self._ca_ref: + return 'CA(ca_ref="{0}")'.format(self._ca_ref) + return 'CA(name="{0}")'.format(self._name) + + +class CAManager(base.BaseEntityManager): + """Entity Manager for Secret entities""" + + def __init__(self, api): + super(CAManager, self).__init__(api, 'cas') + + def get(self, ca_ref): + """ + Retrieve an existing CA from Barbican + + :param str ca_ref: Full HATEOAS reference to a CA + :returns: CA object retrieved from Barbican + :rtype: :class:`barbicanclient.cas.CA` + :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses + :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses + :raises barbicanclient.exceptions.HTTPServerError: 5xx Responses + """ + LOG.debug("Getting ca - CA href: {0}".format(ca_ref)) + base.validate_ref(ca_ref, 'CA') + return CA( + api=self._api, + ca_ref=ca_ref + ) + + def list(self, limit=10, offset=0, name=None): + """ + List CAs for the project + + This method uses the limit and offset parameters for paging, + and also supports filtering. + + :param limit: Max number of CAs returned + :param offset: Offset secrets to begin list + :param name: Name filter for the list + :returns: list of CA objects that satisfy the provided filter + criteria. + :rtype: list + :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses + :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses + :raises barbicanclient.exceptions.HTTPServerError: 5xx Responses + """ + LOG.debug('Listing CAs - offset {0} limit {1}'.format(offset, limit)) + params = {'limit': limit, 'offset': offset} + if name: + params['name'] = name + + response = self._api.get(self._entity, params=params) + + return [ + CA(api=self._api, ca_ref=s) + for s in response.get('cas', []) + ] diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 6e0a47e..e74e5e5 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -20,6 +20,7 @@ from keystoneclient import adapter from keystoneclient.auth.base import BaseAuthPlugin from keystoneclient import session as ks_session +from barbicanclient import cas from barbicanclient import containers from barbicanclient import exceptions from barbicanclient._i18n import _ @@ -167,6 +168,7 @@ class Client(object): self.secrets = secrets.SecretManager(httpclient) self.orders = orders.OrderManager(httpclient) self.containers = containers.ContainerManager(httpclient) + self.cas = cas.CAManager(httpclient) def env(*vars, **kwargs): diff --git a/barbicanclient/tests/test_cas.py b/barbicanclient/tests/test_cas.py new file mode 100644 index 0000000..2b32e12 --- /dev/null +++ b/barbicanclient/tests/test_cas.py @@ -0,0 +1,113 @@ +# 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. +from oslo_utils import timeutils + +from barbicanclient.tests import test_client +from barbicanclient import cas + + +class CAData(object): + def __init__(self): + self.name = u'Test CA' + self.description = u'Test CA description' + self.plugin_name = u'Test CA Plugin' + self.plugin_ca_id = 'plugin_uuid' + + now = timeutils.utcnow() + self.expiration = str(now) + self.created = str(now) + + self.meta = [ + {'name': self.name}, + {'description': self.description} + ] + + self.ca_dict = {'meta': self.meta, + 'status': u'ACTIVE', + 'plugin_name': self.plugin_name, + 'plugin_ca_id': self.plugin_ca_id, + 'created': self.created} + + def get_dict(self, ca_ref=None): + ca = self.ca_dict + if ca_ref: + ca['ca_ref'] = ca_ref + return ca + + +class WhenTestingCAs(test_client.BaseEntityResource): + + def setUp(self): + self._setUp('cas') + + self.ca = CAData() + self.manager = self.client.cas + + def test_should_get_lazy(self): + data = self.ca.get_dict(self.entity_href) + m = self.responses.get(self.entity_href, json=data) + + ca = self.manager.get(ca_ref=self.entity_href) + self.assertIsInstance(ca, cas.CA) + self.assertEqual(self.entity_href, ca._ca_ref) + + # Verify GET wasn't called yet + self.assertFalse(m.called) + + # Check an attribute to trigger lazy-load + self.assertEqual(self.ca.plugin_ca_id, ca.plugin_ca_id) + + # Verify the correct URL was used to make the GET call + self.assertEqual(self.entity_href, m.last_request.url) + + def test_should_get_lazy_in_meta(self): + data = self.ca.get_dict(self.entity_href) + m = self.responses.get(self.entity_href, json=data) + + ca = self.manager.get(ca_ref=self.entity_href) + self.assertIsInstance(ca, cas.CA) + self.assertEqual(self.entity_href, ca._ca_ref) + + # Verify GET wasn't called yet + self.assertFalse(m.called) + + # Check an attribute in meta to trigger lazy-load + self.assertEqual(self.ca.name, ca.name) + + # Verify the correct URL was used to make the GET call + self.assertEqual(self.entity_href, m.last_request.url) + + def test_should_get_list(self): + ca_resp = self.entity_href + + data = {"cas": [ca_resp for v in range(3)]} + m = self.responses.get(self.entity_base, json=data) + + ca_list = self.manager.list(limit=10, offset=5) + self.assertTrue(len(ca_list) == 3) + self.assertIsInstance(ca_list[0], cas.CA) + self.assertEqual(self.entity_href, ca_list[0].ca_ref) + + # Verify the correct URL was used to make the call. + self.assertEqual(self.entity_base, + m.last_request.url.split('?')[0]) + + # Verify that correct information was sent in the call. + self.assertEqual(['10'], m.last_request.qs['limit']) + self.assertEqual(['5'], m.last_request.qs['offset']) + + def test_should_fail_get_invalid_ca(self): + self.assertRaises(ValueError, self.manager.get, + **{'ca_ref': '12345'}) diff --git a/doc/source/reference.rst b/doc/source/reference.rst index 2232a28..b21dbb0 100644 --- a/doc/source/reference.rst +++ b/doc/source/reference.rst @@ -47,6 +47,15 @@ Containers .. autoclass:: barbicanclient.containers.CertificateContainer :members: +Certificate Authorities +======================= + +.. autoclass:: barbicanclient.cas.CAManager + :members: + +.. autoclass:: barbicanclient.cas.CA + :members: + Exceptions ========== @@ -44,6 +44,9 @@ barbican.client = container_list = barbicanclient.barbican_cli.containers:ListContainer container_create = barbicanclient.barbican_cli.containers:CreateContainer + ca_get = barbicanclient.barbican_cli.cas:GetCA + ca_list = barbicanclient.barbican_cli.cas:ListCA + [build_sphinx] source-dir = doc/source build-dir = doc/build |