Added secret consumers list functionality. Unit, smoke

and functional tests were also added.

Change-Id: I093475833cdc6d68ff2d4735a0d4a8d0eb143a53
This commit is contained in:
Mauricio Harley 2023-01-30 17:29:08 +01:00
parent 7f6b3cf790
commit da03fc5cf0
8 changed files with 387 additions and 43 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,14 +69,20 @@ class SecretsTestCase(base.TestCase):
self.cleanup.delete_all_entities()
super(SecretsTestCase, self).tearDown()
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(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 = self.barbicanclient.secrets.create(
**secret_create_defaults_data)
secret_ref = self.cleanup.add_entity(secret)
self.assertIsNotNone(secret_ref)
secret_ref = self._create_test_secret()
resp = self.barbicanclient.secrets.get(secret_ref)
content_types = resp.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.

View File

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

View File

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