Docker behind ufw/iptables & OpenVPN. Need to restrict access to VPN users only

Background / Why would I want to do this?

A client, where their new web server is running their custom application in docker, wants the server to be restricted to VPN access only. They neither want to host the application internally and provide us developers with access to their network, nor do they want an internet based solution with other security options. They have been very specific with their request even down to the encryption methods.

What do I need to do / What have I tried

Unfortunately, docker likes to play with iptables making ports publicly accessible, which we can’t have. I need to disable docker’s ability to mess with iptables and then manually configure either ufw or iptables directly to correctly process the packets between interfaces.

I have Googled this a lot and have tried many different examples that seem close, but few are exactly the same task and my lack of experience with unix firewalls has meant a lot of wasted time where one thing works and another breaks or I simply find myself back to square one, with the dockerised website working with or without the VPN connection.

I’m pretty familial with docker and docker-compose. In this scenario, I’m using jwilder/nginx-proxy, mariadb and a custom debian-stretch container for a ruby application. All this works OK.

I’ve configured OpenVPN on the server suing this guide and have successfully connected via the OpenVPN windows client, so that works. I can currently use this VPN connection and see my IP address change when visiting other sites further confirming that this looks good.

Version numbers and such:-

  • Debian GNU/Linux 9 \n \l
    Docker version 18.09.0, build 4d60db4
    docker-compose version 1.23.2, build 1110ad01
    OpenVPN 2.4.0 x86_64-pc-linux-gnu

Current interfaces: -

  • Docker bridge?
    br-7392762a0f11: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255

  • Main docker interface
    docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
    inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255

  • External WAN
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet [external server ip] netmask 255.255.255.0 broadcast 134.213.56.255

  • Rackspace local
    eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet 10.181.160.253 netmask 255.255.224.0 broadcast 10.181.191.255

  • Loopback
    lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
    inet 127.0.0.1 netmask 255.0.0.0

  • OpenVPN
    tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500
    inet 10.8.0.1 netmask 255.255.255.255 destination 10.8.0.2

I’ve followed this guide to disable dockers iptable manipulation which allows me to continue to use my dockerised application, but this is about as far as I can get without screwing things up.

I wonder if one of you smart chaps with decent networking knowledge might be kind enough to provide me with some definitive instructions preferably using ufw, to get the routing configured correctly so that only when an active VPN connection is up, can anything on the server be accessed, whilst not killing off DNS lookups and other external requests that could be made from within the application container e.g. SMPT connections. Of course I need to be able to get a VPN connection up and running to start with. OpenVPN is using the default UDP port of 1194

Any help would be very much appreciated and I’m sure this would be of use to others also.

Many thanks!

forgive me if i just read the first lines, but are you aware that published ports can be bound to specific ips of the host?

-p {host-ip}:{host-port}:{container-port}

Though, i have no idea if this works if you use the openvpn ip and it is not permanty available.

Thanks for the reply but yes I am aware of that feature but when we’re talking of a client that will run a pen test to confirm VPN access resrtrictins then we need to be aware that the source IP remains the same when accessing a site on the openvpn server host with or without the vpn tunnel being active. Only when visiting external servers via the VPN connection does the source ip change, which does not happen in this scenario.

This question is all about locking down the server to VPN only connections/interfaces, not ip addresses and, of course, not screwing up packet passing that is required between these interfaces unnecessarily.

I think I can answer this one myself now. After upping the logging for UFW firewall (using ‘ufw logging high’), it seems that I was getting confused as to what interface I was using whilst connected to the VPN. my requests were sourced from the interface tun0, not eth0. So, basing my rules on examples provided on the internet and adding a temporary hostfile hack for the application domain so it pointed to the docker interface IP of 172.17.0.1, I found that I was able to use the application just fine when connected to the VPN server and when disconnected the server was inaccessible for all but VPN connections and SSH connections.

Assuming you already have your docker setup and OpenVPN working, here are the step taken to restrict everything to VPN only using UFW: -

#First reset everything to defaults
sudo ufw --force reset

#Defaults to deny everything
sudo ufw default deny incoming
sudo ufw default deny outgoing

sudo ufw allow openvpn
sudo ufw allow in on eth0 from any to any port 53 proto udp

sudo ufw allow from [your external IP for ssh connections] to any port 22

sudo ufw allow in on tun0 from any

#I noticed in the logs that the docker bridge was being blocked so I added br-7392762a0f11

sudo ufw allow out on br-7392762a0f11

sudo ufw allow out to [server external IP/eth0 ip] port 1194

sudo ufw allow out on tun0
sudo ufw allow out on eth0

Now, since we did a reset, you’ll have to redo the alterations made as part of your openVPN configuration by re-editing /etc/ufw/before.rules

#
# rules.before
#
# Rules that should be run before the ufw command line added rules. Custom
# rules should be added to one of these chains:
#   ufw-before-input
#   ufw-before-output
#   ufw-before-forward
#

# START OPENVPN RULES
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]
# Allow traffic from OpenVPN client to eth0 (change to the interface you discovered!)
-A POSTROUTING -s 10.8.0.0/8 -o eth0 -j MASQUERADE
COMMIT
# END OPENVPN RULES

# Don't delete these required lines, otherwise there will be errors
*filter

. . .

#Last of all enable the firewall
sudo ufw enable

Now ufw status verbose shows: -

To                         Action      From
--                         ------      ----
1194                       ALLOW IN    Anywhere
53/udp on eth0             ALLOW IN    Anywhere
22                         ALLOW IN    [my static external IP]
Anywhere on tun0           ALLOW IN    Anywhere
1194 (v6)                  ALLOW IN    Anywhere (v6)
53/udp (v6) on eth0        ALLOW IN    Anywhere (v6)
Anywhere (v6) on tun0      ALLOW IN    Anywhere (v6)

Anywhere                   ALLOW OUT   Anywhere on br-7392762a0f11
[External Server IP] 1194         ALLOW OUT   Anywhere
Anywhere                   ALLOW OUT   Anywhere on tun0
Anywhere                   ALLOW OUT   Anywhere on eth0
Anywhere (v6)              ALLOW OUT   Anywhere (v6) on br-7392762a0f11
Anywhere (v6)              ALLOW OUT   Anywhere (v6) on tun0
Anywhere (v6)              ALLOW OUT   Anywhere (v6) on eth0

As it stand I can also use the VPN connection to browse the web too which is nice!

1 Like