Files
swift/test/unit/common/middleware/crypto/test_kmip_keymaster.py
Tim Burke 0dc1b6250e Multi-key KMIP keymaster
Now that the trivial keymaster supports multiple keys, let's do
something similar for the KMIP keymaster. Additional keys are
configured as:

    key_id_<secret_id> = <KMIP unique identifier>

While it might be tempting to use the unique identifier directly as the
secret_id, the added indirection allows operators to move keys between
different backends, which may cause different identifiers to be issued.

As with the trivial keymaster, the key to use for PUTs and POSTs is
specified with:

    active_root_secret_id = <secret_id>

Change-Id: Ie52508e47d15ec5c4e96902d3c9f5f282d275683
2018-08-17 17:55:09 +00:00

271 lines
11 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2018 OpenStack Foundation
#
# 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 mock
import os
import unittest
from tempfile import mkdtemp
from textwrap import dedent
from shutil import rmtree
import sys
sys.modules['kmip'] = mock.Mock()
sys.modules['kmip.pie'] = mock.Mock()
sys.modules['kmip.pie.client'] = mock.Mock()
from swift.common.middleware.crypto.kmip_keymaster import KmipKeyMaster
class MockProxyKmipClient(object):
def __init__(self, secrets, calls, kwargs):
calls.append(('__init__', kwargs))
self.secrets = secrets
self.calls = calls
def get(self, uid):
self.calls.append(('get', uid))
return self.secrets[uid]
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def create_secret(algorithm_name, length, value):
algorithm = mock.MagicMock()
algorithm.name = algorithm_name
secret = mock.MagicMock(cryptographic_algorithm=algorithm,
cryptographic_length=length,
value=value)
return secret
def create_mock_client(secrets, calls):
def mock_client(*args, **kwargs):
if args:
raise Exception('unexpected args provided: %r' % (args,))
return MockProxyKmipClient(secrets, calls, kwargs)
return mock_client
class TestKmipKeymaster(unittest.TestCase):
def setUp(self):
self.tempdir = mkdtemp()
def tearDown(self):
rmtree(self.tempdir)
def test_config_in_filter_section(self):
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'key_id': '1234'}
secrets = {'1234': create_secret('AES', 256, b'x' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
km = KmipKeyMaster(None, conf)
self.assertEqual({None: b'x' * 32}, km._root_secrets)
self.assertEqual(None, km.active_secret_id)
self.assertIsNone(km.keymaster_config_path)
self.assertEqual(calls, [
('__init__', {'config_file': '/etc/swift/proxy-server.conf',
'config': 'filter:kmip_keymaster'}),
('get', '1234'),
])
def test_multikey_config_in_filter_section(self):
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'key_id': '1234',
'key_id_xyzzy': 'foobar',
'key_id_alt_secret_id': 'foobar',
'active_root_secret_id': 'xyzzy'}
secrets = {'1234': create_secret('AES', 256, b'x' * 32),
'foobar': create_secret('AES', 256, b'y' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
km = KmipKeyMaster(None, conf)
self.assertEqual({None: b'x' * 32, 'xyzzy': b'y' * 32,
'alt_secret_id': b'y' * 32},
km._root_secrets)
self.assertEqual('xyzzy', km.active_secret_id)
self.assertIsNone(km.keymaster_config_path)
self.assertEqual(calls, [
('__init__', {'config_file': '/etc/swift/proxy-server.conf',
'config': 'filter:kmip_keymaster'}),
('get', '1234'),
('get', 'foobar'),
])
def test_bad_active_key(self):
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'key_id': '1234',
'key_id_xyzzy': 'foobar',
'active_root_secret_id': 'unknown'}
secrets = {'1234': create_secret('AES', 256, b'x' * 32),
'foobar': create_secret('AES', 256, b'y' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)), \
self.assertRaises(ValueError) as raised:
KmipKeyMaster(None, conf)
self.assertEqual('No secret loaded for active_root_secret_id unknown',
str(raised.exception))
def test_config_in_separate_file(self):
km_conf = """
[kmip_keymaster]
key_id = 4321
"""
km_config_file = os.path.join(self.tempdir, 'km.conf')
with open(km_config_file, 'wb') as fd:
fd.write(dedent(km_conf))
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'keymaster_config_path': km_config_file}
secrets = {'4321': create_secret('AES', 256, b'x' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
km = KmipKeyMaster(None, conf)
self.assertEqual({None: b'x' * 32}, km._root_secrets)
self.assertEqual(None, km.active_secret_id)
self.assertEqual(km_config_file, km.keymaster_config_path)
self.assertEqual(calls, [
('__init__', {'config_file': km_config_file,
'config': 'kmip_keymaster'}),
('get', '4321')])
def test_multikey_config_in_separate_file(self):
km_conf = """
[kmip_keymaster]
key_id = 4321
key_id_secret_id = another id
active_root_secret_id = secret_id
"""
km_config_file = os.path.join(self.tempdir, 'km.conf')
with open(km_config_file, 'wb') as fd:
fd.write(dedent(km_conf))
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'keymaster_config_path': km_config_file}
secrets = {'4321': create_secret('AES', 256, b'x' * 32),
'another id': create_secret('AES', 256, b'y' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
km = KmipKeyMaster(None, conf)
self.assertEqual({None: b'x' * 32, 'secret_id': b'y' * 32},
km._root_secrets)
self.assertEqual('secret_id', km.active_secret_id)
self.assertEqual(km_config_file, km.keymaster_config_path)
self.assertEqual(calls, [
('__init__', {'config_file': km_config_file,
'config': 'kmip_keymaster'}),
('get', '4321'),
('get', 'another id')])
def test_proxy_server_conf_dir(self):
proxy_server_conf_dir = os.path.join(self.tempdir, 'proxy_server.d')
os.mkdir(proxy_server_conf_dir)
# KmipClient can't read conf from a dir, so check that is caught early
conf = {'__file__': proxy_server_conf_dir,
'__name__': 'filter:kmip_keymaster',
'key_id': '789'}
with self.assertRaises(ValueError) as cm:
KmipKeyMaster(None, conf)
self.assertIn('config cannot be read from conf dir', str(cm.exception))
# ...but a conf file in a conf dir could point back to itself for the
# KmipClient config
km_config_file = os.path.join(proxy_server_conf_dir, '40.conf')
km_conf = """
[filter:kmip_keymaster]
keymaster_config_file = %s
[kmip_keymaster]
key_id = 789
""" % km_config_file
with open(km_config_file, 'wb') as fd:
fd.write(dedent(km_conf))
conf = {'__file__': proxy_server_conf_dir,
'__name__': 'filter:kmip_keymaster',
'keymaster_config_path': km_config_file}
secrets = {'789': create_secret('AES', 256, b'x' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
km = KmipKeyMaster(None, conf)
self.assertEqual({None: b'x' * 32}, km._root_secrets)
self.assertEqual(None, km.active_secret_id)
self.assertEqual(km_config_file, km.keymaster_config_path)
self.assertEqual(calls, [
('__init__', {'config_file': km_config_file,
'config': 'kmip_keymaster'}),
('get', '789')])
def test_bad_key_length(self):
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'key_id': '1234'}
secrets = {'1234': create_secret('AES', 128, b'x' * 16)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
with self.assertRaises(ValueError) as cm:
KmipKeyMaster(None, conf)
self.assertIn('Expected key 1234 to be an AES-256 key',
str(cm.exception))
self.assertEqual(calls, [
('__init__', {'config_file': '/etc/swift/proxy-server.conf',
'config': 'filter:kmip_keymaster'}),
('get', '1234')])
def test_bad_key_algorithm(self):
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster',
'key_id': '1234'}
secrets = {'1234': create_secret('notAES', 256, b'x' * 32)}
calls = []
klass = 'swift.common.middleware.crypto.kmip_keymaster.ProxyKmipClient'
with mock.patch(klass, create_mock_client(secrets, calls)):
with self.assertRaises(ValueError) as cm:
KmipKeyMaster(None, conf)
self.assertIn('Expected key 1234 to be an AES-256 key',
str(cm.exception))
self.assertEqual(calls, [
('__init__', {'config_file': '/etc/swift/proxy-server.conf',
'config': 'filter:kmip_keymaster'}),
('get', '1234')])
def test_missing_key_id(self):
conf = {'__file__': '/etc/swift/proxy-server.conf',
'__name__': 'filter:kmip_keymaster'}
with self.assertRaises(ValueError) as cm:
KmipKeyMaster(None, conf)
self.assertIn('key_id option is required', str(cm.exception))