PyKMIP/kmip/tests/unit/services/server/test_config.py
Peter Hamilton e215ddbe7b Update server config handling to parse auth plugin settings
This change updates server configuration handling, allowing the
server to parse and store configuration settings for authentication
plugins. Unit tests have been added to cover the new functionality.
2018-03-19 16:52:34 -04:00

822 lines
28 KiB
Python

# Copyright (c) 2016 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# 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 logging
import mock
import six
from six.moves import configparser
import testtools
from kmip.core import exceptions
from kmip.services.server import config
class TestKmipServerConfig(testtools.TestCase):
"""
A test suite for the KmipServerConfig tool.
"""
def setUp(self):
super(TestKmipServerConfig, self).setUp()
def tearDown(self):
super(TestKmipServerConfig, self).tearDown()
def test_init(self):
"""
Test that a KmipServerConfig object can be created without error.
"""
c = config.KmipServerConfig()
self.assertIn('enable_tls_client_auth', c.settings.keys())
self.assertEqual(
True,
c.settings.get('enable_tls_client_auth')
)
self.assertIn('tls_cipher_suites', c.settings.keys())
self.assertEqual([], c.settings.get('tls_cipher_suites'))
def test_set_setting(self):
"""
Test that the right errors are raised and methods are called when
setting individual settings.
"""
c = config.KmipServerConfig()
c._set_auth_suite = mock.MagicMock()
c._set_ca_path = mock.MagicMock()
c._set_certificate_path = mock.MagicMock()
c._set_hostname = mock.MagicMock()
c._set_key_path = mock.MagicMock()
c._set_port = mock.MagicMock()
c._set_policy_path = mock.MagicMock()
c._set_enable_tls_client_auth = mock.MagicMock()
c._set_tls_cipher_suites = mock.MagicMock()
c._set_logging_level = mock.MagicMock()
# Test the right error is generated when setting an unsupported
# setting.
args = ('invalid', None)
regex = "Setting 'invalid' is not supported."
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c.set_setting,
*args
)
# Test the right methods are called when setting supported settings.
c.set_setting('hostname', '127.0.0.1')
c._set_hostname.assert_called_once_with('127.0.0.1')
c.set_setting('port', 5696)
c._set_port.assert_called_once_with(5696)
c.set_setting('certificate_path', '/etc/pykmip/certs/server.crt')
c._set_certificate_path.assert_called_once_with(
'/etc/pykmip/certs/server.crt'
)
c.set_setting('key_path', '/etc/pykmip/certs/server.key')
c._set_key_path.assert_called_once_with(
'/etc/pykmip/certs/server.key'
)
c.set_setting('ca_path', '/etc/pykmip/certs/ca.crt')
c._set_ca_path.assert_called_once_with('/etc/pykmip/certs/ca.crt')
c.set_setting('auth_suite', 'Basic')
c._set_auth_suite.assert_called_once_with('Basic')
c.set_setting('policy_path', '/etc/pykmip/policies')
c._set_policy_path.assert_called_once_with('/etc/pykmip/policies')
c.set_setting('enable_tls_client_auth', False)
c._set_enable_tls_client_auth.assert_called_once_with(False)
c.set_setting('tls_cipher_suites', [])
c._set_tls_cipher_suites.assert_called_once_with([])
c.set_setting('logging_level', 'WARNING')
c._set_logging_level.assert_called_once_with('WARNING')
def test_load_settings(self):
"""
Test that the right calls are made and the right errors generated when
loading configuration settings from a configuration file specified by
a path string.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._parse_settings = mock.MagicMock()
c.parse_auth_settings = mock.MagicMock()
# Test that the right calls are made when correctly processing the
# configuration file.
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = True
with mock.patch(
'six.moves.configparser.SafeConfigParser.read'
) as parser_mock:
c.load_settings("/test/path/server.conf")
c._logger.info.assert_any_call(
"Loading server configuration settings from: "
"/test/path/server.conf"
)
parser_mock.assert_called_with("/test/path/server.conf")
self.assertTrue(c._parse_settings.called)
self.assertTrue(c.parse_auth_settings.called)
# Test that a ConfigurationError is generated when the path is invalid.
c._logger.reset_mock()
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = False
args = ('/test/path/server.conf', )
self.assertRaises(
exceptions.ConfigurationError,
c.load_settings,
*args
)
def test_parse_auth_settings(self):
"""
Test that server authentication plugin settings are parsed correctly.
"""
parser = configparser.SafeConfigParser()
parser.add_section('server')
parser.add_section('auth:slugs')
parser.set('auth:slugs', 'enabled', 'True')
parser.set('auth:slugs', 'url', 'http://127.0.0.1:8080/slugs/')
parser.add_section('auth:ldap')
parser.set('auth:ldap', 'enabled', 'False')
parser.set('auth:ldap', 'url', 'http://127.0.0.1:8080/ldap/')
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
self.assertEqual([], c.settings['auth_plugins'])
c.parse_auth_settings(parser)
configs = c.settings['auth_plugins']
self.assertIsInstance(configs, list)
self.assertEqual(2, len(configs))
for c in configs:
self.assertIsInstance(c, tuple)
self.assertEqual(2, len(c))
self.assertIn(c[0], ['auth:slugs', 'auth:ldap'])
self.assertIsInstance(c[1], dict)
if c[0] == 'auth:slugs':
self.assertIn('enabled', six.iterkeys(c[1]))
self.assertEqual('True', c[1]['enabled'])
self.assertIn('url', six.iterkeys(c[1]))
self.assertEqual('http://127.0.0.1:8080/slugs/', c[1]['url'])
elif c[0] == 'auth:ldap':
self.assertIn('enabled', six.iterkeys(c[1]))
self.assertEqual('False', c[1]['enabled'])
self.assertIn('url', six.iterkeys(c[1]))
self.assertEqual('http://127.0.0.1:8080/ldap/', c[1]['url'])
def test_parse_auth_settings_no_config(self):
"""
Test that server authentication plugin settings are parsed correctly,
even when not specified.
"""
parser = configparser.SafeConfigParser()
parser.add_section('server')
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
self.assertEqual([], c.settings['auth_plugins'])
c.parse_auth_settings(parser)
configs = c.settings['auth_plugins']
self.assertIsInstance(configs, list)
self.assertEqual(0, len(configs))
def test_parse_settings(self):
"""
Test that the right methods are called and the right errors generated
when parsing the configuration settings.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_auth_suite = mock.MagicMock()
c._set_ca_path = mock.MagicMock()
c._set_certificate_path = mock.MagicMock()
c._set_hostname = mock.MagicMock()
c._set_key_path = mock.MagicMock()
c._set_port = mock.MagicMock()
c._set_policy_path = mock.MagicMock()
c._set_enable_tls_client_auth = mock.MagicMock()
c._set_tls_cipher_suites = mock.MagicMock()
c._set_logging_level = mock.MagicMock()
# Test that the right calls are made when correctly parsing settings.
parser = configparser.SafeConfigParser()
parser.add_section('server')
parser.set('server', 'hostname', '127.0.0.1')
parser.set('server', 'port', '5696')
parser.set('server', 'certificate_path', '/test/path/server.crt')
parser.set('server', 'key_path', '/test/path/server.key')
parser.set('server', 'ca_path', '/test/path/ca.crt')
parser.set('server', 'auth_suite', 'Basic')
parser.set('server', 'policy_path', '/test/path/policies')
parser.set('server', 'enable_tls_client_auth', 'False')
parser.set(
'server',
'tls_cipher_suites',
"\n TLS_RSA_WITH_AES_256_CBC_SHA256"
)
parser.set('server', 'logging_level', 'ERROR')
c._parse_settings(parser)
c._set_hostname.assert_called_once_with('127.0.0.1')
c._set_port.assert_called_once_with(5696)
c._set_certificate_path.assert_called_once_with(
'/test/path/server.crt'
)
c._set_key_path.assert_called_once_with('/test/path/server.key')
c._set_ca_path.assert_called_once_with('/test/path/ca.crt')
c._set_auth_suite.assert_called_once_with('Basic')
c._set_policy_path.assert_called_once_with('/test/path/policies')
c._set_enable_tls_client_auth.assert_called_once_with(False)
c._set_tls_cipher_suites.assert_called_once_with(
"\n TLS_RSA_WITH_AES_256_CBC_SHA256"
)
c._set_logging_level.assert_called_once_with('ERROR')
# Test that a ConfigurationError is generated when the expected
# section is missing.
parser = configparser.SafeConfigParser()
args = (parser, )
regex = (
"The server configuration file does not have a 'server' section."
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._parse_settings,
*args
)
# Test that a ConfigurationError is generated when an unexpected
# setting is provided.
parser = configparser.SafeConfigParser()
parser.add_section('server')
parser.set('server', 'invalid', 'invalid')
args = (parser, )
regex = (
"Setting 'invalid' is not a supported setting. Please remove it "
"from the configuration file."
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._parse_settings,
*args
)
# Test that a ConfigurationError is generated when an expected
# setting is missing.
parser = configparser.SafeConfigParser()
parser.add_section('server')
args = (parser, )
regex = (
"Setting 'hostname' is missing from the configuration file."
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._parse_settings,
*args
)
# Test that a ConfigurationError is generated when an expected
# setting is missing.
parser = configparser.SafeConfigParser()
parser.add_section('server')
args = (parser, )
regex = (
"Setting 'hostname' is missing from the configuration file."
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._parse_settings,
*args
)
def test_set_hostname(self):
"""
Test that the hostname configuration property can be set correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
# Test that the setting is set correctly with a valid value.
c._set_hostname('127.0.0.1')
self.assertIn('hostname', c.settings.keys())
self.assertEqual('127.0.0.1', c.settings.get('hostname'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
args = (0, )
regex = "The hostname value must be a string."
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_hostname,
*args
)
self.assertNotEqual(0, c.settings.get('hostname'))
def test_set_port(self):
"""
Test that the port configuration property can be set correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
# Test that the setting is set correctly with a valid value.
c._set_port(5696)
self.assertIn('port', c.settings.keys())
self.assertEqual(5696, c.settings.get('port'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
args = ('invalid', )
regex = "The port value must be an integer in the range 0 - 65535."
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_port,
*args
)
self.assertNotEqual('invalid', c.settings.get('port'))
args = (65536, )
regex = "The port value must be an integer in the range 0 - 65535."
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_port,
*args
)
self.assertNotEqual(65536, c.settings.get('port'))
def test_set_certificate_path(self):
"""
Test that the certificate_path configuration property can be set
correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
self.assertNotIn('certificate_path', c.settings.keys())
# Test that the setting is set correctly with a valid value.
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = True
c._set_certificate_path('/test/path/server.crt')
self.assertIn('certificate_path', c.settings.keys())
self.assertEqual(
'/test/path/server.crt',
c.settings.get('certificate_path')
)
c._set_certificate_path(None)
self.assertIn('certificate_path', c.settings.keys())
self.assertEqual(None, c.settings.get('certificate_path'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
args = (0, )
regex = (
"The certificate path value, if specified, must be a valid "
"string path to a certificate file."
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_certificate_path,
*args
)
self.assertNotEqual(0, c.settings.get('certificate_path'))
args = ('/test/path/server.crt', )
regex = (
"The certificate path value, if specified, must be a valid "
"string path to a certificate file."
)
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = False
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_certificate_path,
*args
)
self.assertNotEqual(
'/test/path/server.crt',
c.settings.get('certificate_path')
)
def test_set_key_path(self):
"""
Test that the key_path configuration property can be set correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
self.assertNotIn('key_path', c.settings.keys())
# Test that the setting is set correctly with a valid value.
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = True
c._set_key_path('/test/path/server.key')
self.assertIn('key_path', c.settings.keys())
self.assertEqual(
'/test/path/server.key',
c.settings.get('key_path')
)
c._set_key_path(None)
self.assertIn('key_path', c.settings.keys())
self.assertEqual(None, c.settings.get('key_path'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
args = (0, )
regex = (
"The key path value, if specified, must be a valid string path "
"to a certificate key file."
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_key_path,
*args
)
self.assertNotEqual(0, c.settings.get('key_path'))
args = ('/test/path/server.key', )
regex = (
"The key path value, if specified, must be a valid string path "
"to a certificate key file."
)
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = False
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_key_path,
*args
)
self.assertNotEqual(
'/test/path/server.key',
c.settings.get('key_path')
)
def test_set_ca_path(self):
"""
Test that the ca_path configuration property can be set correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
self.assertNotIn('ca_path', c.settings.keys())
# Test that the setting is set correctly with a valid value.
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = True
c._set_ca_path('/test/path/ca.crt')
self.assertIn('ca_path', c.settings.keys())
self.assertEqual(
'/test/path/ca.crt',
c.settings.get('ca_path')
)
c._set_ca_path(None)
self.assertIn('ca_path', c.settings.keys())
self.assertEqual(None, c.settings.get('ca_path'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
c._logger.reset_mock()
args = (0, )
self.assertRaises(
exceptions.ConfigurationError,
c._set_ca_path,
*args
)
self.assertNotEqual(0, c.settings.get('ca_path'))
args = ('/test/path/ca.crt', )
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = False
self.assertRaises(
exceptions.ConfigurationError,
c._set_ca_path,
*args
)
self.assertNotEqual(0, c.settings.get('ca_path'))
def test_set_auth_suite(self):
"""
Test that the auth_suite configuration property can be set correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
# Test that the setting is set correctly with a valid value.
c._set_auth_suite('Basic')
self.assertIn('auth_suite', c.settings.keys())
self.assertEqual('Basic', c.settings.get('auth_suite'))
c._set_auth_suite('TLS1.2')
self.assertIn('auth_suite', c.settings.keys())
self.assertEqual('TLS1.2', c.settings.get('auth_suite'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
args = ('invalid', )
regex = (
"The authentication suite must be one of the following: "
"Basic, TLS1.2"
)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
regex,
c._set_auth_suite,
*args
)
self.assertNotEqual('invalid', c.settings.get('auth_suite'))
def test_set_policy_path(self):
"""
Test that the policy_path configuration property can be set correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
self.assertNotIn('policy_path', c.settings.keys())
# Test that the setting is set correctly with a valid value.
with mock.patch('os.path.exists') as os_mock:
os_mock.return_value = True
c._set_policy_path('/test/path/policies')
self.assertIn('policy_path', c.settings.keys())
self.assertEqual(
'/test/path/policies',
c.settings.get('policy_path')
)
c._set_policy_path(None)
self.assertIn('policy_path', c.settings.keys())
self.assertEqual(None, c.settings.get('policy_path'))
# Test that a ConfigurationError is generated when setting the wrong
# value.
c._logger.reset_mock()
args = (1, )
self.assertRaises(
exceptions.ConfigurationError,
c._set_policy_path,
*args
)
self.assertNotEqual(1, c.settings.get('policy_path'))
def test_set_enable_tls_client_auth(self):
"""
Test that the enable_tls_client_auth configuration property can be set
correctly.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
# Test that the setting is set correctly with a valid value
c._set_enable_tls_client_auth(False)
self.assertEqual(
False,
c.settings.get('enable_tls_client_auth')
)
c._set_enable_tls_client_auth(None)
self.assertEqual(
True,
c.settings.get('enable_tls_client_auth')
)
c._set_enable_tls_client_auth(True)
self.assertEqual(
True,
c.settings.get('enable_tls_client_auth')
)
# Test that a ConfigurationError is generated when setting the wrong
# value.
args = ('invalid',)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
"The flag enabling the TLS certificate client auth flag check "
"must be a boolean.",
c._set_enable_tls_client_auth,
*args
)
def test_set_tls_cipher_suites(self):
"""
Test that the tls_cipher_suites configuration property can be set
correctly with a value expected from the config file.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_tls_cipher_suites(
"""
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA256
"""
)
self.assertEqual(2, len(c.settings.get('tls_cipher_suites')))
self.assertIn(
'TLS_RSA_WITH_AES_256_CBC_SHA256',
c.settings.get('tls_cipher_suites')
)
self.assertIn(
'TLS_RSA_WITH_AES_128_CBC_SHA256',
c.settings.get('tls_cipher_suites')
)
def test_set_tls_cipher_suites_preparsed(self):
"""
Test that the tls_cipher_suites configuration property can be set
correctly with a preparsed list of TLS cipher suites, the value
expected if the cipher suites were provided via constructor.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_tls_cipher_suites(
[
'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA',
'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384',
'DH-DSS-AES128-SHA'
]
)
self.assertEqual(3, len(c.settings.get('tls_cipher_suites')))
self.assertIn(
'DH-DSS-AES128-SHA',
c.settings.get('tls_cipher_suites')
)
self.assertIn(
'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA',
c.settings.get('tls_cipher_suites')
)
self.assertIn(
'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384',
c.settings.get('tls_cipher_suites')
)
def test_set_tls_cipher_suites_empty(self):
"""
Test that the tls_cipher_suites configuration property can be set
correctly with an empty value.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_tls_cipher_suites(None)
self.assertEqual([], c.settings.get('tls_cipher_suites'))
def test_set_tls_cipher_suites_invalid_value(self):
"""
Test that the right error is raised when an invalid value is used to
set the tls_cipher_suites configuration property.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
args = (1,)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
"The TLS cipher suites must be a set of strings representing "
"cipher suite names.",
c._set_tls_cipher_suites,
*args
)
def test_set_tls_cipher_suites_invalid_list_value(self):
"""
Test that the right error is raised when an invalid list value is used
to set the tls_cipher_suites configuration property.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
args = ([0],)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
"The TLS cipher suites must be a set of strings representing "
"cipher suite names.",
c._set_tls_cipher_suites,
*args
)
def test_set_logging_level(self):
"""
Test that the logging_level configuration property can be set
correctly with a value expected from the config file.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_logging_level("DEBUG")
self.assertEqual(logging.DEBUG, c.settings.get('logging_level'))
def test_set_logging_level_enum(self):
"""
Test that the logging_level configuration property can be set
correctly with a value expected from the server constructor.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_logging_level(logging.DEBUG)
self.assertEqual(logging.DEBUG, c.settings.get('logging_level'))
def test_set_logging_level_default(self):
"""
Test that the logging_level configuration property can be set
correctly without specifying a value.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
c._set_logging_level(logging.DEBUG)
self.assertEqual(logging.DEBUG, c.settings.get('logging_level'))
c._set_logging_level(None)
self.assertEqual(logging.INFO, c.settings.get('logging_level'))
def test_set_logging_level_invalid_value(self):
"""
Test that the right error is raised when an invalid value is used to
set the tls_cipher_suites configuration property.
"""
c = config.KmipServerConfig()
c._logger = mock.MagicMock()
args = (0,)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
"The logging level must be a string representing a valid logging "
"level.",
c._set_logging_level,
*args
)
args = ('invalid',)
self.assertRaisesRegexp(
exceptions.ConfigurationError,
"The logging level must be a string representing a valid logging "
"level.",
c._set_logging_level,
*args
)