Skip to content

Commit

Permalink
Adding dynamic operation policy loading to the KMIP server
Browse files Browse the repository at this point in the history
This change adds support for dynamic operation policy loading.
The server config file now supports a 'policy_path' option that
points to a filesystem directory. Each file in the directory
should contain a JSON policy object. The KMIP server will scan
this directory and attempt to load all valid policies it finds.
The results of this process will be logged.
  • Loading branch information
PeterHamilton committed Nov 10, 2016
1 parent e0b0a5c commit 4a3769e
Show file tree
Hide file tree
Showing 8 changed files with 578 additions and 23 deletions.
62 changes: 62 additions & 0 deletions kmip/core/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,71 @@
# License for the specific language governing permissions and limitations
# under the License.

import json
import six

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 = {
'default': {
enums.ObjectType.CERTIFICATE: {
Expand Down
26 changes: 24 additions & 2 deletions kmip/services/server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def __init__(self):
'certificate_path',
'key_path',
'ca_path',
'auth_suite'
'auth_suite',
'policy_path'
]

def set_setting(self, setting, value):
Expand Down Expand Up @@ -75,8 +76,10 @@ def set_setting(self, setting, value):
self._set_key_path(value)
elif setting == 'ca_path':
self._set_ca_path(value)
else:
elif setting == 'auth_suite':
self._set_auth_suite(value)
else:
self._set_policy_path(value)

def load_settings(self, path):
"""
Expand Down Expand Up @@ -141,6 +144,8 @@ def _parse_settings(self, parser):
self._set_ca_path(parser.get('server', 'ca_path'))
if parser.has_option('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):
if isinstance(value, six.string_types):
Expand Down Expand Up @@ -224,3 +229,20 @@ def _set_auth_suite(self, value):
)
else:
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."
)
72 changes: 69 additions & 3 deletions kmip/services/server/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.

import copy
import logging
import os
import six
import sqlalchemy

Expand Down Expand Up @@ -42,7 +44,7 @@

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 objects
Expand Down Expand Up @@ -77,9 +79,14 @@ class KmipEngine(object):
* Cryptographic usage mask enforcement per object type
"""

def __init__(self):
def __init__(self, policy_path=None):
"""
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')

Expand Down Expand Up @@ -118,10 +125,69 @@ def __init__(self):
}

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

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):
return ''.join([x.capitalize() for x in e.name.split('_')])

Expand Down
34 changes: 30 additions & 4 deletions kmip/services/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def __init__(
ca_path=None,
auth_suite=None,
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.
Expand Down Expand Up @@ -91,6 +93,9 @@ def __init__(
log_path (string): The path to the base server log file
(e.g., '/var/log/pykmip/server.log'). Optional, defaults to
'/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._setup_logging(log_path)
Expand All @@ -103,15 +108,18 @@ def __init__(
certificate_path,
key_path,
ca_path,
auth_suite
auth_suite,
policy_path
)

if self.config.settings.get('auth_suite') == 'TLS1.2':
self.auth_suite = auth.TLS12AuthenticationSuite()
else:
self.auth_suite = auth.BasicAuthenticationSuite()

self._engine = engine.KmipEngine()
self._engine = engine.KmipEngine(
self.config.settings.get('policy_path')
)
self._session_id = 1
self._is_serving = False

Expand Down Expand Up @@ -144,7 +152,9 @@ def _setup_configuration(
certificate_path=None,
key_path=None,
ca_path=None,
auth_suite=None):
auth_suite=None,
policy_path=None
):
if path:
self.config.load_settings(path)

Expand All @@ -160,6 +170,8 @@ def _setup_configuration(
self.config.set_setting('ca_path', ca_path)
if auth_suite:
self.config.set_setting('auth_suite', auth_suite)
if policy_path:
self.config.set_setting('policy_path', policy_path)

def start(self):
"""
Expand Down Expand Up @@ -447,6 +459,18 @@ def build_argument_parser():
"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

Expand All @@ -473,6 +497,8 @@ def main(args=None):
kwargs['config_path'] = opts.config_path
if opts.log_path:
kwargs['log_path'] = opts.log_path
if opts.policy_path:
kwargs['policy_path'] = opts.policy_path

# Create and start the server.
s = KmipServer(**kwargs)
Expand Down

0 comments on commit 4a3769e

Please sign in to comment.