You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ch...@apache.org on 2016/01/11 23:07:18 UTC
qpid-dispatch git commit: Rename policy.py to policy_local.py better
reflecting file contents. In docs: Fix Open arrows; Add work-to-do diagram
Repository: qpid-dispatch
Updated Branches:
refs/heads/crolke-DISPATCH-188-1 13faacc61 -> e76044689
Rename policy.py to policy_local.py better reflecting file contents.
In docs: Fix Open arrows; Add work-to-do diagram
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/e7604468
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/e7604468
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/e7604468
Branch: refs/heads/crolke-DISPATCH-188-1
Commit: e76044689e14a9c0881faa278eb318ded7b6d933
Parents: 13faacc
Author: Chuck Rolke <cr...@redhat.com>
Authored: Mon Jan 11 17:06:03 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Mon Jan 11 17:06:03 2016 -0500
----------------------------------------------------------------------
doc/notes/qdr-policy-01.odt | Bin 36576 -> 36045 bytes
doc/notes/qdr-policy-01.pdf | Bin 103325 -> 101150 bytes
doc/notes/qdr-policy-diagrams-01.odg | Bin 29559 -> 32800 bytes
.../qpid_dispatch_internal/management/policy.py | 701 -------------------
.../management/policy_local.py | 701 +++++++++++++++++++
tests/system_tests_policy.py | 2 +-
6 files changed, 702 insertions(+), 702 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e7604468/doc/notes/qdr-policy-01.odt
----------------------------------------------------------------------
diff --git a/doc/notes/qdr-policy-01.odt b/doc/notes/qdr-policy-01.odt
index 43c1aaf..2a71d75 100644
Binary files a/doc/notes/qdr-policy-01.odt and b/doc/notes/qdr-policy-01.odt differ
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e7604468/doc/notes/qdr-policy-01.pdf
----------------------------------------------------------------------
diff --git a/doc/notes/qdr-policy-01.pdf b/doc/notes/qdr-policy-01.pdf
index 5bccd5b..3931053 100644
Binary files a/doc/notes/qdr-policy-01.pdf and b/doc/notes/qdr-policy-01.pdf differ
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e7604468/doc/notes/qdr-policy-diagrams-01.odg
----------------------------------------------------------------------
diff --git a/doc/notes/qdr-policy-diagrams-01.odg b/doc/notes/qdr-policy-diagrams-01.odg
index ca6f736..5521fb8 100644
Binary files a/doc/notes/qdr-policy-diagrams-01.odg and b/doc/notes/qdr-policy-diagrams-01.odg differ
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e7604468/python/qpid_dispatch_internal/management/policy.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy.py b/python/qpid_dispatch_internal/management/policy.py
deleted file mode 100644
index b9a91b4..0000000
--- a/python/qpid_dispatch_internal/management/policy.py
+++ /dev/null
@@ -1,701 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you 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 sys, os
-import json
-import optparse
-from policy_util import PolicyError, HostStruct, HostAddr, PolicyAppConnectionMgr
-import pdb #; pdb.set_trace()
-
-
-
-"""
-Entity implementing the business logic of user connection/access policy.
-
-Policy is represented several ways:
-
-1. External : json format file
-2. Internal : dictionary
-
-Internal Policy:
-----------------
-
- data['photoserver'] =
- {'groups': {'paidsubscribers': ['p1', 'p2'],
- 'users': ['u1', 'u2']},
- 'policyVersion': 1}
-
-"""
-
-#
-#
-class PolicyKeys():
- # Policy key words
- KW_POLICY_VERSION = "policyVersion"
- KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
- KW_CONNECTION_ORIGINS = "connectionOrigins"
- KW_CONNECTION_POLICY = "connectionPolicy"
- KW_MAXCONN = "maxConnections"
- KW_MAXCONNPERHOST = "maxConnPerHost"
- KW_MAXCONNPERUSER = "maxConnPerUser"
- KW_POLICIES = "policies"
- KW_GROUPS = "groups"
-
- SETTING_MAX_FRAME_SIZE = "maxFrameSize"
- SETTING_MAX_MESSAGE_SIZE = "maxMessageSize"
- SETTING_MAX_RECEIVERS = "maxReceivers"
- SETTING_MAX_SENDERS = "maxSenders"
- SETTING_MAX_SESSION_WINDOW = "maxSessionWindow"
- SETTING_MAX_SESSIONS = "maxSessions"
- SETTING_ALLOW_ANONYMOUS_SENDER = "allowAnonymousSender"
- SETTING_ALLOW_DYNAMIC_SRC = "allowDynamicSrc"
- SETTING_SOURCES = "sources"
- SETTING_TARGETS = "targets"
-#
-#
-class PolicyCompiler():
- """
- Validate incoming configuration for legal schema.
- - Warn about section options that go unused.
- - Disallow negative max connection numbers.
- - Check that connectionOrigins resolve to IP hosts
- """
-
- allowed_options = [
- PolicyKeys.KW_POLICY_VERSION,
- PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT,
- PolicyKeys.KW_CONNECTION_ORIGINS,
- PolicyKeys.KW_CONNECTION_POLICY,
- PolicyKeys.KW_MAXCONN,
- PolicyKeys.KW_MAXCONNPERHOST,
- PolicyKeys.KW_MAXCONNPERUSER,
- PolicyKeys.KW_POLICIES,
- PolicyKeys.KW_GROUPS
- ]
-
-
- def __init__(self):
- """
- Create a validator
- """
- pass
-
-
- def validateNumber(self, val, v_min, v_max, errors):
- """
- Range check a numeric int policy value
- @param[in] val policy value to check
- @param[in] v_min minumum value
- @param[in] v_max maximum value. zero disables check
- @param[out] errors failure message
- @return v_min <= val <= v_max
- """
- error = ""
- try:
- v_int = int(val)
- except Exception, e:
- errors.append("Value '%s' does not resolve to an integer." % val)
- return False
- if v_int < v_min:
- errors.append("Value '%s' is below minimum '%s'." % (val, v_min))
- return False
- if v_max > 0 and v_int > v_max:
- errors.append("Value '%s' is above maximum '%s'." % (val, v_max))
- return False
- return True
-
-
- def compiler_origins(self, name, submap, warnings, errors):
- """
- Handle an origins submap.
- Each origin value is verified. On a successful run the submap
- is replaced parsed lists of HostAddr objects.
- @param[in] name application name
- @param[in,out] submap user input origin list as text strings
- modified in place to be list of HostAddr objects
- @param[out] warnings nonfatal irregularities observed
- @param[out] errors descriptions of failure
- @return - origins is usable. If True then warnings[] may contain useful
- information about fields that are ignored. If False then
- warnings[] may contain info and errors[0] will hold the
- description of why the origin was rejected.
- """
- key = PolicyKeys.KW_CONNECTION_ORIGINS
- newmap = {}
- for coname in submap:
- try:
- olist = submap[coname]
- if not type(olist) is list:
- errors.append("Application '%s' option '%s' connectionOption '%s' must be type 'list' but is '%s'." %
- (name, key, coname, type(olist)))
- return False
- newmap[coname] = []
- for co in olist:
- coha = HostAddr(co)
- newmap[coname].append(coha)
- except Exception, e:
- errors.append("Application '%s' option '%s' connectionOption '%s' failed to translate: '%s'." %
- (name, key, coname, e))
- return False
- submap.update(newmap)
- return True
-
-
- def compiler_policies(self, name, submap, warnings, errors):
- """
- Handle a policies submap
- Validates policy only returning warnings and errors. submap is unchanged
- @param[in] name application name
- @param[in] submap user input policy submap
- @param[out] warnings nonfatal irregularities observed
- @param[out] errors descriptions of failure
- @return - policy is usable. If True then warnings[] may contain useful
- information about fields that are ignored. If False then
- warnings[] may contain info and errors[0] will hold the
- description of why the policy was rejected.
- """
- key = PolicyKeys.KW_POLICIES
- cerror = []
- for pname in submap:
- for setting in submap[pname]:
- sval = submap[pname][setting]
- if setting in [PolicyKeys.SETTING_MAX_FRAME_SIZE,
- PolicyKeys.SETTING_MAX_MESSAGE_SIZE,
- PolicyKeys.SETTING_MAX_RECEIVERS,
- PolicyKeys.SETTING_MAX_SENDERS,
- PolicyKeys.SETTING_MAX_SESSION_WINDOW,
- PolicyKeys.SETTING_MAX_SESSIONS
- ]:
- if not self.validateNumber(sval, 0, 0, cerror):
- errors.append("Application '%s' option '%s' policy '%s' setting '%s' has error '%s'." %
- (name, key, pname, setting, cerror[0]))
- return False
- elif setting in [PolicyKeys.SETTING_ALLOW_ANONYMOUS_SENDER,
- PolicyKeys.SETTING_ALLOW_DYNAMIC_SRC
- ]:
- if not type(sval) is bool:
- errors.append("Application '%s' option '%s' policy '%s' setting '%s' has illegal boolean value '%s'." %
- (name, key, pname, setting, sval))
- return False
- elif setting in [PolicyKeys.SETTING_SOURCES,
- PolicyKeys.SETTING_TARGETS
- ]:
- if not type(sval) is list:
- errors.append("Application '%s' option '%s' policy '%s' setting '%s' must be type 'list' but is '%s'." %
- (name, key, pname, setting, type(sval)))
- return False
- # deduplicate address lists
- sval = list(set(sval))
- submap[pname][setting] = sval
- else:
- warnings.append("Application '%s' option '%s' policy '%s' setting '%s' is ignored." %
- (name, key, pname, setting))
- return True
-
-
- def compile(self, name, policy_in, policy_out, warnings, errors):
- """
- Compile a schema from processed json format to Internal format.
- @param[in] name application name
- @param[in] policy_in raw policy to be validated
- @param[out] policy_out validated Internal format
- @param[out] warnings nonfatal irregularities observed
- @param[out] errors descriptions of failure
- @return - policy is usable. If True then warnings[] may contain useful
- information about fields that are ignored. If False then
- warnings[] may contain info and errors[0] will hold the
- description of why the policy was rejected.
- """
- cerror = []
- # validate the options
- for key, val in policy_in.iteritems():
- if key not in self.allowed_options:
- warnings.append("Application '%s' option '%s' is ignored." %
- (name, key))
- if key == PolicyKeys.KW_POLICY_VERSION:
- if not self.validateNumber(val, 0, 0, cerror):
- errors.append("Application '%s' option '%s' must resolve to a positive integer: '%s'." %
- (name, key, cerror[0]))
- return False
- policy_out[key] = val
- elif key in [PolicyKeys.KW_MAXCONN,
- PolicyKeys.KW_MAXCONNPERHOST,
- PolicyKeys.KW_MAXCONNPERUSER
- ]:
- if not self.validateNumber(val, 0, 65535, cerror):
- msg = ("Application '%s' option '%s' has error '%s'." %
- (name, key, cerror[0]))
- errors.append(msg)
- return False
- policy_out[key] = val
- elif key in [PolicyKeys.KW_CONNECTION_ORIGINS,
- PolicyKeys.KW_CONNECTION_POLICY,
- PolicyKeys.KW_POLICIES,
- PolicyKeys.KW_GROUPS
- ]:
- try:
- if not type(val) is dict:
- errors.append("Application '%s' option '%s' must be of type 'dict' but is '%s'" %
- (name, key, type(val)))
- return False
- if key == PolicyKeys.KW_CONNECTION_ORIGINS:
- if not self.compiler_origins(name, val, warnings, errors):
- return False
- elif key == PolicyKeys.KW_POLICIES:
- if not self.compiler_policies(name, val, warnings, errors):
- return False
- else:
- # deduplicate connectionPolicy and groups lists
- for k,v in val.iteritems():
- v = list(set(v))
- val[k] = v
- policy_out[key] = val
- except Exception, e:
- errors.append("Application '%s' option '%s' error processing map: %s" %
- (name, key, e))
- return False
- return True
-
-class PolicyLocal():
- """
- The policy database.
- """
-
- def __init__(self, folder=""):
- """
- Create instance
- @params folder: relative path from __file__ to conf file folder
- """
- self.policydb = {}
- self.lookup_cache = {}
- self.stats = {}
- self.folder = folder
- self.policy_compiler = PolicyCompiler()
- if not folder == "":
- self.policy_io_read_files()
-
- #
- # Policy file I/O
- #
- def policy_io_read_files(self):
- """
- Read all conf files and create the policies they contain.
- """
- apath = os.path.abspath(os.path.dirname(__file__))
- apath = os.path.join(apath, self.folder)
- for i in os.listdir(apath):
- if i.endswith(".json"):
- self.policy_io_read_file(os.path.join(apath, i))
-
- def policy_io_read_file(self, fn):
- """
- Read a policy config file.
- Validate each policy and commit to policy database.
- @param fn: absolute path to file
- """
- try:
- with open(fn) as json_file:
- cp = json.load(json_file)
-
- except Exception, e:
- raise PolicyError(
- "Error processing policy configuration file '%s' : %s" % (fn, e))
- newpolicies = {}
- for app_name, app_policy in cp.iteritems():
- warnings = []
- diag = []
- candidate = {}
- if not self.policy_compiler.compile(app_name, app_policy, candidate, warnings, diag):
- msg = "Policy file '%s' is invalid: %s" % (fn, diag[0])
- raise PolicyError( msg )
- if len(warnings) > 0:
- print ("LogMe: Policy file '%s' application '%s' has warnings: %s" %
- (fn, app_name, warnings))
- newpolicies[app_name] = candidate
- # Log a warning if policy from one config file replaces another.
- # TODO: Should this throw? Do we increment the policy version per load?
- for c in newpolicies:
- c_ver = 0
- e_ver = 0
- c_pol = newpolicies[c]
- if PolicyKeys.KW_POLICY_VERSION in c_pol:
- c_ver = int(c_pol[PolicyKeys.KW_POLICY_VERSION])
- if c in self.policydb:
- e_pol = self.policydb[c]
- if PolicyKeys.KW_POLICY_VERSION in e_pol:
- e_ver = int(e_pol[PolicyKeys.KW_POLICY_VERSION])
- if c_ver < e_ver:
- kw = "downgrades"
- elif c_ver == e_ver:
- kw = "replaces"
- else:
- kw = "upgrades"
- msg = ("LogMe: WARNING Policy file '%s' application '%s' policy version '%s' %s existing policy version '%s'." %
- (fn, c, c_ver, kw, e_ver))
- print msg
- for c in newpolicies:
- c_pol = newpolicies[c]
- c_max = 0
- c_max_u = 0
- c_max_h = 0
- if PolicyKeys.KW_MAXCONN in c_pol:
- c_max = c_pol[PolicyKeys.KW_MAXCONN]
- if PolicyKeys.KW_MAXCONNPERUSER in c_pol:
- c_max_u = c_pol[PolicyKeys.KW_MAXCONNPERUSER]
- if PolicyKeys.KW_MAXCONNPERHOST in c_pol:
- c_max_h = c_pol[PolicyKeys.KW_MAXCONNPERHOST]
- if c in self.stats:
- self.stats[c].update(c_max, c_max_u, c_max_h)
- else:
- self.stats[c] = PolicyAppConnectionMgr(c_max, c_max_u, c_max_h)
- self.policydb.update(newpolicies)
-
-
- #
- # Service interfaces
- #
- def policy_create(self, name, policy):
- """
- Create named policy
- @param name: application name
- @param policy: policy data in raw user format
- """
- warnings = []
- diag = []
- candidate = {}
- result = self.policy_compiler.compile(name, policy, candidate, warnings, diag)
- if not result:
- raise PolicyError( "Policy '%s' is invalid: %s" % (name, diag[0]) )
- if len(warnings) > 0:
- print ("LogMe: Application '%s' has warnings: %s" %
- (name, warnings))
- self.policydb[name] = candidate
- # TODO: Create stats
-
- def policy_read(self, name):
- """
- Read policy for named application
- @param[in] name application name
- @return policy data in raw user format
- """
- return self.policydb[name]
-
- def policy_update(self, name, policy):
- """
- Update named policy
- @param[in] name application name
- @param[in] policy data in raw user input
- """
- if not name in self.policydb:
- raise PolicyError("Policy '%s' does not exist" % name)
- self.policy_create(name, policy)
-
- def policy_delete(self, name):
- """
- Delete named policy
- @param[in] name application name
- """
- if not name in self.policydb:
- raise PolicyError("Policy '%s' does not exist" % name)
- del self.policydb[name]
-
- #
- # db enumerator
- #
- def policy_db_get_names(self):
- """
- Return a list of application names in this policy
- """
- return self.policydb.keys()
-
-
- #
- # Runtime query interface
- #
- def policy_aggregate_limits(self, upolicy, policy, settingname):
- """
- Force a max count value into user policy
- param[in,out] upolicy user policy receiving aggregations
- param[in] policy Internal policy holding settings to be aggregated
- param[in] settingname setting of interest
- """
- if settingname in policy:
- upolicy[settingname] = policy[settingname]
-
- def policy_aggregate_policy_int(self, upolicy, policy, groups, settingname):
- """
- Pull int out of policy.policies[group] and install into upolicy.
- Integers are set to max(new, existing)
- param[in,out] upolicy user policy receiving aggregations
- param[in] policy Internal policy holding settings to be aggregated
- param[in] settingname setting of interest
- """
- if not PolicyKeys.KW_POLICIES in policy:
- return
- policies = policy[PolicyKeys.KW_POLICIES]
- for group in groups:
- if group in policies:
- rpol = policies[group]
- if settingname in rpol:
- sp = rpol[settingname]
- if settingname in upolicy:
- up = upolicy[settingname]
- if sp > up:
- # policy bumps up user setting
- upolicy[settingname] = sp
- else:
- # user policy is already better
- pass
- else:
- # user policy doesn't have setting so force it
- upolicy[settingname] = sp
- else:
- # no setting of this name in the group's policy
- pass
- else:
- # no policy for this group
- pass
-
- def policy_aggregate_policy_bool(self, upolicy, policy, groups, settingname):
- """
- Pull bool out of policy and install into upolicy if true
- param[in,out] upolicy user policy receiving aggregations
- param[in] policy Internal policy holding settings to be aggregated
- param[in] settingname setting of interest
- """
- if not PolicyKeys.KW_POLICIES in policy:
- return
- policies = policy[PolicyKeys.KW_POLICIES]
- for group in groups:
- if group in policies:
- rpol = policies[group]
- if settingname in rpol:
- if rpol[settingname]:
- upolicy[settingname] = True
- else:
- # no setting of this name in the group's policy
- pass
- else:
- # no policy for this group
- pass
-
- def policy_aggregate_policy_list(self, upolicy, policy, groups, settingname):
- """
- Pull list out of policy and append into upolicy
- param[in,out] upolicy user policy receiving aggregations
- param[in] policy Internal policy holding settings to be aggregated
- param[in] settingname setting of interest
- """
- if not PolicyKeys.KW_POLICIES in policy:
- return
- policies = policy[PolicyKeys.KW_POLICIES]
- for group in groups:
- if group in policies:
- rpol = policies[group]
- if settingname in rpol:
- sp = rpol[settingname]
- if settingname in upolicy:
- upolicy[settingname].extend( sp )
- upolicy[settingname] = list(set(upolicy[settingname]))
- else:
- # user policy doesn't have setting so force it
- upolicy[settingname] = sp
- else:
- # no setting of this name in the group's policy
- pass
- else:
- # no policy for this group
- pass
-
- def policy_lookup_settings(self, user, host, app, upolicy):
- """
- Determine if a user on host accessing app through AMQP Open is allowed
- according to the policy access rules.
- If allowed then return the policy settings.
- @param[in] user connection authId
- @param[in] host connection remote host numeric IP address
- @param[in] app application user is accessing
- @param[out] upolicy dict holding connection and policy values
- @return if allowed by policy
- # Note: the upolicy output is a non-nested dict with settings of interest
- # TODO: figure out decent defaults for upolicy settings that are undefined
- """
- try:
- lookup_id = user + "|" + host + "|" + app
- if lookup_id in self.lookup_cache:
- upolicy.update( self.lookup_cache[lookup_id] )
- return True
-
- settings = self.policydb[app]
- # User allowed to connect from host?
- allowed = False
- restricted = False
- uhs = HostStruct(host)
- ugroups = []
- if PolicyKeys.KW_GROUPS in settings:
- for r in settings[PolicyKeys.KW_GROUPS]:
- if user in settings[PolicyKeys.KW_GROUPS][r]:
- restricted = True
- ugroups.append(r)
- uorigins = []
- if PolicyKeys.KW_CONNECTION_POLICY in settings:
- for ur in ugroups:
- if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
- uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
- if PolicyKeys.KW_CONNECTION_ORIGINS in settings:
- for co in settings[PolicyKeys.KW_CONNECTION_ORIGINS]:
- if co in uorigins:
- for cohost in settings[PolicyKeys.KW_CONNECTION_ORIGINS][co]:
- if cohost.match_bin(uhs):
- allowed = True
- break
- if allowed:
- break
- if not allowed and not restricted:
- if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
- allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
- if not allowed:
- return False
- # Return connection limits and aggregation of group settings
- ugroups.append(user) # user groups also includes username directly
- self.policy_aggregate_limits (upolicy, settings, PolicyKeys.KW_POLICY_VERSION)
- self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_FRAME_SIZE)
- self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_MESSAGE_SIZE)
- self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SESSION_WINDOW)
- self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SESSIONS)
- self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SENDERS)
- self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_RECEIVERS)
- self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.SETTING_ALLOW_DYNAMIC_SRC)
- self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.SETTING_ALLOW_ANONYMOUS_SENDER)
- self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.SETTING_SOURCES)
- self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.SETTING_TARGETS)
- c_upolicy = {}
- c_upolicy.update(upolicy)
- self.lookup_cache[lookup_id] = c_upolicy
- return True
- except Exception, e:
- #print str(e)
- #pdb.set_trace()
- return False
-
- def policy_lookup(self, conn_id, user, host, app, upolicy):
- """
- Determine if a user on host accessing app through AMQP Open is allowed:
- - verify to the policy access rules.
- - track user/host connection limits
- If allowed then return the policy settings.
- @param[in] conn_id unique connection identifier
- @param[in] user connection authId
- @param[in] host connection remote host numeric IP address
- @param[in] app application user is accessing
- @param[out] upolicy dict holding connection and policy values
- @return if allowed by policy
- # Note: the upolicy output is a non-nested dict with settings of interest
- # TODO: figure out decent defaults for upolicy settings that are undefined
- """
- if not self.policy_lookup_settings(user, host, app, upolicy):
- # TODO: print ("LogMe: connection denied by connection access rules")
- return False
- assert(app in self.stats)
- diags = []
- if not self.stats[app].can_connect(conn_id, user, host, diags):
- # TODO: print ("LogMe: connection denied by connection count limits: %s" % diags)
- return False
- return True
-
-
-#
-# HACK ALERT: Temporary
-# Functions related to main
-#
-class ExitStatus(Exception):
- """Raised if a command wants a non-0 exit status from the script"""
- def __init__(self, status): self.status = status
-
-def main_except(argv):
-
- usage = "usage: %prog [options]\nRead and print all conf files in a folder."
- parser = optparse.OptionParser(usage=usage)
- parser.set_defaults(folder="../../../tests/policy-1")
- parser.add_option("-f", "--folder", action="store", type="string", dest="folder",
- help="Use named folder instead of policy-1")
- parser.add_option("-d", "--dump", action="store_true", dest="dump",
- help="Dump policy details")
-
- (options, args) = parser.parse_args()
-
- policy = PolicyLocal(options.folder)
-
- print("policy names: %s" % policy.policy_db_get_names())
-
- if not options.dump:
- return
-
- # Exercise a few functions
- # Empty policy
- policy2 = PolicyLocal()
-
- print("Print some Policy details:")
- for pname in policy.policy_db_get_names():
- print("policy : %s" % pname)
- p = ("%s" % policy.policy_read(pname))
- print(p.replace('\\n', '\n'))
-
- # Lookups
- upolicy = {}
- # pdb.set_trace()
- res1 = policy.policy_lookup('192.168.100.5:33332', 'zeke', '192.168.100.5', 'photoserver', upolicy)
- print "\nLookup zeke from 192.168.100.5. Expect true and maxFrameSize 44444. Expecting True, result is %s" % res1
- print "\nResulting policy is: %s" % upolicy
- # Hit the cache
- upolicy2 = {}
- res2 = policy.policy_lookup('192.168.100.5:33335', 'zeke', '192.168.100.5', 'photoserver', upolicy2)
- # Print the stats
- print "\npolicy stats: %s" % policy.stats
-
- upolicy = {}
- res3 = policy.policy_lookup('72.135.2.9:33333', 'ellen', '72.135.2.9', 'photoserver', upolicy)
- print "\nLookup ellen from 72.135.2.9. Expect true and maxFrameSize 666666. Result is %s" % res3
- print "Resulting policy is: %s" % upolicy
-
- upolicy = {}
- res4 = policy2.policy_lookup('72.135.2.9:33334', 'ellen', '72.135.2.9', 'photoserver', upolicy)
- print "\nLookup policy2 ellen from 72.135.2.9. Expect false. Result is %s" % res4
-
- if not (res1 and res2 and res3 and not res4):
- print "Tests FAIL"
- else:
- print "Tests PASS"
-
-def main(argv):
- try:
- main_except(argv)
- return 0
- except ExitStatus, e:
- return e.status
- except Exception, e:
- print "%s: %s"%(type(e).__name__, e)
- return 1
-
-if __name__ == "__main__":
- sys.exit(main(sys.argv))
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e7604468/python/qpid_dispatch_internal/management/policy_local.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy_local.py b/python/qpid_dispatch_internal/management/policy_local.py
new file mode 100644
index 0000000..b9a91b4
--- /dev/null
+++ b/python/qpid_dispatch_internal/management/policy_local.py
@@ -0,0 +1,701 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 sys, os
+import json
+import optparse
+from policy_util import PolicyError, HostStruct, HostAddr, PolicyAppConnectionMgr
+import pdb #; pdb.set_trace()
+
+
+
+"""
+Entity implementing the business logic of user connection/access policy.
+
+Policy is represented several ways:
+
+1. External : json format file
+2. Internal : dictionary
+
+Internal Policy:
+----------------
+
+ data['photoserver'] =
+ {'groups': {'paidsubscribers': ['p1', 'p2'],
+ 'users': ['u1', 'u2']},
+ 'policyVersion': 1}
+
+"""
+
+#
+#
+class PolicyKeys():
+ # Policy key words
+ KW_POLICY_VERSION = "policyVersion"
+ KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
+ KW_CONNECTION_ORIGINS = "connectionOrigins"
+ KW_CONNECTION_POLICY = "connectionPolicy"
+ KW_MAXCONN = "maxConnections"
+ KW_MAXCONNPERHOST = "maxConnPerHost"
+ KW_MAXCONNPERUSER = "maxConnPerUser"
+ KW_POLICIES = "policies"
+ KW_GROUPS = "groups"
+
+ SETTING_MAX_FRAME_SIZE = "maxFrameSize"
+ SETTING_MAX_MESSAGE_SIZE = "maxMessageSize"
+ SETTING_MAX_RECEIVERS = "maxReceivers"
+ SETTING_MAX_SENDERS = "maxSenders"
+ SETTING_MAX_SESSION_WINDOW = "maxSessionWindow"
+ SETTING_MAX_SESSIONS = "maxSessions"
+ SETTING_ALLOW_ANONYMOUS_SENDER = "allowAnonymousSender"
+ SETTING_ALLOW_DYNAMIC_SRC = "allowDynamicSrc"
+ SETTING_SOURCES = "sources"
+ SETTING_TARGETS = "targets"
+#
+#
+class PolicyCompiler():
+ """
+ Validate incoming configuration for legal schema.
+ - Warn about section options that go unused.
+ - Disallow negative max connection numbers.
+ - Check that connectionOrigins resolve to IP hosts
+ """
+
+ allowed_options = [
+ PolicyKeys.KW_POLICY_VERSION,
+ PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT,
+ PolicyKeys.KW_CONNECTION_ORIGINS,
+ PolicyKeys.KW_CONNECTION_POLICY,
+ PolicyKeys.KW_MAXCONN,
+ PolicyKeys.KW_MAXCONNPERHOST,
+ PolicyKeys.KW_MAXCONNPERUSER,
+ PolicyKeys.KW_POLICIES,
+ PolicyKeys.KW_GROUPS
+ ]
+
+
+ def __init__(self):
+ """
+ Create a validator
+ """
+ pass
+
+
+ def validateNumber(self, val, v_min, v_max, errors):
+ """
+ Range check a numeric int policy value
+ @param[in] val policy value to check
+ @param[in] v_min minumum value
+ @param[in] v_max maximum value. zero disables check
+ @param[out] errors failure message
+ @return v_min <= val <= v_max
+ """
+ error = ""
+ try:
+ v_int = int(val)
+ except Exception, e:
+ errors.append("Value '%s' does not resolve to an integer." % val)
+ return False
+ if v_int < v_min:
+ errors.append("Value '%s' is below minimum '%s'." % (val, v_min))
+ return False
+ if v_max > 0 and v_int > v_max:
+ errors.append("Value '%s' is above maximum '%s'." % (val, v_max))
+ return False
+ return True
+
+
+ def compiler_origins(self, name, submap, warnings, errors):
+ """
+ Handle an origins submap.
+ Each origin value is verified. On a successful run the submap
+ is replaced parsed lists of HostAddr objects.
+ @param[in] name application name
+ @param[in,out] submap user input origin list as text strings
+ modified in place to be list of HostAddr objects
+ @param[out] warnings nonfatal irregularities observed
+ @param[out] errors descriptions of failure
+ @return - origins is usable. If True then warnings[] may contain useful
+ information about fields that are ignored. If False then
+ warnings[] may contain info and errors[0] will hold the
+ description of why the origin was rejected.
+ """
+ key = PolicyKeys.KW_CONNECTION_ORIGINS
+ newmap = {}
+ for coname in submap:
+ try:
+ olist = submap[coname]
+ if not type(olist) is list:
+ errors.append("Application '%s' option '%s' connectionOption '%s' must be type 'list' but is '%s'." %
+ (name, key, coname, type(olist)))
+ return False
+ newmap[coname] = []
+ for co in olist:
+ coha = HostAddr(co)
+ newmap[coname].append(coha)
+ except Exception, e:
+ errors.append("Application '%s' option '%s' connectionOption '%s' failed to translate: '%s'." %
+ (name, key, coname, e))
+ return False
+ submap.update(newmap)
+ return True
+
+
+ def compiler_policies(self, name, submap, warnings, errors):
+ """
+ Handle a policies submap
+ Validates policy only returning warnings and errors. submap is unchanged
+ @param[in] name application name
+ @param[in] submap user input policy submap
+ @param[out] warnings nonfatal irregularities observed
+ @param[out] errors descriptions of failure
+ @return - policy is usable. If True then warnings[] may contain useful
+ information about fields that are ignored. If False then
+ warnings[] may contain info and errors[0] will hold the
+ description of why the policy was rejected.
+ """
+ key = PolicyKeys.KW_POLICIES
+ cerror = []
+ for pname in submap:
+ for setting in submap[pname]:
+ sval = submap[pname][setting]
+ if setting in [PolicyKeys.SETTING_MAX_FRAME_SIZE,
+ PolicyKeys.SETTING_MAX_MESSAGE_SIZE,
+ PolicyKeys.SETTING_MAX_RECEIVERS,
+ PolicyKeys.SETTING_MAX_SENDERS,
+ PolicyKeys.SETTING_MAX_SESSION_WINDOW,
+ PolicyKeys.SETTING_MAX_SESSIONS
+ ]:
+ if not self.validateNumber(sval, 0, 0, cerror):
+ errors.append("Application '%s' option '%s' policy '%s' setting '%s' has error '%s'." %
+ (name, key, pname, setting, cerror[0]))
+ return False
+ elif setting in [PolicyKeys.SETTING_ALLOW_ANONYMOUS_SENDER,
+ PolicyKeys.SETTING_ALLOW_DYNAMIC_SRC
+ ]:
+ if not type(sval) is bool:
+ errors.append("Application '%s' option '%s' policy '%s' setting '%s' has illegal boolean value '%s'." %
+ (name, key, pname, setting, sval))
+ return False
+ elif setting in [PolicyKeys.SETTING_SOURCES,
+ PolicyKeys.SETTING_TARGETS
+ ]:
+ if not type(sval) is list:
+ errors.append("Application '%s' option '%s' policy '%s' setting '%s' must be type 'list' but is '%s'." %
+ (name, key, pname, setting, type(sval)))
+ return False
+ # deduplicate address lists
+ sval = list(set(sval))
+ submap[pname][setting] = sval
+ else:
+ warnings.append("Application '%s' option '%s' policy '%s' setting '%s' is ignored." %
+ (name, key, pname, setting))
+ return True
+
+
+ def compile(self, name, policy_in, policy_out, warnings, errors):
+ """
+ Compile a schema from processed json format to Internal format.
+ @param[in] name application name
+ @param[in] policy_in raw policy to be validated
+ @param[out] policy_out validated Internal format
+ @param[out] warnings nonfatal irregularities observed
+ @param[out] errors descriptions of failure
+ @return - policy is usable. If True then warnings[] may contain useful
+ information about fields that are ignored. If False then
+ warnings[] may contain info and errors[0] will hold the
+ description of why the policy was rejected.
+ """
+ cerror = []
+ # validate the options
+ for key, val in policy_in.iteritems():
+ if key not in self.allowed_options:
+ warnings.append("Application '%s' option '%s' is ignored." %
+ (name, key))
+ if key == PolicyKeys.KW_POLICY_VERSION:
+ if not self.validateNumber(val, 0, 0, cerror):
+ errors.append("Application '%s' option '%s' must resolve to a positive integer: '%s'." %
+ (name, key, cerror[0]))
+ return False
+ policy_out[key] = val
+ elif key in [PolicyKeys.KW_MAXCONN,
+ PolicyKeys.KW_MAXCONNPERHOST,
+ PolicyKeys.KW_MAXCONNPERUSER
+ ]:
+ if not self.validateNumber(val, 0, 65535, cerror):
+ msg = ("Application '%s' option '%s' has error '%s'." %
+ (name, key, cerror[0]))
+ errors.append(msg)
+ return False
+ policy_out[key] = val
+ elif key in [PolicyKeys.KW_CONNECTION_ORIGINS,
+ PolicyKeys.KW_CONNECTION_POLICY,
+ PolicyKeys.KW_POLICIES,
+ PolicyKeys.KW_GROUPS
+ ]:
+ try:
+ if not type(val) is dict:
+ errors.append("Application '%s' option '%s' must be of type 'dict' but is '%s'" %
+ (name, key, type(val)))
+ return False
+ if key == PolicyKeys.KW_CONNECTION_ORIGINS:
+ if not self.compiler_origins(name, val, warnings, errors):
+ return False
+ elif key == PolicyKeys.KW_POLICIES:
+ if not self.compiler_policies(name, val, warnings, errors):
+ return False
+ else:
+ # deduplicate connectionPolicy and groups lists
+ for k,v in val.iteritems():
+ v = list(set(v))
+ val[k] = v
+ policy_out[key] = val
+ except Exception, e:
+ errors.append("Application '%s' option '%s' error processing map: %s" %
+ (name, key, e))
+ return False
+ return True
+
+class PolicyLocal():
+ """
+ The policy database.
+ """
+
+ def __init__(self, folder=""):
+ """
+ Create instance
+ @params folder: relative path from __file__ to conf file folder
+ """
+ self.policydb = {}
+ self.lookup_cache = {}
+ self.stats = {}
+ self.folder = folder
+ self.policy_compiler = PolicyCompiler()
+ if not folder == "":
+ self.policy_io_read_files()
+
+ #
+ # Policy file I/O
+ #
+ def policy_io_read_files(self):
+ """
+ Read all conf files and create the policies they contain.
+ """
+ apath = os.path.abspath(os.path.dirname(__file__))
+ apath = os.path.join(apath, self.folder)
+ for i in os.listdir(apath):
+ if i.endswith(".json"):
+ self.policy_io_read_file(os.path.join(apath, i))
+
+ def policy_io_read_file(self, fn):
+ """
+ Read a policy config file.
+ Validate each policy and commit to policy database.
+ @param fn: absolute path to file
+ """
+ try:
+ with open(fn) as json_file:
+ cp = json.load(json_file)
+
+ except Exception, e:
+ raise PolicyError(
+ "Error processing policy configuration file '%s' : %s" % (fn, e))
+ newpolicies = {}
+ for app_name, app_policy in cp.iteritems():
+ warnings = []
+ diag = []
+ candidate = {}
+ if not self.policy_compiler.compile(app_name, app_policy, candidate, warnings, diag):
+ msg = "Policy file '%s' is invalid: %s" % (fn, diag[0])
+ raise PolicyError( msg )
+ if len(warnings) > 0:
+ print ("LogMe: Policy file '%s' application '%s' has warnings: %s" %
+ (fn, app_name, warnings))
+ newpolicies[app_name] = candidate
+ # Log a warning if policy from one config file replaces another.
+ # TODO: Should this throw? Do we increment the policy version per load?
+ for c in newpolicies:
+ c_ver = 0
+ e_ver = 0
+ c_pol = newpolicies[c]
+ if PolicyKeys.KW_POLICY_VERSION in c_pol:
+ c_ver = int(c_pol[PolicyKeys.KW_POLICY_VERSION])
+ if c in self.policydb:
+ e_pol = self.policydb[c]
+ if PolicyKeys.KW_POLICY_VERSION in e_pol:
+ e_ver = int(e_pol[PolicyKeys.KW_POLICY_VERSION])
+ if c_ver < e_ver:
+ kw = "downgrades"
+ elif c_ver == e_ver:
+ kw = "replaces"
+ else:
+ kw = "upgrades"
+ msg = ("LogMe: WARNING Policy file '%s' application '%s' policy version '%s' %s existing policy version '%s'." %
+ (fn, c, c_ver, kw, e_ver))
+ print msg
+ for c in newpolicies:
+ c_pol = newpolicies[c]
+ c_max = 0
+ c_max_u = 0
+ c_max_h = 0
+ if PolicyKeys.KW_MAXCONN in c_pol:
+ c_max = c_pol[PolicyKeys.KW_MAXCONN]
+ if PolicyKeys.KW_MAXCONNPERUSER in c_pol:
+ c_max_u = c_pol[PolicyKeys.KW_MAXCONNPERUSER]
+ if PolicyKeys.KW_MAXCONNPERHOST in c_pol:
+ c_max_h = c_pol[PolicyKeys.KW_MAXCONNPERHOST]
+ if c in self.stats:
+ self.stats[c].update(c_max, c_max_u, c_max_h)
+ else:
+ self.stats[c] = PolicyAppConnectionMgr(c_max, c_max_u, c_max_h)
+ self.policydb.update(newpolicies)
+
+
+ #
+ # Service interfaces
+ #
+ def policy_create(self, name, policy):
+ """
+ Create named policy
+ @param name: application name
+ @param policy: policy data in raw user format
+ """
+ warnings = []
+ diag = []
+ candidate = {}
+ result = self.policy_compiler.compile(name, policy, candidate, warnings, diag)
+ if not result:
+ raise PolicyError( "Policy '%s' is invalid: %s" % (name, diag[0]) )
+ if len(warnings) > 0:
+ print ("LogMe: Application '%s' has warnings: %s" %
+ (name, warnings))
+ self.policydb[name] = candidate
+ # TODO: Create stats
+
+ def policy_read(self, name):
+ """
+ Read policy for named application
+ @param[in] name application name
+ @return policy data in raw user format
+ """
+ return self.policydb[name]
+
+ def policy_update(self, name, policy):
+ """
+ Update named policy
+ @param[in] name application name
+ @param[in] policy data in raw user input
+ """
+ if not name in self.policydb:
+ raise PolicyError("Policy '%s' does not exist" % name)
+ self.policy_create(name, policy)
+
+ def policy_delete(self, name):
+ """
+ Delete named policy
+ @param[in] name application name
+ """
+ if not name in self.policydb:
+ raise PolicyError("Policy '%s' does not exist" % name)
+ del self.policydb[name]
+
+ #
+ # db enumerator
+ #
+ def policy_db_get_names(self):
+ """
+ Return a list of application names in this policy
+ """
+ return self.policydb.keys()
+
+
+ #
+ # Runtime query interface
+ #
+ def policy_aggregate_limits(self, upolicy, policy, settingname):
+ """
+ Force a max count value into user policy
+ param[in,out] upolicy user policy receiving aggregations
+ param[in] policy Internal policy holding settings to be aggregated
+ param[in] settingname setting of interest
+ """
+ if settingname in policy:
+ upolicy[settingname] = policy[settingname]
+
+ def policy_aggregate_policy_int(self, upolicy, policy, groups, settingname):
+ """
+ Pull int out of policy.policies[group] and install into upolicy.
+ Integers are set to max(new, existing)
+ param[in,out] upolicy user policy receiving aggregations
+ param[in] policy Internal policy holding settings to be aggregated
+ param[in] settingname setting of interest
+ """
+ if not PolicyKeys.KW_POLICIES in policy:
+ return
+ policies = policy[PolicyKeys.KW_POLICIES]
+ for group in groups:
+ if group in policies:
+ rpol = policies[group]
+ if settingname in rpol:
+ sp = rpol[settingname]
+ if settingname in upolicy:
+ up = upolicy[settingname]
+ if sp > up:
+ # policy bumps up user setting
+ upolicy[settingname] = sp
+ else:
+ # user policy is already better
+ pass
+ else:
+ # user policy doesn't have setting so force it
+ upolicy[settingname] = sp
+ else:
+ # no setting of this name in the group's policy
+ pass
+ else:
+ # no policy for this group
+ pass
+
+ def policy_aggregate_policy_bool(self, upolicy, policy, groups, settingname):
+ """
+ Pull bool out of policy and install into upolicy if true
+ param[in,out] upolicy user policy receiving aggregations
+ param[in] policy Internal policy holding settings to be aggregated
+ param[in] settingname setting of interest
+ """
+ if not PolicyKeys.KW_POLICIES in policy:
+ return
+ policies = policy[PolicyKeys.KW_POLICIES]
+ for group in groups:
+ if group in policies:
+ rpol = policies[group]
+ if settingname in rpol:
+ if rpol[settingname]:
+ upolicy[settingname] = True
+ else:
+ # no setting of this name in the group's policy
+ pass
+ else:
+ # no policy for this group
+ pass
+
+ def policy_aggregate_policy_list(self, upolicy, policy, groups, settingname):
+ """
+ Pull list out of policy and append into upolicy
+ param[in,out] upolicy user policy receiving aggregations
+ param[in] policy Internal policy holding settings to be aggregated
+ param[in] settingname setting of interest
+ """
+ if not PolicyKeys.KW_POLICIES in policy:
+ return
+ policies = policy[PolicyKeys.KW_POLICIES]
+ for group in groups:
+ if group in policies:
+ rpol = policies[group]
+ if settingname in rpol:
+ sp = rpol[settingname]
+ if settingname in upolicy:
+ upolicy[settingname].extend( sp )
+ upolicy[settingname] = list(set(upolicy[settingname]))
+ else:
+ # user policy doesn't have setting so force it
+ upolicy[settingname] = sp
+ else:
+ # no setting of this name in the group's policy
+ pass
+ else:
+ # no policy for this group
+ pass
+
+ def policy_lookup_settings(self, user, host, app, upolicy):
+ """
+ Determine if a user on host accessing app through AMQP Open is allowed
+ according to the policy access rules.
+ If allowed then return the policy settings.
+ @param[in] user connection authId
+ @param[in] host connection remote host numeric IP address
+ @param[in] app application user is accessing
+ @param[out] upolicy dict holding connection and policy values
+ @return if allowed by policy
+ # Note: the upolicy output is a non-nested dict with settings of interest
+ # TODO: figure out decent defaults for upolicy settings that are undefined
+ """
+ try:
+ lookup_id = user + "|" + host + "|" + app
+ if lookup_id in self.lookup_cache:
+ upolicy.update( self.lookup_cache[lookup_id] )
+ return True
+
+ settings = self.policydb[app]
+ # User allowed to connect from host?
+ allowed = False
+ restricted = False
+ uhs = HostStruct(host)
+ ugroups = []
+ if PolicyKeys.KW_GROUPS in settings:
+ for r in settings[PolicyKeys.KW_GROUPS]:
+ if user in settings[PolicyKeys.KW_GROUPS][r]:
+ restricted = True
+ ugroups.append(r)
+ uorigins = []
+ if PolicyKeys.KW_CONNECTION_POLICY in settings:
+ for ur in ugroups:
+ if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
+ uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
+ if PolicyKeys.KW_CONNECTION_ORIGINS in settings:
+ for co in settings[PolicyKeys.KW_CONNECTION_ORIGINS]:
+ if co in uorigins:
+ for cohost in settings[PolicyKeys.KW_CONNECTION_ORIGINS][co]:
+ if cohost.match_bin(uhs):
+ allowed = True
+ break
+ if allowed:
+ break
+ if not allowed and not restricted:
+ if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
+ allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
+ if not allowed:
+ return False
+ # Return connection limits and aggregation of group settings
+ ugroups.append(user) # user groups also includes username directly
+ self.policy_aggregate_limits (upolicy, settings, PolicyKeys.KW_POLICY_VERSION)
+ self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_FRAME_SIZE)
+ self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_MESSAGE_SIZE)
+ self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SESSION_WINDOW)
+ self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SESSIONS)
+ self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SENDERS)
+ self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_RECEIVERS)
+ self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.SETTING_ALLOW_DYNAMIC_SRC)
+ self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.SETTING_ALLOW_ANONYMOUS_SENDER)
+ self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.SETTING_SOURCES)
+ self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.SETTING_TARGETS)
+ c_upolicy = {}
+ c_upolicy.update(upolicy)
+ self.lookup_cache[lookup_id] = c_upolicy
+ return True
+ except Exception, e:
+ #print str(e)
+ #pdb.set_trace()
+ return False
+
+ def policy_lookup(self, conn_id, user, host, app, upolicy):
+ """
+ Determine if a user on host accessing app through AMQP Open is allowed:
+ - verify to the policy access rules.
+ - track user/host connection limits
+ If allowed then return the policy settings.
+ @param[in] conn_id unique connection identifier
+ @param[in] user connection authId
+ @param[in] host connection remote host numeric IP address
+ @param[in] app application user is accessing
+ @param[out] upolicy dict holding connection and policy values
+ @return if allowed by policy
+ # Note: the upolicy output is a non-nested dict with settings of interest
+ # TODO: figure out decent defaults for upolicy settings that are undefined
+ """
+ if not self.policy_lookup_settings(user, host, app, upolicy):
+ # TODO: print ("LogMe: connection denied by connection access rules")
+ return False
+ assert(app in self.stats)
+ diags = []
+ if not self.stats[app].can_connect(conn_id, user, host, diags):
+ # TODO: print ("LogMe: connection denied by connection count limits: %s" % diags)
+ return False
+ return True
+
+
+#
+# HACK ALERT: Temporary
+# Functions related to main
+#
+class ExitStatus(Exception):
+ """Raised if a command wants a non-0 exit status from the script"""
+ def __init__(self, status): self.status = status
+
+def main_except(argv):
+
+ usage = "usage: %prog [options]\nRead and print all conf files in a folder."
+ parser = optparse.OptionParser(usage=usage)
+ parser.set_defaults(folder="../../../tests/policy-1")
+ parser.add_option("-f", "--folder", action="store", type="string", dest="folder",
+ help="Use named folder instead of policy-1")
+ parser.add_option("-d", "--dump", action="store_true", dest="dump",
+ help="Dump policy details")
+
+ (options, args) = parser.parse_args()
+
+ policy = PolicyLocal(options.folder)
+
+ print("policy names: %s" % policy.policy_db_get_names())
+
+ if not options.dump:
+ return
+
+ # Exercise a few functions
+ # Empty policy
+ policy2 = PolicyLocal()
+
+ print("Print some Policy details:")
+ for pname in policy.policy_db_get_names():
+ print("policy : %s" % pname)
+ p = ("%s" % policy.policy_read(pname))
+ print(p.replace('\\n', '\n'))
+
+ # Lookups
+ upolicy = {}
+ # pdb.set_trace()
+ res1 = policy.policy_lookup('192.168.100.5:33332', 'zeke', '192.168.100.5', 'photoserver', upolicy)
+ print "\nLookup zeke from 192.168.100.5. Expect true and maxFrameSize 44444. Expecting True, result is %s" % res1
+ print "\nResulting policy is: %s" % upolicy
+ # Hit the cache
+ upolicy2 = {}
+ res2 = policy.policy_lookup('192.168.100.5:33335', 'zeke', '192.168.100.5', 'photoserver', upolicy2)
+ # Print the stats
+ print "\npolicy stats: %s" % policy.stats
+
+ upolicy = {}
+ res3 = policy.policy_lookup('72.135.2.9:33333', 'ellen', '72.135.2.9', 'photoserver', upolicy)
+ print "\nLookup ellen from 72.135.2.9. Expect true and maxFrameSize 666666. Result is %s" % res3
+ print "Resulting policy is: %s" % upolicy
+
+ upolicy = {}
+ res4 = policy2.policy_lookup('72.135.2.9:33334', 'ellen', '72.135.2.9', 'photoserver', upolicy)
+ print "\nLookup policy2 ellen from 72.135.2.9. Expect false. Result is %s" % res4
+
+ if not (res1 and res2 and res3 and not res4):
+ print "Tests FAIL"
+ else:
+ print "Tests PASS"
+
+def main(argv):
+ try:
+ main_except(argv)
+ return 0
+ except ExitStatus, e:
+ return e.status
+ except Exception, e:
+ print "%s: %s"%(type(e).__name__, e)
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e7604468/tests/system_tests_policy.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_policy.py b/tests/system_tests_policy.py
index 7c2ab70..fa531e4 100644
--- a/tests/system_tests_policy.py
+++ b/tests/system_tests_policy.py
@@ -32,7 +32,7 @@ from system_test import TIMEOUT
from qpid_dispatch_internal.management.policy_util import \
HostAddr, PolicyError, HostStruct, PolicyAppConnectionMgr
-from qpid_dispatch_internal.management.policy import PolicyLocal
+from qpid_dispatch_internal.management.policy_local import PolicyLocal
class AbsoluteConnectionCountLimit(TestCase):
"""
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org