Repository: qpid-dispatch Updated Branches: refs/heads/crolke-DISPATCH-188-1 dfb8cfda4 -> 1036c0efc
Squashed commit of the following: commit cb6f40ffbeae3dda38de785d35bd4969cfa5fc7f Author: Chuck Rolke <[email protected]> Date: Fri Jan 29 12:27:45 2016 -0500 Adjust agent to new schema. Fix example ruleset. commit 6bd202413dec306d3814d1fee5e400708352aa85 Author: Chuck Rolke <[email protected]> Date: Fri Jan 29 12:19:01 2016 -0500 collapse settings into ruleset map; fix up test rulesets to match new schema commit db1d89a0f142a704adcb83489008c81f97a307a2 Author: Chuck Rolke <[email protected]> Date: Fri Jan 29 11:49:03 2016 -0500 Tidy up the self tests. commit 85b958e67db35708b81478d2a08b63dfa32af198 Author: Chuck Rolke <[email protected]> Date: Fri Jan 29 09:23:01 2016 -0500 cwip commit 455bc738c3a012c2b081efb16e49823ca7da1c0b Author: Chuck Rolke <[email protected]> Date: Thu Jan 28 14:10:51 2016 -0500 Split unit tests out of system test Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/1036c0ef Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/1036c0ef Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/1036c0ef Branch: refs/heads/crolke-DISPATCH-188-1 Commit: 1036c0efcd75d9d674708fed65b6c51fd888bc79 Parents: dfb8cfd Author: Chuck Rolke <[email protected]> Authored: Fri Jan 29 12:29:39 2016 -0500 Committer: Chuck Rolke <[email protected]> Committed: Fri Jan 29 12:29:39 2016 -0500 ---------------------------------------------------------------------- python/qpid_dispatch/management/qdrouter.json | 106 +----- .../qpid_dispatch_internal/management/agent.py | 9 +- .../policy/policy_local.py | 377 +++++++++---------- tests/CMakeLists.txt | 3 + tests/policy-1/policy-photoserver.json | 1 - tests/policy-1/test-router-with-policy.json | 196 +++++----- tests/router_policy_test.py | 243 ++++++++++++ tests/system_tests_policy.py | 259 +------------ 8 files changed, 522 insertions(+), 672 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/python/qpid_dispatch/management/qdrouter.json ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json index 9565ba7..0944b3d 100644 --- a/python/qpid_dispatch/management/qdrouter.json +++ b/python/qpid_dispatch/management/qdrouter.json @@ -1018,7 +1018,7 @@ } }, - "policyAccessRuleset": { + "policyRuleset": { "description": "Per application definition of the locations from which users may connect and the groups to which users belong.", "extends": "configurationEntity", "attributes": { @@ -1072,11 +1072,17 @@ "default": false, "required": false, "create": true + }, + "settings": { + "type": "map", + "description": "A map where each key is a connection group name and the corresponding settings for that group.", + "required": false, + "create": true } } }, - "policyAccessStats": { + "policyStats": { "description": "Per application connection and access statistics.", "extends": "operationalEntity", "attributes": { @@ -1094,104 +1100,8 @@ "perHostState": { "type": "map", "description": "A map where the key is the host name and the value is a list of the host's connections." - } - - } - - }, - - "policyAppSettings": { - "description": "For a given application and user group define the policy settings applied to the user's AMQP connection.", - "extends": "configurationEntity", - "attributes": { - "applicationName": { - "type": "string", - "description": "The application to which these settings apply.", - "required": true - }, - "userGroupName": { - "type": "string", - "description": "The user group to which these settings apply.", - "required": true }, - "maxFrameSize": { - "type": "integer", - "description": "Largest frame that may be sent on this connection. Zero implies system default. (AMQP Open, max-frame-size)", - "default": 65536, - "required": false, - "create": true - }, - "maxMessageSize": { - "type": "integer", - "description": "Largest message size supported by links created on this connection. Zero implies system default. (AMQP Attach, max-message-size)", - "default": 0, - "required": false, - "create": true - }, - "maxSessionWindow": { - "type": "integer", - "description": "Largest incoming and outgoing window for sessions created on this connection. Zero implies system default. (AMQP Begin, incoming-window, outgoing-window)", - "default": 2147483647, - "required": false, - "create": true - }, - "maxSessions": { - "type": "integer", - "description": "Maximum number of sessions that may be created on this connection. Zero implies system default. (AMQP Open, channel-max)", - "default": 10, - "required": false, - "create": true - }, - "maxSenders": { - "type": "integer", - "description": "Maximum number of sending links that may be created on this connection. Zero implies system default.", - "default": 10, - "required": false, - "create": true - }, - "maxReceivers": { - "type": "integer", - "description": "Maximum number of receiving links that may be created on this connection. Zero implies system default.", - "required": false, - "create": true - }, - "allowDynamicSrc": { - "type": "boolean", - "description": "This connection is allowed to use the dynamic link source feature.", - "default": false, - "required": false, - "create": true - }, - "allowAnonymousSender": { - "type": "boolean", - "description": "This connection is allowed to use the Anonymous Sender feature.", - "default": false, - "required": false, - "create": true - }, - "sources": { - "type": "string", - "description": "List of Source addresses allowed when creating receiving links.", - "required": false, - "create": true - }, - "targets": { - "type": "string", - "description": "List of Target addresses allowed when creating sending links.", - "required": false, - "create": true - } - } - }, - "policyAppStats": { - "description": "Per application policy enforcement statistics.", - "extends": "operationalEntity", - "attributes": { - "name": { - "type": "string", - "description": "The application name." - }, "maxSendersDenied": {"type": "integer", "graph": true}, "maxReceiversDenied": {"type": "integer", "graph": true}, "dynamicSrcDenied": {"type": "integer", "graph": true}, http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/python/qpid_dispatch_internal/management/agent.py ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch_internal/management/agent.py b/python/qpid_dispatch_internal/management/agent.py index 013eddb..994b0dd 100644 --- a/python/qpid_dispatch_internal/management/agent.py +++ b/python/qpid_dispatch_internal/management/agent.py @@ -285,20 +285,13 @@ class PolicyEntity(EntityAdapter): def _identifier(self): return self.attributes.get('module') -class PolicyAccessRulesetEntity(EntityAdapter): +class PolicyRulesetEntity(EntityAdapter): def create(self): self._policy.create_ruleset(self.attributes) def _identifier(self): return self.attributes.get('applicationName') -class PolicyAppSettingsEntity(EntityAdapter): - def create(self): - self._policy.create_settings(self.attributes) - - def _identifier(self): - return self.attributes.get('applicationName') + "_" + self.attributes.get('userGroupName') - def _addr_port_identifier(entity): for attr in ['addr', 'port']: # Set default values if need be entity.attributes.setdefault( http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/python/qpid_dispatch_internal/policy/policy_local.py ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch_internal/policy/policy_local.py b/python/qpid_dispatch_internal/policy/policy_local.py index 7cc17de..d3b5e72 100644 --- a/python/qpid_dispatch_internal/policy/policy_local.py +++ b/python/qpid_dispatch_internal/policy/policy_local.py @@ -27,8 +27,6 @@ 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. """ @@ -43,13 +41,14 @@ class PolicyKeys(object): KW_APPLICATION_NAME = "applicationName" # Policy ruleset key words - KW_MAXCONN = "maxConnections" - KW_MAXCONNPERHOST = "maxConnPerHost" - KW_MAXCONNPERUSER = "maxConnPerUser" - KW_USER_GROUPS = "userGroups" - KW_CONNECTION_GROUPS = "connectionGroups" - KW_CONNECTION_POLICY = "connectionIngressPolicies" - KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault" + KW_MAXCONN = "maxConnections" + KW_MAXCONNPERHOST = "maxConnPerHost" + KW_MAXCONNPERUSER = "maxConnPerUser" + KW_USER_GROUPS = "userGroups" + KW_CONNECTION_GROUPS = "connectionGroups" + KW_CONNECTION_INGRESS_POLICIES = "connectionIngressPolicies" + KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault" + KW_SETTINGS = "settings" # Policy settings key words KW_USER_GROUP_NAME = "userGroupName" @@ -73,6 +72,9 @@ class PolicyKeys(object): # Config file separator character for names in a list KC_CONFIG_LIST_SEP = "," + + # user-to-group computed map in compiled ruleset + RULESET_U2G_MAP = "U2G" # # class PolicyCompiler(object): @@ -93,16 +95,12 @@ class PolicyCompiler(object): PolicyKeys.KW_MAXCONNPERUSER, PolicyKeys.KW_USER_GROUPS, PolicyKeys.KW_CONNECTION_GROUPS, - PolicyKeys.KW_CONNECTION_POLICY, - PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT + PolicyKeys.KW_CONNECTION_INGRESS_POLICIES, + PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT, + PolicyKeys.KW_SETTINGS ] allowed_settings_options = [ - PolicyKeys.KW_IGNORED_NAME, - PolicyKeys.KW_IGNORED_IDENTITY, - PolicyKeys.KW_IGNORED_TYPE, - PolicyKeys.KW_APPLICATION_NAME, - PolicyKeys.KW_USER_GROUP_NAME, PolicyKeys.KW_MAX_FRAME_SIZE, PolicyKeys.KW_MAX_MESSAGE_SIZE, PolicyKeys.KW_MAX_SESSION_WINDOW, @@ -192,6 +190,18 @@ class PolicyCompiler(object): warnings[] may contain info and errors[0] will hold the description of why the policy was rejected. """ + # rulesets may not come through standard config so make nice defaults + policy_out[PolicyKeys.KW_MAX_FRAME_SIZE] = 65536 + policy_out[PolicyKeys.KW_MAX_MESSAGE_SIZE] = 0 + policy_out[PolicyKeys.KW_MAX_SESSION_WINDOW] = 2147483647 + policy_out[PolicyKeys.KW_MAX_SESSIONS] = 10 + policy_out[PolicyKeys.KW_MAX_SENDERS] = 10 + policy_out[PolicyKeys.KW_MAX_RECEIVERS] = 10 + policy_out[PolicyKeys.KW_ALLOW_DYNAMIC_SRC] = False + policy_out[PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER] = False + policy_out[PolicyKeys.KW_SOURCES] = [] + policy_out[PolicyKeys.KW_TARGETS] = [] + cerror = [] for key, val in policy_in.iteritems(): if key not in self.allowed_settings_options: @@ -241,6 +251,16 @@ class PolicyCompiler(object): description of why the policy was rejected. """ cerror = [] + # rulesets may not come through standard config so make nice defaults + policy_out[PolicyKeys.KW_MAXCONN] = 0 + policy_out[PolicyKeys.KW_MAXCONNPERHOST] = 0 + policy_out[PolicyKeys.KW_MAXCONNPERUSER] = 0 + policy_out[PolicyKeys.KW_USER_GROUPS] = {} + policy_out[PolicyKeys.KW_CONNECTION_GROUPS] = {} + policy_out[PolicyKeys.KW_CONNECTION_INGRESS_POLICIES] = {} + policy_out[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT] = False + policy_out[PolicyKeys.KW_SETTINGS] = {} + # validate the options for key, val in policy_in.iteritems(): if key not in self.allowed_ruleset_options: @@ -258,7 +278,7 @@ class PolicyCompiler(object): policy_out[key] = val elif key in [PolicyKeys.KW_USER_GROUPS, PolicyKeys.KW_CONNECTION_GROUPS, - PolicyKeys.KW_CONNECTION_POLICY + PolicyKeys.KW_CONNECTION_INGRESS_POLICIES ]: try: if not type(val) is dict: @@ -266,6 +286,8 @@ class PolicyCompiler(object): (name, key, type(val))) return False if key == PolicyKeys.KW_CONNECTION_GROUPS: + # Conection groups are lists of IP addresses that need to be + # converted into binary structures for comparisons. if not self.compile_connection_groups(name, val, warnings, errors): return False else: @@ -279,6 +301,57 @@ class PolicyCompiler(object): errors.append("Application '%s' option '%s' error processing map: %s" % (name, key, e)) return False + elif key in [PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]: + if not type(val) is bool: + errors.append("Application '%s' option '%s' must be of type 'bool' but is '%s'" % + (name, key, type(val))) + return False + policy_out[key] = val + elif key in [PolicyKeys.KW_SETTINGS]: + 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 + for skey, sval in val.iteritems(): + newsettings = {} + if not self.compile_app_settings(name, skey, sval, newsettings, warnings, errors): + return False + policy_out[key][skey] = {} + policy_out[key][skey].update(newsettings) + + # Verify that each user is in only one group. + # Verify that each user group has defined settings + # Create user-to-group map for looling up user's group + policy_out[PolicyKeys.RULESET_U2G_MAP] = {} + if PolicyKeys.KW_USER_GROUPS in policy_out: + for group, userlist in policy_out[PolicyKeys.KW_USER_GROUPS].iteritems(): + for user in userlist: + if user in policy_out[PolicyKeys.RULESET_U2G_MAP]: + errors.append("Application '%s' user '%s' is in multiple user groups '%s' and '%s'" % + (name, user, policy_out[PolicyKeys.RULESET_U2G_MAP][user], group)) + return False + else: + policy_out[PolicyKeys.RULESET_U2G_MAP][user] = group + if not group in policy_out[PolicyKeys.KW_SETTINGS]: + errors.append("Application '%s' user group '%s' has no defined settings" % + (name, group)) + return False + + # Default connections require a default settings + if policy_out[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]: + if not PolicyKeys.KW_DEFAULT_SETTINGS in policy_out[PolicyKeys.KW_SETTINGS]: + errors.append("Application '%s' allows connections by default but default settings are not defined" % + (name)) + return False + + # Each ingress policy name references must exist in connection_groups + for cipname, cip in policy_out[PolicyKeys.KW_CONNECTION_INGRESS_POLICIES].iteritems(): + for co in cip: + if not co in policy_out[PolicyKeys.KW_CONNECTION_GROUPS]: + errors.append("Application '%s' connection ingress policy '%s' references connection group '%s' but that group does not exist" + (name, cipname, co)) + return False + return True class PolicyLocal(object): @@ -291,13 +364,24 @@ class PolicyLocal(object): Create instance @params folder: relative path from __file__ to conf file folder """ - self.policydb = {} + # rulesetdb is a map + # key : application name + # val : ruleset for this app + # created by configuration + # augmented by policy compiler + self.rulesetdb = {} + + # settingsdb is a map + # key : <application name> + # val : a map + # key : <user group name> + # val : settings to use for user's connection + # created by configuration self.settingsdb = {} - self.lookup_cache = {} - self.stats = {} - self.policy_compiler = PolicyCompiler() - self.name_lookup_cache = {} - self.blob_lookup_cache = {} + + # _policy_compiler is a function + # validates incoming policy and readies it for internal use + self._policy_compiler = PolicyCompiler() # # Service interfaces @@ -311,13 +395,13 @@ class PolicyLocal(object): diag = [] candidate = {} name = attributes[PolicyKeys.KW_APPLICATION_NAME] - result = self.policy_compiler.compile_access_ruleset(name, attributes, candidate, warnings, diag) + result = self._policy_compiler.compile_access_ruleset(name, attributes, 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 + self.rulesetdb[name] = candidate # TODO: Create stats def create_settings(self, attributes): @@ -330,7 +414,7 @@ class PolicyLocal(object): candidate = {} app_name = attributes[PolicyKeys.KW_APPLICATION_NAME] usergroup = attributes[PolicyKeys.KW_USER_GROUP_NAME] - result = self.policy_compiler.compile_app_settings(app_name, usergroup, attributes, candidate, warnings, diag) + result = self._policy_compiler.compile_app_settings(app_name, usergroup, attributes, candidate, warnings, diag) if not result: raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, diag[0]) ) if len(warnings) > 0: @@ -346,7 +430,7 @@ class PolicyLocal(object): @param[in] name application name @return policy data in raw user format """ - return self.policydb[name] + return self.rulesetdb[name] def policy_update(self, name, policy): """ @@ -354,7 +438,7 @@ class PolicyLocal(object): @param[in] name application name @param[in] policy data in raw user input """ - if not name in self.policydb: + if not name in self.rulesetdb: raise PolicyError("Policy '%s' does not exist" % name) self.policy_create(name, policy) @@ -363,9 +447,9 @@ class PolicyLocal(object): Delete named policy @param[in] name application name """ - if not name in self.policydb: + if not name in self.rulesetdb: raise PolicyError("Policy '%s' does not exist" % name) - del self.policydb[name] + del self.rulesetdb[name] # # db enumerator @@ -374,101 +458,13 @@ class PolicyLocal(object): """ Return a list of application names in this policy """ - return self.policydb.keys() + return self.rulesetdb.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, appsettings, 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 - """ - for group in groups: - if group in appsettings: - rpol = appsettings[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, appsettings, 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 - """ - for group in groups: - if group in appsettings: - rpol = appsettings[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, appsettings, 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 - """ - for group in groups: - if group in appsettings: - rpol = appsettings[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 lookup_user(self, user, host, app, conn_name, policyname): + def lookup_user(self, user, host, app, conn_name): """ Lookup function called from C. Determine if a user on host accessing app through AMQP Open is allowed @@ -477,117 +473,96 @@ class PolicyLocal(object): @param[in] user connection authId @param[in] host connection remote host numeric IP address as string @param[in] app application user is accessing - @param[out] policyname name of the policy settings blob for this user - @return if allowed by policy + @return settings user-group name if allowed; "" if not allowed # Note: the upolicy[0] output is list of group names joined with '|'. TODO: handle the AccessStats """ try: - lookup_id = user + "|" + host + "|" + app - if lookup_id in self.name_lookup_cache: - policyname.append( self.name_lookup_cache[lookup_id] ) - return True - - if not app in self.policydb: + if not app in self.rulesetdb: # TODO: ("LogMe: no policy defined for application %s" % app) - policyname.append("") - return False + return "" - settings = self.policydb[app] - # User allowed to connect from host? - allowed = False - restricted = False - uhs = HostStruct(host) - ugroups = [] - if PolicyKeys.KW_USER_GROUPS in settings: - for r in settings[PolicyKeys.KW_USER_GROUPS]: - if user in settings[PolicyKeys.KW_USER_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_GROUPS in settings: - for co in settings[PolicyKeys.KW_CONNECTION_GROUPS]: - if co in uorigins: - for cohost in settings[PolicyKeys.KW_CONNECTION_GROUPS][co]: - if cohost.match_bin(uhs): - allowed = True - break + ruleset = self.rulesetdb[app] + # User in a group or default? + if user in ruleset[PolicyKeys.RULESET_U2G_MAP]: + usergroup = ruleset[PolicyKeys.RULESET_U2G_MAP][user] + else: + if ruleset[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]: + usergroup = PolicyKeys.KW_DEFAULT_SETTINGS + else: + # User is not in a group and default is disallowed. So no go. + return "" + # User in usergroup allowed to connect from host? + if usergroup in ruleset[PolicyKeys.KW_CONNECTION_INGRESS_POLICIES]: + # User's usergroup is restricted to connecting from a host + # defined by the group's ingress policy + allowed = False + uhs = HostStruct(host) + cglist = ruleset[PolicyKeys.KW_CONNECTION_INGRESS_POLICIES][usergroup] + for cg in cglist: + for cohost in ruleset[PolicyKeys.KW_CONNECTION_GROUPS][cg]: + 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] + else: + # User's usergroup has no ingress policy so allow + allowed = True if not allowed: - return False - if not restricted: - ugroups.append(PolicyKeys.KW_DEFAULT_SETTINGS) - # - ugroups.sort() - result = "|".join(ugroups) - self.name_lookup_cache[lookup_id] = result - policyname.append(result) - return True + return "" + + # This user passes administrative approval. + # TODO: Count connection limits and possibly deny + + # Return success + return usergroup except Exception, e: #print str(e) #pdb.set_trace() - return False + return "" def lookup_settings(self, appname, name, upolicy): """ Given a settings name, return the aggregated policy blob. @param[in] appname: application user is accessing - @param[in] name: user group name or concatenation of names of the policy settings blob + @param[in] name: user group name @param[out] upolicy: dict holding policy values - the settings blob + TODO: make this a c struct @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: - cachekey = appname + "|" + name - if cachekey in self.blob_lookup_cache: - upolicy.update( self.blob_lookup_cache[cachekey] ) - return True - settings = self.settingsdb[appname] - ugroups = name.split("|") - self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_FRAME_SIZE) - self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_MESSAGE_SIZE) - self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SESSION_WINDOW) - self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SESSIONS) - self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SENDERS) - self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_RECEIVERS) - self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.KW_ALLOW_DYNAMIC_SRC) - self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER) - self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.KW_SOURCES) - self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.KW_TARGETS) - c_upolicy = {} - c_upolicy.update(upolicy) - self.blob_lookup_cache[cachekey] = c_upolicy + if not appname in self.rulesetdb: + # TODO: ("LogMe: no policy defined for application %s" % app) + return "" + + ruleset = self.rulesetdb[appname] + + if not name in ruleset[PolicyKeys.KW_SETTINGS]: + # TODO: ("LogMe: no user group settings for application %s group %s" % (app, name)) + return "" + + upolicy.update(ruleset[PolicyKeys.KW_SETTINGS][name]) return True except Exception, e: #print str(e) #pdb.set_trace() - return False + return "" def test_load_config(self): - ruleset_str = '["policyAccessRuleset", {"applicationName": "photoserver","maxConnections": 50,"maxConnPerUser": 5,"maxConnPerHost": 20,"userGroups": {"anonymous": "anonymous","users": "u1, u2","paidsubscribers": "p1, p2","test": "zeke, ynot","admin": "alice, bob, ellen","superuser": "ellen"},"connectionGroups": {"Ten18": "10.18.0.0-10.18.255.255","EllensWS": "72.135.2.9","TheLabs": "10.48.0.0-10.48.255.255, 192.168.100.0-192.168.100.255","localhost": "127.0.0.1, ::1","TheWorld": "*"},"connectionIngressPolicies": {"anonymous": "TheWorld","users": "TheWorld","paidsubscribers": "TheWorld","test": "TheLabs","admin": "Ten18, TheLabs, localhost","superuser": "EllensWS, localhost"},"connectionAllowDefault": true}]' + ruleset_str = '["policyAccessRuleset", {"applicationName": "photoserver","maxConnections": 50,"maxConnPerUser": 5,"maxConnPerHost": 20,"userGroups": {"anonymous": "anonymous","users": "u1, u2","paidsubscribers": "p1, p2","test": "zeke, ynot","admin": "alice, bob","superuser": "ellen"},"connectionGroups": {"Ten18": "10.18.0.0-10.18.255.255","EllensWS": "72.135.2.9","TheLabs": "10.48.0.0-10.48.255.255, 192.168.100.0-192.168.100.255","localhost": "127.0.0.1, ::1","TheWorld": "*"},"connectionIngressPolicies": {"anonymous": "TheWorld","users": "TheWorld","paidsubscribers": "TheWorld","test": "TheLabs","admin": "Ten18, TheLabs, localhost","superuser": "EllensWS, localhost"},"connectionAllowDefault": true,' + ruleset_str += '"settings": {' + ruleset_str += '"anonymous": {"maxFrameSize": 111111,"maxMessageSize": 111111,"maxSessionWindow": 111111,"maxSessions": 1,"maxSenders": 11,"maxReceivers": 11,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public", "targets": ""},' + ruleset_str += '"users": {"maxFrameSize": 222222,"maxMessageSize": 222222,"maxSessionWindow": 222222,"maxSessions": 2,"maxSenders": 22,"maxReceivers": 22,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public, private", "targets": "public"},' + ruleset_str += '"paidsubscribers":{"maxFrameSize": 333333,"maxMessageSize": 333333,"maxSessionWindow": 333333,"maxSessions": 3,"maxSenders": 33,"maxReceivers": 33,"allowDynamicSrc": true, "allowAnonymousSender": false,"sources": "public, private", "targets": "public, private"},' + ruleset_str += '"test": {"maxFrameSize": 444444,"maxMessageSize": 444444,"maxSessionWindow": 444444,"maxSessions": 4,"maxSenders": 44,"maxReceivers": 44,"allowDynamicSrc": true, "allowAnonymousSender": true, "sources": "private", "targets": "private"},' + ruleset_str += '"admin": {"maxFrameSize": 555555,"maxMessageSize": 555555,"maxSessionWindow": 555555,"maxSessions": 5,"maxSenders": 55,"maxReceivers": 55,"allowDynamicSrc": true, "allowAnonymousSender": true, "sources": "public, private, management", "targets": "public, private, management"},' + ruleset_str += '"superuser": {"maxFrameSize": 666666,"maxMessageSize": 666666,"maxSessionWindow": 666666,"maxSessions": 6,"maxSenders": 66,"maxReceivers": 66,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public, private, management, root","targets": "public, private, management, root"},' + ruleset_str += '"default": {"maxFrameSize": 222222,"maxMessageSize": 222222,"maxSessionWindow": 222222,"maxSessions": 2,"maxSenders": 22,"maxReceivers": 22,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public, private", "targets": "public"}' + ruleset_str += '}}]' ruleset = json.loads(ruleset_str) self.create_ruleset(ruleset[1]) - - settings_strs = [] - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"anonymous", "maxFrameSize": 111111,"maxMessageSize": 111111,"maxSessionWindow": 111111,"maxSessions": 1,"maxSenders": 11,"maxReceivers": 11,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public", "targets": ""}]') - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"users", "maxFrameSize": 222222,"maxMessageSize": 222222,"maxSessionWindow": 222222,"maxSessions": 2,"maxSenders": 22,"maxReceivers": 22,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public, private", "targets": "public"}]') - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"paidsubscribers","maxFrameSize": 333333,"maxMessageSize": 333333,"maxSessionWindow": 333333,"maxSessions": 3,"maxSenders": 33,"maxReceivers": 33,"allowDynamicSrc": true, "allowAnonymousSender": false,"sources": "public, private", "targets": "public, private"}]') - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"test", "maxFrameSize": 444444,"maxMessageSize": 444444,"maxSessionWindow": 444444,"maxSessions": 4,"maxSenders": 44,"maxReceivers": 44,"allowDynamicSrc": true, "allowAnonymousSender": true, "sources": "private", "targets": "private"}]') - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"admin", "maxFrameSize": 555555,"maxMessageSize": 555555,"maxSessionWindow": 555555,"maxSessions": 5,"maxSenders": 55,"maxReceivers": 55,"allowDynamicSrc": true, "allowAnonymousSender": true, "sources": "public, private, management", "targets": "public, private, management"}]') - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"superuser", "maxFrameSize": 666666,"maxMessageSize": 666666,"maxSessionWindow": 666666,"maxSessions": 6,"maxSenders": 66,"maxReceivers": 66,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public, private, management, root","targets": "public, private, management, root"}]') - settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"default", "maxFrameSize": 222222,"maxMessageSize": 222222,"maxSessionWindow": 222222,"maxSessions": 2,"maxSenders": 22,"maxReceivers": 22,"allowDynamicSrc": false,"allowAnonymousSender": false,"sources": "public, private", "targets": "public"}]') - - for sstr in settings_strs: - settings = json.loads(sstr) - self.create_settings(settings[1]) http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/tests/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d2d5e29..b43903a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -54,6 +54,7 @@ target_link_libraries(unit_tests_size qpid-dispatch) # While moving the files make sure that configure_file() is called on any .py.in files resulting in .py file configure_file(${CMAKE_CURRENT_SOURCE_DIR}/system_tests_sasl_plain.py.in ${CMAKE_CURRENT_BINARY_DIR}/system_tests_sasl_plain.py) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/router_engine_test.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/router_policy_test.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/system_tests_broker.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/system_tests_management.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/system_tests_one_router.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) @@ -80,6 +81,7 @@ add_test(unit_tests ${TEST_WRAP} --vg unit_tests ${CMAKE_CURRENT_SOUR # Add all sytem_tests* using add_test add_test(router_tests ${TEST_WRAP} -s ${CMAKE_CURRENT_BINARY_DIR}/router_engine_test.py -v) +add_test(router_tests_policy ${TEST_WRAP} -s ${CMAKE_CURRENT_BINARY_DIR}/router_policy_test.py -v) add_test(system_tests_broker ${TEST_WRAP} -s ${CMAKE_CURRENT_BINARY_DIR}/system_tests_broker.py -v) add_test(system_tests_management ${TEST_WRAP} -s ${CMAKE_CURRENT_BINARY_DIR}/system_tests_management.py -v) add_test(system_tests_one_router ${TEST_WRAP} -s ${CMAKE_CURRENT_BINARY_DIR}/system_tests_one_router.py -v) @@ -100,6 +102,7 @@ add_test(management_tests ${TEST_WRAP} -m unittest -v management) # set(SYSTEM_TEST_FILES ${CMAKE_CURRENT_BINARY_DIR}/router_engine_test.py + ${CMAKE_CURRENT_BINARY_DIR}/router_policy_test.py ${CMAKE_CURRENT_BINARY_DIR}/run_system_tests.py ${CMAKE_CURRENT_BINARY_DIR}/system_test.py ${CMAKE_CURRENT_BINARY_DIR}/system_tests_one_router.py http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/tests/policy-1/policy-photoserver.json ---------------------------------------------------------------------- diff --git a/tests/policy-1/policy-photoserver.json b/tests/policy-1/policy-photoserver.json index 56b804a..6e13689 100644 --- a/tests/policy-1/policy-photoserver.json +++ b/tests/policy-1/policy-photoserver.json @@ -1,6 +1,5 @@ { "photoserver": { - "policyVersion": 1, "maxConnections": 10, "maxConnPerUser": 5, "maxConnPerHost": 5, http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/tests/policy-1/test-router-with-policy.json ---------------------------------------------------------------------- diff --git a/tests/policy-1/test-router-with-policy.json b/tests/policy-1/test-router-with-policy.json index 4988093..dcf2de0 100644 --- a/tests/policy-1/test-router-with-policy.json +++ b/tests/policy-1/test-router-with-policy.json @@ -54,7 +54,7 @@ ["policy", { "maximumConnections": 2 }], - ["policyAccessRuleset", { + ["policyRuleset", { "applicationName": "photoserver", "maxConnections": 50, "maxConnPerUser": 5, @@ -64,7 +64,7 @@ "users": "u1, u2", "paidsubscribers": "p1, p2", "test": "zeke, ynot", - "admin": "alice, bob, ellen", + "admin": "alice, bob", "superuser": "ellen" }, "connectionGroups": { @@ -82,111 +82,93 @@ "admin": "Ten18, TheLabs, localhost", "superuser": "EllensWS, localhost" }, - "connectionAllowDefault": true - } - ], - ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "anonymous", - "maxFrameSize": 111111, - "maxMessageSize": 111111, - "maxSessionWindow": 111111, - "maxSessions": 1, - "maxSenders": 11, - "maxReceivers": 11, - "allowDynamicSrc": false, - "allowAnonymousSender": false, - "sources": "public", - "targets": "" - } - ], - ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "users", - "maxFrameSize": 222222, - "maxMessageSize": 222222, - "maxSessionWindow": 222222, - "maxSessions": 2, - "maxSenders": 22, - "maxReceivers": 22, - "allowDynamicSrc": false, - "allowAnonymousSender": false, - "sources": "public, private", - "targets": "public" - } - ], - ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "paidsubscribers", - "maxFrameSize": 333333, - "maxMessageSize": 333333, - "maxSessionWindow": 333333, - "maxSessions": 3, - "maxSenders": 33, - "maxReceivers": 33, - "allowDynamicSrc": true, - "allowAnonymousSender": false, - "sources": "public, private", - "targets": "public, private" - } - ], - ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "test", - "maxFrameSize": 444444, - "maxMessageSize": 444444, - "maxSessionWindow": 444444, - "maxSessions": 4, - "maxSenders": 44, - "maxReceivers": 44, - "allowDynamicSrc": true, - "allowAnonymousSender": true, - "sources": "private", - "targets": "private" - } - ], - ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "admin", - "maxFrameSize": 555555, - "maxMessageSize": 555555, - "maxSessionWindow": 555555, - "maxSessions": 5, - "maxSenders": 55, - "maxReceivers": 55, - "allowDynamicSrc": true, - "allowAnonymousSender": true, - "sources": "public, private, management", - "targets": "public, private, management" - } - ], - ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "superuser", - "maxFrameSize": 666666, - "maxMessageSize": 666666, - "maxSessionWindow": 666666, - "maxSessions": 6, - "maxSenders": 66, - "maxReceivers": 66, - "allowDynamicSrc": false, - "allowAnonymousSender": false, - "sources": "public, private, management, root", - "targets": "public, private, management, root" - } - ], ["policyAppSettings", { - "applicationName": "photoserver", - "userGroupName": "default", - "maxFrameSize": 222222, - "maxMessageSize": 222222, - "maxSessionWindow": 222222, - "maxSessions": 2, - "maxSenders": 22, - "maxReceivers": 22, - "allowDynamicSrc": false, - "allowAnonymousSender": false, - "sources": "public, private", - "targets": "public" + "connectionAllowDefault": true, + "settings": { + "anonymous" : { + "maxFrameSize": 111111, + "maxMessageSize": 111111, + "maxSessionWindow": 111111, + "maxSessions": 1, + "maxSenders": 11, + "maxReceivers": 11, + "allowDynamicSrc": false, + "allowAnonymousSender": false, + "sources": "public", + "targets": "" + }, + "users" : { + "maxFrameSize": 222222, + "maxMessageSize": 222222, + "maxSessionWindow": 222222, + "maxSessions": 2, + "maxSenders": 22, + "maxReceivers": 22, + "allowDynamicSrc": false, + "allowAnonymousSender": false, + "sources": "public, private", + "targets": "public" + }, + "paidsubscribers" : { + "maxFrameSize": 333333, + "maxMessageSize": 333333, + "maxSessionWindow": 333333, + "maxSessions": 3, + "maxSenders": 33, + "maxReceivers": 33, + "allowDynamicSrc": true, + "allowAnonymousSender": false, + "sources": "public, private", + "targets": "public, private" + }, + "test" : { + "maxFrameSize": 444444, + "maxMessageSize": 444444, + "maxSessionWindow": 444444, + "maxSessions": 4, + "maxSenders": 44, + "maxReceivers": 44, + "allowDynamicSrc": true, + "allowAnonymousSender": true, + "sources": "private", + "targets": "private" + }, + "admin" : { + "maxFrameSize": 555555, + "maxMessageSize": 555555, + "maxSessionWindow": 555555, + "maxSessions": 5, + "maxSenders": 55, + "maxReceivers": 55, + "allowDynamicSrc": true, + "allowAnonymousSender": true, + "sources": "public, private, management", + "targets": "public, private, management" + }, + "superuser" : { + "maxFrameSize": 666666, + "maxMessageSize": 666666, + "maxSessionWindow": 666666, + "maxSessions": 6, + "maxSenders": 66, + "maxReceivers": 66, + "allowDynamicSrc": false, + "allowAnonymousSender": false, + "sources": "public, private, management, root", + "targets": "public, private, management, root" + }, + "default" : { + "maxFrameSize": 222222, + "maxMessageSize": 222222, + "maxSessionWindow": 222222, + "maxSessions": 2, + "maxSenders": 22, + "maxReceivers": 22, + "allowDynamicSrc": false, + "allowAnonymousSender": false, + "sources": "public, private", + "targets": "public" + } + } } ] ] http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/tests/router_policy_test.py ---------------------------------------------------------------------- diff --git a/tests/router_policy_test.py b/tests/router_policy_test.py new file mode 100644 index 0000000..5d2505d --- /dev/null +++ b/tests/router_policy_test.py @@ -0,0 +1,243 @@ +# +# 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 unittest + +from qpid_dispatch_internal.policy.policy_util import HostAddr +from qpid_dispatch_internal.policy.policy_util import HostStruct +from qpid_dispatch_internal.policy.policy_util import PolicyError +from qpid_dispatch_internal.policy.policy_util import PolicyAppConnectionMgr +from qpid_dispatch_internal.policy.policy_local import PolicyLocal +from system_test import TestCase, main_module + +class PolicyHostAddrTest(TestCase): + + def expect_deny(self, badhostname, msg): + denied = False + try: + xxx = HostStruct(badhostname) + except PolicyError: + denied = True + self.assertTrue(denied, ("%s" % msg)) + + def check_hostaddr_match(self, tHostAddr, tString, expectOk=True): + # check that the string is a match for the addr + # check that the internal struct version matches, too + ha = HostStruct(tString) + if expectOk: + self.assertTrue( tHostAddr.match_str(tString) ) + self.assertTrue( tHostAddr.match_bin(ha) ) + else: + self.assertFalse( tHostAddr.match_str(tString) ) + self.assertFalse( tHostAddr.match_bin(ha) ) + + def test_policy_hostaddr_ipv4(self): + # Create simple host and range + aaa = HostAddr("192.168.1.1") + bbb = HostAddr("1.1.1.1,1.1.1.255") + # Verify host and range + self.check_hostaddr_match(aaa, "192.168.1.1") + self.check_hostaddr_match(aaa, "1.1.1.1", False) + self.check_hostaddr_match(aaa, "192.168.1.2", False) + self.check_hostaddr_match(bbb, "1.1.1.1") + self.check_hostaddr_match(bbb, "1.1.1.254") + self.check_hostaddr_match(bbb, "1.1.1.0", False) + self.check_hostaddr_match(bbb, "1.1.2.0", False) + + def test_policy_hostaddr_ipv6(self): + if not HostAddr.has_ipv6: + self.skipTest("System IPv6 support is not available") + # Create simple host and range + aaa = HostAddr("::1") + bbb = HostAddr("::1,::ffff") + ccc = HostAddr("ffff::0,ffff:ffff::0") + # Verify host and range + self.check_hostaddr_match(aaa, "::1") + self.check_hostaddr_match(aaa, "::2", False) + self.check_hostaddr_match(aaa, "ffff:ffff::0", False) + self.check_hostaddr_match(bbb, "::1") + self.check_hostaddr_match(bbb, "::fffe") + self.check_hostaddr_match(bbb, "::1:0", False) + self.check_hostaddr_match(bbb, "ffff::0", False) + self.check_hostaddr_match(ccc, "ffff::1") + self.check_hostaddr_match(ccc, "ffff:fffe:ffff:ffff::ffff") + self.check_hostaddr_match(ccc, "ffff:ffff::1", False) + self.check_hostaddr_match(ccc, "ffff:ffff:ffff:ffff::ffff", False) + + def test_policy_hostaddr_ipv4_wildcard(self): + aaa = HostAddr("*") + self.check_hostaddr_match(aaa,"0.0.0.0") + self.check_hostaddr_match(aaa,"127.0.0.1") + self.check_hostaddr_match(aaa,"255.254.253.252") + + + def test_policy_hostaddr_ipv6_wildcard(self): + if not HostAddr.has_ipv6: + self.skipTest("System IPv6 support is not available") + aaa = HostAddr("*") + self.check_hostaddr_match(aaa,"::0") + self.check_hostaddr_match(aaa,"::1") + self.check_hostaddr_match(aaa,"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + + def test_policy_malformed_hostaddr_ipv4(self): + self.expect_deny( "0.0.0.0.0", "Name or service not known") + self.expect_deny( "1.1.1.1,2.2.2.2,3.3.3.3", "arg count") + self.expect_deny( "9.9.9.9,8.8.8.8", "a > b") + + def test_policy_malformed_hostaddr_ipv6(self): + if not HostAddr.has_ipv6: + self.skipTest("System IPv6 support is not available") + self.expect_deny( "1::2::3", "Name or service not known") + self.expect_deny( "::1,::2,::3", "arg count") + self.expect_deny( "0:ff:0,0:fe:ffff:ffff::0", "a > b") + +class PolicyFile(TestCase): + + policy = PolicyLocal() + policy.test_load_config() + + def dict_compare(self, d1, d2): + d1_keys = set(d1.keys()) + d2_keys = set(d2.keys()) + intersect_keys = d1_keys.intersection(d2_keys) + added = d1_keys - d2_keys + removed = d2_keys - d1_keys + modified = {o : (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]} + same = set(o for o in intersect_keys if d1[o] == d2[o]) + return len(added) == 0 and len(removed) == 0 and len(modified) == 0 + + def test_policy1_test_zeke_ok(self): + p1 = PolicyFile.policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33333') + self.assertTrue(p1 == 'test') + upolicy = {} + self.assertTrue( + PolicyFile.policy.lookup_settings('photoserver', p1, upolicy) + ) + self.assertTrue(upolicy['maxFrameSize'] == 444444) + self.assertTrue(upolicy['maxMessageSize'] == 444444) + self.assertTrue(upolicy['maxSessionWindow'] == 444444) + self.assertTrue(upolicy['maxSessions'] == 4) + self.assertTrue(upolicy['maxSenders'] == 44) + self.assertTrue(upolicy['maxReceivers'] == 44) + self.assertTrue(upolicy['allowAnonymousSender']) + self.assertTrue(upolicy['allowDynamicSrc']) + self.assertTrue(len(upolicy['targets']) == 1) + self.assertTrue('private' in upolicy['targets']) + self.assertTrue(len(upolicy['sources']) == 1) + self.assertTrue('private' in upolicy['sources']) + + def test_policy1_test_zeke_bad_IP(self): + self.assertTrue( + PolicyFile.policy.lookup_user('zeke', '10.18.0.1', 'photoserver', "connid") == '') + self.assertTrue( + PolicyFile.policy.lookup_user('zeke', '72.135.2.9', 'photoserver', "connid") == '') + self.assertTrue( + PolicyFile.policy.lookup_user('zeke', '127.0.0.1', 'photoserver', "connid") == '') + + def test_policy1_test_zeke_bad_app(self): + self.assertTrue( + PolicyFile.policy.lookup_user('zeke', '192.168.100.5','galleria', "connid") == '') + + def test_policy1_test_users_same_permissions(self): + zname = PolicyFile.policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33333') + yname = PolicyFile.policy.lookup_user('ynot', '10.48.255.254', 'photoserver', '192.168.100.5:33334') + self.assertTrue( zname == yname ) + + +class PolicyAppConnectionMgrTests(TestCase): + + def test_policy_app_conn_mgr_fail_by_total(self): + stats = PolicyAppConnectionMgr(1, 2, 2) + diags = [] + self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) + self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) + self.assertTrue(len(diags) == 1) + self.assertTrue('by total' in diags[0]) + + def test_policy_app_conn_mgr_fail_by_user(self): + stats = PolicyAppConnectionMgr(3, 1, 2) + diags = [] + self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) + self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) + self.assertTrue(len(diags) == 1) + self.assertTrue('per user' in diags[0]) + + def test_policy_app_conn_mgr_fail_by_hosts(self): + stats = PolicyAppConnectionMgr(3, 2, 1) + diags = [] + self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) + self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) + self.assertTrue(len(diags) == 1) + self.assertTrue('per host' in diags[0]) + + def test_policy_app_conn_mgr_fail_by_user_hosts(self): + stats = PolicyAppConnectionMgr(3, 1, 1) + diags = [] + self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) + self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) + self.assertTrue(len(diags) == 2) + self.assertTrue('per user' in diags[0] or 'per user' in diags[1]) + self.assertTrue('per host' in diags[0] or 'per host' in diags[1]) + + def test_policy_app_conn_mgr_update(self): + stats = PolicyAppConnectionMgr(3, 1, 2) + diags = [] + self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) + self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) + self.assertTrue(len(diags) == 1) + self.assertTrue('per user' in diags[0]) + diags = [] + stats.update(3, 2, 2) + self.assertTrue(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) + + def test_policy_app_conn_mgr_create_bad_settings(self): + denied = False + try: + stats = PolicyAppConnectionMgr(-3, 1, 2) + except PolicyError: + denied = True + self.assertTrue(denied, "Failed to detect negative setting value.") + + def test_policy_app_conn_mgr_update_bad_settings(self): + denied = False + try: + stats = PolicyAppConnectionMgr(0, 0, 0) + except PolicyError: + denied = True + self.assertFalse(denied, "Should allow all zeros.") + try: + stats.update(0, -1, 0) + except PolicyError: + denied = True + self.assertTrue(denied, "Failed to detect negative setting value.") + + def test_policy_app_conn_mgr_larger_counts(self): + stats = PolicyAppConnectionMgr(10000, 10000, 10000) + diags = [] + for i in range(0, 10000): + self.assertTrue(stats.can_connect('1.1.1.1:' + str(i), 'chuck', '1.1.1.1', diags)) + self.assertTrue(len(diags) == 0) + self.assertFalse(stats.can_connect('1.1.1.1:10000', 'chuck', '1.1.1.1', diags)) + self.assertTrue(len(diags) == 3) + self.assertTrue(stats.connections_active == 10000) + self.assertTrue(stats.connections_approved == 10000) + self.assertTrue(stats.connections_denied == 1) + +if __name__ == '__main__': + unittest.main(main_module()) http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1036c0ef/tests/system_tests_policy.py ---------------------------------------------------------------------- diff --git a/tests/system_tests_policy.py b/tests/system_tests_policy.py index 14dfbf0..74f93c4 100644 --- a/tests/system_tests_policy.py +++ b/tests/system_tests_policy.py @@ -17,23 +17,11 @@ # under the License. # -import sys import unittest -from time import sleep from system_test import TestCase, Qdrouterd, main_module - -from proton import Message from proton import ConnectionException -from proton.reactor import AtMostOnce from proton.utils import BlockingConnection, LinkDetached -from qpid_dispatch.management.client import Node -from system_test import TIMEOUT - -from qpid_dispatch_internal.policy.policy_util import \ - HostAddr, PolicyError, HostStruct, PolicyAppConnectionMgr -from qpid_dispatch_internal.policy.policy_local import PolicyLocal - class AbsoluteConnectionCountLimit(TestCase): """ Verify that connections beyond the absolute limit are denied @@ -65,7 +53,7 @@ class AbsoluteConnectionCountLimit(TestCase): except ConnectionException: denied = True - self.assertFalse(denied) # connections that should open did not open + self.assertFalse(denied) # assert if connections that should open did not open # third connection should be denied denied = False @@ -74,253 +62,10 @@ class AbsoluteConnectionCountLimit(TestCase): except ConnectionException: denied = True - self.assertTrue(denied) # connection that should not open did open + self.assertTrue(denied) # assert if connection that should not open did open bc1.close() bc2.close() -class PolicyHostAddrTest(TestCase): - - def expect_deny(self, badhostname, msg): - denied = False - try: - xxx = HostStruct(badhostname) - except PolicyError: - denied = True - self.assertTrue(denied, ("%s" % msg)) - - def check_hostaddr_match(self, tHostAddr, tString, expectOk=True): - # check that the string is a match for the addr - # check that the internal struct version matches, too - ha = HostStruct(tString) - if expectOk: - self.assertTrue( tHostAddr.match_str(tString) ) - self.assertTrue( tHostAddr.match_bin(ha) ) - else: - self.assertFalse( tHostAddr.match_str(tString) ) - self.assertFalse( tHostAddr.match_bin(ha) ) - - def test_policy_hostaddr_ipv4(self): - # Create simple host and range - aaa = HostAddr("192.168.1.1") - bbb = HostAddr("1.1.1.1,1.1.1.255") - # Verify host and range - self.check_hostaddr_match(aaa, "192.168.1.1") - self.check_hostaddr_match(aaa, "1.1.1.1", False) - self.check_hostaddr_match(aaa, "192.168.1.2", False) - self.check_hostaddr_match(bbb, "1.1.1.1") - self.check_hostaddr_match(bbb, "1.1.1.254") - self.check_hostaddr_match(bbb, "1.1.1.0", False) - self.check_hostaddr_match(bbb, "1.1.2.0", False) - - def test_policy_hostaddr_ipv6(self): - if not HostAddr.has_ipv6: - self.skipTest("System IPv6 support is not available") - # Create simple host and range - aaa = HostAddr("::1") - bbb = HostAddr("::1,::ffff") - ccc = HostAddr("ffff::0,ffff:ffff::0") - # Verify host and range - self.check_hostaddr_match(aaa, "::1") - self.check_hostaddr_match(aaa, "::2", False) - self.check_hostaddr_match(aaa, "ffff:ffff::0", False) - self.check_hostaddr_match(bbb, "::1") - self.check_hostaddr_match(bbb, "::fffe") - self.check_hostaddr_match(bbb, "::1:0", False) - self.check_hostaddr_match(bbb, "ffff::0", False) - self.check_hostaddr_match(ccc, "ffff::1") - self.check_hostaddr_match(ccc, "ffff:fffe:ffff:ffff::ffff") - self.check_hostaddr_match(ccc, "ffff:ffff::1", False) - self.check_hostaddr_match(ccc, "ffff:ffff:ffff:ffff::ffff", False) - - def test_policy_hostaddr_ipv4_wildcard(self): - aaa = HostAddr("*") - self.check_hostaddr_match(aaa,"0.0.0.0") - self.check_hostaddr_match(aaa,"127.0.0.1") - self.check_hostaddr_match(aaa,"255.254.253.252") - - - def test_policy_hostaddr_ipv6_wildcard(self): - if not HostAddr.has_ipv6: - self.skipTest("System IPv6 support is not available") - aaa = HostAddr("*") - self.check_hostaddr_match(aaa,"::0") - self.check_hostaddr_match(aaa,"::1") - self.check_hostaddr_match(aaa,"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") - - def test_policy_malformed_hostaddr_ipv4(self): - self.expect_deny( "0.0.0.0.0", "Name or service not known") - self.expect_deny( "1.1.1.1,2.2.2.2,3.3.3.3", "arg count") - self.expect_deny( "9.9.9.9,8.8.8.8", "a > b") - - def test_policy_malformed_hostaddr_ipv6(self): - if not HostAddr.has_ipv6: - self.skipTest("System IPv6 support is not available") - self.expect_deny( "1::2::3", "Name or service not known") - self.expect_deny( "::1,::2,::3", "arg count") - self.expect_deny( "0:ff:0,0:fe:ffff:ffff::0", "a > b") - -class PolicyFile(TestCase): - - policy = PolicyLocal() - policy.test_load_config() - - def dict_compare(self, d1, d2): - d1_keys = set(d1.keys()) - d2_keys = set(d2.keys()) - intersect_keys = d1_keys.intersection(d2_keys) - added = d1_keys - d2_keys - removed = d2_keys - d1_keys - modified = {o : (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]} - same = set(o for o in intersect_keys if d1[o] == d2[o]) - return len(added) == 0 and len(removed) == 0 and len(modified) == 0 - - def test_policy1_test_zeke_ok(self): - unames = [] - self.assertTrue( - PolicyFile.policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33333', unames) - ) - upolicy = {} - self.assertTrue( - PolicyFile.policy.lookup_settings('photoserver', unames[0], upolicy) - ) - self.assertTrue(upolicy['maxFrameSize'] == 444444) - self.assertTrue(upolicy['maxMessageSize'] == 444444) - self.assertTrue(upolicy['maxSessionWindow'] == 444444) - self.assertTrue(upolicy['maxSessions'] == 4) - self.assertTrue(upolicy['maxSenders'] == 44) - self.assertTrue(upolicy['maxReceivers'] == 44) - self.assertTrue(upolicy['allowAnonymousSender']) - self.assertTrue(upolicy['allowDynamicSrc']) - self.assertTrue(len(upolicy['targets']) == 1) - self.assertTrue('private' in upolicy['targets']) - self.assertTrue(len(upolicy['sources']) == 1) - self.assertTrue('private' in upolicy['sources']) - - def test_policy1_test_zeke_bad_IP(self): - unames = [] - self.assertFalse( - PolicyFile.policy.lookup_user('zeke', '10.18.0.1', 'photoserver', "connid", unames) ) - self.assertFalse( - PolicyFile.policy.lookup_user('zeke', '72.135.2.9', 'photoserver', "connid", unames) ) - self.assertFalse( - PolicyFile.policy.lookup_user('zeke', '127.0.0.1', 'photoserver', "connid", unames) ) - - def test_policy1_test_zeke_bad_app(self): - unames = [] - self.assertFalse( - PolicyFile.policy.lookup_user('zeke', '192.168.100.5','galleria', "connid", unames) ) - - def test_policy1_test_users_same_permissions(self): - znames = [] - self.assertTrue( - PolicyFile.policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33333', znames) ) - ynames = [] - self.assertTrue( - PolicyFile.policy.lookup_user('ynot', '10.48.255.254', 'photoserver', '192.168.100.5:33334', ynames) ) - self.assertTrue( znames[0] == ynames[0] ) - - def test_policy1_superuser_aggregation(self): - unames = [] - self.assertTrue( - PolicyFile.policy.lookup_user('ellen', '72.135.2.9', 'photoserver', '75.135.2.9:33333', unames) - ) - upolicy = {} - self.assertTrue( - PolicyFile.policy.lookup_settings('photoserver', unames[0], upolicy) - ) - self.assertTrue(upolicy['maxFrameSize'] == 666666) - self.assertTrue(upolicy['maxMessageSize'] == 666666) - self.assertTrue(upolicy['maxSessionWindow'] == 666666) - self.assertTrue(upolicy['maxSessions'] == 6) - self.assertTrue(upolicy['maxSenders'] == 66) - self.assertTrue(upolicy['maxReceivers'] == 66) - self.assertTrue(upolicy['allowAnonymousSender']) - self.assertTrue(upolicy['allowDynamicSrc']) - addrs = ['public', 'private','management', 'root'] - self.assertTrue(len(upolicy['targets']) == 4) - self.assertTrue(len(upolicy['sources']) == 4) - for s in addrs: self.assertTrue(s in upolicy['targets']) - for s in addrs: self.assertTrue(s in upolicy['sources']) - -class PolicyAppConnectionMgrTests(TestCase): - - def test_policy_app_conn_mgr_fail_by_total(self): - stats = PolicyAppConnectionMgr(1, 2, 2) - diags = [] - self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) - self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) - self.assertTrue(len(diags) == 1) - self.assertTrue('by total' in diags[0]) - - def test_policy_app_conn_mgr_fail_by_user(self): - stats = PolicyAppConnectionMgr(3, 1, 2) - diags = [] - self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) - self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) - self.assertTrue(len(diags) == 1) - self.assertTrue('per user' in diags[0]) - - def test_policy_app_conn_mgr_fail_by_hosts(self): - stats = PolicyAppConnectionMgr(3, 2, 1) - diags = [] - self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) - self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) - self.assertTrue(len(diags) == 1) - self.assertTrue('per host' in diags[0]) - - def test_policy_app_conn_mgr_fail_by_user_hosts(self): - stats = PolicyAppConnectionMgr(3, 1, 1) - diags = [] - self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) - self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) - self.assertTrue(len(diags) == 2) - self.assertTrue('per user' in diags[0] or 'per user' in diags[1]) - self.assertTrue('per host' in diags[0] or 'per host' in diags[1]) - - def test_policy_app_conn_mgr_update(self): - stats = PolicyAppConnectionMgr(3, 1, 2) - diags = [] - self.assertTrue(stats.can_connect('10.10.10.10:10000', 'chuck', '10.10.10.10', diags)) - self.assertFalse(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) - self.assertTrue(len(diags) == 1) - self.assertTrue('per user' in diags[0]) - diags = [] - stats.update(3, 2, 2) - self.assertTrue(stats.can_connect('10.10.10.10:10001', 'chuck', '10.10.10.10', diags)) - - def test_policy_app_conn_mgr_create_bad_settings(self): - denied = False - try: - stats = PolicyAppConnectionMgr(-3, 1, 2) - except PolicyError: - denied = True - self.assertTrue(denied, "Failed to detect negative setting value.") - - def test_policy_app_conn_mgr_update_bad_settings(self): - denied = False - try: - stats = PolicyAppConnectionMgr(0, 0, 0) - except PolicyError: - denied = True - self.assertFalse(denied, "Should allow all zeros.") - try: - stats.update(0, -1, 0) - except PolicyError: - denied = True - self.assertTrue(denied, "Failed to detect negative setting value.") - - def test_policy_app_conn_mgr_larger_counts(self): - stats = PolicyAppConnectionMgr(10000, 10000, 10000) - diags = [] - for i in range(0, 10000): - self.assertTrue(stats.can_connect('1.1.1.1:' + str(i), 'chuck', '1.1.1.1', diags)) - self.assertTrue(len(diags) == 0) - self.assertFalse(stats.can_connect('1.1.1.1:10000', 'chuck', '1.1.1.1', diags)) - self.assertTrue(len(diags) == 3) - self.assertTrue(stats.connections_active == 10000) - self.assertTrue(stats.connections_approved == 10000) - self.assertTrue(stats.connections_denied == 1) - if __name__ == '__main__': unittest.main(main_module()) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
