qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From c...@apache.org
Subject [2/2] qpid-dispatch git commit: Prepare for rework to get complete configuration from qdrouter.json.
Date Mon, 18 Jan 2016 21:21:52 GMT
Prepare for rework to get complete configuration from qdrouter.json.


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/c284c20b
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/c284c20b
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/c284c20b

Branch: refs/heads/crolke-DISPATCH-188-1
Commit: c284c20b828c5f491d7137a180029a819f7c7354
Parents: 6558a5d
Author: Chuck Rolke <crolke@redhat.com>
Authored: Mon Jan 18 16:18:48 2016 -0500
Committer: Chuck Rolke <crolke@redhat.com>
Committed: Mon Jan 18 16:18:48 2016 -0500

----------------------------------------------------------------------
 python/qpid_dispatch/management/qdrouter.json   | 169 +++++++++++--------
 .../qpid_dispatch_internal/management/config.py |  23 ++-
 .../management/policy_local.py                  | 136 ++++++++++++---
 3 files changed, 233 insertions(+), 95 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c284c20b/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index 87c45ef..9d1aae0 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -1001,173 +1001,204 @@
         },
 
         "policy": {
-            "description": "Defines user connection and access policy",
+            "description": "Defines global connection limit",
             "extends": "configurationEntity",
             "singleton": true,
             "attributes": {
                 "maximumConnections": {
                     "type": "integer",
                     "default": 0,
-                    "description": "The maximum number of concurrent client connections allowed
to this router. Zero implies no limit.",
+                    "description": "Global maximum number of concurrent client connections
allowed. Zero implies no limit. This limit is always enforced even if no other policy settings
have been defined.",
                     "required": false,
                     "create": true
                 },
-                "policyDb": {
-                    "type": "path",
-                    "description": "The path to the folder that holds local policy definition
files.",
-		    "required": false,
-                    "create": true
-                },
                 "connectionsProcessed": {"type": "integer", "graph": true},
                 "connectionsDenied": {"type": "integer", "graph": true},
                 "connectionsCurrent": {"type": "integer", "graph": true}
             }
         },
 
-        "policy.appConnections": {
-            "description": "Defines per application connection limits, counts, and state.",
-            "extends": "operationalEntity",
+        "policy.accessRuleset": {
+            "description": "Per application definition of the locations from which users
may connect and the groups to which users belong.",
+            "extends": "configurationEntity",
             "attributes": {
-                "appName": {
+                "name": {
                     "type": "string",
-                    "description": "Application to which these statistics apply",
-                    "create": true
+                    "description": "The application name.",
+                    "required": true
                 },
-
                 "maxConnections": {
                     "type": "integer",
-                    "description": "The maximum number of concurrent client connections allowed
to this application. Zero implies no limit."
-                    },
+                    "default": 0,
+                    "description": "Maximum number of concurrent client connections allowed.
Zero implies no limit.",
+                    "required": false,
+                    "create": true
+                },
                 "maxConnPerUser": {
                     "type": "integer",
-                    "description": "The maximum number of concurrent connections allowed
to this application for a single authenticated user. Zero implies no limit."
-                    },
+                    "default": 0,
+                    "description": "Maximum number of concurrent client connections allowed
for any single user. Zero implies no limit.",
+                    "required": false,
+                    "create": true
+                },
                 "maxConnPerHost": {
                     "type": "integer",
-                    "description": "The maximum number of concurrent connections allowed
to this application from a remote host. Zero implies no limit."
+                    "default": 0,
+                    "description": "Maximum number of concurrent client connections allowed
for any remote host. Zero implies no limit.",
+                    "required": false,
+                    "create": true
                 },
-
-                "connectionsApproved": {"type": "integer", "graph": true},
-                "connectionsDenied": {"type": "integer", "graph": true},
-                "connectionsActive": {"type": "integer", "graph": true},
-                "perUserState": {
+                "userGroups": {
                     "type": "map",
-                    "description": "Index is authenticated username; data is comma separated
string naming the user's connections."
+                    "description": "A map where each key is a user group name and the corresponding
value is a list of users in that group. Users who are assigned to one or more groups are deemed
'restricted'. Restricted users are subject to connection ingress policy and are assigned policy
settings based on the assigned user groups. Unrestricted users may be allowed or denied. If
unrestricted users are allowed to connect then they are assigned to user group default.",
+                    "required": false,
+                    "create": true
                 },
-                "perHostState": {
+                "connectionGroups": {
                     "type": "map",
-                    "description": "Index is remote host; data is comma separated string
naming the host's connections."
+                    "description": "A map where each key is a connection group name and the
corresponding value is a list of IP addresses in that group. IP addresses may be numeric IPv4
or IPv6 host address or comma separated host ranges.  The wildcard host address '*' represents
any host address.",
+                    "required": false,
+                    "create": true
+                },
+                "connectionIngressPolicies": {
+                    "type": "map",
+                    "description": "A map where each key is a user group name and the corresponding
value is a list of connection groups. Users who are members of the user group are allowed
to connect only from a host in one of the named connection policy groups.",
+                    "required": false,
+                    "create": true
+                },
+                "connectionAllowDefault": {
+                    "type": "boolean",
+                    "description": "Unrestricted users, those who are not members of a defined
user group, are allowed to connect to this application.",
+                    "default": false,
+                    "required": false,
+                    "create": true
                 }
             }
         },
-
-        "policy.appStatistics": {
-            "description": "Aggregated statistics for one application.",
+        "policy.accessStats": {
+            "description": "Per application connection and access statistics.",
             "extends": "operationalEntity",
             "attributes": {
-                "appName": {
+                "name": {
                     "type": "string",
-                    "description": "Application to which these statistics apply",
-                    "create": true
+                    "description": "The application name."
+                },
+                "connectionsApproved": {"type": "integer", "graph": true},
+                "connectionsDenied": {"type": "integer", "graph": true},
+                "connectionsCurrent": {"type": "integer", "graph": true},
+                "perUsrerState": {
+                    "type": "map",
+                    "description": "A map where the key is the authenticated user name and
the value is a list of the user's connections."
                 },
+                "perHostState": {
+                    "type": "map",
+                    "description": "A map where the key is the host name and the value is
a list of the host's connections."
+                }
 
-                "maxSendersDenials": {"type": "integer", "graph": true},
-                "maxReceiversDenials": {"type": "integer", "graph": true},
-                "dynamicSrcDenials": {"type": "integer", "graph": true},
-                "anonmousSenderDenials": {"type": "integer", "graph": true},
-                "sourceDenials": {"type": "integer", "graph": true},
-                "targetsDenials": {"type": "integer", "graph": true}
             }
-        },
 
-        "policy.appGroupSettings": {
-            "description": "Defines control values for a group of users in one application",
+        },
+        "policy.settings": {
+            "description": "For a given application and user group define the policy settings
applied to the user's AMQP connection.",
             "extends": "configurationEntity",
             "attributes": {
-                "appName": {
+                "applicationName": {
                     "type": "string",
-                    "description": "Application to which these settings apply",
-                    "create": true
+                    "description": "The application to which these settings apply.",
+                    "required": true
                 },
-                "appGroupName": {
+                "groupName": {
                     "type": "string",
-                    "description": "Rule set name",
-                    "create": true
+                    "description": "The user group to which these settings apply.",
+                    "required": true
                 },
-
                 "maxFrameSize": {
                     "type": "integer",
-                    "default": 0,
-                    "description": "Largest frame that may be used on this connection. Value
placed into forwarded AMQP Open.max-frame-size field. Zero implies inserting system default.",
+                    "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,
-                    "description": "Largest message that may be used on this connection.
Value placed into forwarded AMQP Attach.max-message-size field. Zero implies inserting system
default.",
                     "required": false,
                     "create": true
                 },
                 "maxSessionWindow": {
                     "type": "integer",
-                    "default": 0,
-                    "description": "Largest incoming and outgoing window that may be used
on this connection. Value placed into forwarded AMQP Begin.incoming-window and .outgoing-window
fields. Zero implies inserting system default.",
+                    "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",
-                    "default": 0,
-                    "description": "Maximum number of simultaneous sessions that may be used
on this connection. Value placed into forwarded AMQP Open.channel-max field. Zero implies
inserting system default.",
+                    "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",
-                    "default": 0,
-                    "description": "Maximum number of simultaneous senders that may be used
on this connection. Zero implies system default.",
+                    "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",
-                    "default": 0,
-                    "description": "Maximum number of simultaneous receivers that may be
used on this connection. Zero implies system default.",
+                    "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,
-                    "description": "A receiver link created on this connection is allowed
to set the dynamic flag to true.",
                     "required": false,
                     "create": true
                 },
                 "allowAnonymousSender": {
                     "type": "boolean",
+                    "description": "This connection is allowed to use the Anonymous Sender
feature.",
                     "default": false,
-                    "description": "A sender link created on this connection is allowed to
have a blank target address.",
                     "required": false,
                     "create": true
                 },
                 "sources": {
-                    "type": "string",
-                    "default": 0,
-                    "description": "Comma separated list of allowed source addresses to be
specified by receive links. A blank list denies all access. A list with a single wildcard
address '*' allows all access. Simple beginsWith and endsWith may be specified by prefixing
and postfixing a string with '*'. Username substitution is supported by using the $USER token
in a string.",
+                    "type": "list",
+                    "description": "List of Source addresses allowed when creating receiving
links.",
                     "required": false,
                     "create": true
                 },
                 "targets": {
-                    "type": "string",
-                    "default": 0,
-                    "description": "Comma separated list of allowed target addresses to be
specified by sending links. A blank list denies all access. A list with a single wildcard
address '*' allows all access. Simple beginsWith and endsWith may be specified by prefixing
and postfixing a string with '*'. Username substitution is supported by using the $USER token
in a string.",
+                    "type": "list",
+                    "description": "List of Target addresses allowed when creating sending
links.",
                     "required": false,
                     "create": true
                 }
             }
         },
 
+        "policy.stats": {
+            "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},
+                "anonymousSenderDenied": {"type": "integer", "graph": true},
+                "linkSourceDenied": {"type": "integer", "graph": true},
+                "linkTargetDenied": {"type": "integer", "graph": true}
+            }
+        },
+
         "dummy": {
             "description": "Dummy entity for test purposes.",
             "extends": "entity",

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c284c20b/python/qpid_dispatch_internal/management/config.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/config.py b/python/qpid_dispatch_internal/management/config.py
index 65ab05b..979932e 100644
--- a/python/qpid_dispatch_internal/management/config.py
+++ b/python/qpid_dispatch_internal/management/config.py
@@ -30,13 +30,13 @@ from .qdrouter import QdSchema
 class Config(object):
     """Load config entities from qdrouterd.conf and validated against L{QdSchema}."""
 
-    def __init__(self, filename=None, schema=QdSchema()):
+    def __init__(self, filename=None, schema=QdSchema(), raw_json=False):
         self.schema = schema
         self.config_types = [et for et in schema.entity_types.itervalues()
                              if schema.is_configuration(et)]
         if filename:
             try:
-                self.load(filename)
+                self.load(filename, raw_json)
             except Exception, e:
                 raise Exception, "Cannot load configuration file %s: %s" % (filename, e),
sys.exc_info()[2]
         else:
@@ -67,6 +67,17 @@ class Config(object):
             s[1] = dict((camelcase(k), v) for k, v in s[1].iteritems())
         return sections
 
+    @staticmethod
+    def _parserawjson(lines):
+        """Parse raw json config file format into a section list"""
+        def sub(line):
+            """Do substitutions to make line json-friendly"""
+            line = line.split('#')[0].strip() # Strip comments
+            return line
+        js_text = "%s"%("".join([sub(l) for l in lines]))
+        sections = json.loads(js_text)
+        return sections
+
 
     def _expand(self, content):
         """
@@ -90,16 +101,18 @@ class Config(object):
         return [_expand_section(s, annotations) for s in content
                 if self.schema.is_configuration(self.schema.entity_type(s[0], False))]
 
-    def load(self, source):
+    def load(self, source, raw_json=False):
         """
         Load a configuration file.
         @param source: A file name, open file object or iterable list of lines
+        @param raw_json: Source is pure json not needing conf-style substitutions
         """
         if isinstance(source, basestring):
+            raw_json |= source.endswith(".json")
             with open(source) as f:
-                self.load(f)
+                self.load(f, raw_json)
         else:
-            sections = self._parse(source)
+            sections = self._parserawjson(source) if raw_json else self._parse(source)
             # Add missing singleton sections
             for et in self.config_types:
                 if et.singleton and not [s for s in sections if s[0] == et.short_name]:

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c284c20b/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
index b9a91b4..8084fdc 100644
--- a/python/qpid_dispatch_internal/management/policy_local.py
+++ b/python/qpid_dispatch_internal/management/policy_local.py
@@ -31,25 +31,11 @@ import pdb #; pdb.set_trace()
 
 """
 Entity implementing the business logic of user connection/access policy.
-
-Policy is represented several ways:
-
-1. External       : json format file
-2. Internal       : dictionary
-
-Internal Policy:
-----------------
-
-    data['photoserver'] = 
-    {'groups': {'paidsubscribers': ['p1', 'p2'],
-               'users': ['u1', 'u2']}, 
-     'policyVersion': 1}
-
 """
 
 #
 #
-class PolicyKeys():
+class PolicyKeys(object):
     # Policy key words
     KW_POLICY_VERSION           = "policyVersion"
     KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
@@ -73,7 +59,7 @@ class PolicyKeys():
     SETTING_TARGETS                = "targets"
 #
 #
-class PolicyCompiler():
+class PolicyCompiler(object):
     """
     Validate incoming configuration for legal schema.
     - Warn about section options that go unused.
@@ -276,7 +262,7 @@ class PolicyCompiler():
                     return False
         return True
 
-class PolicyLocal():
+class PolicyLocal(object):
     """
     The policy database.
     """
@@ -291,6 +277,8 @@ class PolicyLocal():
         self.stats = {}
         self.folder = folder
         self.policy_compiler = PolicyCompiler()
+        self.name_lookup_cache = {}
+        self.blob_lookup_cache = {}
         if not folder == "":
             self.policy_io_read_files()
 
@@ -529,6 +517,7 @@ class PolicyLocal():
 
     def policy_lookup_settings(self, user, host, app, upolicy):
         """
+        HACK ALERT - delete this lookup. It is obsolete.
         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.
@@ -577,7 +566,6 @@ class PolicyLocal():
             if not allowed:
                 return False
             # Return connection limits and aggregation of group settings
-            ugroups.append(user) # user groups also includes username directly
             self.policy_aggregate_limits     (upolicy, settings, PolicyKeys.KW_POLICY_VERSION)
             self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_FRAME_SIZE)
             self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_MESSAGE_SIZE)
@@ -600,6 +588,7 @@ class PolicyLocal():
 
     def policy_lookup(self, conn_id, user, host, app, upolicy):
         """
+        HACK ALERT - delete this lookup. It is obsolete.
         Determine if a user on host accessing app through AMQP Open is allowed:
         - verify to the policy access rules. 
         - track user/host connection limits
@@ -623,6 +612,102 @@ class PolicyLocal():
             return False
         return True
 
+    def lookup_users_policyname(self, user, host, app, policyname):
+        """
+        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 output is a concatenated list of policy blob names.
+        """
+        try:
+            lookup_id = user + "|" + host + "|" + app
+            if lookup_id in self.name_lookup_cache:
+                policyname.append( self.name_lookup_cache[lookup_id] )
+                return True
+
+            settings = self.policydb[app]
+            # User allowed to connect from host?
+            allowed = False
+            restricted = False
+            uhs = HostStruct(host)
+            ugroups = []
+            if PolicyKeys.KW_GROUPS in settings:
+                for r in settings[PolicyKeys.KW_GROUPS]:
+                    if user in settings[PolicyKeys.KW_GROUPS][r]:
+                        restricted = True
+                        ugroups.append(r)
+            uorigins = []
+            if PolicyKeys.KW_CONNECTION_POLICY in settings:
+                for ur in ugroups:
+                    if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
+                        uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
+            if PolicyKeys.KW_CONNECTION_ORIGINS in settings:
+                for co in settings[PolicyKeys.KW_CONNECTION_ORIGINS]:
+                    if co in uorigins:
+                        for cohost in settings[PolicyKeys.KW_CONNECTION_ORIGINS][co]:
+                            if cohost.match_bin(uhs):
+                                allowed = True
+                                break
+                    if allowed:
+                        break
+            if not allowed and not restricted:
+                if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
+                    allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
+            if not allowed:
+                return False
+            # Return connection limits and aggregation of group settings
+            ugroups.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_named_settings(self, app, policyname, upolicy):
+        """
+        Given a settings name, return the aggregated policy blob.
+        @param[in] app application user is accessing
+        @param[in] policyname name of the policy settings blob
+        @param[out] upolicy dict holding policy values
+        @return if allowed by policy
+        # Note: the upolicy output is a non-nested dict with settings of interest
+        # TODO: figure out decent defaults for upolicy settings that are undefined
+        """
+        try:
+            cachekey = app + "|" + policyname
+            if cachekey in self.blob_lookup_cache:
+                upolicy.update( self.blob_lookup_cache[cachekey] )
+                return True
+            settings = self.policydb[app]
+            ugroups = policyname.split("|")
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_FRAME_SIZE)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_MESSAGE_SIZE)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SESSION_WINDOW)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SESSIONS)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_SENDERS)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.SETTING_MAX_RECEIVERS)
+            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.SETTING_ALLOW_DYNAMIC_SRC)
+            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.SETTING_ALLOW_ANONYMOUS_SENDER)
+            self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.SETTING_SOURCES)
+            self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.SETTING_TARGETS)
+            c_upolicy = {}
+            c_upolicy.update(upolicy)
+            self.blob_lookup_cache[cachekey] = c_upolicy
+            return True
+        except Exception, e:
+            #print str(e)
+            #pdb.set_trace()
+            return False
+
 
 #
 # HACK ALERT: Temporary
@@ -678,11 +763,20 @@ def main_except(argv):
     print "\nLookup ellen from 72.135.2.9. Expect true and maxFrameSize 666666. Result is
%s" % res3
     print "Resulting policy is: %s" % upolicy
 
-    upolicy = {}
-    res4 = policy2.policy_lookup('72.135.2.9:33334', 'ellen', '72.135.2.9', 'photoserver',
upolicy)
+    upolicy4 = {}
+    res4 = policy2.policy_lookup('72.135.2.9:33334', 'ellen', '72.135.2.9', 'photoserver',
upolicy4)
     print "\nLookup policy2 ellen from 72.135.2.9. Expect false. Result is %s" % res4
 
-    if not (res1 and res2 and res3 and not res4):
+    policyname5 = []
+    res5 = policy.lookup_users_policyname('ellen', '72.135.2.9', 'photoserver', policyname5)
+    print "\nLookup user's policyname: %s" % policyname5
+
+    upolicy6 = {}
+    res6 = policy.lookup_named_settings('photoserver', policyname5[0], upolicy6)
+    res6a = upolicy6['maxFrameSize'] == 666666
+    print "\nNamed settings lookup result = %s, and value check = %s" % (res6, res6a)
+
+    if not (res1 and res2 and res3 and not res4 and res5 and res6 and res6a):
         print "Tests FAIL"
     else:
         print "Tests PASS"


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


Mime
View raw message