Configuration Decisions

We have to agreen on some ip addresses and GCE question that are easily summarized as shell environment variables


The LAN_DEV must match a valid ethernet device in the GCE virtual machine, that may be eth0 or in my case ens4.

Setting up the Infrastructure

First, let’s create an external static IP address that we’ll assign to the VPN server.

gcloud --project ${GCE_PROJECT} compute addresses create ${VPN_INSTANCE}-ip \
    --region ${REGION}

VPN_PUBLIC_IP=$(gcloud --project ${GCE_PROJECT} compute addresses list | \
                grep -w ${VPN_INSTANCE} | awk '{print $3}')

Now, we’re creating the virtual machine, and attach the just created external static IP address to it. We have to make sure, that we pass --can-ip-forward, as we’re using it also for other instances as gateway to reach the other side.

gcloud --project ${GCE_PROJECT} compute instances create ${VPN_INSTANCE} \
    --zone ${ZONE} \
    --machine-type "f1-micro" \
    --subnet "default" \
    --address ${VPN_PUBLIC_IP} \
    --can-ip-forward \
    --maintenance-policy "MIGRATE" \
    --tags ipsec \
    --image "ubuntu-1604-xenial-v20170202" \
    --image-project "ubuntu-os-cloud" \
    --boot-disk-size "10" \
    --boot-disk-type "pd-standard" \
    --boot-disk-device-name "${VPN_INSTANCE}"

We now have to make sure, that any traffic within the GCE project, targeting an IP within the destination range $VPN_NET is being routed to our newly created VPN instance.

gcloud --project=${GCE_PROJECT} compute routes create ${VPN_INSTANCE}-gateway \
    --next-hop-instance=${VPN_INSTANCE} \

We will also allow any incoming traffic from private IPs within

gcloud --project=${GCE_PROJECT} compute firewall-rules create allow-internal \
    --allow icmp,udp,tcp \

Finally we have to open up UDP port 500 and UDP port 4500 on our IPsec virtual instance. As we’ve tagged that instance with at least the tag ipsec, we’ll now create a firewall rule that matches against that target, to permit IPsec traffic.

gcloud --project ${GCE_PROJECT} compute firewall-rules create "ipsec-vpn" \
    --allow udp:500,udp:4500 --network "default" --target-tags "ipsec"

Configuring the IPsec VPN server

So, outside configuration is done, let’s SSH into the VPN instance and become root.

Don’t forget to quickly copy’n’paste (adjusted) the configuration environment variables from the very beginning of this article.

gcloud --project ${GCE_PROJECT} compute ssh ${VPN_INSTANCE} --zone ${ZONE}
sudo -s

Ensure IP-forwarding is enabled within the kernel, and allow IP source NAT’ing, simply via the iptables-MASQUERADE target, and also add the IPsec internal VPN IP address to this instance..

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o ${LAN_DEV} -j MASQUERADE
ip addr add ${VPN_IP}/${VPN_NET#*\/} dev ${LAN_DEV}

Now, install racoon, the IPsec tool, and dnsmasq as a convenient DNS server for the VPN clients.

We have to fix a small issue with the racoon.service systemd file that ships with Ubuntu 16.04 though.

apt-get install -y racoon dnsmasq
sed -i -e "s,^PIDFile=.*,PIDFile=/var/run/racoon/," \

As we’re using PSK+xauth hybrid authentication, we’ll need a system user, and by example, I simply create a vpn user that VPN clients can use to authenticate.

# (this is an interim solution only: use whatever fits best for you)
useradd -m ${VPN_USER}
echo ${VPN_USER}:${VPN_PASS} | chpasswd

Optionally, we can supply a MOTD (message of the day) file, that will be prompted to the VPN client upon successful.

cat <<-EOF >/etc/racoon/motd
Howdy, Rowdy!

We’ve to configure the pre shared key (PSK), here, we default to secretly for any (*) source IP. This file must be kept private.

cat <<-EOF >/etc/racoon/psk.txt
* secretly

chmod 0400 /etc/racoon/psk.txt

Finally, the big bang of a IPsec racoon server configuration file.

cat <<-EOF >/etc/racoon/racoon.conf
log notify;
path pre_shared_key "/etc/racoon/psk.txt";
path certificate "/etc/racoon/certs";
path pidfile "/var/run/racoon/";

remote anonymous {
  exchange_mode main, base, aggressive;
  my_identifier fqdn "${VPN_INSTANCE}";

  passive on;
  generate_policy on;
  nat_traversal on;
  lifetime time 1 hour;
  ike_frag on;
  esp_frag 552;
  proposal_check obey;

  proposal {
    encryption_algorithm aes;
    hash_algorithm sha1;
    authentication_method xauth_psk_server;
    dh_group 2;

sainfo anonymous {
  #pfs_group 2;
  lifetime time 12 hour;
  #lifetime byte 50 MB;
  encryption_algorithm aes, 3des;
  authentication_algorithm hmac_sha1, hmac_md5;
  compression_algorithm deflate;

mode_cfg {
  # configuration source from data given in this section
  conf_source local;

  # authentication source user database on the system
  accounting system;      # via utmp
  auth_source system;     # user auth source: system (shadow), pam, ldap
# group_source system;    # group authentication: system, ldap
# auth_groups "vpn";      # VPN user must be in at least that group

  # VPN client DNS configuration
  dns4 ${VPN_IP};
  wins4 ${VPN_IP};
  default_domain "$(grep ^search /etc/resolv.conf | awk '{print $2}')";

  # VPN client IP address pool
  network4 ${VPN_CLIENT_IP_BASE};
  pool_size ${VPN_CLIENT_IP_COUNT};
  netmask4 ${VPN_CLIENT_IP_MASK};

  banner "/etc/racoon/motd";

Just reload the systemd service files, and start dnsmasq as well as racoon.

systemctl daemon-reload
systemctl start dnsmasq.service
systemctl start racoon.service

Now, you’re set to connect to your IPsec-native VPN server. And by the way, on OS X that works out of the box, whereas for Linux, you’ll have to do some magic configuration (see another post) to connect to your IPsec VPN.

Further Reading