Arturo Borrero Gonz lez: openvpn deployment with Debian Stretch
Debian Stretch feels like an excellent release by the Debian project.
The final stable release is about to happen in the short term.
Among the great things you can do with Debian, you could set up a VPN using
the openvpn software.
In this blog post I will describe how I ve deployed myself an openvpn server
using Debian Stretch, my network environment and my configurations & workflow.
Before all, I would like to reference my requisites and the characteristics of
what I needed:
Thanks to the amazing and tireless work of the Alberto Gonzalez Iniesta
(DD), the openvpn package in debian is in very good shape, ready
to use.
In vpn.example.com, install the required packages:
Two git repositories will be used, one for the openvpn configuration and
another for nftables (the nftables config is described later):
The repositories belong to the git group, a system group we create to let
systems admins operate the server using git:
For the openvpn git repository, we need at least this git hook
(file
For this hook to work, sudo permissions are required (file
Please review this sudoers file to match your environment and security
requirements.
The openvpn package deploys several systemd services:
We don t need all of them, we can use the simple
And put a content like this:
We can move on now to configure nftables to perform the NATs.
First, it s good to load the NAT configuration at boot time, so you need a
service file like this (
The nftables git hooks are implemented as described in
nftables managed with git. We are interested in the git hooks:
(file
This hook moves our nftables configuration to
Don t forget the
You end with at leats these files in the openvpn git tree:
Please note that if you commit a change to
Since the server is in fact routing packets between the tunnel and the public
network, we require forwarding enabled in sysctl:
Of course, the VPN clients will require a
Workflow for the system admins:
- a VPN server which allows internet clients to access our datacenter internal network (intranet) securely
- strong authentications mechanisms for the users (user/password + client certificate)
- the user/password information is stored in a LDAP server of the datacenter
- support for several (hundreds?) of clients
- only need to route certain subnets (intranet) through the VPN, not the entire network traffic of the clients
- full IPv4 & IPv6 dual stack support, of course
- a group of system admins will perform changes to the configurations, adding and deleting clients
- clients connect via internet to our openvpn server, vpn.example.com
- the openvpn server validates the connection and the tunnel is established (green)
- now the client is virtually inside our network (blue)
- the client wants to access some intranet resource, the tunnel traffic is NATed (red)
- client 1 tunnel: 192.168.100.11, fd00:0:1::11
- client 1 public NAT: x.x.x.11, x:x::11
- client 2 tunnel: 192.168.100.12, fd00:0:1::12
- client 2 public NAT: x.x.x.12, x:x::12
- [ ]
/etc/network/interfaces
file, adjust to your network environments):
auto lo
iface lo inet loopback
# main public IPv4 address of vpn.example.com
allow-hotplug eth0
iface eth0 inet static
address x.x.x.4
netmask 255.255.255.0
gateway x.x.x.1
# main public IPv6 address of vpn.example.com
iface eth0 inet6 static
address x:x:x:x::4
netmask 64
gateway x:x:x:x::1
# NAT Public IPv4 addresses (used to NAT tunnel of client 1)
auto eth0:11
iface eth0:11 inet static
address x.x.x.11
netmask 255.255.255.0
# NAT Public IPv6 addresses (used to NAT tunnel of client 1)
iface eth0:11 inet6 static
address x:x:x:x::11
netmask 64
# NAT Public IPv4 addresses (used to NAT tunnel of client 2)
auto eth0:12
iface eth0:12 inet static
address x.x.x.12
netmask 255.255.255.0
# NAT Public IPv6 addresses (used to NAT tunnel of client 2)
iface eth0:12 inet6 static
address x:x:x:x::12
netmask 64
% sudo aptitude install openvpn openvpn-auth-ldap nftables git sudo
% sudo mkdir -p /srv/git/vpn.example.com-nft.git
% sudo git init --bare /srv/git/vpn.example.com-nft.git
% sudo mkdir -p /srv/git/vpn.example.com-openvpn.git
% sudo git init --bare /srv/git/vpn.example.com-openvpn.git
% sudo chown -R :git /srv/git/*
% sudo chmod -R g+rw /srv/git/*
% sudo addgroup --system git
% sudo adduser admin1 git
% sudo adduser admin2 git
/srv/git/vpn.example.com-openvpn.git/hooks/post-receive
with
execution permission):
#!/bin/bash
NAME="hooks/post-receive"
OPENVPN_ROOT="/etc/openvpn"
export GIT_WORK_TREE="$OPENVPN_ROOT"
UNAME=$(uname -n)
info()
echo "$ UNAME $ NAME $1 ..."
info "checkout latest data to $GIT_WORK_TREE"
sudo git checkout -f
info "cleaning untracked files and dirs at $GIT_WORK_TREE"
sudo git clean -f -d
/etc/sudoers.d/openvpn-git
):
User_Alias OPERATORS = admin1, admin2
Defaults env_keep += "GIT_WORK_TREE"
OPERATORS ALL=(ALL) NOPASSWD:/usr/bin/git checkout -f
OPERATORS ALL=(ALL) NOPASSWD:/usr/bin/git clean -f -d
% dpkg -L openvpn grep service
/lib/systemd/system/openvpn-client@.service
/lib/systemd/system/openvpn-server@.service
/lib/systemd/system/openvpn.service
/lib/systemd/system/openvpn@.service
openvpn.service
:
% sudo systemctl edit --full openvpn.service
% systemctl cat openvpn.service
# /etc/systemd/system/openvpn.service
[Unit]
Description=OpenVPN server
Documentation=man:openvpn(8)
Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn23ManPage
Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO
[Service]
PrivateTmp=true
KillMode=mixed
Type=forking
ExecStart=/usr/sbin/openvpn --daemon ovpn --status /run/openvpn/%i.status 10 --cd /etc/openvpn --config /etc/openvpn/server.conf --writepid /run/openvpn/server.pid
PIDFile=/run/openvpn/server.pid
ExecReload=/bin/kill -HUP $MAINPID
WorkingDirectory=/etc/openvpn
ProtectSystem=yes
CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH CAP_AUDIT_WRITE
LimitNPROC=10
DeviceAllow=/dev/null rw
DeviceAllow=/dev/net/tun rw
[Install]
WantedBy=multi-user.target
/etc/systemd/system/nftables.service
):
[Unit]
Description=nftables
Documentation=man:nft(8) http://wiki.nftables.org
[Service]
Type=oneshot
RemainAfterExit=yes
StandardInput=null
ProtectSystem=full
ProtectHome=true
WorkingDirectory=/etc/nftables.d
ExecStart=/usr/sbin/nft -f ruleset.nft
ExecReload=/usr/sbin/nft -f ruleset.nft
ExecStop=/usr/sbin/nft flush ruleset
[Install]
WantedBy=multi-user.target
/srv/git/vpn.example.com-nft.git/hooks/post-receive
):
#!/bin/bash
NAME="hooks/post-receive"
NFT_ROOT="/etc/nftables.d"
RULESET="$ NFT_ROOT /ruleset.nft"
export GIT_WORK_TREE="$NFT_ROOT"
UNAME=$(uname -n)
info()
echo "$ UNAME $ NAME $1 ..."
info "checkout latest data to $GIT_WORK_TREE"
sudo git checkout -f
info "cleaning untracked files and dirs at $GIT_WORK_TREE"
sudo git clean -f -d
info "deploying new ruleset"
set -e
cd $NFT_ROOT && sudo nft -f $RULESET
info "new ruleset deployment was OK"
/etc/nftables.d
and then
applies it to the kernel. So a single commit changes the runtime configuration
of the server.
You could implement some QA using the git hook update
, check
this file!
Remember, git hooks requires exec permissions to work.
Of course, you will need again a sudo policy for these nft hooks.
Finally, we can start configuring both openvpn and nftables using git.
For the VPN you will require the configure the PKI side: server certificates,
and the CA signing your client s certificates. You can check openvpn s own
documentation about this.
Your first commit for openvpn could be the server.conf
file:
plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so common-auth
mode server
user nobody
group nogroup
port 1194
proto udp6
daemon
comp-lzo
persist-key
persist-tun
tls-server
cert /etc/ssl/private/vpn.example.com_pub.crt
key /etc/ssl/private/vpn.example.com_priv.pem
ca /etc/ssl/cacert/clients_ca.pem
dh /etc/ssl/certs/dh2048.pem
cipher AES-128-CBC
dev tun
topology subnet
server 192.168.100.0 255.255.255.0
server-ipv6 fd00:0:1:35::/64
ccd-exclusive
client-config-dir ccd
max-clients 100
inactive 43200
keepalive 10 360
log-append /var/log/openvpn.log
status /var/log/openvpn-status.log
status-version 1
verb 4
mute 20
ccd/
directory. This directory contains a file per user
using the VPN service. Each file is named after the CN of the client
certificate:
# private addresses for client 1
ifconfig-push 192.168.100.11 255.255.255.0
ifconfig-ipv6-push fd00:0:1::11/64
# routes to the intranet network
push "route-ipv6 x:x:x:x::/64"
push "route x.x.3.128 255.255.255.240"
# private addresses for client 2
ifconfig-push 192.168.100.12 255.255.255.0
ifconfig-ipv6-push fd00:0:1::12/64
# routes to the intranet network
push "route-ipv6 x:x:x:x::/64"
push "route x.x.3.128 255.255.255.240"
server.conf
ccd/CN=CLIENT_1
ccd/CN=CLIENT_2
ccd/
, the changes are read
at runtime by openvpn. In the other hand, changes to server.conf
require
you to restart the openvpn service by hand.
Remember, the addressing is like this:
(DIA source file)
In the nftables git tree, you should put a ruleset like this (a single file
named ruleset.nft
is valid):
flush ruleset
table ip nat
map mapping_ipv4_snat
type ipv4_addr : ipv4_addr
elements = 192.168.100.11 : x.x.x.11,
192.168.100.12 : x.x.x.12
map mapping_ipv4_dnat
type ipv4_addr : ipv4_addr
elements = x.x.x.11 : 192.168.100.11,
x.x.x.12 : 192.168.100.12
chain prerouting
type nat hook prerouting priority -100; policy accept;
dnat to ip daddr map @mapping_ipv4_dnat
chain postrouting
type nat hook postrouting priority 100; policy accept;
oifname "eth0" snat to ip saddr map @mapping_ipv4_snat
table ip6 nat
map mapping_ipv6_snat
type ipv6_addr : ipv6_addr
elements = fd00:0:1::11 : x:x:x::11,
fd00:0:1::12 : x:x:x::12
map mapping_ipv6_dnat
type ipv6_addr : ipv6_addr
elements = x:x:x::11 : fd00:0:1::11,
x:x:x::12 : fd00:0:1::12
chain prerouting
type nat hook prerouting priority -100; policy accept;
dnat to ip6 daddr map @mapping_ipv6_dnat
chain postrouting
type nat hook postrouting priority 100; policy accept;
oifname "eth0" snat to ip6 saddr map @mapping_ipv6_snat
table inet filter
chain forward
type filter hook forward priority 0; policy accept;
# some forwarding filtering policy, if required, for both IPv4 and IPv6
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1
client.conf
file which looks
like this:
client
remote vpn.example.com 1194
dev tun
proto udp
resolv-retry infinite
comp-lzo
verb 5
nobind
persist-key
persist-tun
user nobody
group nogroup
tls-client
ca /etc/ssl/cacert/server_ca.crt
pkcs12 /home/user/mycertificate.p12
verify-x509-name vpn.example.com name
cipher AES-128-CBC
auth-user-pass
auth-nocache
- git clone the openvpn repo
- modify ccd/ and server.conf
- git commit the changes, push to the server
- if server.conf was modified, restart openvpn
- git clone the nftables repo
- modify ruleset
- git commit the changes, push to the server