Codebase list python-castellan / fc0fc79
Support handling legacy all-zeros key ID This patch addresses a specific use case, where a user has encrypted volumes based on the fixed_key used by Cinder's and Nova's ConfKeyManager. The user wishes to switch to Barbican, but existing volumes must continue to function during the migration period. The code conditionally adds a shim around the backend KeyManager when both of these conditions are met: 1) The configuration contains a fixed_key value. This essentially signals the ConfKeyManager has been in use at one time 2) The current backend is *not* the ConfKeyManager When the shim is active, a MigrationKeyManager class is dynamically created that extends the backend's KeyManager class. The MigrationKeyManager exists solely to override two functions: o The KeyManager.get() function detects requests for the secret associated with the fixed_key, which is identified by an all-zeros key ID. - Requests for the all-zeros key ID are handled by mimicing the ConfKeyManager's response, which is a secret derived from the fixed_key. - Requests for any other key ID are passed on to the real backend. o The KeyManager.delete() function is similar: - Requests to delete the all-zeros key ID are essentially ignored, just as is done by the ConfKeyManager. - Requests to delete any other key ID are passed on to the real backend. All other KeyManager functions are not overridden, and will therefore be handled directly by the real backend. SecurityImpact Change-Id: Ia5316490201c33e23a4206838d5a4fb3dd00f527 Alan Bishop 6 years ago
4 changed file(s) with 211 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
1111 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212 # License for the specific language governing permissions and limitations
1313 # under the License.
14 from castellan.key_manager import migration
1415 from oslo_config import cfg
1516 from oslo_log import log as logging
1617 from oslo_utils import importutils
3940 conf.key_manager.backend,
4041 invoke_on_load=True,
4142 invoke_args=[conf])
42 return mgr.driver
43 key_mgr = mgr.driver
4344 except exception.NoMatches:
4445 LOG.warning("Deprecation Warning : %s is not a stevedore based driver,"
4546 " trying to load it as a class", conf.key_manager.backend)
4647 cls = importutils.import_class(conf.key_manager.backend)
47 return cls(configuration=conf)
48 key_mgr = cls(configuration=conf)
49
50 return migration.handle_migration(conf, key_mgr)
0 # Copyright 2017 Red Hat, Inc.
1 # All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
14 import binascii
15 from castellan.common import exception
16 from castellan.common.objects import symmetric_key
17 from oslo_config import cfg
18 from oslo_log import log as logging
19
20 LOG = logging.getLogger(__name__)
21
22
23 def handle_migration(conf, key_mgr):
24 try:
25 conf.register_opt(cfg.StrOpt('fixed_key'), group='key_manager')
26 except cfg.DuplicateOptError:
27 pass
28
29 if conf.key_manager.fixed_key is not None and \
30 not conf.key_manager.backend.endswith('ConfKeyManager'):
31
32 LOG.warning("Using MigrationKeyManager to provide support for legacy"
33 " fixed_key encryption")
34
35 class MigrationKeyManager(type(key_mgr)):
36 def __init__(self, configuration):
37 self.fixed_key = configuration.key_manager.fixed_key
38 self.fixed_key_id = '00000000-0000-0000-0000-000000000000'
39 super(MigrationKeyManager, self).__init__(configuration)
40
41 def get(self, context, managed_object_id):
42 if managed_object_id == self.fixed_key_id:
43 LOG.debug("Processing request for secret associated"
44 " with fixed_key key ID")
45
46 if context is None:
47 raise exception.Forbidden()
48
49 key_bytes = bytes(binascii.unhexlify(self.fixed_key))
50 secret = symmetric_key.SymmetricKey('AES',
51 len(key_bytes) * 8,
52 key_bytes)
53 else:
54 secret = super(MigrationKeyManager, self).get(
55 context, managed_object_id)
56 return secret
57
58 def delete(self, context, managed_object_id):
59 if managed_object_id == self.fixed_key_id:
60 LOG.debug("Not deleting key associated with"
61 " fixed_key key ID")
62
63 if context is None:
64 raise exception.Forbidden()
65 else:
66 super(MigrationKeyManager, self).delete(context,
67 managed_object_id)
68
69 key_mgr = MigrationKeyManager(configuration=conf)
70
71 return key_mgr
0 # Copyright 2017 Red Hat, Inc.
1 # All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
14
15 """
16 Test cases for the migration key manager.
17 """
18
19 import binascii
20 import mock
21
22 from oslo_config import cfg
23
24 from castellan.common import exception
25 from castellan.common.objects import symmetric_key as key
26 from castellan import key_manager
27 from castellan.key_manager import not_implemented_key_manager
28 from castellan.tests.unit.key_manager import test_key_manager
29
30 CONF = cfg.CONF
31
32
33 class ConfKeyManager(not_implemented_key_manager.NotImplementedKeyManager):
34 pass
35
36
37 class MigrationKeyManagerTestCase(test_key_manager.KeyManagerTestCase):
38
39 def _create_key_manager(self):
40 self.fixed_key = '1' * 64
41 try:
42 self.conf.register_opt(cfg.StrOpt('fixed_key'),
43 group='key_manager')
44 except cfg.DuplicateOptError:
45 pass
46 self.conf.set_override('fixed_key',
47 self.fixed_key,
48 group='key_manager')
49 return key_manager.API(self.conf)
50
51 def setUp(self):
52 super(MigrationKeyManagerTestCase, self).setUp()
53
54 # Create fake context (actual contents doesn't matter).
55 self.ctxt = mock.Mock()
56
57 fixed_key_bytes = bytes(binascii.unhexlify(self.fixed_key))
58 fixed_key_length = len(fixed_key_bytes) * 8
59 self.fixed_key_secret = key.SymmetricKey('AES',
60 fixed_key_length,
61 fixed_key_bytes)
62 self.fixed_key_id = '00000000-0000-0000-0000-000000000000'
63 self.other_key_id = "d152fa13-2b41-42ca-a934-6c21566c0f40"
64
65 def test_get_fixed_key(self):
66 self.assertEqual('MigrationKeyManager', type(self.key_mgr).__name__)
67 secret = self.key_mgr.get(self.ctxt, self.fixed_key_id)
68 self.assertEqual(self.fixed_key_secret, secret)
69
70 def test_get_fixed_key_fail_bad_context(self):
71 self.assertRaises(exception.Forbidden,
72 self.key_mgr.get,
73 context=None,
74 managed_object_id=self.fixed_key_id)
75
76 def test_delete_fixed_key(self):
77 self.key_mgr.delete(self.ctxt, self.fixed_key_id)
78 # Delete looks like it succeeded, but nothing actually happened.
79 secret = self.key_mgr.get(self.ctxt, self.fixed_key_id)
80 self.assertEqual(self.fixed_key_secret, secret)
81
82 def test_delete_fixed_key_fail_bad_context(self):
83 self.assertRaises(exception.Forbidden,
84 self.key_mgr.delete,
85 context=None,
86 managed_object_id=self.fixed_key_id)
87
88 def test_get_other_key(self):
89 # Request to get other_key_id should be passed on to the backend,
90 # who will throw an error because we don't have a valid context.
91 self.assertRaises(exception.KeyManagerError,
92 self.key_mgr.get,
93 context=self.ctxt,
94 managed_object_id=self.other_key_id)
95
96 def test_delete_other_key(self):
97 # Request to delete other_key_id should be passed on to the backend,
98 # who will throw an error because we don't have a valid context.
99 self.assertRaises(exception.KeyManagerError,
100 self.key_mgr.delete,
101 context=self.ctxt,
102 managed_object_id=self.other_key_id)
103
104 def test_no_fixed_key(self):
105 conf = self.conf
106 conf.set_override('fixed_key', None, group='key_manager')
107 key_mgr = key_manager.API(conf)
108 self.assertNotEqual('MigrationKeyManager', type(key_mgr).__name__)
109 self.assertRaises(exception.KeyManagerError,
110 key_mgr.get,
111 context=self.ctxt,
112 managed_object_id=self.fixed_key_id)
113
114 def test_using_conf_key_manager(self):
115 conf = self.conf
116 ckm_backend = 'castellan.tests.unit.key_manager.' \
117 'test_migration_key_manager.ConfKeyManager'
118 conf.set_override('backend', ckm_backend, group='key_manager')
119 key_mgr = key_manager.API(conf)
120 self.assertNotEqual('MigrationKeyManager', type(key_mgr).__name__)
121 self.assertRaises(NotImplementedError,
122 key_mgr.get,
123 context=self.ctxt,
124 managed_object_id=self.fixed_key_id)
0 ---
1 features:
2 - |
3 Enhance the key manager to handle requests containing the special (all
4 zeros) managed object ID associated with Cinder's and Nova's legacy
5 ConfKeyManager. The purpose of this feature is to help users migrate from
6 the ConfKeyManager to a modern key manager such as Barbican. The feature
7 works by ensuring the ConfKeyManager's all-zeros key ID continues to
8 function when Barbican or Vault is the key manager.