summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMauricio Harley <mharley@redhat.com>2023-01-30 17:29:08 +0100
committerMauricio Harley <mharley@redhat.com>2023-02-10 18:22:43 +0100
commitda03fc5cf0774f4bcd884279356452deeac4e700 (patch)
tree2a7145f3686888942368c911edcfba6ff1c40353
parent7f6b3cf790e7d37e85fb38d300fb43573f31184c (diff)
downloadpython-barbicanclient-da03fc5cf0774f4bcd884279356452deeac4e700.tar.gz
Added secret consumers list functionality. Unit, smoke
and functional tests were also added. Change-Id: I093475833cdc6d68ff2d4735a0d4a8d0eb143a53
-rw-r--r--barbicanclient/barbican_cli/v1/secrets.py24
-rw-r--r--barbicanclient/tests/v1/test_consumers.py32
-rw-r--r--barbicanclient/v1/secrets.py67
-rw-r--r--functionaltests/cli/v1/behaviors/consumer_behaviors.py14
-rw-r--r--functionaltests/cli/v1/smoke/test_consumer.py143
-rw-r--r--functionaltests/client/v1/functional/test_secrets.py139
-rw-r--r--releasenotes/notes/add-secret-consumers-a65cd6b22d28184d.yaml10
-rw-r--r--setup.cfg1
8 files changed, 387 insertions, 43 deletions
diff --git a/barbicanclient/barbican_cli/v1/secrets.py b/barbicanclient/barbican_cli/v1/secrets.py
index 3ce2348..a036ff1 100644
--- a/barbicanclient/barbican_cli/v1/secrets.py
+++ b/barbicanclient/barbican_cli/v1/secrets.py
@@ -252,3 +252,27 @@ class DeleteConsumer(command.Command):
args.service_type_name,
args.resource_type,
args.resource_id)
+
+
+class ListConsumer(lister.Lister):
+ """List consumers of a secret."""
+
+ def get_parser(self, prog_name):
+ parser = super(ListConsumer, self).get_parser(prog_name)
+ parser.add_argument('URI', help='The URI reference for the secret')
+ 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)
+ return parser
+
+ def take_action(self, args):
+ obj_list = self.app.client_manager.key_manager.secrets.list_consumers(
+ secret_ref=args.URI, limit=args.limit, offset=args.offset)
+
+ return secrets.SecretConsumers._list_objects(obj_list)
diff --git a/barbicanclient/tests/v1/test_consumers.py b/barbicanclient/tests/v1/test_consumers.py
index 7b0c400..989981e 100644
--- a/barbicanclient/tests/v1/test_consumers.py
+++ b/barbicanclient/tests/v1/test_consumers.py
@@ -127,3 +127,35 @@ class WhenTestingConsumers(test_client.BaseEntityResource):
def test_should_delete_from_manager_without_consumers_and_force(self):
self._delete_from_manager(self.entity_href, force=True)
+
+ def _list_consumers(self, secret_ref, consumers=[]):
+ mock_get_secret_for_client(self.client, consumers)
+ return self.manager.list_consumers(secret_ref)
+
+ def test_list_consumers_from_secret_without_consumers(self):
+ consumer_list = self._list_consumers(self.entity_href)
+ self.assertTrue(len(consumer_list) == 0)
+
+ def test_list_consumers_from_secret_with_consumers(self):
+ consumers = [{'service': 'service_test1',
+ 'resource_type': 'type_test1',
+ 'resource_id': 'id_test1'},
+ {'service': 'service_test2',
+ 'resource_type': 'type_test2',
+ 'resource_id': 'id_test2'}]
+ consumer_list = self._list_consumers(self.entity_href, consumers)
+
+ for elem in range(len(consumers)):
+ self.assertTrue(
+ consumer_list[elem].service ==
+ consumers[elem]['service'])
+ self.assertTrue(
+ consumer_list[elem].resource_type ==
+ consumers[elem]['resource_type'])
+ self.assertTrue(
+ consumer_list[elem].resource_id ==
+ consumers[elem]['resource_id'])
+
+ def test_should_fail_list_consumers_invalid_secret(self):
+ self.assertRaises(ValueError, self.manager.list_consumers,
+ **{'secret_ref': '12345'})
diff --git a/barbicanclient/v1/secrets.py b/barbicanclient/v1/secrets.py
index 9f3ab8f..ca3c249 100644
--- a/barbicanclient/v1/secrets.py
+++ b/barbicanclient/v1/secrets.py
@@ -44,6 +44,49 @@ def immutable_after_save(func):
return wrapper
+class SecretConsumersFormatter(formatter.EntityFormatter):
+
+ columns = ("Service",
+ "Resource type",
+ "Resource id",
+ "Created"
+ )
+
+ def _get_formatted_data(self):
+ data = (self.service,
+ self.resource_type,
+ self.resource_id,
+ self.created
+ )
+ return data
+
+
+class SecretConsumers(SecretConsumersFormatter):
+ """Secrets consumers managed by Barbican
+
+ Secrets might or might not have consumers.
+ """
+
+ def __init__(self, secret_ref, service, resource_type, resource_id,
+ created=None, updated=None, status=None):
+
+ self.secret_ref = secret_ref
+ self.service = service
+ self.resource_type = resource_type
+ self.resource_id = resource_id
+ self.created = created
+ self.updated = updated
+ self.status = status
+
+ def __repr__(self):
+ return ('SecretConsumers(secret_ref="{0}", service="{1}", '
+ 'resource_type="{2}", resource_id="{3}", '
+ 'created="{4}", updated="{5}", status="{6}")'
+ .format(self.secret_ref, self.service,
+ self.resource_type, self.resource_id,
+ self.created, self.updated, self.status))
+
+
class SecretFormatter(formatter.EntityFormatter):
columns = ("Secret href",
@@ -689,3 +732,27 @@ class SecretManager(base.BaseEntityManager):
}
self._api.delete(href, json=consumer_dict)
+
+ def list_consumers(self, secret_ref, limit=10, offset=0):
+ """List consumers of the secret
+
+ :param secret_ref: Full HATEOAS reference to a secret, or a UUID
+ :param limit: Max number of consumers returned
+ :param offset: Offset secrets to begin list
+ :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
+ :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
+ :raises barbicanclient.exceptions.HTTPServerError: 5xx Responses
+ """
+ LOG.debug('Listing consumers of secret {0}'.format(secret_ref))
+ self._enforce_microversion()
+ secret_uuid = base.validate_ref_and_return_uuid(
+ secret_ref, 'secret')
+ href = '{0}/{1}/consumers'.format(self._entity, secret_uuid)
+
+ params = {'limit': limit, 'offset': offset}
+ response = self._api.get(href, params=params)
+
+ return [
+ SecretConsumers(secret_ref=secret_ref, **s)
+ for s in response.get('consumers', [])
+ ]
diff --git a/functionaltests/cli/v1/behaviors/consumer_behaviors.py b/functionaltests/cli/v1/behaviors/consumer_behaviors.py
index 14d5578..af7f0d6 100644
--- a/functionaltests/cli/v1/behaviors/consumer_behaviors.py
+++ b/functionaltests/cli/v1/behaviors/consumer_behaviors.py
@@ -46,3 +46,17 @@ class ConsumerBehaviors(base_behaviors.BaseBehaviors):
argv.extend([secret_href])
stdout, stderr = self.issue_barbican_command(argv)
+
+ def list_consumers(self, secret_href):
+ argv = ['secret', 'consumer', 'list']
+ self.add_auth_and_endpoint(argv)
+
+ argv.extend([secret_href])
+
+ stdout, stderr = self.issue_barbican_command(argv)
+
+ if len(stderr) > 0 or stdout == '\n':
+ return []
+ else:
+ consumers = self._prettytable_to_list(stdout)
+ return consumers
diff --git a/functionaltests/cli/v1/smoke/test_consumer.py b/functionaltests/cli/v1/smoke/test_consumer.py
new file mode 100644
index 0000000..72496b9
--- /dev/null
+++ b/functionaltests/cli/v1/smoke/test_consumer.py
@@ -0,0 +1,143 @@
+# Copyright 2022 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.
+
+from functionaltests.cli.base import CmdLineTestCase
+from functionaltests.cli.v1.behaviors.consumer_behaviors import (
+ ConsumerBehaviors)
+from functionaltests.cli.v1.behaviors.secret_behaviors import SecretBehaviors
+from functionaltests import utils
+from testtools import testcase
+
+
+@utils.parameterized_test_case
+class ConsumerTestCase(CmdLineTestCase):
+
+ def setUp(self):
+ super(ConsumerTestCase, self).setUp()
+ self.consumer_behaviors = ConsumerBehaviors()
+ self.secret_behaviors = SecretBehaviors()
+
+ def tearDown(self):
+ super(ConsumerTestCase, self).tearDown()
+ self.secret_behaviors.delete_all_created_secrets()
+
+ def _create_secret(self):
+ secret_href = self.secret_behaviors.store_secret()
+ secret = self.secret_behaviors.get_secret(secret_href)
+ return secret['Secret href']
+
+ def _create_secret_with_consumer(self, consumer):
+ secret_href = self._create_secret()
+ self.consumer_behaviors.register_consumer(
+ secret_href, consumer["service"], consumer["resource_type"],
+ consumer["resource_id"])
+ return secret_href
+
+ def _register_consumer(self, secret_href, consumer):
+ self.consumer_behaviors.register_consumer(
+ secret_href, consumer["service"], consumer["resource_type"],
+ consumer["resource_id"])
+
+ @testcase.attr('positive')
+ def test_register_consumer_on_empty_secret(self):
+ consumer = {
+ 'service': 'service', 'resource_type': 'type',
+ 'resource_id': 'id', 'created': 'created'
+ }
+ secret_href = self._create_secret_with_consumer(consumer)
+
+ secret = self.consumer_behaviors.list_consumers(secret_href)
+ # Because "created" is non-deterministic, we need to assign
+ # its value before running the loop below.
+ secret[0]['Created'] = consumer['created']
+ # The CLI's output is slighted different in terms of headers.
+ # So, we have to rename their keys to the consumer dictionary's keys.
+ for (k1, v1), (k2, v2) in zip(list(secret[0].items()),
+ consumer.items()):
+ secret[0][k2] = secret[0].pop(k1)
+ self.assertDictEqual(consumer, secret[0])
+
+ @testcase.attr('positive')
+ def test_register_duplicated_service_name_consumer(self):
+ consumer = {
+ 'service': 'service', 'resource_type': 'type', 'resource_id': 'id'
+ }
+ secret_href = self._create_secret_with_consumer(consumer)
+ second_consumer = {
+ 'service': 'service', 'resource_type': 'type2',
+ 'resource_id': 'id2'
+ }
+ self._register_consumer(secret_href, second_consumer)
+ self._register_consumer(secret_href, second_consumer)
+ consumers_list = self.consumer_behaviors.list_consumers(secret_href)
+ self.assertEqual(2, len(consumers_list))
+
+ @testcase.attr('positive')
+ def test_register_duplicated_resource_type_consumer(self):
+ consumer = {
+ 'service': 'service', 'resource_type': 'type',
+ 'resource_id': 'id'
+ }
+ secret_href = self._create_secret_with_consumer(consumer)
+ second_consumer = {
+ 'service': 'service2', 'resource_type': 'type',
+ 'resource_id': 'id2'
+ }
+ self._register_consumer(secret_href, second_consumer)
+ consumers_list = self.consumer_behaviors.list_consumers(secret_href)
+ self.assertEqual(2, len(consumers_list))
+
+ @testcase.attr('positive')
+ def test_register_duplicated_resource_id_consumer(self):
+ consumer = {
+ 'service': 'service', 'resource_type': 'type', 'resource_id': 'id'
+ }
+ secret_href = self._create_secret_with_consumer(consumer)
+ second_consumer = {
+ 'service': 'service2', 'resource_type': 'type2',
+ 'resource_id': 'id'
+ }
+ self._register_consumer(secret_href, second_consumer)
+ consumers_list = self.consumer_behaviors.list_consumers(secret_href)
+ self.assertEqual(2, len(consumers_list))
+
+ @testcase.attr('positive')
+ def test_remove_consumer(self):
+ consumer = {
+ 'service': 'service', 'resource_type': 'type',
+ 'resource_id': 'id'
+ }
+ secret_href = self._create_secret_with_consumer(consumer)
+
+ self.consumer_behaviors.remove_consumer(
+ secret_href, consumer["service"], consumer["resource_type"],
+ consumer["resource_id"])
+
+ consumers = self.consumer_behaviors.list_consumers(secret_href)
+ self.assertEqual(0, len(consumers))
+
+ @testcase.attr('positive')
+ def test_list_consumer_secret_with_multiple_consumers(self):
+ first_consumer = {
+ 'service': 'service1', 'resource_type': 'type1',
+ 'resource_id': 'id1'}
+ secret_href = self._create_secret_with_consumer(first_consumer)
+
+ second_consumer = {
+ 'service': 'service2', 'resource_type': 'type2',
+ 'resource_id': 'id2'}
+ self._register_consumer(secret_href, second_consumer)
+
+ consumers = self.consumer_behaviors.list_consumers(secret_href)
+ self.assertEqual(2, len(consumers))
diff --git a/functionaltests/client/v1/functional/test_secrets.py b/functionaltests/client/v1/functional/test_secrets.py
index 645094a..fbee75a 100644
--- a/functionaltests/client/v1/functional/test_secrets.py
+++ b/functionaltests/client/v1/functional/test_secrets.py
@@ -69,15 +69,21 @@ class SecretsTestCase(base.TestCase):
self.cleanup.delete_all_entities()
super(SecretsTestCase, self).tearDown()
- @testcase.attr('positive')
- def test_secret_create_defaults_check_content_types(self):
- """Check that set content-type attribute is retained in metadata."""
- secret = self.barbicanclient.secrets.create(
+ def _create_test_secret(self):
+ """Helper module to create a secret withouth consumers"""
+ new_secret = self.barbicanclient.secrets.create(
**secret_create_defaults_data)
- secret_ref = self.cleanup.add_entity(secret)
+ secret_ref = self.cleanup.add_entity(new_secret)
self.assertIsNotNone(secret_ref)
+ return secret_ref
+
+ @testcase.attr('positive')
+ def test_secret_create_defaults_check_content_types(self):
+ """Check that set content-type attribute is retained in metadata."""
+ secret_ref = self._create_test_secret()
+
resp = self.barbicanclient.secrets.get(secret_ref)
content_types = resp.content_types
self.assertIsNotNone(content_types)
@@ -106,11 +112,7 @@ class SecretsTestCase(base.TestCase):
By default, 'read' ACL settings are there for a secret.
"""
- test_model = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(test_model)
- self.assertIsNotNone(secret_ref)
+ secret_ref = self._create_test_secret()
secret_entity = self.barbicanclient.secrets.get(secret_ref)
self.assertIsNotNone(secret_entity.acls)
@@ -179,11 +181,7 @@ class SecretsTestCase(base.TestCase):
in the register_consumers list, then remove each consumer
in the remove_consumers list.
"""
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
- self.assertIsNotNone(secret_ref)
+ secret_ref = self._create_test_secret()
for consumer in register_consumers:
secret = self.barbicanclient.secrets.register_consumer(
@@ -231,11 +229,7 @@ class SecretsTestCase(base.TestCase):
providing all of the required positional arguments
(service, resource_type, resource_id).
"""
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
- self.assertIsNotNone(secret_ref)
+ secret_ref = self._create_test_secret()
for consumer in register_consumers:
e = self.assertRaises(
@@ -267,11 +261,7 @@ class SecretsTestCase(base.TestCase):
providing all of the required positional arguments
(service, resource_type, resource_id).
"""
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
- self.assertIsNotNone(secret_ref)
+ secret_ref = self._create_test_secret()
secret = self.barbicanclient.secrets.register_consumer(
secret_ref,
@@ -290,10 +280,7 @@ class SecretsTestCase(base.TestCase):
@testcase.attr('positive')
def test_secret_delete_without_consumers_no_force(self):
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
+ secret_ref = self._create_test_secret()
self.barbicanclient.secrets.delete(secret_ref, force=False)
resp = self.barbicanclient.secrets.get(secret_ref)
@@ -302,10 +289,7 @@ class SecretsTestCase(base.TestCase):
@testcase.attr('positive')
def test_secret_delete_without_consumers_with_force(self):
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
+ secret_ref = self._create_test_secret()
self.barbicanclient.secrets.delete(secret_ref, force=True)
resp = self.barbicanclient.secrets.get(secret_ref)
@@ -319,11 +303,7 @@ class SecretsTestCase(base.TestCase):
Tries to delete a secret with consumers, but
without providing the 'force' parameter.
"""
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
- self.assertIsNotNone(secret_ref)
+ secret_ref = self._create_test_secret()
secret = self.barbicanclient.secrets.register_consumer(
secret_ref,
@@ -346,11 +326,7 @@ class SecretsTestCase(base.TestCase):
Tries to delete a secret with consumers,
making the 'force' parameter equals True.
"""
- new_secret = self.barbicanclient.secrets.create(
- **secret_create_defaults_data)
-
- secret_ref = self.cleanup.add_entity(new_secret)
- self.assertIsNotNone(secret_ref)
+ secret_ref = self._create_test_secret()
secret = self.barbicanclient.secrets.register_consumer(
secret_ref,
@@ -365,6 +341,83 @@ class SecretsTestCase(base.TestCase):
self.assertRaises(exceptions.HTTPClientError, getattr, resp, "name")
self.cleanup.delete_entity(secret_ref)
+ @testcase.attr('positive')
+ def test_consumers_list_secret_without_consumers(self):
+ """Lists consumers from a secret without consumers"""
+ secret_ref = self._create_test_secret()
+
+ consumers_list = self.barbicanclient.secrets.list_consumers(
+ secret_ref)
+ self.assertTrue(len(consumers_list) == 0)
+
+ self.cleanup.delete_entity(secret_ref)
+ self.barbicanclient.secrets.delete(secret_ref, True)
+
+ @testcase.attr('positive')
+ def test_consumers_list_secret_with_consumers(self):
+ """Lists consumers from a secret with consumers"""
+ secret_ref = self._create_test_secret()
+
+ consumers = [{
+ 'service': 'service1',
+ 'resource_type': 'type1',
+ 'resource_id': 'id1'}, {
+ 'service': 'service2',
+ 'resource_type': 'type2',
+ 'resource_id': 'id2'}]
+
+ for consumer in consumers:
+ _ = self.barbicanclient.secrets.register_consumer(
+ secret_ref,
+ service=consumer['service'],
+ resource_type=consumer['resource_type'],
+ resource_id=consumer['resource_id']
+ )
+
+ consumers_list = self.barbicanclient.secrets.list_consumers(
+ secret_ref)
+
+ for elem in range(len(consumers)):
+ self.assertTrue(
+ consumers_list[elem].service ==
+ consumers[elem]['service'])
+ self.assertTrue(
+ consumers_list[elem].resource_type ==
+ consumers[elem]['resource_type'])
+ self.assertTrue(
+ consumers_list[elem].resource_id ==
+ consumers[elem]['resource_id'])
+
+ self.cleanup.delete_entity(secret_ref)
+ self.barbicanclient.secrets.delete(secret_ref, True)
+
+ @testcase.attr('negative')
+ def test_consumers_list_secret_doesnt_exist(self):
+ """Tries to list consumers from a non-existent secret"""
+ e = self.assertRaises(exceptions.HTTPClientError,
+ self.barbicanclient.secrets.list_consumers,
+ '9999999f-f99f-49f9-9fff-f99f999ff9ff')
+
+ self.assertIn("Secret not found", str(e))
+
+ @testcase.attr('negative')
+ def test_consumers_list_secret_invalid_uuid(self):
+ """Tries to list consumers providing an invalid secret UUID"""
+ e = self.assertRaises(exceptions.HTTPClientError,
+ self.barbicanclient.secrets.list_consumers,
+ '9999999f-ffff-ffff-9fff-f99f999ff9ff')
+
+ self.assertIn("Provided secret id is invalid.", str(e))
+
+ @testcase.attr('negative')
+ def test_consumers_list_invalid_secret(self):
+ """Tries to list consumers providing an invalid secret"""
+ e = self.assertRaises(ValueError,
+ self.barbicanclient.secrets.list_consumers,
+ 'abcde')
+
+ self.assertIn("secret incorrectly specified.", str(e))
+
@testcase.attr('negative')
def test_secret_delete_doesnt_exist(self):
"""Deletes a non-existent secret.
diff --git a/releasenotes/notes/add-secret-consumers-a65cd6b22d28184d.yaml b/releasenotes/notes/add-secret-consumers-a65cd6b22d28184d.yaml
new file mode 100644
index 0000000..443e735
--- /dev/null
+++ b/releasenotes/notes/add-secret-consumers-a65cd6b22d28184d.yaml
@@ -0,0 +1,10 @@
+---
+features: >
+ The Barbican API has been extended to allow secrets to have one or
+ more consumers. This extension has been documented here:
+ https://docs.openstack.org/barbican/latest/api/reference/secret_consumers.html
+
+ This functionality has now been exposed in the barbican client.
+ Users may add, remove or delete consumers by calling new mechods on the
+ SecretManager. In addition, new CLI options have been provided to add, remove
+ and list consumers. See the documentation for details.
diff --git a/setup.cfg b/setup.cfg
index 52237fe..28f053d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,6 +51,7 @@ openstack.key_manager.v1 =
secret_consumer_create = barbicanclient.barbican_cli.v1.secrets:CreateConsumer
secret_consumer_delete = barbicanclient.barbican_cli.v1.secrets:DeleteConsumer
+ secret_consumer_list = barbicanclient.barbican_cli.v1.secrets:ListConsumer
ca_get = barbicanclient.barbican_cli.v1.cas:GetCA
ca_list = barbicanclient.barbican_cli.v1.cas:ListCA