Per VPN user network ACLs using Netfilter
Python Shell
Switch branches/tags
Nothing to show



Per VPN user network ACLs using Netfilter and OpenVPN.


While the script is fast, it still hangs OpenVPN for a second while running, which may be felt by VPN users. To minimize the impact the script blocks all connections then fork off and return control to openvpn. The fork finishes the rule setup.

learn-address /usr/lib/openvpn/plugins/

If using LDAP for user authentication, add this to the OpenVPN server configuration:


For any OpenVPN server configuration, also add this (or a sudo wrapper to this script, if needed. You may also use capabilities.):

#This is necessary so that openvpn delete the ip after 20s, assuming the client has gone away
#This may not be needed for TCP VPNs since the VPN knows when the client has gone away. (have not tested)
#For the same reason, remember to kill a client for at least 20s, or SIGUSR1 openvpn if you're changing ACLs for that client.
keepalive 10 20

Change the settings in /etc/openvpn/netfilter_openvpn.conf.

In general, you'll want to store the rules in /etc/openvpn/netfilter/rules/* and have a list of files such as "vpn_teamA.rules". Users belonging to the LDAP group name teamA will get those rules. You'll also want to have /etc/openvpn/netfilter/users/* for per user rules. Likewise, make sure that the paths of the 'iptables', 'ipset', and '' commands are correct for your system.

Don't forget to push the proper routing rules for the network/IPs you allow.

Rule format

Rules are formatted as list of ips or networks (anything netfilter understands for an address or network really), such as:

# Lines starting with a # sign can be used for comments
# Obviously you might want to use slightly more useful rules ;-)

Script logic

learn-address is an OpenVPN hook called when the remote client is being set an IP address at the VPN server side. It calls the netfilter script, which in turn will load the netfilter (iptables) rules for that IP address, per given cn name (cn name is the username in the certificate, or the LDAP user name).

If the script fails for any reason, OpenVPN will deny packets to come through.

When a user successfully connects to OpenVPN, will create a set for firewall rules for this user. The custom rules are added into a new chain named after the VPN IP of the user:

Chain (3 references)
 pkts bytes target     prot opt in     out     source               destination
 5925  854K ACCEPT     all  --  *      *             ctstate ESTABLISHED /* ulfr at */
  688 46972 ACCEPT     all  --  *      *           match-set dst /* ulfr groups: vpn_caribou;vpn_pokemon;vpn_ninjas;*/
   24  2016 LOG        all  --  *      *             /* ulfr at */ LOG flags 0 level 4 prefix `DROP'
   24  2016 DROP       all  --  *      *             /* ulfr at */

A jump target is added to INPUT, OUTPUT and FORWARD to send all traffic originating from the VPN IP to the custom chain:

Chain INPUT (policy ACCEPT 92762 packets, 15M bytes)
 3320  264K  all  --  *      *
Chain OUTPUT (policy ACCEPT 136K packets, 138M bytes)
 2196  549K  all  --  *      *  
Chain FORWARD (policy ACCEPT 126K packets, 127M bytes)
 1120 90205  all  --  *      *

You'll notice the comments are there for ease of troubleshooting, you can grep through "iptables -L -n" and find out which user or group has access to what easily.

To reduce the amount of rules created, when the LDAP ACLs only contains a list of destination subnets, these subnets are added into an IPSet. The IPSet is named after the VPN IP of the user.

Type: hash:net
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 17968
References: 1


You can list the rules and sets of a particular user with the script named ''.

You can delete all of the rules and sets of a given VPN IP using the script named ''.


On rule upgrades, start the script '' within '' until all rules are upgraded. You may also do this all the time but this has a performance cost associated.