TransitionToSecurityProxies
Transition to security proxies
This document describes the recent change to the Zope 3 security system introducing name-based permission settings and security proxies.
Impatient readers can skip to the second section from the end of the document to get a quick summary of needed changes to application code.
Previous to these changes, programmers specified permissions for
objects, typically by specifying the permissions needed to access
instances of classes and their methods. Special code interpreters
made calls to security managers to validate access to objects. The
security managers checked for __permission__ attributes and
disallowed access if the security context (e.g. the authenticated
user) didn't have the necessary permission. This approach had a
number of drawbacks:
- It required storing
__permission__attributes on objects. This is a bit invasive, at least in principle. - Objects that could not have
__permission__attributes, like strings, numbers, lists, and so on, could not be accessed in untrusted contexts. - Special untrusted interpreters were needed. This represented a major implementation burden, especially for creating untrusted versions of Python or Perl. The untrusted (restricted) Python implementation has proven to be a major source of instability in Zope 2.
- Security holes could arise from calling trusted code from untrusted code. Malicious untrusted code could trick trusted code into performing unauthorized operations by passing it unexpected objects.
To address these issues, two significant changes have been introduced:
- Security proxies
- Name-based permissions
Security Proxies
Security proxies surround objects to prevent unauthorized access. Untrusted code is only allowed to access basic objects, such as strings and numbers, or security proxies.
When an object is passed to untrusted code, it is wrapped in a security proxy unless it is already wrapped. Security proxies mediate all accesses to the wrapped object. Operations on security proxies return security proxies as well. Security proxies passed from untrusted code to trusted code remain wrapped, so untrusted code can't trick trusted code into performing operations that the untrusted code could not perform.
With care, trusted code can explicitly unwrap security proxies and gain additional access. In particular, security proxies cannot be stored in the Zope object database. If an object wrapped by a security proxy is to be stored in another object, the security proxy must be removed.
When a method is accessed in a security proxy, the method attribute access results in the method wrapped in a security proxy. If the return value is a simple value, like a string or number, it is returned as usual, otherwise, the value will be wrapped in a security proxy and returned.
When an operation, such as call or item access is performed on a security proxy, then access to the corresponding Python special method is checked. For example, for a call operation, the __call__ method will be checked. The return value from the operation is treated in the same way as for function calls.
Security proxies are configured with "Checker" objects. Checker
objects can be created using the Zope.Security.Checker.Checker
factory. The factory takes a function as an argument. The function
will be called with an attribute name and returns one of:
- A permission id for the permission needed to access the attribute,
- None, indicating that access is forbidden, or
- the special value
Zope.Security.Checker.CheckerPublicto indicate that the attribute access is always allowed.
Typically, the checker constructor is passed a dictionary get
method. For example, to create a Checker that provides access to
the foo method and to the call operation only if the
security context has the Zope.ManageContent permission, the
following code may be used:
from Zope.Security.Checker import Checker
checker = Checker({'foo': 'Zope.ManageContent',
'__call__': 'Zope.ManageContent'}.get)
In this example, with a few exceptions, access to any other attributes or operations is forbidden.
A proxy for an object can be created explicitly by passing an object and a checker to the proxy factory:
from Zope.Security.Proxy import ProxyFactory
proxied_object = ProxyFactory(ob, checker)
Proxies are rarely created explicitly. Rather, proxies are created automatically for objects when necessary. This is done by looking up checkers from a checker registry. For this reason, it's important to register checkers for application classes and modules. This is normally done using the protectClass ZCML directive. Checkers can be created and registered from Python code. Registering checkers for application objects is better done from ZCML.
For low-level objects that are used in a variety of situation, it
may not be practical to provide permission-based access
control. Examples include lists, dictionaries, tuples and
BTrees?. For objects like these, it's best to provide public
read-only access by default and rely on application objects to
mediate access to their low-level objects. The
Zope.Security.Checker module provides APIs? for creating and
registering checkers from Python code.
Note that a number of attributes and operations are always public:
- Comparison (
__lt__,__gt__,__le__,__ge__,__eq__,__ne__) - Boolean value (
__nonzero__) - Hash (
__hash__) - Class and interface assertions (
__class__,__implements__)
Access to these objects need not and cannot be controlled.
The "stringification" operators methods (__str__ and __repr__)
are forbidden by default and must be controlled to be accessed.
Name-based permissions
As might be obvious from the checker description above, permissions are now associated with names, not values. This has the important benefit that all attributes are protected the same way. Attributes like strings and lists are as easy to protect as methods.
protectClass ZCML directive
Access to instances of a class is controlled with the ZCML
protectClass directive, which has changed. For example, to
allow access to the title and description attributes, the call
operation, and the container interface of a class, a directive
like the following might be used:
<security:protectClass name=".SomeClass.">
<security:protect
names="title, description, __call__"
permission="Zope.View" />
<security:protect
interface="Zope.App.OFS.Container.IContainer.IReadContainer"
permission="Zope.View" />
<security:protect
interface="Zope.App.OFS.Container.IContainer.IWriteContainer"
permission="Zope.ManageContent" />
</security:protectClass>
The protectClass directive has changed in three important ways:
- The
methodsattribute has been renamed tonames, since all object attributes are protected by name. - The
instancessubdirective has been eliminated, as it no longer makes any sense. - The
protectClassdirective now applies only to the named class. Subclasses are unaffected. You can explicitly reuse security assertions from a base class by using thelike_untoattribute. For example:<security:protectClass name=".RootFolder." like_unto=".Folder." />
Context wrappers
The Zope.ContextWrapper package provides support for creating
and inspecting low-level context wrappers, which are Zope 3's
replacement for acquisition wrappers. The facilities in this
package do not support security proxies. For this reason, a new
module, Zope.Proxy.ContextWrapper has been provided to work with
context-wrappers in a security-proxy-aware fashion. See
Zope.Proxy.IContextWrapper.py for documentation of the
facilities provided by Zope.Proxy.ContextWrapper. Most code
currently using Zope.ContextWrapper will need to be changed to
use Zope.Proxy.ContextWrapper.
Proxy introspection and removal
The Zope.Proxy.ProxyIntrospection module provides facilities for
detecting and removing proxies. The function removeAllProxies
can be used to remove proxies from objects prior to calling
proxy-unaware facilities and prior to saving objects
persistently. For example, while the __class__ and
__implements__ attributes are available in proxies, the
attribute values themselves are proxies. The Python isinstance
builtin function and the interface isImplementedBy method won't
work with proxies.
Issues
There are a number of open issues with security proxies.
- Security proxies tend to spread and show up in unexpected places. The Zope publication process starts by wrapping the root object in a proxy. All non-basic objects accessed during URL traversal and in later processing tend to be proxies. As a result, much computation performed by trusted code is performed on and controlled by proxies. This is good and bad. It's good that trusting trusted code is protected from doing harm by being passed proxies, however, many more security assertions are necessary to control access to objects that were previously uncontrolled.
- There aren't good rules yet for when it is safe to remove proxy wrappers. It will often be necessary for trusted code to strip objects of their security proxies. There need to be better tools and facilities for doing this safely.
- To perform security checks, the Zope security policy needs
access to object context (i.e. acquisition context). In
particular, the containment of proxied objects needs to be
available to the security policy so that permission grants and
denials can be found.
It is a bit unclear where the responsibility for tracking object context lies. In Zope 2, object context was maintained fairly ubiquitously by acquisition. In Zope 3, we don't have acquisition and don't currently require objects or containers to manage their own context. Rather, context is managed by traversal facilities. It is likely that this won't be enough now that access to far more objects is checked.
Near future bonus
In the near future, the ZCML view, adapter, and utility directives
will gain a permission_id attribute. If this attribute is set,
then component methods will be protected and it won't be necessary
to use protectClass directives for these components. This should
greatly improve the readability of ZCML files.
Summary of changes needed to work with the new security machinery
Here is a summary of changes necessary to existing Zope 3 software:
protectClassdirectives need to be changed:- Rename
methodsattribute tonames. - Remove
instancessubdirectives.
- Rename
- The
protectClassdirective now applies only to the named class. Subclasses are unaffected. You can explicitly reuse security assertions from a base class by using thelike_untoattribute. For example:<security:protectClass name=".RootFolder." like_unto=".Folder." /> - It is likely that additional
protectClassdirectives will be needed, because many accessed are being checked now that weren't being checked before. - Code using the
Zope.ContextWrapperpackage need to useZope.App.ContextWrapperinstead. - Code that needs access to otherwise forbidden attributes, or
that needs to store proxied objects will need to use facilities
provided by
Zope.Proxy.ProxyIntrospection. - The
SecurityManagementandSecurityManagerpackages have moved fromZope.App.SecuritytoZope.Security.
Summary
I've tried to quickly provide a description of the recent changes to the Zope 3 security system. If you find that something is unclear or incorrect, please comment or ask questions.
- gvanrossum (Apr 29, 2002 10:04 am; Comment #1)
- Where does "like_unto" come from? Is it valley-speak
or biblical language? Either way I think it's too cute...
The Proxy implementation does call the checker for comparisons, __nonzero__, __hash__ (however, it can't wrap the result in a proxy; fortunately that's unnecessary since these always return ints or bools).
There is no technical reason to special-case __class__ (these are new-style classes, where __class__ can be overridden by a property).
If you want to make an exception for something, I'd say always allow __repr__; this helps debugging (it's painful if you get something and you can't see what it is because __repr__ is disallowed).
I haven't figured out how to make the Job Board example work. Maybe Shane's debug hack will help. bwarsaw (Apr 29, 2002 1:27 pm; Comment #2) -- Something nags me:
> With care, trusted code can explicitly unwrap security proxies and > gain additional access. In particular, security proxies cannot be > stored in the Zope object database. If an object wrapped by a > security proxy is to be stored in another object, the security proxy > must be removed.
- Lets say untrusted code is building up a small object containment hierarchy and then wants to save this in the database. This is going to be a problem, unless one of two things happens: 1) setattrs() always unwrap before storing the attribute on the underlying object (this is okay as long as the getattr() always wraps before returning the object; 2) you do a "deep unwrap" before you attempt to store the object in the db. This latter you could probably do with a trusted proxy-aware pickler
- but one which defends against sneak attacks that try to execute code during pickling/unpickling.
I'm with Guido: "like_unto" is too cutesy. What about "protect_like"?
- poster (Apr 29, 2002 1:39 pm; Comment #3)
> - Code using the
Zope.ContextWrapperpackage need to use >Zope.App.ContextWrapperinstead.This should be
Zope.Proxy.ContextWrapper, right?- hathawsh (Apr 29, 2002 7:04 pm; Comment #4)
> Lets say untrusted code is building up a small object containment > hierarchy and then wants to save this in the database. This is going to > be a problem, unless one of two things happens: 1) setattrs() always > unwrap before storing the attribute on the underlying object (this is > okay as long as the getattr() always wraps before returning the object; > 2) you do a "deep unwrap" before you attempt to store the object in the > db. This latter you could probably do with a trusted proxy-aware > pickler -- but one which defends against sneak attacks that try to > execute code during pickling/unpickling.
Note that untrusted code has no ability to set attributes except by asking trusted code to do it, so in the current model, there is no way for untrusted code to change ZODB directly (unless it gets access to an unwrapped mutable object). This does put a burden on trusted code, which now has to be aware of security wrappers in order to store objects that may have come from untrusted code. However, in Zope 2 the vast majority of trusted code only expects strings, numbers, and "None" from untrusted code, and those objects are never wrapped in security.
- klm (May 1, 2002 2:57 pm; Comment #5)
like_untodoesn't sound right to me. how about 'as':<security:protectClass name=".RootFolder." as=".Folder." /> == "Protect RootFolder as Folder is protected"...
