Merge "Update Barbican functional tests"
Jenkins authored 8 years ago
Gerrit Code Review committed 8 years ago
20 | 20 | |
21 | 21 | import uuid |
22 | 22 | |
23 | from barbicanclient import exceptions as barbican_exceptions | |
24 | 23 | from keystoneclient.v3 import client |
24 | from oslo_config import cfg | |
25 | 25 | from oslo_context import context |
26 | from oslotest import base | |
26 | 27 | |
27 | 28 | from castellan.common import exception |
28 | from castellan.common.objects import symmetric_key | |
29 | 29 | from castellan.key_manager import barbican_key_manager |
30 | 30 | from castellan.tests.functional import config |
31 | 31 | from castellan.tests.functional.key_manager import test_key_manager |
34 | 34 | CONF = config.get_config() |
35 | 35 | |
36 | 36 | |
37 | class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): | |
37 | class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase, | |
38 | base.BaseTestCase): | |
38 | 39 | |
39 | 40 | def _create_key_manager(self): |
40 | return barbican_key_manager.BarbicanKeyManager() | |
41 | return barbican_key_manager.BarbicanKeyManager(cfg.CONF) | |
41 | 42 | |
42 | 43 | def setUp(self): |
43 | 44 | super(BarbicanKeyManagerTestCase, self).setUp() |
48 | 49 | keystone_client = client.Client(username=username, |
49 | 50 | password=password, |
50 | 51 | project_name=project_name, |
51 | auth_url=auth_url) | |
52 | auth_url=auth_url, | |
53 | project_domain_id='default') | |
54 | project_list = keystone_client.projects.list(name=project_name) | |
55 | ||
52 | 56 | self.ctxt = context.RequestContext( |
53 | auth_token=keystone_client.auth_token) | |
57 | auth_token=keystone_client.auth_token, | |
58 | tenant=project_list[0].id) | |
54 | 59 | |
55 | 60 | def tearDown(self): |
56 | 61 | super(BarbicanKeyManagerTestCase, self).tearDown() |
57 | ||
58 | def test_create_key(self): | |
59 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
60 | algorithm='AES', | |
61 | length=256) | |
62 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
63 | self.assertIsNotNone(key_uuid) | |
64 | 62 | |
65 | 63 | def test_create_null_context(self): |
66 | 64 | self.assertRaises(exception.Forbidden, |
67 | 65 | self.key_mgr.create_key, None, 'AES', 256) |
68 | 66 | |
69 | def test_delete_symmetric_key(self): | |
70 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
71 | algorithm='AES', | |
72 | length=256) | |
73 | self.key_mgr.delete_key(self.ctxt, key_uuid) | |
74 | try: | |
75 | self.key_mgr.get_key(self.ctxt, key_uuid) | |
76 | except barbican_exceptions.HTTPClientError as e: | |
77 | self.assertEqual(404, e.status_code) | |
78 | else: | |
79 | self.fail('No exception when deleting non-existent key') | |
67 | def test_create_key_pair_null_context(self): | |
68 | self.assertRaises(exception.Forbidden, | |
69 | self.key_mgr.create_key_pair, None, 'RSA', 2048) | |
80 | 70 | |
81 | 71 | def test_delete_null_context(self): |
82 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
83 | algorithm='AES', | |
84 | length=256) | |
85 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
72 | key_uuid = self._get_valid_object_uuid( | |
73 | test_key_manager._get_test_symmetric_key()) | |
74 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
86 | 75 | self.assertRaises(exception.Forbidden, |
87 | self.key_mgr.delete_key, None, key_uuid) | |
76 | self.key_mgr.delete, None, key_uuid) | |
88 | 77 | |
89 | def test_delete_null_key(self): | |
78 | def test_delete_null_object(self): | |
90 | 79 | self.assertRaises(exception.KeyManagerError, |
91 | self.key_mgr.delete_key, self.ctxt, None) | |
80 | self.key_mgr.delete, self.ctxt, None) | |
92 | 81 | |
93 | def test_delete_unknown_key(self): | |
94 | bad_key_uuid = str(uuid.uuid4()) | |
95 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
96 | self.key_mgr.delete_key, self.ctxt, bad_key_uuid) | |
97 | ||
98 | def test_get_key(self): | |
99 | secret_key = b'\x01\x02\xA0\xB3' | |
100 | key = symmetric_key.SymmetricKey('AES', secret_key) | |
101 | ||
102 | uuid = self.key_mgr.store_key(self.ctxt, key) | |
103 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, uuid) | |
104 | ||
105 | retrieved_key = self.key_mgr.get_key(self.ctxt, uuid) | |
106 | self.assertEqual(key.get_encoded(), retrieved_key.get_encoded()) | |
82 | def test_delete_unknown_object(self): | |
83 | unknown_uuid = str(uuid.uuid4()) | |
84 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
85 | self.key_mgr.delete, self.ctxt, unknown_uuid) | |
107 | 86 | |
108 | 87 | def test_get_null_context(self): |
109 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
110 | algorithm='AES', | |
111 | length=256) | |
88 | key_uuid = self._get_valid_object_uuid( | |
89 | test_key_manager._get_test_symmetric_key()) | |
112 | 90 | self.assertRaises(exception.Forbidden, |
113 | self.key_mgr.get_key, None, key_uuid) | |
91 | self.key_mgr.get, None, key_uuid) | |
114 | 92 | |
115 | def test_get_null_key(self): | |
116 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
117 | algorithm='AES', | |
118 | length=256) | |
119 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
93 | def test_get_null_object(self): | |
120 | 94 | self.assertRaises(exception.KeyManagerError, |
121 | self.key_mgr.get_key, self.ctxt, None) | |
95 | self.key_mgr.get, self.ctxt, None) | |
122 | 96 | |
123 | 97 | def test_get_unknown_key(self): |
124 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
125 | algorithm='AES', | |
126 | length=256) | |
127 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
128 | 98 | bad_key_uuid = str(uuid.uuid4()) |
129 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
130 | self.key_mgr.get_key, self.ctxt, bad_key_uuid) | |
131 | ||
132 | def test_store(self): | |
133 | secret_key = b'\x01\x02\xA0\xB3' | |
134 | key = symmetric_key.SymmetricKey('AES', secret_key) | |
135 | ||
136 | uuid = self.key_mgr.store_key(self.ctxt, key) | |
137 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, uuid) | |
138 | ||
139 | retrieved_key = self.key_mgr.get_key(self.ctxt, uuid) | |
140 | self.assertEqual(key.get_encoded(), retrieved_key.get_encoded()) | |
99 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
100 | self.key_mgr.get, self.ctxt, bad_key_uuid) | |
141 | 101 | |
142 | 102 | def test_store_null_context(self): |
143 | secret_key = b'\x01\x02\xA0\xB3' | |
144 | key = symmetric_key.SymmetricKey('AES', secret_key) | |
103 | key = test_key_manager._get_test_symmetric_key() | |
145 | 104 | |
146 | 105 | self.assertRaises(exception.Forbidden, |
147 | self.key_mgr.store_key, None, key) | |
106 | self.key_mgr.store, None, key) |
13 | 13 | # under the License. |
14 | 14 | |
15 | 15 | """ |
16 | Test cases for the key manager. | |
16 | Test cases for a key manager. | |
17 | ||
18 | These test cases should pass against any key manager. | |
17 | 19 | """ |
18 | 20 | |
19 | from castellan.tests import base | |
21 | from castellan.common import exception | |
22 | from castellan.common.objects import opaque_data | |
23 | from castellan.common.objects import passphrase | |
24 | from castellan.common.objects import private_key | |
25 | from castellan.common.objects import public_key | |
26 | from castellan.common.objects import symmetric_key | |
27 | from castellan.common.objects import x_509 | |
28 | from castellan.tests import utils | |
20 | 29 | |
21 | 30 | |
22 | class KeyManagerTestCase(base.TestCase): | |
31 | def _get_test_symmetric_key(): | |
32 | key_bytes = bytes(utils.get_symmetric_key()) | |
33 | bit_length = 128 | |
34 | key = symmetric_key.SymmetricKey('AES', bit_length, key_bytes) | |
35 | return key | |
36 | ||
37 | ||
38 | def _get_test_public_key(): | |
39 | key_bytes = bytes(utils.get_public_key_der()) | |
40 | bit_length = 2048 | |
41 | key = public_key.PublicKey('RSA', bit_length, key_bytes) | |
42 | return key | |
43 | ||
44 | ||
45 | def _get_test_private_key(): | |
46 | key_bytes = bytes(utils.get_private_key_der()) | |
47 | bit_length = 2048 | |
48 | key = private_key.PrivateKey('RSA', bit_length, key_bytes) | |
49 | return key | |
50 | ||
51 | ||
52 | def _get_test_certificate(): | |
53 | data = bytes(utils.get_certificate_der()) | |
54 | cert = x_509.X509(data) | |
55 | return cert | |
56 | ||
57 | ||
58 | def _get_test_opaque_data(): | |
59 | data = bytes(b'opaque data') | |
60 | opaque_object = opaque_data.OpaqueData(data) | |
61 | return opaque_object | |
62 | ||
63 | ||
64 | def _get_test_passphrase(): | |
65 | data = bytes(b'passphrase') | |
66 | passphrase_object = passphrase.Passphrase(data) | |
67 | return passphrase_object | |
68 | ||
69 | ||
70 | @utils.parameterized_test_case | |
71 | class KeyManagerTestCase(object): | |
23 | 72 | |
24 | 73 | def _create_key_manager(self): |
25 | 74 | raise NotImplementedError() |
26 | 75 | |
27 | 76 | def setUp(self): |
28 | 77 | super(KeyManagerTestCase, self).setUp() |
78 | self.key_mgr = self._create_key_manager() | |
29 | 79 | |
30 | self.key_mgr = self._create_key_manager() | |
80 | def _get_valid_object_uuid(self, managed_object): | |
81 | object_uuid = self.key_mgr.store(self.ctxt, managed_object) | |
82 | self.assertIsNotNone(object_uuid) | |
83 | return object_uuid | |
84 | ||
85 | def test_create_key(self): | |
86 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
87 | algorithm='AES', | |
88 | length=256) | |
89 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
90 | self.assertIsNotNone(key_uuid) | |
91 | ||
92 | def test_create_key_pair(self): | |
93 | private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair( | |
94 | self.ctxt, | |
95 | algorithm='RSA', | |
96 | length=2048) | |
97 | ||
98 | self.addCleanup(self.key_mgr.delete, self.ctxt, private_key_uuid) | |
99 | self.addCleanup(self.key_mgr.delete, self.ctxt, public_key_uuid) | |
100 | ||
101 | self.assertIsNotNone(private_key_uuid) | |
102 | self.assertIsNotNone(public_key_uuid) | |
103 | self.assertNotEqual(private_key_uuid, public_key_uuid) | |
104 | ||
105 | @utils.parameterized_dataset({ | |
106 | 'symmetric_key': [_get_test_symmetric_key()], | |
107 | 'public_key': [_get_test_public_key()], | |
108 | 'private_key': [_get_test_private_key()], | |
109 | 'certificate': [_get_test_certificate()], | |
110 | 'passphrase': [_get_test_passphrase()], | |
111 | 'opaque_data': [_get_test_opaque_data()], | |
112 | }) | |
113 | def test_delete(self, managed_object): | |
114 | object_uuid = self._get_valid_object_uuid(managed_object) | |
115 | self.key_mgr.delete(self.ctxt, object_uuid) | |
116 | try: | |
117 | self.key_mgr.get(self.ctxt, object_uuid) | |
118 | except exception.ManagedObjectNotFoundError: | |
119 | pass | |
120 | else: | |
121 | self.fail('No exception when deleting non-existent key') | |
122 | ||
123 | @utils.parameterized_dataset({ | |
124 | 'symmetric_key': [_get_test_symmetric_key()], | |
125 | 'public_key': [_get_test_public_key()], | |
126 | 'private_key': [_get_test_private_key()], | |
127 | 'certificate': [_get_test_certificate()], | |
128 | 'passphrase': [_get_test_passphrase()], | |
129 | 'opaque_data': [_get_test_opaque_data()], | |
130 | }) | |
131 | def test_get(self, managed_object): | |
132 | uuid = self._get_valid_object_uuid(managed_object) | |
133 | self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) | |
134 | ||
135 | retrieved_object = self.key_mgr.get(self.ctxt, uuid) | |
136 | self.assertEqual(managed_object.get_encoded(), | |
137 | retrieved_object.get_encoded()) | |
138 | ||
139 | @utils.parameterized_dataset({ | |
140 | 'symmetric_key': [_get_test_symmetric_key()], | |
141 | 'public_key': [_get_test_public_key()], | |
142 | 'private_key': [_get_test_private_key()], | |
143 | 'certificate': [_get_test_certificate()], | |
144 | 'passphrase': [_get_test_passphrase()], | |
145 | 'opaque_data': [_get_test_opaque_data()], | |
146 | }) | |
147 | def test_store(self, managed_object): | |
148 | uuid = self.key_mgr.store(self.ctxt, managed_object) | |
149 | self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) | |
150 | ||
151 | retrieved_object = self.key_mgr.get(self.ctxt, uuid) | |
152 | self.assertEqual(managed_object.get_encoded(), | |
153 | retrieved_object.get_encoded()) |
170 | 170 | self.key_mgr.delete(self.ctxt, self.key_id) |
171 | 171 | self.delete.assert_called_once_with(self.secret_ref) |
172 | 172 | |
173 | def test_delete_none_key(self): | |
173 | def test_delete_unknown_key(self): | |
174 | 174 | self.assertRaises(exception.KeyManagerError, |
175 | 175 | self.key_mgr.delete, self.ctxt, None) |
176 | ||
177 | def test_delete_unkown_key(self): | |
178 | side_effect = barbican_exceptions.HTTPClientError('key not found') | |
179 | side_effect.status_code = 404 | |
180 | self.mock_barbican.secrets.delete = mock.Mock(side_effect=side_effect) | |
181 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
182 | self.key_mgr.delete, self.ctxt, self.key_id) | |
183 | 176 | |
184 | 177 | def test_delete_with_error(self): |
185 | 178 | self.mock_barbican.secrets.delete = mock.Mock( |
206 | 199 | self.assertRaises(exception.Forbidden, |
207 | 200 | self.key_mgr.get, None, self.key_id) |
208 | 201 | |
209 | def test_get_none_key(self): | |
202 | def test_get_unknown_key(self): | |
210 | 203 | self.assertRaises(exception.KeyManagerError, |
211 | 204 | self.key_mgr.get, self.ctxt, None) |
212 | ||
213 | def test_get_unknown_key(self): | |
214 | side_effect = barbican_exceptions.HTTPClientError('key not found') | |
215 | side_effect.status_code = 404 | |
216 | self.mock_barbican.secrets.get = mock.Mock(side_effect=side_effect) | |
217 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
218 | self.key_mgr.get, self.ctxt, self.key_id) | |
219 | 205 | |
220 | 206 | def test_get_with_error(self): |
221 | 207 | self.mock_barbican.secrets.get = mock.Mock( |
14 | 14 | |
15 | 15 | |
16 | 16 | """These utility functions are borrowed from Barbican's testing utilities.""" |
17 | ||
18 | import functools | |
19 | import types | |
20 | ||
21 | import six | |
22 | ||
23 | ||
24 | def construct_new_test_function(original_func, name, build_params): | |
25 | """Builds a new test function based on parameterized data. | |
26 | ||
27 | :param original_func: The original test function that is used as a template | |
28 | :param name: The fullname of the new test function | |
29 | :param build_params: A dictionary or list containing args or kwargs | |
30 | for the new test | |
31 | :return: A new function object | |
32 | """ | |
33 | new_func = types.FunctionType( | |
34 | six.get_function_code(original_func), | |
35 | six.get_function_globals(original_func), | |
36 | name=name, | |
37 | argdefs=six.get_function_defaults(original_func) | |
38 | ) | |
39 | ||
40 | for key, val in six.iteritems(original_func.__dict__): | |
41 | if key != 'build_data': | |
42 | new_func.__dict__[key] = val | |
43 | ||
44 | # Support either an arg list or kwarg dict for our data | |
45 | build_args = build_params if isinstance(build_params, list) else [] | |
46 | build_kwargs = build_params if isinstance(build_params, dict) else {} | |
47 | ||
48 | # Build a test wrapper to execute with our kwargs | |
49 | def test_wrapper(func, test_args, test_kwargs): | |
50 | @functools.wraps(func) | |
51 | def wrapper(self): | |
52 | return func(self, *test_args, **test_kwargs) | |
53 | return wrapper | |
54 | ||
55 | return test_wrapper(new_func, build_args, build_kwargs) | |
56 | ||
57 | ||
58 | def process_parameterized_function(name, func_obj, build_data): | |
59 | """Build lists of functions to add and remove to a test case.""" | |
60 | to_remove = [] | |
61 | to_add = [] | |
62 | ||
63 | for subtest_name, params in six.iteritems(build_data): | |
64 | # Build new test function | |
65 | func_name = '{0}_{1}'.format(name, subtest_name) | |
66 | new_func = construct_new_test_function(func_obj, func_name, params) | |
67 | ||
68 | # Mark the new function as needed to be added to the class | |
69 | to_add.append((func_name, new_func)) | |
70 | ||
71 | # Mark key for removal | |
72 | to_remove.append(name) | |
73 | ||
74 | return to_remove, to_add | |
75 | ||
76 | ||
77 | def parameterized_test_case(cls): | |
78 | """Class decorator to process parameterized tests | |
79 | ||
80 | This allows for parameterization to be used for potentially any | |
81 | unittest compatible runner; including testr and py.test. | |
82 | """ | |
83 | tests_to_remove = [] | |
84 | tests_to_add = [] | |
85 | for key, val in six.iteritems(vars(cls)): | |
86 | # Only process tests with build data on them | |
87 | if key.startswith('test_') and val.__dict__.get('build_data'): | |
88 | to_remove, to_add = process_parameterized_function( | |
89 | name=key, | |
90 | func_obj=val, | |
91 | build_data=val.__dict__.get('build_data') | |
92 | ) | |
93 | tests_to_remove.extend(to_remove) | |
94 | tests_to_add.extend(to_add) | |
95 | ||
96 | # Add all new test functions | |
97 | [setattr(cls, name, func) for name, func in tests_to_add] | |
98 | ||
99 | # Remove all old test function templates (if they still exist) | |
100 | [delattr(cls, key) for key in tests_to_remove if hasattr(cls, key)] | |
101 | return cls | |
102 | ||
103 | ||
104 | def parameterized_dataset(build_data): | |
105 | """Simple decorator to mark a test method for processing.""" | |
106 | def decorator(func): | |
107 | func.__dict__['build_data'] = build_data | |
108 | return func | |
109 | return decorator | |
17 | 110 | |
18 | 111 | |
19 | 112 | def get_certificate_der(): |
210 | 303 | b'\x01\x15\xcd\x52\x83\x3f\x06\x67\xfd\xa1\x2d\x2b\x07\xba\x32' |
211 | 304 | b'\x62\x21\x07\x2f\x02\x03\x01\x00\x01') |
212 | 305 | return key_der |
306 | ||
307 | ||
308 | def get_symmetric_key(): | |
309 | """Returns symmetric key bytes | |
310 | ||
311 | 16 bytes that were randomly generated. Form a 128 bit key. | |
312 | """ | |
313 | symmetric_key = ( | |
314 | b'\x92\xcf\x1e\xd9\x54\xea\x30\x70\xd8\xc2\x48\xae\xc1\xc8\x72\xa3') | |
315 | return symmetric_key |