mirror of
https://github.com/OpenKMIP/PyKMIP.git
synced 2025-07-16 18:44:23 +02:00
Merge pull request #198 from OpenKMIP/feat/add-operation-policy-loading
Adding dynamic operation policy loading to the KMIP server
This commit is contained in:
commit
a9264e612f
@ -13,9 +13,71 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import six
|
||||||
|
|
||||||
from kmip.core import enums
|
from kmip.core import enums
|
||||||
|
|
||||||
|
|
||||||
|
def read_policy_from_file(path):
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
try:
|
||||||
|
policy_blob = json.loads(f.read())
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(
|
||||||
|
"An error occurred while attempting to parse the JSON "
|
||||||
|
"file. {0}".format(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
policies = dict()
|
||||||
|
|
||||||
|
for name, object_policies in six.iteritems(policy_blob):
|
||||||
|
processed_object_policies = dict()
|
||||||
|
|
||||||
|
for object_type, operation_policies in six.iteritems(object_policies):
|
||||||
|
processed_operation_policies = dict()
|
||||||
|
|
||||||
|
for operation, permission in six.iteritems(operation_policies):
|
||||||
|
|
||||||
|
try:
|
||||||
|
enum_operation = enums.Operation[operation]
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(
|
||||||
|
"'{0}' is not a valid Operation value.".format(
|
||||||
|
operation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
enum_policy = enums.Policy[permission]
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(
|
||||||
|
"'{0}' is not a valid Policy value.".format(
|
||||||
|
permission
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_operation_policies.update([
|
||||||
|
(enum_operation, enum_policy)
|
||||||
|
])
|
||||||
|
|
||||||
|
try:
|
||||||
|
enum_type = enums.ObjectType[object_type]
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(
|
||||||
|
"'{0}' is not a valid ObjectType value.".format(
|
||||||
|
object_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_object_policies.update([
|
||||||
|
(enum_type, processed_operation_policies)
|
||||||
|
])
|
||||||
|
|
||||||
|
policies.update([(name, processed_object_policies)])
|
||||||
|
|
||||||
|
return policies
|
||||||
|
|
||||||
|
|
||||||
policies = {
|
policies = {
|
||||||
'default': {
|
'default': {
|
||||||
enums.ObjectType.CERTIFICATE: {
|
enums.ObjectType.CERTIFICATE: {
|
||||||
|
@ -41,7 +41,8 @@ class KmipServerConfig(object):
|
|||||||
'certificate_path',
|
'certificate_path',
|
||||||
'key_path',
|
'key_path',
|
||||||
'ca_path',
|
'ca_path',
|
||||||
'auth_suite'
|
'auth_suite',
|
||||||
|
'policy_path'
|
||||||
]
|
]
|
||||||
|
|
||||||
def set_setting(self, setting, value):
|
def set_setting(self, setting, value):
|
||||||
@ -75,8 +76,10 @@ class KmipServerConfig(object):
|
|||||||
self._set_key_path(value)
|
self._set_key_path(value)
|
||||||
elif setting == 'ca_path':
|
elif setting == 'ca_path':
|
||||||
self._set_ca_path(value)
|
self._set_ca_path(value)
|
||||||
else:
|
elif setting == 'auth_suite':
|
||||||
self._set_auth_suite(value)
|
self._set_auth_suite(value)
|
||||||
|
else:
|
||||||
|
self._set_policy_path(value)
|
||||||
|
|
||||||
def load_settings(self, path):
|
def load_settings(self, path):
|
||||||
"""
|
"""
|
||||||
@ -141,6 +144,8 @@ class KmipServerConfig(object):
|
|||||||
self._set_ca_path(parser.get('server', 'ca_path'))
|
self._set_ca_path(parser.get('server', 'ca_path'))
|
||||||
if parser.has_option('server', 'auth_suite'):
|
if parser.has_option('server', 'auth_suite'):
|
||||||
self._set_auth_suite(parser.get('server', 'auth_suite'))
|
self._set_auth_suite(parser.get('server', 'auth_suite'))
|
||||||
|
if parser.has_option('server', 'policy_path'):
|
||||||
|
self._set_policy_path(parser.get('server', 'policy_path'))
|
||||||
|
|
||||||
def _set_hostname(self, value):
|
def _set_hostname(self, value):
|
||||||
if isinstance(value, six.string_types):
|
if isinstance(value, six.string_types):
|
||||||
@ -224,3 +229,20 @@ class KmipServerConfig(object):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.settings['auth_suite'] = value
|
self.settings['auth_suite'] = value
|
||||||
|
|
||||||
|
def _set_policy_path(self, value):
|
||||||
|
if value is None:
|
||||||
|
self.settings['policy_path'] = None
|
||||||
|
elif isinstance(value, six.string_types):
|
||||||
|
if os.path.exists(value):
|
||||||
|
self.settings['policy_path'] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.ConfigurationError(
|
||||||
|
"The policy path value, if specified, must be a valid "
|
||||||
|
"string path to a filesystem directory."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise exceptions.ConfigurationError(
|
||||||
|
"The policy path, if specified, must be a valid string path "
|
||||||
|
"to a filesystem directory."
|
||||||
|
)
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import six
|
import six
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@ -42,7 +44,7 @@ from kmip.core.messages.payloads import register
|
|||||||
|
|
||||||
from kmip.core import misc
|
from kmip.core import misc
|
||||||
|
|
||||||
from kmip.core.policy import policies
|
from kmip.core import policy as operation_policy
|
||||||
|
|
||||||
from kmip.pie import factory
|
from kmip.pie import factory
|
||||||
from kmip.pie import objects
|
from kmip.pie import objects
|
||||||
@ -77,9 +79,14 @@ class KmipEngine(object):
|
|||||||
* Cryptographic usage mask enforcement per object type
|
* Cryptographic usage mask enforcement per object type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, policy_path=None):
|
||||||
"""
|
"""
|
||||||
Create a KmipEngine.
|
Create a KmipEngine.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
policy_path (string): The path to the filesystem directory
|
||||||
|
containing PyKMIP server operation policy JSON files.
|
||||||
|
Optional, defaults to None.
|
||||||
"""
|
"""
|
||||||
self._logger = logging.getLogger('kmip.server.engine')
|
self._logger = logging.getLogger('kmip.server.engine')
|
||||||
|
|
||||||
@ -118,10 +125,69 @@ class KmipEngine(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._attribute_policy = policy.AttributePolicy(self._protocol_version)
|
self._attribute_policy = policy.AttributePolicy(self._protocol_version)
|
||||||
self._operation_policies = policies
|
self._operation_policies = copy.deepcopy(operation_policy.policies)
|
||||||
|
self._load_operation_policies(policy_path)
|
||||||
|
|
||||||
self._client_identity = None
|
self._client_identity = None
|
||||||
|
|
||||||
|
def _load_operation_policies(self, policy_path):
|
||||||
|
if (policy_path is None) or (not os.path.isdir(policy_path)):
|
||||||
|
self._logger.warning(
|
||||||
|
"The specified operation policy directory ({0}) is not "
|
||||||
|
"valid. No user-defined policies will be loaded".format(
|
||||||
|
policy_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return dict()
|
||||||
|
else:
|
||||||
|
self._logger.info(
|
||||||
|
"Loading user-defined operation policy files from: {0}".format(
|
||||||
|
policy_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for filename in os.listdir(policy_path):
|
||||||
|
file_path = os.path.join(policy_path, filename)
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
self._logger.info(
|
||||||
|
"Loading user_defined operation policies "
|
||||||
|
"from file: {0}".format(file_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
policies = operation_policy.read_policy_from_file(
|
||||||
|
file_path
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
self._logger.error(
|
||||||
|
"A failure occurred while loading policies."
|
||||||
|
)
|
||||||
|
self._logger.exception(e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
reserved_policies = ['default', 'public']
|
||||||
|
for policy_name in six.iterkeys(policies):
|
||||||
|
if policy_name in reserved_policies:
|
||||||
|
self._logger.warning(
|
||||||
|
"Loaded policy '{0}' overwrites a reserved "
|
||||||
|
"policy and will be thrown out.".format(
|
||||||
|
policy_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif policy_name in six.iterkeys(
|
||||||
|
self._operation_policies
|
||||||
|
):
|
||||||
|
self._logger.warning(
|
||||||
|
"Loaded policy '{0}' overwrites a "
|
||||||
|
"preexisting policy and will be thrown "
|
||||||
|
"out.".format(policy_name)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._operation_policies.update([(
|
||||||
|
policy_name,
|
||||||
|
policies.get(policy_name)
|
||||||
|
)])
|
||||||
|
|
||||||
def _get_enum_string(self, e):
|
def _get_enum_string(self, e):
|
||||||
return ''.join([x.capitalize() for x in e.name.split('_')])
|
return ''.join([x.capitalize() for x in e.name.split('_')])
|
||||||
|
|
||||||
|
@ -50,7 +50,9 @@ class KmipServer(object):
|
|||||||
ca_path=None,
|
ca_path=None,
|
||||||
auth_suite=None,
|
auth_suite=None,
|
||||||
config_path='/etc/pykmip/server.conf',
|
config_path='/etc/pykmip/server.conf',
|
||||||
log_path='/var/log/pykmip/server.log'):
|
log_path='/var/log/pykmip/server.log',
|
||||||
|
policy_path='/etc/pykmip/policies'
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a KmipServer.
|
Create a KmipServer.
|
||||||
|
|
||||||
@ -91,6 +93,9 @@ class KmipServer(object):
|
|||||||
log_path (string): The path to the base server log file
|
log_path (string): The path to the base server log file
|
||||||
(e.g., '/var/log/pykmip/server.log'). Optional, defaults to
|
(e.g., '/var/log/pykmip/server.log'). Optional, defaults to
|
||||||
'/var/log/pykmip/server.log'.
|
'/var/log/pykmip/server.log'.
|
||||||
|
policy_path (string): The path to the filesystem directory
|
||||||
|
containing PyKMIP server operation policy JSON files.
|
||||||
|
Optional, defaults to '/etc/pykmip/policies'.
|
||||||
"""
|
"""
|
||||||
self._logger = logging.getLogger('kmip.server')
|
self._logger = logging.getLogger('kmip.server')
|
||||||
self._setup_logging(log_path)
|
self._setup_logging(log_path)
|
||||||
@ -103,7 +108,8 @@ class KmipServer(object):
|
|||||||
certificate_path,
|
certificate_path,
|
||||||
key_path,
|
key_path,
|
||||||
ca_path,
|
ca_path,
|
||||||
auth_suite
|
auth_suite,
|
||||||
|
policy_path
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.config.settings.get('auth_suite') == 'TLS1.2':
|
if self.config.settings.get('auth_suite') == 'TLS1.2':
|
||||||
@ -111,7 +117,9 @@ class KmipServer(object):
|
|||||||
else:
|
else:
|
||||||
self.auth_suite = auth.BasicAuthenticationSuite()
|
self.auth_suite = auth.BasicAuthenticationSuite()
|
||||||
|
|
||||||
self._engine = engine.KmipEngine()
|
self._engine = engine.KmipEngine(
|
||||||
|
self.config.settings.get('policy_path')
|
||||||
|
)
|
||||||
self._session_id = 1
|
self._session_id = 1
|
||||||
self._is_serving = False
|
self._is_serving = False
|
||||||
|
|
||||||
@ -144,7 +152,9 @@ class KmipServer(object):
|
|||||||
certificate_path=None,
|
certificate_path=None,
|
||||||
key_path=None,
|
key_path=None,
|
||||||
ca_path=None,
|
ca_path=None,
|
||||||
auth_suite=None):
|
auth_suite=None,
|
||||||
|
policy_path=None
|
||||||
|
):
|
||||||
if path:
|
if path:
|
||||||
self.config.load_settings(path)
|
self.config.load_settings(path)
|
||||||
|
|
||||||
@ -160,6 +170,8 @@ class KmipServer(object):
|
|||||||
self.config.set_setting('ca_path', ca_path)
|
self.config.set_setting('ca_path', ca_path)
|
||||||
if auth_suite:
|
if auth_suite:
|
||||||
self.config.set_setting('auth_suite', auth_suite)
|
self.config.set_setting('auth_suite', auth_suite)
|
||||||
|
if policy_path:
|
||||||
|
self.config.set_setting('policy_path', policy_path)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
@ -447,6 +459,18 @@ def build_argument_parser():
|
|||||||
"A string representing a path to a log file. Defaults to None."
|
"A string representing a path to a log file. Defaults to None."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
parser.add_option(
|
||||||
|
"-o",
|
||||||
|
"--policy_path",
|
||||||
|
action="store",
|
||||||
|
type="str",
|
||||||
|
default=None,
|
||||||
|
dest="policy_path",
|
||||||
|
help=(
|
||||||
|
"A string representing a path to the operation policy filesystem "
|
||||||
|
"directory. Optional, defaults to None."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -473,6 +497,8 @@ def main(args=None):
|
|||||||
kwargs['config_path'] = opts.config_path
|
kwargs['config_path'] = opts.config_path
|
||||||
if opts.log_path:
|
if opts.log_path:
|
||||||
kwargs['log_path'] = opts.log_path
|
kwargs['log_path'] = opts.log_path
|
||||||
|
if opts.policy_path:
|
||||||
|
kwargs['policy_path'] = opts.policy_path
|
||||||
|
|
||||||
# Create and start the server.
|
# Create and start the server.
|
||||||
s = KmipServer(**kwargs)
|
s = KmipServer(**kwargs)
|
||||||
|
130
kmip/tests/unit/core/test_policy.py
Normal file
130
kmip/tests/unit/core/test_policy.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# 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 shutil
|
||||||
|
import tempfile
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from kmip.core import enums
|
||||||
|
from kmip.core import policy
|
||||||
|
|
||||||
|
|
||||||
|
class TestPolicy(testtools.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPolicy, self).setUp()
|
||||||
|
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, self.temp_dir)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestPolicy, self).tearDown()
|
||||||
|
|
||||||
|
def test_read_policy_from_file(self):
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir,
|
||||||
|
delete=False
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"CERTIFICATE": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
policies = policy.read_policy_from_file(policy_file.name)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(policies))
|
||||||
|
self.assertIn('test', policies.keys())
|
||||||
|
|
||||||
|
test_policy = {
|
||||||
|
enums.ObjectType.CERTIFICATE: {
|
||||||
|
enums.Operation.LOCATE: enums.Policy.ALLOW_ALL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(test_policy, policies.get('test'))
|
||||||
|
|
||||||
|
def test_read_policy_from_file_empty(self):
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir,
|
||||||
|
delete=False
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write('')
|
||||||
|
|
||||||
|
args = (policy_file.name, )
|
||||||
|
regex = "An error occurred while attempting to parse the JSON file."
|
||||||
|
self.assertRaisesRegexp(
|
||||||
|
ValueError,
|
||||||
|
regex,
|
||||||
|
policy.read_policy_from_file,
|
||||||
|
*args
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_read_policy_from_file_bad_object_type(self):
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir,
|
||||||
|
delete=False
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"INVALID": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = (policy_file.name, )
|
||||||
|
regex = "'INVALID' is not a valid ObjectType value."
|
||||||
|
self.assertRaisesRegexp(
|
||||||
|
ValueError,
|
||||||
|
regex,
|
||||||
|
policy.read_policy_from_file,
|
||||||
|
*args
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_read_policy_from_file_bad_operation(self):
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir,
|
||||||
|
delete=False
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"CERTIFICATE": {"INVALID": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = (policy_file.name, )
|
||||||
|
regex = "'INVALID' is not a valid Operation value."
|
||||||
|
self.assertRaisesRegexp(
|
||||||
|
ValueError,
|
||||||
|
regex,
|
||||||
|
policy.read_policy_from_file,
|
||||||
|
*args
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_read_policy_from_file_bad_permission(self):
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir,
|
||||||
|
delete=False
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"CERTIFICATE": {"LOCATE": "INVALID"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = (policy_file.name, )
|
||||||
|
regex = "'INVALID' is not a valid Policy value."
|
||||||
|
self.assertRaisesRegexp(
|
||||||
|
ValueError,
|
||||||
|
regex,
|
||||||
|
policy.read_policy_from_file,
|
||||||
|
*args
|
||||||
|
)
|
@ -53,6 +53,7 @@ class TestKmipServerConfig(testtools.TestCase):
|
|||||||
c._set_hostname = mock.MagicMock()
|
c._set_hostname = mock.MagicMock()
|
||||||
c._set_key_path = mock.MagicMock()
|
c._set_key_path = mock.MagicMock()
|
||||||
c._set_port = mock.MagicMock()
|
c._set_port = mock.MagicMock()
|
||||||
|
c._set_policy_path = mock.MagicMock()
|
||||||
|
|
||||||
# Test the right error is generated when setting an unsupported
|
# Test the right error is generated when setting an unsupported
|
||||||
# setting.
|
# setting.
|
||||||
@ -88,6 +89,9 @@ class TestKmipServerConfig(testtools.TestCase):
|
|||||||
c.set_setting('auth_suite', 'Basic')
|
c.set_setting('auth_suite', 'Basic')
|
||||||
c._set_auth_suite.assert_called_once_with('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')
|
||||||
|
|
||||||
def test_load_settings(self):
|
def test_load_settings(self):
|
||||||
"""
|
"""
|
||||||
Test that the right calls are made and the right errors generated when
|
Test that the right calls are made and the right errors generated when
|
||||||
@ -139,6 +143,7 @@ class TestKmipServerConfig(testtools.TestCase):
|
|||||||
c._set_hostname = mock.MagicMock()
|
c._set_hostname = mock.MagicMock()
|
||||||
c._set_key_path = mock.MagicMock()
|
c._set_key_path = mock.MagicMock()
|
||||||
c._set_port = mock.MagicMock()
|
c._set_port = mock.MagicMock()
|
||||||
|
c._set_policy_path = mock.MagicMock()
|
||||||
|
|
||||||
# Test that the right calls are made when correctly parsing settings.
|
# Test that the right calls are made when correctly parsing settings.
|
||||||
parser = configparser.SafeConfigParser()
|
parser = configparser.SafeConfigParser()
|
||||||
@ -149,6 +154,7 @@ class TestKmipServerConfig(testtools.TestCase):
|
|||||||
parser.set('server', 'key_path', '/test/path/server.key')
|
parser.set('server', 'key_path', '/test/path/server.key')
|
||||||
parser.set('server', 'ca_path', '/test/path/ca.crt')
|
parser.set('server', 'ca_path', '/test/path/ca.crt')
|
||||||
parser.set('server', 'auth_suite', 'Basic')
|
parser.set('server', 'auth_suite', 'Basic')
|
||||||
|
parser.set('server', 'policy_path', '/test/path/policies')
|
||||||
|
|
||||||
c._parse_settings(parser)
|
c._parse_settings(parser)
|
||||||
|
|
||||||
@ -160,6 +166,7 @@ class TestKmipServerConfig(testtools.TestCase):
|
|||||||
c._set_key_path.assert_called_once_with('/test/path/server.key')
|
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_ca_path.assert_called_once_with('/test/path/ca.crt')
|
||||||
c._set_auth_suite.assert_called_once_with('Basic')
|
c._set_auth_suite.assert_called_once_with('Basic')
|
||||||
|
c._set_policy_path.assert_called_once_with('/test/path/policies')
|
||||||
|
|
||||||
# Test that a ConfigurationError is generated when the expected
|
# Test that a ConfigurationError is generated when the expected
|
||||||
# section is missing.
|
# section is missing.
|
||||||
@ -478,3 +485,48 @@ class TestKmipServerConfig(testtools.TestCase):
|
|||||||
*args
|
*args
|
||||||
)
|
)
|
||||||
self.assertNotEqual('invalid', c.settings.get('auth_suite'))
|
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 = (0, )
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ConfigurationError,
|
||||||
|
c._set_policy_path,
|
||||||
|
*args
|
||||||
|
)
|
||||||
|
self.assertNotEqual(0, c.settings.get('policy_path'))
|
||||||
|
|
||||||
|
args = ('/test/path/policies', )
|
||||||
|
with mock.patch('os.path.exists') as os_mock:
|
||||||
|
os_mock.return_value = False
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ConfigurationError,
|
||||||
|
c._set_policy_path,
|
||||||
|
*args
|
||||||
|
)
|
||||||
|
self.assertNotEqual(0, c.settings.get('policy_path'))
|
||||||
|
@ -14,10 +14,12 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
import shutil
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from sqlalchemy.orm import exc
|
from sqlalchemy.orm import exc
|
||||||
|
|
||||||
|
import tempfile
|
||||||
import testtools
|
import testtools
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -74,6 +76,9 @@ class TestKmipEngine(testtools.TestCase):
|
|||||||
bind=self.engine
|
bind=self.engine
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, self.temp_dir)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestKmipEngine, self).tearDown()
|
super(TestKmipEngine, self).tearDown()
|
||||||
|
|
||||||
@ -133,6 +138,175 @@ class TestKmipEngine(testtools.TestCase):
|
|||||||
"""
|
"""
|
||||||
engine.KmipEngine()
|
engine.KmipEngine()
|
||||||
|
|
||||||
|
def test_load_operation_policies(self):
|
||||||
|
"""
|
||||||
|
Test that the KmipEngine can correctly load operation policies.
|
||||||
|
"""
|
||||||
|
e = engine.KmipEngine()
|
||||||
|
e._logger = mock.MagicMock()
|
||||||
|
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"CERTIFICATE": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(e._operation_policies))
|
||||||
|
|
||||||
|
e._load_operation_policies(self.temp_dir)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user-defined operation policy files from: {0}".format(
|
||||||
|
self.temp_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user_defined operation policies from file: {0}".format(
|
||||||
|
policy_file.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(3, len(e._operation_policies))
|
||||||
|
self.assertIn('test', e._operation_policies.keys())
|
||||||
|
|
||||||
|
test_policy = {
|
||||||
|
enums.ObjectType.CERTIFICATE: {
|
||||||
|
enums.Operation.LOCATE: enums.Policy.ALLOW_ALL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(test_policy, e._operation_policies.get('test'))
|
||||||
|
|
||||||
|
def test_load_operation_policies_with_file_read_error(self):
|
||||||
|
"""
|
||||||
|
Test that the KmipEngine can correctly handle load errors.
|
||||||
|
"""
|
||||||
|
e = engine.KmipEngine()
|
||||||
|
e._logger = mock.MagicMock()
|
||||||
|
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"INVALID": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(e._operation_policies))
|
||||||
|
|
||||||
|
e._load_operation_policies(self.temp_dir)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user-defined operation policy files from: {0}".format(
|
||||||
|
self.temp_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user_defined operation policies from file: {0}".format(
|
||||||
|
policy_file.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.error.assert_called_once_with(
|
||||||
|
"A failure occurred while loading policies."
|
||||||
|
)
|
||||||
|
e._logger.exception.assert_called_once()
|
||||||
|
|
||||||
|
self.assertEqual(2, len(e._operation_policies))
|
||||||
|
|
||||||
|
def test_load_operation_policies_with_reserved(self):
|
||||||
|
"""
|
||||||
|
Test that the KmipEngine can correctly load operation policies, even
|
||||||
|
when a policy attempts to overwrite a reserved one.
|
||||||
|
"""
|
||||||
|
e = engine.KmipEngine()
|
||||||
|
e._logger = mock.MagicMock()
|
||||||
|
|
||||||
|
policy_file = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir
|
||||||
|
)
|
||||||
|
with open(policy_file.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"public": {"CERTIFICATE": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(e._operation_policies))
|
||||||
|
|
||||||
|
e._load_operation_policies(self.temp_dir)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user-defined operation policy files from: {0}".format(
|
||||||
|
self.temp_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user_defined operation policies from file: {0}".format(
|
||||||
|
policy_file.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.warning.assert_called_once_with(
|
||||||
|
"Loaded policy 'public' overwrites a reserved policy and will "
|
||||||
|
"be thrown out."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(e._operation_policies))
|
||||||
|
|
||||||
|
def test_load_operation_policies_with_duplicate(self):
|
||||||
|
"""
|
||||||
|
Test that the KmipEngine can correctly load operation policies, even
|
||||||
|
when a policy is defined multiple times.
|
||||||
|
"""
|
||||||
|
e = engine.KmipEngine()
|
||||||
|
e._logger = mock.MagicMock()
|
||||||
|
|
||||||
|
policy_file_a = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir
|
||||||
|
)
|
||||||
|
with open(policy_file_a.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"CERTIFICATE": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
policy_file_b = tempfile.NamedTemporaryFile(
|
||||||
|
dir=self.temp_dir
|
||||||
|
)
|
||||||
|
with open(policy_file_b.name, 'w') as f:
|
||||||
|
f.write(
|
||||||
|
'{"test": {"CERTIFICATE": {"LOCATE": "ALLOW_ALL"}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(e._operation_policies))
|
||||||
|
|
||||||
|
e._load_operation_policies(self.temp_dir)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user-defined operation policy files from: {0}".format(
|
||||||
|
self.temp_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user_defined operation policies from file: {0}".format(
|
||||||
|
policy_file_a.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.info.assert_any_call(
|
||||||
|
"Loading user_defined operation policies from file: {0}".format(
|
||||||
|
policy_file_b.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e._logger.warning.assert_called_once_with(
|
||||||
|
"Loaded policy 'test' overwrites a preexisting policy and will "
|
||||||
|
"be thrown out."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(3, len(e._operation_policies))
|
||||||
|
self.assertIn('test', e._operation_policies.keys())
|
||||||
|
|
||||||
|
test_policy = {
|
||||||
|
enums.ObjectType.CERTIFICATE: {
|
||||||
|
enums.Operation.LOCATE: enums.Policy.ALLOW_ALL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(test_policy, e._operation_policies.get('test'))
|
||||||
|
|
||||||
def test_version_operation_match(self):
|
def test_version_operation_match(self):
|
||||||
"""
|
"""
|
||||||
Test that a valid response is generated when trying to invoke an
|
Test that a valid response is generated when trying to invoke an
|
||||||
|
@ -91,13 +91,17 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
self.assertTrue(s._logger.addHandler.called)
|
self.assertTrue(s._logger.addHandler.called)
|
||||||
s._logger.setLevel.assert_called_once_with(logging.INFO)
|
s._logger.setLevel.assert_called_once_with(logging.INFO)
|
||||||
|
|
||||||
|
@mock.patch('kmip.services.server.engine.KmipEngine')
|
||||||
@mock.patch('kmip.services.auth.TLS12AuthenticationSuite')
|
@mock.patch('kmip.services.auth.TLS12AuthenticationSuite')
|
||||||
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
||||||
def test_setup_configuration(self, logging_mock, auth_mock):
|
def test_setup_configuration(self, logging_mock, auth_mock, engine_mock):
|
||||||
"""
|
"""
|
||||||
Test that the server setup configuration works without error.
|
Test that the server setup configuration works without error.
|
||||||
"""
|
"""
|
||||||
s = server.KmipServer(config_path=None)
|
s = server.KmipServer(
|
||||||
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
|
)
|
||||||
s.config = mock.MagicMock()
|
s.config = mock.MagicMock()
|
||||||
|
|
||||||
# Test the right calls are made when reinvoking config setup
|
# Test the right calls are made when reinvoking config setup
|
||||||
@ -108,7 +112,8 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
'/etc/pykmip/certs/server.crt',
|
'/etc/pykmip/certs/server.crt',
|
||||||
'/etc/pykmip/certs/server.key',
|
'/etc/pykmip/certs/server.key',
|
||||||
'/etc/pykmip/certs/ca.crt',
|
'/etc/pykmip/certs/ca.crt',
|
||||||
'Basic'
|
'Basic',
|
||||||
|
'/etc/pykmip/policies'
|
||||||
)
|
)
|
||||||
|
|
||||||
s.config.load_settings.assert_called_with('/etc/pykmip/server.conf')
|
s.config.load_settings.assert_called_with('/etc/pykmip/server.conf')
|
||||||
@ -127,14 +132,23 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
'/etc/pykmip/certs/ca.crt'
|
'/etc/pykmip/certs/ca.crt'
|
||||||
)
|
)
|
||||||
s.config.set_setting.assert_any_call('auth_suite', 'Basic')
|
s.config.set_setting.assert_any_call('auth_suite', 'Basic')
|
||||||
|
s.config.set_setting.assert_any_call(
|
||||||
|
'policy_path',
|
||||||
|
'/etc/pykmip/policies'
|
||||||
|
)
|
||||||
|
|
||||||
# Test that an attempt is made to instantiate the TLS 1.2 auth suite
|
# Test that an attempt is made to instantiate the TLS 1.2 auth suite
|
||||||
s = server.KmipServer(auth_suite='TLS1.2', config_path=None)
|
s = server.KmipServer(
|
||||||
|
auth_suite='TLS1.2',
|
||||||
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
|
)
|
||||||
self.assertEqual('TLS1.2', s.config.settings.get('auth_suite'))
|
self.assertEqual('TLS1.2', s.config.settings.get('auth_suite'))
|
||||||
self.assertIsNotNone(s.auth_suite)
|
self.assertIsNotNone(s.auth_suite)
|
||||||
|
|
||||||
|
@mock.patch('kmip.services.server.engine.KmipEngine')
|
||||||
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
||||||
def test_start(self, logging_mock):
|
def test_start(self, logging_mock, engine_mock):
|
||||||
"""
|
"""
|
||||||
Test that starting the KmipServer either runs as expected or generates
|
Test that starting the KmipServer either runs as expected or generates
|
||||||
the expected error.
|
the expected error.
|
||||||
@ -145,7 +159,8 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
s = server.KmipServer(
|
s = server.KmipServer(
|
||||||
hostname='127.0.0.1',
|
hostname='127.0.0.1',
|
||||||
port=5696,
|
port=5696,
|
||||||
config_path=None
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
)
|
)
|
||||||
s._logger = mock.MagicMock()
|
s._logger = mock.MagicMock()
|
||||||
|
|
||||||
@ -205,8 +220,9 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
)
|
)
|
||||||
s._logger.exception.assert_called_once_with(test_exception)
|
s._logger.exception.assert_called_once_with(test_exception)
|
||||||
|
|
||||||
|
@mock.patch('kmip.services.server.engine.KmipEngine')
|
||||||
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
||||||
def test_stop(self, logging_mock):
|
def test_stop(self, logging_mock, engine_mock):
|
||||||
"""
|
"""
|
||||||
Test that the right calls and log messages are triggered while
|
Test that the right calls and log messages are triggered while
|
||||||
cleaning up the server and any remaining sessions.
|
cleaning up the server and any remaining sessions.
|
||||||
@ -214,7 +230,8 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
s = server.KmipServer(
|
s = server.KmipServer(
|
||||||
hostname='127.0.0.1',
|
hostname='127.0.0.1',
|
||||||
port=5696,
|
port=5696,
|
||||||
config_path=None
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
)
|
)
|
||||||
s._logger = mock.MagicMock()
|
s._logger = mock.MagicMock()
|
||||||
s._socket = mock.MagicMock()
|
s._socket = mock.MagicMock()
|
||||||
@ -321,8 +338,9 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
s._socket.close.assert_called_once_with()
|
s._socket.close.assert_called_once_with()
|
||||||
s._logger.exception(test_exception)
|
s._logger.exception(test_exception)
|
||||||
|
|
||||||
|
@mock.patch('kmip.services.server.engine.KmipEngine')
|
||||||
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
||||||
def test_serve(self, logging_mock):
|
def test_serve(self, logging_mock, engine_mock):
|
||||||
"""
|
"""
|
||||||
Test that the right calls and log messages are triggered while
|
Test that the right calls and log messages are triggered while
|
||||||
serving connections.
|
serving connections.
|
||||||
@ -330,7 +348,8 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
s = server.KmipServer(
|
s = server.KmipServer(
|
||||||
hostname='127.0.0.1',
|
hostname='127.0.0.1',
|
||||||
port=5696,
|
port=5696,
|
||||||
config_path=None
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
)
|
)
|
||||||
s._is_serving = True
|
s._is_serving = True
|
||||||
s._logger = mock.MagicMock()
|
s._logger = mock.MagicMock()
|
||||||
@ -400,8 +419,9 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
handler(None, None)
|
handler(None, None)
|
||||||
self.assertFalse(s._is_serving)
|
self.assertFalse(s._is_serving)
|
||||||
|
|
||||||
|
@mock.patch('kmip.services.server.engine.KmipEngine')
|
||||||
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
||||||
def test_setup_connection_handler(self, logging_mock):
|
def test_setup_connection_handler(self, logging_mock, engine_mock):
|
||||||
"""
|
"""
|
||||||
Test that a KmipSession can be successfully created and spun off from
|
Test that a KmipSession can be successfully created and spun off from
|
||||||
the KmipServer.
|
the KmipServer.
|
||||||
@ -409,7 +429,8 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
s = server.KmipServer(
|
s = server.KmipServer(
|
||||||
hostname='127.0.0.1',
|
hostname='127.0.0.1',
|
||||||
port=5696,
|
port=5696,
|
||||||
config_path=None
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
)
|
)
|
||||||
s._logger = mock.MagicMock()
|
s._logger = mock.MagicMock()
|
||||||
|
|
||||||
@ -455,8 +476,9 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(3, s._session_id)
|
self.assertEqual(3, s._session_id)
|
||||||
|
|
||||||
|
@mock.patch('kmip.services.server.engine.KmipEngine')
|
||||||
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
@mock.patch('kmip.services.server.server.KmipServer._setup_logging')
|
||||||
def test_as_context_manager(self, logging_mock):
|
def test_as_context_manager(self, logging_mock, engine_mock):
|
||||||
"""
|
"""
|
||||||
Test that the right methods are called when the KmipServer is used
|
Test that the right methods are called when the KmipServer is used
|
||||||
as a context manager.
|
as a context manager.
|
||||||
@ -464,7 +486,8 @@ class TestKmipServer(testtools.TestCase):
|
|||||||
s = server.KmipServer(
|
s = server.KmipServer(
|
||||||
hostname='127.0.0.1',
|
hostname='127.0.0.1',
|
||||||
port=5696,
|
port=5696,
|
||||||
config_path=None
|
config_path=None,
|
||||||
|
policy_path=None
|
||||||
)
|
)
|
||||||
s._logger = mock.MagicMock()
|
s._logger = mock.MagicMock()
|
||||||
s.start = mock.MagicMock()
|
s.start = mock.MagicMock()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user