Jailing specific processes inside a VPN
by Niklas Haas on May 9, 2017
Tagged as: networking, linux, tips.
I’ve always wondered how difficult it would be to do something like this, so I decided to give it a try. Turns out the answer is, since the addition of UID matching to ip rule
, not very difficult.
iproute2 configuration
The basic approach is to give the VPN interface a separate routing table, and redirect suspect processes to that routing table instead. Since working with numeric IDs directly is sort of a pain, you can give them friendly names:
cat /etc/iproute2/rt_tables
$ #
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
1 vpn
Confining your process to a specific user
Since ip rule
can only match based on UID, rather than PID (which is more stable anyway), the first step is making sure your process is running under some suitable user. For example, suppose you’re trying to isolate transmission-daemon
, then the appropriate user would be transmission
, which (at least on my system) transmission-daemon
gets run under. If your program lacks such a convenient user, then you could always add your own and use something like sudo to switch to it, e.g.:
cat /etc/sudoers.d/rtorrent
$ joe ALL = (rtorrent) NOPASSWD: /usr/bin/rtorrent
Then user joe
could use sudo -u rtorrent /usr/bin/rtorrent
to run rtorrent as a separate user rtorrent
.
OpenVPN configuration
The second part of the configuration is making sure to set up the correct routing table as part of OpenVPN’s initialization. For the purposes of this example, I want to ignore the VPN provider’s pushed routes (since they try overriding my system-wide routing to go through their VPN, whereas I only want it for certain processes), which the addition of route-noexec
solves.
cat /etc/openvpn/example/openvpn.conf
$ ...
script-security 2
route-noexec
route-up /etc/openvpn/example/route.sh
route-pre-down /etc/openvpn/example/route-down.sh
cat /etc/openvpn/example/route.sh
$ #!/bin/sh
sudo ip route add default via $route_vpn_gateway table vpn
# Confine transmission and rtorrent to this table (as an example)
for user in rtorrent transmission; do
uid=$(id -u $user)
sudo ip rule add uidrange $uid-$uid table vpn
done
cat /etc/openvpn/example/route-down.sh
$ #!/bin/sh
sudo ip route flush table vpn
# Delete all ip rules that mention this table
while sudo ip rule del table vpn; do :; done
The magic happens due to the ip rule
invocation. Basically, it creates a rule that looks like this:
ip rule list
$ 0: from all lookup local
32765: from all uidrange 141-141 lookup vpn
32766: from all lookup main
32767: from all lookup default
This means that any packet originating from UID 141-141
(i.e. transmission
) will get routed as according to the table vpn
, which looks like this: (as an example)
ip route list table vpn
$ default via 10.128.0.1 dev tun0
ip
and root privileges
For these scripts to work, openvpn needs to be able to execute ip
commands (with root privilege). You could either accomplish this by preventing openvpn
from ever dropping privileges (bad), or, as I prefer, using sudo
to re-gain access to ip
for the openvpn user:
cat /etc/sudoers.d/openvpn
$ openvpn ALL = (root) NOPASSWD: /bin/ip
Note that dropping privileges for OpenVPN is done by adding something like the following to your openvpn.conf
:
persist-key
persist-tun
user openvpn
group openvpn
Linux configuration
It’s possible that due to the way source route verification works under Linux, you will not receive any replies directed your way (and e.g. ping
as the confined user will fail). The solution to this is setting rp_filter
to 2, e.g.
cat /etc/sysctl.d/20-disable-rp_filter.conf
$ net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
followed by sysctl -p
.
If it still doesn’t work, you may need to flush the routing cache, i.e. ip route flush cache
.
Disclaimer and warning
A word on DNS
If you use a local DNS server (e.g. one pushed by your DHCP server), then DNS lookups from the confined
user will fail, because there’s no appropriate route for the local DNS server. There are several solutions to this:
- Use a public DNS server that’s accessible via the VPN as well.
- Hard-code domains you care about to
/etc/hosts
. - Add an extra route for your local DNS server to the
vpn
table.
While #3 seems the most attractive, this is a privacy risk because DNS requests will leak your real IP! Only do this if you’re sure you know what you’re signing yourself up for.
Other sources of IP leaks
It’s possible that all your effort will be for naught and your client will find other ways of leaking your ‘real’ IP to the internet. Unless you have carefully audited and tested your specific program, do NOT take this guide as any sort of guarantee. WebRTC, torrent clients etc. have all found ways to inadvertently de-anonymize VPN users.
One website you can use for testing these sorts of things is ipleak.net
, which includes support for testing torrent clients in particular. Handy if you just want to make sure your client isn’t egregiously advertising your real IP to trackers.