Add ability to add and list CAs

Change-Id: Ice07a597658facc5d774d4c4eff9e3093121c851
Implements: blueprint identify-cas
This commit is contained in:
Ade Lee
2015-07-30 00:47:58 -04:00
parent 0727445654
commit b3be322d9d
6 changed files with 419 additions and 0 deletions

View File

@@ -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)

236
barbicanclient/cas.py Normal file
View File

@@ -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', [])
]

View File

@@ -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):

View File

@@ -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'})

View File

@@ -47,6 +47,15 @@ Containers
.. autoclass:: barbicanclient.containers.CertificateContainer
:members:
Certificate Authorities
=======================
.. autoclass:: barbicanclient.cas.CAManager
:members:
.. autoclass:: barbicanclient.cas.CA
:members:
Exceptions
==========

View File

@@ -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