IPv4/6 ACLs for Plone Content
This is a short tech article about how to provide access control to pieces of your plone content based on the client's IP address. I was able to implement this for IPv6 (which was my goal) and will utilize a Plone product AutoRole, Google Code ipaddr-py, python scripts, Zope external methods and portal tab actions with conditions.
Intro
First, I'm running on Zope/Zeo 2.10.6-final, python 2.4.5, netbsd4(Xen) on my production site. The netbsd host is IPv4/IPv6 dual stacked. A simple Apache 2.2 configuration is the frontend for Plone using mod_rewrite. I choose to accomplish this by creating a tab which links to a folder and both are only visible/accessible when the client is connection from IPv6 space.
Installing AutoRole and a Python IPv4/6 Manipulation Module
Once you install AutoRole use of the of the attached diff files to patch AutoRole.py for use with either ipaddr-py or IPy. Feel free to modify the code if don't like it, but I'm currently working to get the IPy patch merged into the AutoRole svn repository. IPy was choosen b/c it can be called via buildout as a dependency to AutoRole.
To begin go ahead and install either ipaddr-py (from Google Code) or IPy (from PyPi). The latter can be done by adding IPy to the eggs section in buildout.cfg. Once you have this use the Python prompt to get a feel for the module you chose to install.
In [1]: import ipaddr
In [2]: clientIP = ipaddr.IP('2001:470:7:3a0::2')
In [3]: networkIP = ipaddr.IP('2001:470:7:3a0::/64')
In [4]: clientIP.version
Out[4]: 6
In [5]: networkIP.network == clientIP.ip & networkIP.netmask
Out[5]: True
In [6]: networkIP.__contains__(clientIP)
Out[6]: True
In [7]: clientIP = ipaddr.IP('2001:470:e0bb::6')
In [8]: networkIP.__contains__(clientIP)
Out[8]: False
Creating the External Method
Now you can start by creating your External Method file in the Extensions folder of your instance. Mine was a simple file called ip_restrictions.py with a single method 'ipv6_client_check' that return a boolean value. The method takes two arguments (clientIP & networkIP) and basically performs the test in the above example. My method looks like the following:
import ipaddr
def ipv6_client_check(clientIP, networkIP):
clientIP = ipaddr.IP(clientIP)
networkIP = ipaddr.IP(networkIP)
if clientIP.version == 6:
return networkIP.__contains__(clientIP)
return False
Now we can turn to the ZMI and create our External Methods and portal_tab actions. I started by creating a folder under portal_skins called 'myscripts'. The first thing we will create is our External Method. Once you've added this the following three fields need to be filled.
- Id: isIPv6Client:
- A name you will use to make calls to the External Method from ZPT and place in ZMI.
- Module Name: ip_restrictions:
- the name of the file in the Extensions directory without the '.py'
- Function Name: ipv6_client_check:
- the name of the method you're calling
Creating the Python Script in ZMI
Next, I created a Python Script titled 'IPv6Client'. This script will be called by our portal_tab actions and return a boolean value. I suppose I could have called the External Method directly from the portal_tab action, but it's a single text field that I didn't want to complicate. Also, the Python script will first test if the environment variable from the request object exists. The script contains the following code which pulls the client's IP address from the request getClientAddr method call'. Then calls our isIPv6Client External Method to test if the client's IP is within the IPv6 Global Unicast segment '2000::/3'. Notice the call to the External Method is proceeded by 'context'. Also, if you need to modify the code of your External Method you don't have to restart the Plone instants, instead just edit your file, then edit your External Method in ZMI and click 'Save Change' and your changes will be reflected within Plone. To allow the 'getClientAddr' method to retrieve the actual client IP and not the Apache servers IP you will need to add the trusted-proxy directive to you zope.conf file (syntax: trusted-proxy = <ip-address>).
request = container.REQUEST return context.isIPv6Client(request.getClientAddr(), '2000::/3') return False
Creating the portal_tab & portal_actions
Now go to 'portal_actions -> portal_tabs' under your SITE in ZMI. Here you will add a CMF Action using the name that will be visible on the tab - mine is called 'IPv6'. The two fields of most interest are 'URL' (path to which the tab links) and 'Condition' (an expression that will call our IPv6Client python script). The returned boolean value will decide if the portal_tab is visible or not. Lastly, select the 'Visible' check box and your corresponding page or folder will be set to 'Exclude from Navigation'. My URL and Condition fields look like the following:
URL (Expression): string:${portal_url}/ipv6
Codition (Expression): here/IPv6Client
At this point you should be able to test the visibility of the tab when accessed from IPv4 vs. IPv6. Next we will tackle the use of AutoRole to assign permissions to the page/folder based on the clients IP address.
Creating Permissions for AutoRole
First, I created a custom role called IPv6Client. Now go to your page/folder that will be accessible only to the IPv6 clients from the ZMI and access the Security tab. Here, you will basically locate all the permissions that are not inherited and given to anonymous. You will give that same permission to the predefined or custom role of your choice. The additional security of using AutoRole prevents an IPv4 client from accessing the page/folder directly (http://fourings.com/ipv6 in my case).
Now proceed with testing and tweaking those Security permissions on your page/folder. The reason I mention this is the AutoRole actually has to treat the client as authenticated (obviously they cannot remain anonymous) so when you access your IPv6 page you'll see some of the tabs commonly seen when you login. However, this can be managed - I'll let you experiment and learn.
Patch diff Files for AutoRole.py

RSS
Previous:
Internal/External DNS
