summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAde Lee <alee@redhat.com>2015-07-30 00:47:58 -0400
committerAde Lee <alee@redhat.com>2015-08-03 12:47:53 -0400
commitb3be322d9de4876c4ca323097a251a98891ef0be (patch)
tree8cfdde244b39cb83eddc2973702b8aeca5138f06
parent072744565418a3cff8702d0961efc8029f5b3a02 (diff)
downloadpython-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.py56
-rw-r--r--barbicanclient/cas.py236
-rw-r--r--barbicanclient/client.py2
-rw-r--r--barbicanclient/tests/test_cas.py113
-rw-r--r--doc/source/reference.rst9
-rw-r--r--setup.cfg3
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
==========
diff --git a/setup.cfg b/setup.cfg
index 58e5f11..37838f7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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