The Decsec declarative security machinery computes an access control list based on the traversal of a path.
Decsec is meant to be used as a component in a processing chain such as WSGI, although it could be used for any application.
Decsec refrains from enforcing a security policy. It only injects enough information into "the processing chain" to make it possible for your application to enforce its own policy. You may ignore the information provided by Decsec entirely within your application if you so choose.
You are meant to make use of Decsec by protecting resources (which are assumed to live at paths) with permissions.
Decsec provides APIs to check and manipulate ACLs within your application.
A path is a forward-slash-separated string which represents a context within your application. Often, this may just be the path implied by the URL of a web request.
A user is an object representing a person.
A group is an object representing zero or more other users or groups.
A principal is an abstract notion of a user or group. In various places within Decsec, a user or group can be used interchangeably, so it's useful to canonize this abstraction.
Permissions are strings that represent actions which can be performed in the context of your application.
A permission group is a set of of zero or more permissions or permission groups. A permission group is to a permission as a group is to a user.
An access control list is an ordered list of rules that are meant to be processed to enforce security. Each rule specifies at least three things: a principal, a permission or pgroup, whether that permission or pgroup is allowed or denied. A rule may also include an optional condition. A rule is also known as an ACE, or "access control entry".
A simple example of an access control list in natural language is as follows:
In the above example, there are three "rules" or ACEs (access control entries). As hinted at by the numbers before them, their relative order is meaningful.
The first rule "allow all users to read" has an action ("allow"), a principal ("all users") and a permission ("read"). There are only two actions that are meaningful to Decsec: "allow" and "deny". These are self-explanatory. The principal "all users" is expressed in Decsec using a principal id (a user or group identifier). In this case, "all users" can be expressed in Decsec using the special Decsec group "system.Everyone". The permission "read" in terms of Decsec implies a permission or pgroup identifier. For the purposes of demonstration, we will use the pgroup identifer "application.Read" to represent a set of permissions.
So the first rule "allow all users to read" when translated into a Decsec ACE can be expressed in terms of the Python dictionary:
{'action':'allow', 'principal':'system.Everyone',
'permission':'application.Read'}
We also choose to represent the predicate "write" with the pgroup identifier "application.Write" and the subject "authenticated users" with the special Decsec group "system.Authenticated".
The ACL that represents the above ruleset therefore can be expressed in the terms of this Python list:
[ {'action':'allow', 'principal':'system.Everyone',
'permission':'application.Read'},
{'action':'allow', 'principal':'system.Authenticated',
'permission':'application.Write'} ]
The last rule in the above example ("deny access to all users") is implied in every ACL, so it needn't be explicitly supplied. This is the default rule that will be guaranteed to deny access during encforcement if none of the other rules in an ACL generate an allow or deny. This follows the general security rule "everything which is not explicitly allowed is denied".
Another simple example of an access control list is as follows:
This might be expressed in Decsec terms as:
[ {'action':'allow', 'principal':'Bob', 'permission':'system.Write'},
{'action':'allow', 'principal':'Ray', 'permission':'system.Read'},
{'action':'deny', 'principal':'Staff', 'permission':'system.Delete'} ]
A more complicated example might include a condition. For example, the ruleset below is a bit more complicated:
In Decsec this becomes the Python list:
[ {'action':'allow', 'principal':'Bob', 'permission':'system.Write'
'condition': 'python: path("request/REMOTE_ADDR") == "192.168.1.5"'},
{'action':'allow', 'principal':'Ray', 'permission':'system.Read'},
{'action':'deny', 'principal':'Staff', 'permission':'system.Delete'} ]
Conditions are written as TALES (Template Attribute Language Expression Syntax) expressions within Decsec. These expressions are not evaluated by Decsec during traversal or ACL creation, they are only evaluated when an the ACL is processed by your application. The available top-level bindings within Decsec TALES condition expressions are application-defined.
Decsec resolves a path to an ACL in the following way:
The root of the Decsec path space always has an ACL, so if a path cannot be resolved at all, the root ACL is used implicitly.
Note that an ACL is not composed dynamically during traversal. One and only one static ACL will be found at the end of traversal.
Although an ACL is really just declarative data, the data must be processed by your application for it to be useful. For this purpose, an ACL in Decsec is represented as an object with an API that is supplied to your Python application. This object is passed into the application through the processing chain (perhaps in the request object for a web application).
An ACL object has one method named "check". The "check" method return value is a boolean. The "check" method accepts two positional arguments: "princid" and "permission" and a single optional keyword argument "expr_context". Its method signature is shown below and its intent is indicated in the docstring:
def check(princid, permission, expr_context=None):
""" This method returns True if an ACE within this ACL allows
the user or group implied by 'princid' to carry out an
operation implied by the permission or pgroup implied by
'permission'. This method returns False if the this ACL
denies the operation implicitly by finding no allow match or
explicitly via an ACE 'deny' action. The optional
'expr_context' argument is used to resolve Decsec condition
expressions. If it is not supplied, ACEs in this ACL which
possess a condition expression are skipped. """
The princid argument is the meant to be the identifier of the user
who is currently making a request. This user's group memberships
will be accounted for during processing of Decsec ACEs that mention
groups. The permission argument is meant to be the distinct
permission which protects the content resource (or "view") that
matches the request path. Pgroups will be "flattened" into distinct
permissions during Decsec ACL processing, so if an ACL mentions a
pgroup that contains this permission, it will be accounted for in
the result.
If you do supply an expression context via expr_context, ACEs with
conditions will be processed. Errors raised during a condition
expression evaluation on an ACE will cause the ACE to deny.
Decsec provides a GUI application to manage the creation of nodes and the association of nodes with ACLs. When you visit a node in the GUI, you can choose to:
If you change the ACL of a node which itself has subnodes, the
system will prompt you to choose whether to apply the ACL to all
subnodes. If you choose yes, you can apply the ACL to all subnodes
in merge or overwrite mode. Choosing overwrite will
effectively delete all subnodes. Choosing merge will use a
best-effort algorithm to merge the rules in the changed ACL to all
subnodes with their own distinct ACLs.
Your application may programmatically add/remove nodes from Decsec or change an ACL associated with a node and any other features implied by the GUI application.
Decsec can operate in a transactional mode to support systems which need to change data transactionally. XXX This needs to be fleshed out.
A "decsec.map" object will be added to the processing chain that represents the node tree. It supports the following methods:
def addNode(self, path):
""" Adds a node at path """
def delNode(self, path):
""" Deletes the node (and all subnodes) represented at 'path' """
def changeNode(self, path, acl, cascade=None):
""" Changes the ACL data at 'path' using the acl data implied
by 'acl'. If 'cascade' is 'overwrite', delete all subnodes
underneath 'path' in the map. If 'cascade' is 'merge', merge
the ACL supplied into all subnodes that possess their own
ACLs """
Decsec will obtain its user data from arbitrarily-defined user sources (such as LDAP, UNIX, SQL, etc). Zope's PAS will be used as a model.
Decsec will obtain its user data from arbitrarily-defined group sources (such as LDAP, UNIX, SQL, etc). Zope's PAS will be used as a model.
Your applications define the available permission identifiers. You can inject these permission into Decsec programmatically as necessary to make it easier for administrators to define security policies.
Your application may group permissions into pgroups and inject these pgroups into Decsec programmatically. An administrator may create new pgroups which group other pgroups and permissions arbitrarily.
When a Decsec ACL object's "check" method is called, a log entry is written describing the processing result. The log entry contains the path at which the ACL was found in the node map, a description of the last ACE processed, and the final result of the processing (allowed or denied). If an error occurs during condition expression evaluation, the error traceback will also be included.
Zope's internal security machinery associates a security policy with an object. Decsec associates a security policy with a path. If an object moves from one place to another within Zope (thereby effecting its path) and this object has its own local security settings, those settings come along with it into the new place. In Decsec, they do not. If this behavior is desired in your application, the object's settings must be moved separately using a Decsec API call in terms of a path. Likewise, if an object with security settings is removed, you must remove the Decsec data for the path represented by the object from within your application. This can likely be automated through the use of a content-aware event system.
Zope's security machinery enforces its security policy during and/or after object graph traversal. Decsec does not enforce a security policy without help from your application.
Decsec allows you to associate access control lists with paths. Zope (2, at least) does not use access control lists.
If you mean this to make sense to either security folks or Zope folks, that isn't quite precise;
a role is actually an "abstract grant" of one or more permissions. The "permission group" itself
is a separate notion. Note that some applications in Zopeland use Zope's roles to fake out
*user groups*; others use them as you indicate.
2. "The first rule ... has an action ('allow'), a subject ('all users'), and a predicate ('read')"
In RDF-speak, the predicate would be "is allowed" and the object would be "read". Isn't the
"object" really just a permission? WHy don't we just call it that?
3. "The last rule ... ("denay access to all users") is implied"
Why? Why not make it explicit?
4. I imagine that many applications are going to want to have ACLs "inherited" and "overridden"
(but then you are basically back to Zope's model).
5. "Zope's internal security machinery controls access to objects."
True for Zope2; Zope3 protects "names", not objects, and does so at each
'getattr' (via security proxies).
5. "Zope's security machinery enforces its security policy during traversal."
For clarity: in Zope2, this statement is true, *except* during "publishing" traversal (which is
what you mostly care about); there, the publishing traversal is done *without* access checks,
(but roles are collected), and the published object is tested (this is the "inside out" policy
which makes ChrisW foam at the mouth).
Replies to this comment