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/27 23:28:29 UTC
[1/5] qpid-dispatch git commit: settings sources and targets are
strings. cut/paste error in test policy
Repository: qpid-dispatch
Updated Branches:
refs/heads/crolke-DISPATCH-188-1 6bfd1272d -> dfb8cfda4
settings sources and targets are strings. cut/paste error in test policy
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/11e9f078
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/11e9f078
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/11e9f078
Branch: refs/heads/crolke-DISPATCH-188-1
Commit: 11e9f0784c975b44e00155bb21734886237d8003
Parents: 6bfd127
Author: Chuck Rolke <cr...@redhat.com>
Authored: Mon Jan 25 15:31:54 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Mon Jan 25 15:31:54 2016 -0500
----------------------------------------------------------------------
python/qpid_dispatch/management/qdrouter.json | 4 ++--
tests/policy-1/test-router-with-policy.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/11e9f078/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index 923cf80..9565ba7 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -1170,13 +1170,13 @@
"create": true
},
"sources": {
- "type": "list",
+ "type": "string",
"description": "List of Source addresses allowed when creating receiving links.",
"required": false,
"create": true
},
"targets": {
- "type": "list",
+ "type": "string",
"description": "List of Target addresses allowed when creating sending links.",
"required": false,
"create": true
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/11e9f078/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 38a6f08..4988093 100644
--- a/tests/policy-1/test-router-with-policy.json
+++ b/tests/policy-1/test-router-with-policy.json
@@ -176,7 +176,7 @@
}
], ["policyAppSettings", {
"applicationName": "photoserver",
- "userGroupName": "users",
+ "userGroupName": "default",
"maxFrameSize": 222222,
"maxMessageSize": 222222,
"maxSessionWindow": 222222,
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[5/5] qpid-dispatch git commit: Move policy code to
qpid_dispatch_internal/policy/*. Remove policy test-as-main code.
Posted by ch...@apache.org.
Move policy code to qpid_dispatch_internal/policy/*.
Remove policy test-as-main code.
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/dfb8cfda
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/dfb8cfda
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/dfb8cfda
Branch: refs/heads/crolke-DISPATCH-188-1
Commit: dfb8cfda47daebb95f2487dd85aa9f1b2685281a
Parents: 2fb2ef4
Author: Chuck Rolke <cr...@redhat.com>
Authored: Wed Jan 27 17:27:05 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Wed Jan 27 17:27:05 2016 -0500
----------------------------------------------------------------------
.../qpid_dispatch_internal/management/agent.py | 2 +-
.../management/policy_local.py | 713 -------------------
.../management/policy_util.py | 335 ---------
.../qpid_dispatch_internal/policy/__init__.py | 20 +
.../policy/policy_local.py | 593 +++++++++++++++
.../policy/policy_util.py | 335 +++++++++
tests/system_tests_policy.py | 4 +-
7 files changed, 951 insertions(+), 1051 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/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 0d53bc1..013eddb 100644
--- a/python/qpid_dispatch_internal/management/agent.py
+++ b/python/qpid_dispatch_internal/management/agent.py
@@ -81,7 +81,7 @@ from .schema import ValidationError, SchemaEntity, EntityType
from .qdrouter import QdSchema
from ..router.message import Message
from ..router.address import Address
-from policy_local import PolicyLocal
+from ..policy.policy_local import PolicyLocal
def dictstr(d):
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/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
deleted file mode 100644
index 6c455a7..0000000
--- a/python/qpid_dispatch_internal/management/policy_local.py
+++ /dev/null
@@ -1,713 +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.
-"""
-
-#
-#
-class PolicyKeys(object):
- # Common key words
- KW_IGNORED_NAME = "name"
- KW_IGNORED_IDENTITY = "identity"
- KW_IGNORED_TYPE = "type"
- 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"
-
- # Policy settings key words
- KW_USER_GROUP_NAME = "userGroupName"
- KW_MAX_FRAME_SIZE = "maxFrameSize"
- KW_MAX_MESSAGE_SIZE = "maxMessageSize"
- KW_MAX_SESSION_WINDOW = "maxSessionWindow"
- KW_MAX_SESSIONS = "maxSessions"
- KW_MAX_SENDERS = "maxSenders"
- KW_MAX_RECEIVERS = "maxReceivers"
- KW_ALLOW_DYNAMIC_SRC = "allowDynamicSrc"
- KW_ALLOW_ANONYMOUS_SENDER = "allowAnonymousSender"
- KW_SOURCES = "sources"
- KW_TARGETS = "targets"
-
- # What settings does a user get when allowed to connect but
- # not restricted by a user group?
- KW_DEFAULT_SETTINGS = "default"
-
- # Config file separator character for two IP addresses in a range
- KC_CONFIG_IP_SEP = "-"
-
- # Config file separator character for names in a list
- KC_CONFIG_LIST_SEP = ","
-#
-#
-class PolicyCompiler(object):
- """
- 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_ruleset_options = [
- PolicyKeys.KW_IGNORED_NAME,
- PolicyKeys.KW_IGNORED_IDENTITY,
- PolicyKeys.KW_IGNORED_TYPE,
- PolicyKeys.KW_APPLICATION_NAME,
- PolicyKeys.KW_MAXCONN,
- PolicyKeys.KW_MAXCONNPERHOST,
- PolicyKeys.KW_MAXCONNPERUSER,
- PolicyKeys.KW_USER_GROUPS,
- PolicyKeys.KW_CONNECTION_GROUPS,
- PolicyKeys.KW_CONNECTION_POLICY,
- PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT
- ]
-
- 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,
- PolicyKeys.KW_MAX_SESSIONS,
- PolicyKeys.KW_MAX_SENDERS,
- PolicyKeys.KW_MAX_RECEIVERS,
- PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
- PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
- PolicyKeys.KW_SOURCES,
- PolicyKeys.KW_TARGETS
- ]
-
- 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 compile_connection_groups(self, name, submap, warnings, errors):
- """
- Handle an connectionGroups 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_GROUPS
- newmap = {}
- for coname in submap:
- try:
- ostr = str(submap[coname])
- olist = [x.strip(' ') for x in ostr.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
- newmap[coname] = []
- for co in olist:
- coha = HostAddr(co, PolicyKeys.KC_CONFIG_IP_SEP)
- 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 compile_app_settings(self, appname, usergroup, policy_in, policy_out, warnings, errors):
- """
- Compile a schema from processed json format to local internal format.
- @param[in] name application name
- @param[in] policy_in user config settings
- @param[out] policy_out validated Internal format
- @param[out] warnings nonfatal irregularities observed
- @param[out] errors descriptions of failure
- @return - settings are 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 = []
- for key, val in policy_in.iteritems():
- if key not in self.allowed_settings_options:
- warnings.append("Application '%s' user group '%s' option '%s' is ignored." %
- (appname, usergroup, key))
- if key in [PolicyKeys.KW_MAX_FRAME_SIZE,
- PolicyKeys.KW_MAX_MESSAGE_SIZE,
- PolicyKeys.KW_MAX_RECEIVERS,
- PolicyKeys.KW_MAX_SENDERS,
- PolicyKeys.KW_MAX_SESSION_WINDOW,
- PolicyKeys.KW_MAX_SESSIONS
- ]:
- if not self.validateNumber(val, 0, 0, cerror):
- errors.append("Application '%s' user group '%s' option '%s' has error '%s'." %
- (appname, usergroup, key, cerror[0]))
- return False
- policy_out[key] = val
- elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
- PolicyKeys.KW_ALLOW_DYNAMIC_SRC
- ]:
- if not type(val) is bool:
- errors.append("Application '%s' user group '%s' option '%s' has illegal boolean value '%s'." %
- (appname, usergroup, key, val))
- return False
- policy_out[key] = val
- elif key in [PolicyKeys.KW_SOURCES,
- PolicyKeys.KW_TARGETS
- ]:
- val = [x.strip(' ') for x in val.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
- # deduplicate address lists
- val = list(set(val))
- policy_out[key] = val
- return True
-
-
- def compile_access_ruleset(self, name, policy_in, policy_out, warnings, errors):
- """
- Compile a schema from processed json format to local 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_ruleset_options:
- warnings.append("Application '%s' option '%s' is ignored." %
- (name, key))
- if 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_USER_GROUPS,
- PolicyKeys.KW_CONNECTION_GROUPS,
- PolicyKeys.KW_CONNECTION_POLICY
- ]:
- 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_GROUPS:
- if not self.compile_connection_groups(name, val, warnings, errors):
- return False
- else:
- # deduplicate connectionIngressPolicy and userGroups lists
- for k,v in val.iteritems():
- v = [x.strip(' ') for x in v.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
- 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(object):
- """
- The policy database.
- """
-
- def __init__(self):
- """
- Create instance
- @params folder: relative path from __file__ to conf file folder
- """
- self.policydb = {}
- self.settingsdb = {}
- self.lookup_cache = {}
- self.stats = {}
- self.policy_compiler = PolicyCompiler()
- self.name_lookup_cache = {}
- self.blob_lookup_cache = {}
-
- #
- # Service interfaces
- #
- def create_ruleset(self, attributes):
- """
- Create named policy ruleset
- @param[in] attributes: from config
- """
- warnings = []
- diag = []
- candidate = {}
- name = attributes[PolicyKeys.KW_APPLICATION_NAME]
- 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
- # TODO: Create stats
-
- def create_settings(self, attributes):
- """
- Create named policy ruleset
- @param[in] attributes: from config
- """
- warnings = []
- diag = []
- 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)
- if not result:
- raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, diag[0]) )
- if len(warnings) > 0:
- print ("LogMe: Application '%s' has warnings: %s" %
- (app_name, warnings))
- if not app_name in self.settingsdb:
- self.settingsdb[app_name] = {}
- self.settingsdb[app_name][usergroup] = candidate # create named settings
-
- 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, 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):
- """
- Lookup function called from C.
- 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 name
- @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
- # 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:
- # TODO: ("LogMe: no policy defined for application %s" % app)
- policyname.append("")
- return False
-
- 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
- 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
- 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
-
- except Exception, e:
- #print str(e)
- #pdb.set_trace()
- return False
-
- 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[out] upolicy: dict holding policy values - the settings blob
- @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
- return True
- except Exception, e:
- #print str(e)
- #pdb.set_trace()
- return False
-
- 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 = 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])
-
-
-#
-# 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):
-
- def read_files(policy, path):
- """
- Read all .json conf files in path and create the policies they contain.
- @param policy: The policy_local to receive the configuration.
- @param path: The path relative to policy_local.py
- """
- apath = os.path.abspath(os.path.dirname(__file__))
- apath = os.path.join(apath, path)
- for i in os.listdir(apath):
- if i.endswith(".json"):
- read_file(policy, os.path.join(apath, i))
-
- def read_file(policy, fn):
- """
- Read a qdrouterd config file and extract the policy sections.
- @param policy: The policy_local to receive the configuration.
- @param fn: absolute path to file
- """
- try:
- with open(fn) as json_file:
- cp = json.load(json_file)
- for i in range(0, len(cp)):
- if cp[i][0] == "policyAccessRuleset":
- policy.create_ruleset(cp[i][1])
- elif cp[i][0] == "policyAppSettings":
- policy.create_settings(cp[i][1])
- else:
- # some config option we don't care about
- pass
- except Exception, e:
- # complain but otherwise ignore errors
- print("Error processing policy configuration file '%s' : %s" % (fn, e))
-
- usage = "usage: %prog [options]\nExercise policy_local functions."
- parser = optparse.OptionParser(usage=usage)
- parser.set_defaults(folder="")
- parser.add_option("-f", "--folder", action="store", type="string", dest="folder",
- help="Built-in configuration settings are loaded by default or by using an empty folder string."
- " Use '-f /some/path' to load a config from all .json files in that folder."
- " Paths may be absolute or relative to policy_local.py.")
- parser.add_option("-e", "--exercise", action="store_true", dest="exercise",
- help="Run canned tests. Expect canned tests to work with configs in ../../../tests/policy-1.")
-
- (options, args) = parser.parse_args()
-
- policy = PolicyLocal()
-
- if options.folder == "":
- # Empty folder name uses built-in configuration
- policy.test_load_config()
- else:
- # Load all .json files in given folder
- read_files(policy, options.folder)
-
- print("Policy rulesets available: %s" % policy.policy_db_get_names())
-
- if not options.exercise:
- 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
- policynames = []
- # pdb.set_trace()
- res1 = policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33334', policynames)
- print "\nLookup zeke from 192.168.100.5. Expecting True, result is %s" % res1
- print "\nResulting policy expecting 'test', is: %s" % policynames[0]
- # Hit the cache
- policynames = []
- res2 = policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33335', policynames)
-
- policynames3 = []
- res3 = policy.lookup_user('ellen', '72.135.2.9', 'photoserver', '72.135.2.9:33333', policynames3)
- print "\nLookup ellen from 72.135.2.9. Expect true. Result is %s" % res3
- print "Resulting policy is: %s" % policynames[0]
-
- policynames = []
- res4 = policy2.lookup_user('ellen', '72.135.2.9', 'photoserver', '72.135.2.9:33334', policynames)
- print "\nLookup policy2 ellen from 72.135.2.9. Expect false. Result is %s" % res4
-
- upolicy6 = {}
- res6 = policy.lookup_settings('photoserver', policynames3[0], upolicy6)
- res6a = upolicy6['maxFrameSize'] == 666666
- print "\nNamed settings lookup result = %s, and value check = %s" % (res6, res6a)
-
- print ("Tests success: %s" % (res1 and res2 and res3 and not res4 and res6 and res6a))
-
-
-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/dfb8cfda/python/qpid_dispatch_internal/management/policy_util.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy_util.py b/python/qpid_dispatch_internal/management/policy_util.py
deleted file mode 100644
index edcd01f..0000000
--- a/python/qpid_dispatch_internal/management/policy_util.py
+++ /dev/null
@@ -1,335 +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 socket
-import binascii
-
-
-#
-#
-class PolicyError(Exception):
- def __init__(self, value):
- self.value = value
- def __str__(self):
- return repr(self.value)
-
-#
-#
-class HostStruct():
- """
- HostStruct represents a single, binary socket address from getaddrinfo
- - name : name given to constructor; numeric IP or host name
- - saddr : net name resolved by getaddrinfo; numeric IP
- - family : saddr.family; int
- - binary : saddr packed binary address; binary string
- """
- families = [socket.AF_INET]
- famnames = ["IPv4"]
- if socket.has_ipv6:
- families.append(socket.AF_INET6)
- famnames.append("IPv6")
-
- def __init__(self, hostname):
- """
- Given a host name text string, return the socket info for it.
- @param[in] hostname host IP address to parse
- """
- try:
- res = socket.getaddrinfo(hostname, 0)
- if len(res) == 0:
- raise PolicyError("HostStruct: '%s' did not resolve to an IP address" % hostname)
- foundFirst = False
- saddr = ""
- sfamily = socket.AF_UNSPEC
- for i0 in range(0, len(res)):
- family, dum0, dum1, dum2, sockaddr = res[i0]
- if not foundFirst:
- if family in self.families:
- saddr = sockaddr[0]
- sfamily = family
- foundFirst = True
- else:
- if family in self.families:
- if not saddr == sockaddr[0] or not sfamily == family:
- raise PolicyError("HostStruct: '%s' resolves to multiple IP addresses" %
- hostname)
-
- if not foundFirst:
- raise PolicyError("HostStruct: '%s' did not resolve to one of the supported address family" %
- hostname)
- self.name = hostname
- self.saddr = saddr
- self.family = sfamily
- self.binary = socket.inet_pton(family, saddr)
- return
- except Exception, e:
- raise PolicyError("HostStruct: '%s' failed to resolve: '%s'" %
- (hostname, e))
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return self.__str__()
-
- def dump(self):
- return ("(%s, %s, %s, %s)" %
- (self.name,
- self.saddr,
- "AF_INET" if self.family == socket.AF_INET else "AF_INET6",
- binascii.hexlify(self.binary)))
-
-#
-#
-class HostAddr():
- """
- Provide HostIP address ranges and comparison functions.
- A HostIP may be:
- - single address: 10.10.1.1
- - a pair of addresses: 10.10.0.0,10.10.255.255
- - a wildcard: *
- Only IPv4 and IPv6 are supported.
- - No unix sockets.
- HostIP names must resolve to a single IP address.
- Address pairs define a range.
- - The second address must be numerically larger than the first address.
- - The addresses must be of the same address 'family', IPv4 or IPv6.
- The wildcard '*' matches all address IPv4 or IPv6.
- IPv6 support is conditional based on underlying OS network options.
- Raises a PolicyError on validation error in constructor.
- """
-
- def has_ipv6(self):
- return socket.has_ipv6
-
- def __init__(self, hostspec, separator=","):
- """
- Parse host spec into binary structures to use for comparisons.
- Validate the hostspec to enforce usage rules.
- """
- self.hoststructs = []
-
- if hostspec == "*":
- self.wildcard = True
- else:
- self.wildcard = False
-
- hosts = [x.strip() for x in hostspec.split(separator)]
-
- # hosts must contain one or two host specs
- if len(hosts) not in [1, 2]:
- raise PolicyError("hostspec must contain 1 or 2 host names")
- self.hoststructs.append(HostStruct(hosts[0]))
- if len(hosts) > 1:
- self.hoststructs.append(HostStruct(hosts[1]))
- if not self.hoststructs[0].family == self.hoststructs[1].family:
- raise PolicyError("mixed IPv4 and IPv6 host specs in range not allowed")
- c0 = self.memcmp(self.hoststructs[0].binary, self.hoststructs[1].binary)
- if c0 > 0:
- raise PolicyError("host specs in range must have lower numeric address first")
-
- def __str__(self):
- if self.wildcard:
- return "*"
- res = self.hoststructs[0].name
- if len(self.hoststructs) > 1:
- res += "," + self.hoststructs[1].name
- return res
-
- def __repr__(self):
- return self.__str__()
-
- def dump(self):
- if self.wildcard:
- return "(*)"
- res = "(" + self.hoststructs[0].dump()
- if len(self.hoststructs) > 1:
- res += "," + self.hoststructs[1].dump()
- res += ")"
- return res
-
- def memcmp(self, a, b):
- res = 0
- for i in range(0,len(a)):
- if a[i] > b[i]:
- res = 1
- break;
- elif a[i] < b[i]:
- res = -1
- break
- return res
-
- def match_bin(self, candidate):
- """
- Does the candidate hoststruct match the IP or range of IP addresses represented by this?
- @param[in] candidate the IP address to be tested
- @return candidate matches this or not
- """
- if self.wildcard:
- return True
- try:
- if not candidate.family == self.hoststructs[0].family:
- # sorry, wrong AF_INET family
- return False
- c0 = self.memcmp(candidate.binary, self.hoststructs[0].binary)
- if len(self.hoststructs) == 1:
- return c0 == 0
- c1 = self.memcmp(candidate.binary, self.hoststructs[1].binary)
- return c0 >= 0 and c1 <= 0
- except PolicyError:
- return False
- except Exception, e:
- assert isinstance(candidate, HostStruct), \
- ("Wrong type. Expected HostStruct but received %s" % candidate.__class__.__name__)
- return False
-
- def match_str(self, candidate):
- """
- Does the candidate string match the IP or range represented by this?
- @param[in] candidate the IP address to be tested
- @return candidate matches this or not
- """
- try:
- hoststruct = HostStruct(candidate)
- except PolicyError:
- return False
- return self.match_bin(hoststruct)
-
-#
-#
-class PolicyAppConnectionMgr():
- """
- Track policy user/host connection limits and statistics for one app.
- # limits - set at creation and by update()
- max_total : 20
- max_per_user : 5
- max_per_host : 10
- # statistics - maintained for the lifetime of corresponding application
- connections_approved : N
- connections_denied : N
- # live state - maintained for the lifetime of corresponding application
- connections_active : 5
- per_host_state : { 'host1' : [conn1, conn2, conn3],
- 'host2' : [conn4, conn5] }
- per_user_state : { 'user1' : [conn1, conn2, conn3],
- 'user2' : [conn4, conn5] }
- """
- def __init__(self, maxconn, maxconnperuser, maxconnperhost):
- """
- The object is constructed with the policy limits and zeroed counts.
- @param[in] maxconn maximum total concurrent connections
- @param[in] maxconnperuser maximum total conncurrent connections for each user
- @param[in] maxconnperuser maximum total conncurrent connections for each host
- """
- if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
- raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
- self.max_total = maxconn
- self.max_per_user = maxconnperuser
- self.max_per_host = maxconnperhost
- self.connections_approved = 0
- self.connections_denied = 0
- self.connections_active = 0
- self.per_host_state = {}
- self.per_user_state = {}
-
- def __str__(self):
- res = ("Connection Limits: total: %s, per user: %s, per host: %s\n" %
- (self.max_total, self.max_per_user, self.max_per_host))
- res += ("Connections Statistics: total approved: %s, total denied: %s" %
- (self.connections_approved, self.connections_denied))
- res += ("Connection State: total current: %s" % self.connections_active)
- res += ("User state: %s\n" % self.per_user_state)
- res += ("Host state: %s" % self.per_host_state)
- return res
-
- def __repr__(self):
- return self.__str__()
-
- def update(self, maxconn, maxconnperuser, maxconnperhost):
- """
- Reset connection limits
- @param[in] maxconn maximum total concurrent connections
- @param[in] maxconnperuser maximum total conncurrent connections for each user
- @param[in] maxconnperuser maximum total conncurrent connections for each host
- """
- if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
- raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
- self.max_total = maxconn
- self.max_per_user = maxconnperuser
- self.max_per_host = maxconnperhost
-
- def can_connect(self, conn_id, user, host, diags):
- """
- Register a connection attempt.
- If all the connection limit rules pass then add the
- user/host to the connection tables.
- @param[in] conn_id unique ID for connection, usually IP:port
- @param[in] user authenticated user ID
- @param[in] host IP address of host
- @param[out] diags on failure holds 1, 2, or 3 error strings
- @return connection is allowed and tracked in state tables
- """
- n_user = 0
- if user in self.per_user_state:
- n_user = len(self.per_user_state[user])
- n_host = 0
- if host in self.per_host_state:
- n_host = len(self.per_host_state[host])
-
- allowbytotal = self.max_total == 0 or self.connections_active < self.max_total
- allowbyuser = self.max_per_user == 0 or n_user < self.max_per_user
- allowbyhost = self.max_per_host == 0 or n_host < self.max_per_host
-
- if allowbytotal and allowbyuser and allowbyhost:
- if not user in self.per_user_state:
- self.per_user_state[user] = []
- self.per_user_state[user].append(conn_id)
- if not host in self.per_host_state:
- self.per_host_state[host] = []
- self.per_host_state[host].append(conn_id)
- self.connections_active += 1
- self.connections_approved += 1
- return True
- else:
- if not allowbytotal:
- diags.append("LogMe: INFO user '%s' from host '%s' denied connection by total connection limit" %
- (user, host))
- if not allowbyuser:
- diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per user limit" %
- (user, host))
- if not allowbyhost:
- diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per host limit" %
- (user, host))
- self.connections_denied += 1
- return False
-
- def disconnect(self, conn_id, user, host):
- """
- Unregister a connection
- """
- assert(self.connections_active > 0)
- assert(user in self.per_user_state)
- assert(conn_id in self.per_user_state[user])
- assert(host in self.max_per_host)
- assert(conn_id in self.max_per_host[host])
- self.connections_active -= 1
- self.per_user_state[user].remove(conn_id)
- self.per_host_state[host].remove(conn_id)
-
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/policy/__init__.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/policy/__init__.py b/python/qpid_dispatch_internal/policy/__init__.py
new file mode 100644
index 0000000..6417447
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/__init__.py
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+"""Qpid Dispatch internal policy package."""
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/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
new file mode 100644
index 0000000..7cc17de
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/policy_local.py
@@ -0,0 +1,593 @@
+#
+# 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.
+"""
+
+#
+#
+class PolicyKeys(object):
+ # Common key words
+ KW_IGNORED_NAME = "name"
+ KW_IGNORED_IDENTITY = "identity"
+ KW_IGNORED_TYPE = "type"
+ 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"
+
+ # Policy settings key words
+ KW_USER_GROUP_NAME = "userGroupName"
+ KW_MAX_FRAME_SIZE = "maxFrameSize"
+ KW_MAX_MESSAGE_SIZE = "maxMessageSize"
+ KW_MAX_SESSION_WINDOW = "maxSessionWindow"
+ KW_MAX_SESSIONS = "maxSessions"
+ KW_MAX_SENDERS = "maxSenders"
+ KW_MAX_RECEIVERS = "maxReceivers"
+ KW_ALLOW_DYNAMIC_SRC = "allowDynamicSrc"
+ KW_ALLOW_ANONYMOUS_SENDER = "allowAnonymousSender"
+ KW_SOURCES = "sources"
+ KW_TARGETS = "targets"
+
+ # What settings does a user get when allowed to connect but
+ # not restricted by a user group?
+ KW_DEFAULT_SETTINGS = "default"
+
+ # Config file separator character for two IP addresses in a range
+ KC_CONFIG_IP_SEP = "-"
+
+ # Config file separator character for names in a list
+ KC_CONFIG_LIST_SEP = ","
+#
+#
+class PolicyCompiler(object):
+ """
+ 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_ruleset_options = [
+ PolicyKeys.KW_IGNORED_NAME,
+ PolicyKeys.KW_IGNORED_IDENTITY,
+ PolicyKeys.KW_IGNORED_TYPE,
+ PolicyKeys.KW_APPLICATION_NAME,
+ PolicyKeys.KW_MAXCONN,
+ PolicyKeys.KW_MAXCONNPERHOST,
+ PolicyKeys.KW_MAXCONNPERUSER,
+ PolicyKeys.KW_USER_GROUPS,
+ PolicyKeys.KW_CONNECTION_GROUPS,
+ PolicyKeys.KW_CONNECTION_POLICY,
+ PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT
+ ]
+
+ 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,
+ PolicyKeys.KW_MAX_SESSIONS,
+ PolicyKeys.KW_MAX_SENDERS,
+ PolicyKeys.KW_MAX_RECEIVERS,
+ PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
+ PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+ PolicyKeys.KW_SOURCES,
+ PolicyKeys.KW_TARGETS
+ ]
+
+ 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 compile_connection_groups(self, name, submap, warnings, errors):
+ """
+ Handle an connectionGroups 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_GROUPS
+ newmap = {}
+ for coname in submap:
+ try:
+ ostr = str(submap[coname])
+ olist = [x.strip(' ') for x in ostr.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+ newmap[coname] = []
+ for co in olist:
+ coha = HostAddr(co, PolicyKeys.KC_CONFIG_IP_SEP)
+ 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 compile_app_settings(self, appname, usergroup, policy_in, policy_out, warnings, errors):
+ """
+ Compile a schema from processed json format to local internal format.
+ @param[in] name application name
+ @param[in] policy_in user config settings
+ @param[out] policy_out validated Internal format
+ @param[out] warnings nonfatal irregularities observed
+ @param[out] errors descriptions of failure
+ @return - settings are 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 = []
+ for key, val in policy_in.iteritems():
+ if key not in self.allowed_settings_options:
+ warnings.append("Application '%s' user group '%s' option '%s' is ignored." %
+ (appname, usergroup, key))
+ if key in [PolicyKeys.KW_MAX_FRAME_SIZE,
+ PolicyKeys.KW_MAX_MESSAGE_SIZE,
+ PolicyKeys.KW_MAX_RECEIVERS,
+ PolicyKeys.KW_MAX_SENDERS,
+ PolicyKeys.KW_MAX_SESSION_WINDOW,
+ PolicyKeys.KW_MAX_SESSIONS
+ ]:
+ if not self.validateNumber(val, 0, 0, cerror):
+ errors.append("Application '%s' user group '%s' option '%s' has error '%s'." %
+ (appname, usergroup, key, cerror[0]))
+ return False
+ policy_out[key] = val
+ elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+ PolicyKeys.KW_ALLOW_DYNAMIC_SRC
+ ]:
+ if not type(val) is bool:
+ errors.append("Application '%s' user group '%s' option '%s' has illegal boolean value '%s'." %
+ (appname, usergroup, key, val))
+ return False
+ policy_out[key] = val
+ elif key in [PolicyKeys.KW_SOURCES,
+ PolicyKeys.KW_TARGETS
+ ]:
+ val = [x.strip(' ') for x in val.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+ # deduplicate address lists
+ val = list(set(val))
+ policy_out[key] = val
+ return True
+
+
+ def compile_access_ruleset(self, name, policy_in, policy_out, warnings, errors):
+ """
+ Compile a schema from processed json format to local 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_ruleset_options:
+ warnings.append("Application '%s' option '%s' is ignored." %
+ (name, key))
+ if 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_USER_GROUPS,
+ PolicyKeys.KW_CONNECTION_GROUPS,
+ PolicyKeys.KW_CONNECTION_POLICY
+ ]:
+ 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_GROUPS:
+ if not self.compile_connection_groups(name, val, warnings, errors):
+ return False
+ else:
+ # deduplicate connectionIngressPolicy and userGroups lists
+ for k,v in val.iteritems():
+ v = [x.strip(' ') for x in v.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+ 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(object):
+ """
+ The policy database.
+ """
+
+ def __init__(self):
+ """
+ Create instance
+ @params folder: relative path from __file__ to conf file folder
+ """
+ self.policydb = {}
+ self.settingsdb = {}
+ self.lookup_cache = {}
+ self.stats = {}
+ self.policy_compiler = PolicyCompiler()
+ self.name_lookup_cache = {}
+ self.blob_lookup_cache = {}
+
+ #
+ # Service interfaces
+ #
+ def create_ruleset(self, attributes):
+ """
+ Create named policy ruleset
+ @param[in] attributes: from config
+ """
+ warnings = []
+ diag = []
+ candidate = {}
+ name = attributes[PolicyKeys.KW_APPLICATION_NAME]
+ 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
+ # TODO: Create stats
+
+ def create_settings(self, attributes):
+ """
+ Create named policy ruleset
+ @param[in] attributes: from config
+ """
+ warnings = []
+ diag = []
+ 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)
+ if not result:
+ raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, diag[0]) )
+ if len(warnings) > 0:
+ print ("LogMe: Application '%s' has warnings: %s" %
+ (app_name, warnings))
+ if not app_name in self.settingsdb:
+ self.settingsdb[app_name] = {}
+ self.settingsdb[app_name][usergroup] = candidate # create named settings
+
+ 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, 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):
+ """
+ Lookup function called from C.
+ 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 name
+ @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
+ # 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:
+ # TODO: ("LogMe: no policy defined for application %s" % app)
+ policyname.append("")
+ return False
+
+ 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
+ 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
+ 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
+
+ except Exception, e:
+ #print str(e)
+ #pdb.set_trace()
+ return False
+
+ 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[out] upolicy: dict holding policy values - the settings blob
+ @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
+ return True
+ except Exception, e:
+ #print str(e)
+ #pdb.set_trace()
+ return False
+
+ 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 = 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])
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[2/5] qpid-dispatch git commit: Add example .json file replacement
for common .conf file.
Posted by ch...@apache.org.
Add example .json file replacement for common .conf file.
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/bb094238
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/bb094238
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/bb094238
Branch: refs/heads/crolke-DISPATCH-188-1
Commit: bb09423817c21ec61284240412f7abc5ddff78a4
Parents: 11e9f07
Author: Chuck Rolke <cr...@redhat.com>
Authored: Wed Jan 27 10:19:26 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Wed Jan 27 10:19:26 2016 -0500
----------------------------------------------------------------------
tests/config-1/A.json | 81 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/bb094238/tests/config-1/A.json
----------------------------------------------------------------------
diff --git a/tests/config-1/A.json b/tests/config-1/A.json
new file mode 100644
index 0000000..01d62e1
--- /dev/null
+++ b/tests/config-1/A.json
@@ -0,0 +1,81 @@
+##
+## 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
+##
+
+
+[
+##
+## Container section - Configures the general operation of the AMQP container.
+##
+ ["container", {
+##
+## workerThreads - The number of threads that will be created to
+## process message traffic and other application work (timers, nonAmqp
+## file descriptors, etc.)
+##
+## The number of threads should be related to the number of available
+## processor cores. To fully utilize a quad-core system, set the
+## number of threads to 4.
+##
+ "workerThreads": 4,
+
+##
+## containerName - The name of the AMQP container. If not specified,
+## the container name will be set to a value of the container's
+## choosing. The automatically assigned container name is not
+## guaranteed to be persistent across restarts of the container.
+##
+ "containerName": "Qpid.Dispatch.Router.A"
+ }],
+
+ ["router", {
+ "mode": "standalone",
+ "routerId": "QDR"
+ }],
+
+##
+## Listeners and Connectors
+##
+ ["listener", {
+ "addr": "0.0.0.0",
+ "port": 20000,
+ "saslMechanisms": "ANONYMOUS"
+ }],
+ ["fixedAddress", {
+ "prefix": "/closest/",
+ "fanout": "single",
+ "bias": "closest"
+ }],
+
+ ["fixedAddress", {
+ "prefix": "/spread/",
+ "fanout": "single",
+ "bias": "spread"
+ }],
+
+ ["fixedAddress", {
+ "prefix": "/multicast/",
+ "fanout": "multiple"
+ }],
+
+ ["fixedAddress", {
+ "prefix": "/",
+ "fanout": "multiple"
+ }]
+]
+
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[4/5] qpid-dispatch git commit: Move policy code to
qpid_dispatch_internal/policy/*. Remove policy test-as-main code.
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/policy/policy_util.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/policy/policy_util.py b/python/qpid_dispatch_internal/policy/policy_util.py
new file mode 100644
index 0000000..edcd01f
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/policy_util.py
@@ -0,0 +1,335 @@
+#
+# 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 socket
+import binascii
+
+
+#
+#
+class PolicyError(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+#
+#
+class HostStruct():
+ """
+ HostStruct represents a single, binary socket address from getaddrinfo
+ - name : name given to constructor; numeric IP or host name
+ - saddr : net name resolved by getaddrinfo; numeric IP
+ - family : saddr.family; int
+ - binary : saddr packed binary address; binary string
+ """
+ families = [socket.AF_INET]
+ famnames = ["IPv4"]
+ if socket.has_ipv6:
+ families.append(socket.AF_INET6)
+ famnames.append("IPv6")
+
+ def __init__(self, hostname):
+ """
+ Given a host name text string, return the socket info for it.
+ @param[in] hostname host IP address to parse
+ """
+ try:
+ res = socket.getaddrinfo(hostname, 0)
+ if len(res) == 0:
+ raise PolicyError("HostStruct: '%s' did not resolve to an IP address" % hostname)
+ foundFirst = False
+ saddr = ""
+ sfamily = socket.AF_UNSPEC
+ for i0 in range(0, len(res)):
+ family, dum0, dum1, dum2, sockaddr = res[i0]
+ if not foundFirst:
+ if family in self.families:
+ saddr = sockaddr[0]
+ sfamily = family
+ foundFirst = True
+ else:
+ if family in self.families:
+ if not saddr == sockaddr[0] or not sfamily == family:
+ raise PolicyError("HostStruct: '%s' resolves to multiple IP addresses" %
+ hostname)
+
+ if not foundFirst:
+ raise PolicyError("HostStruct: '%s' did not resolve to one of the supported address family" %
+ hostname)
+ self.name = hostname
+ self.saddr = saddr
+ self.family = sfamily
+ self.binary = socket.inet_pton(family, saddr)
+ return
+ except Exception, e:
+ raise PolicyError("HostStruct: '%s' failed to resolve: '%s'" %
+ (hostname, e))
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return self.__str__()
+
+ def dump(self):
+ return ("(%s, %s, %s, %s)" %
+ (self.name,
+ self.saddr,
+ "AF_INET" if self.family == socket.AF_INET else "AF_INET6",
+ binascii.hexlify(self.binary)))
+
+#
+#
+class HostAddr():
+ """
+ Provide HostIP address ranges and comparison functions.
+ A HostIP may be:
+ - single address: 10.10.1.1
+ - a pair of addresses: 10.10.0.0,10.10.255.255
+ - a wildcard: *
+ Only IPv4 and IPv6 are supported.
+ - No unix sockets.
+ HostIP names must resolve to a single IP address.
+ Address pairs define a range.
+ - The second address must be numerically larger than the first address.
+ - The addresses must be of the same address 'family', IPv4 or IPv6.
+ The wildcard '*' matches all address IPv4 or IPv6.
+ IPv6 support is conditional based on underlying OS network options.
+ Raises a PolicyError on validation error in constructor.
+ """
+
+ def has_ipv6(self):
+ return socket.has_ipv6
+
+ def __init__(self, hostspec, separator=","):
+ """
+ Parse host spec into binary structures to use for comparisons.
+ Validate the hostspec to enforce usage rules.
+ """
+ self.hoststructs = []
+
+ if hostspec == "*":
+ self.wildcard = True
+ else:
+ self.wildcard = False
+
+ hosts = [x.strip() for x in hostspec.split(separator)]
+
+ # hosts must contain one or two host specs
+ if len(hosts) not in [1, 2]:
+ raise PolicyError("hostspec must contain 1 or 2 host names")
+ self.hoststructs.append(HostStruct(hosts[0]))
+ if len(hosts) > 1:
+ self.hoststructs.append(HostStruct(hosts[1]))
+ if not self.hoststructs[0].family == self.hoststructs[1].family:
+ raise PolicyError("mixed IPv4 and IPv6 host specs in range not allowed")
+ c0 = self.memcmp(self.hoststructs[0].binary, self.hoststructs[1].binary)
+ if c0 > 0:
+ raise PolicyError("host specs in range must have lower numeric address first")
+
+ def __str__(self):
+ if self.wildcard:
+ return "*"
+ res = self.hoststructs[0].name
+ if len(self.hoststructs) > 1:
+ res += "," + self.hoststructs[1].name
+ return res
+
+ def __repr__(self):
+ return self.__str__()
+
+ def dump(self):
+ if self.wildcard:
+ return "(*)"
+ res = "(" + self.hoststructs[0].dump()
+ if len(self.hoststructs) > 1:
+ res += "," + self.hoststructs[1].dump()
+ res += ")"
+ return res
+
+ def memcmp(self, a, b):
+ res = 0
+ for i in range(0,len(a)):
+ if a[i] > b[i]:
+ res = 1
+ break;
+ elif a[i] < b[i]:
+ res = -1
+ break
+ return res
+
+ def match_bin(self, candidate):
+ """
+ Does the candidate hoststruct match the IP or range of IP addresses represented by this?
+ @param[in] candidate the IP address to be tested
+ @return candidate matches this or not
+ """
+ if self.wildcard:
+ return True
+ try:
+ if not candidate.family == self.hoststructs[0].family:
+ # sorry, wrong AF_INET family
+ return False
+ c0 = self.memcmp(candidate.binary, self.hoststructs[0].binary)
+ if len(self.hoststructs) == 1:
+ return c0 == 0
+ c1 = self.memcmp(candidate.binary, self.hoststructs[1].binary)
+ return c0 >= 0 and c1 <= 0
+ except PolicyError:
+ return False
+ except Exception, e:
+ assert isinstance(candidate, HostStruct), \
+ ("Wrong type. Expected HostStruct but received %s" % candidate.__class__.__name__)
+ return False
+
+ def match_str(self, candidate):
+ """
+ Does the candidate string match the IP or range represented by this?
+ @param[in] candidate the IP address to be tested
+ @return candidate matches this or not
+ """
+ try:
+ hoststruct = HostStruct(candidate)
+ except PolicyError:
+ return False
+ return self.match_bin(hoststruct)
+
+#
+#
+class PolicyAppConnectionMgr():
+ """
+ Track policy user/host connection limits and statistics for one app.
+ # limits - set at creation and by update()
+ max_total : 20
+ max_per_user : 5
+ max_per_host : 10
+ # statistics - maintained for the lifetime of corresponding application
+ connections_approved : N
+ connections_denied : N
+ # live state - maintained for the lifetime of corresponding application
+ connections_active : 5
+ per_host_state : { 'host1' : [conn1, conn2, conn3],
+ 'host2' : [conn4, conn5] }
+ per_user_state : { 'user1' : [conn1, conn2, conn3],
+ 'user2' : [conn4, conn5] }
+ """
+ def __init__(self, maxconn, maxconnperuser, maxconnperhost):
+ """
+ The object is constructed with the policy limits and zeroed counts.
+ @param[in] maxconn maximum total concurrent connections
+ @param[in] maxconnperuser maximum total conncurrent connections for each user
+ @param[in] maxconnperuser maximum total conncurrent connections for each host
+ """
+ if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
+ raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
+ self.max_total = maxconn
+ self.max_per_user = maxconnperuser
+ self.max_per_host = maxconnperhost
+ self.connections_approved = 0
+ self.connections_denied = 0
+ self.connections_active = 0
+ self.per_host_state = {}
+ self.per_user_state = {}
+
+ def __str__(self):
+ res = ("Connection Limits: total: %s, per user: %s, per host: %s\n" %
+ (self.max_total, self.max_per_user, self.max_per_host))
+ res += ("Connections Statistics: total approved: %s, total denied: %s" %
+ (self.connections_approved, self.connections_denied))
+ res += ("Connection State: total current: %s" % self.connections_active)
+ res += ("User state: %s\n" % self.per_user_state)
+ res += ("Host state: %s" % self.per_host_state)
+ return res
+
+ def __repr__(self):
+ return self.__str__()
+
+ def update(self, maxconn, maxconnperuser, maxconnperhost):
+ """
+ Reset connection limits
+ @param[in] maxconn maximum total concurrent connections
+ @param[in] maxconnperuser maximum total conncurrent connections for each user
+ @param[in] maxconnperuser maximum total conncurrent connections for each host
+ """
+ if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
+ raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
+ self.max_total = maxconn
+ self.max_per_user = maxconnperuser
+ self.max_per_host = maxconnperhost
+
+ def can_connect(self, conn_id, user, host, diags):
+ """
+ Register a connection attempt.
+ If all the connection limit rules pass then add the
+ user/host to the connection tables.
+ @param[in] conn_id unique ID for connection, usually IP:port
+ @param[in] user authenticated user ID
+ @param[in] host IP address of host
+ @param[out] diags on failure holds 1, 2, or 3 error strings
+ @return connection is allowed and tracked in state tables
+ """
+ n_user = 0
+ if user in self.per_user_state:
+ n_user = len(self.per_user_state[user])
+ n_host = 0
+ if host in self.per_host_state:
+ n_host = len(self.per_host_state[host])
+
+ allowbytotal = self.max_total == 0 or self.connections_active < self.max_total
+ allowbyuser = self.max_per_user == 0 or n_user < self.max_per_user
+ allowbyhost = self.max_per_host == 0 or n_host < self.max_per_host
+
+ if allowbytotal and allowbyuser and allowbyhost:
+ if not user in self.per_user_state:
+ self.per_user_state[user] = []
+ self.per_user_state[user].append(conn_id)
+ if not host in self.per_host_state:
+ self.per_host_state[host] = []
+ self.per_host_state[host].append(conn_id)
+ self.connections_active += 1
+ self.connections_approved += 1
+ return True
+ else:
+ if not allowbytotal:
+ diags.append("LogMe: INFO user '%s' from host '%s' denied connection by total connection limit" %
+ (user, host))
+ if not allowbyuser:
+ diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per user limit" %
+ (user, host))
+ if not allowbyhost:
+ diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per host limit" %
+ (user, host))
+ self.connections_denied += 1
+ return False
+
+ def disconnect(self, conn_id, user, host):
+ """
+ Unregister a connection
+ """
+ assert(self.connections_active > 0)
+ assert(user in self.per_user_state)
+ assert(conn_id in self.per_user_state[user])
+ assert(host in self.max_per_host)
+ assert(conn_id in self.max_per_host[host])
+ self.connections_active -= 1
+ self.per_user_state[user].remove(conn_id)
+ self.per_host_state[host].remove(conn_id)
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/tests/system_tests_policy.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_policy.py b/tests/system_tests_policy.py
index be7389f..14dfbf0 100644
--- a/tests/system_tests_policy.py
+++ b/tests/system_tests_policy.py
@@ -30,9 +30,9 @@ from proton.utils import BlockingConnection, LinkDetached
from qpid_dispatch.management.client import Node
from system_test import TIMEOUT
-from qpid_dispatch_internal.management.policy_util import \
+from qpid_dispatch_internal.policy.policy_util import \
HostAddr, PolicyError, HostStruct, PolicyAppConnectionMgr
-from qpid_dispatch_internal.management.policy_local import PolicyLocal
+from qpid_dispatch_internal.policy.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
[3/5] qpid-dispatch git commit: Revert "DISPATCH-187 - Add
requirement that the depended-on PythonLibs package be version 2."
Posted by ch...@apache.org.
Revert "DISPATCH-187 - Add requirement that the depended-on PythonLibs package be version 2."
This reverts commit 3eee868e6ed4449c02de6a179cb595f321a37371.
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/2fb2ef41
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/2fb2ef41
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/2fb2ef41
Branch: refs/heads/crolke-DISPATCH-188-1
Commit: 2fb2ef41787427ffe1531f6e35e59eb65ec9fd70
Parents: bb09423
Author: Ted Ross <tr...@redhat.com>
Authored: Wed Nov 4 17:07:36 2015 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Wed Jan 27 11:29:43 2016 -0500
----------------------------------------------------------------------
CMakeLists.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2fb2ef41/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f289a29..0d55205 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,8 +36,8 @@ include(CheckLibraryExists)
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckIncludeFiles)
-
-find_package(PythonLibs 2 REQUIRED)
+include(FindPythonInterp)
+include(FindPythonLibs)
enable_testing()
include (CTest)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org