We'll need a specific hierarchy of folders and files in our S3 bucket, so that the bootstrap script (enabling our OpenVPN server auto-configuration at startup) finds all the required elements to do its job.
We'll first prepare this entire hierarchy locally, so that we can :
easily check/edit/recheck our file contents and the names of all files and sub-folders
The files we'll put in the bucket will mostly be used in a Linux environment, so they should be saved in "Unix mode" for End-Of-Line control characters :
Windows is the world of CRLF (Carriage Return + Line Feed)
Unix/Linux-based systems use LF (Line Feed) exclusively and DON'T LIKE the CR pseudo-character (you can usually see them in error messages, listed as $13 or Ctrl-M or ^M)
So please use a proper editor which enables you to choose LF/Unix "end-of-line" mode before saving your files :
Building the hierarchy locally
We'll create a hierarchy of folders actually designed to "host" not only our own current setup, but also several similar setups if needed.
Do you remember the my-fg-ovpn-s3 sub-folder we created long ago ? That's what we'll use it for.
We'll begin by 2 folders at the top level : deploy and userdata
Then within deploy, we'll create 3 sub-folders : conf, init and refresh like this :
This folder will contain one bootstrap file for each "configuration name" that has an auto-configuration script : our own my-fg-ovpn will shortly have one, so we'll give it a UserData file.
This type of file is in fact a set of commands executed by an AWS EC2 "Instance" (=a virtual server) only when it boots for the first time.
So these files are like mini-shell scripts and we'll call them "configuration name"-userdata.sh
In our setup, the "userdata script" will download and run a much bigger setup script that will do the auto-configuration work, as well as start OpenVPN.
Let's look at the beginning of the sample userdata file :
You will NEED to customize the values at lines 4 to 6 below, according to your own choices
We have 2 (or 3) values here, that have to reflect the choices you made earlier :
at line 4 you need to give YOUR chosen "configuration name"
at line 5 you need to give the exact name of YOUR S3 bucket
at line 6 you see the VPN Private IP network
The VPN private IP network
Just like you have a private IP on your home network connection, when you are connected to the VPN you get another private IP with it, and that VPN private IP will be in the IP network of line 6 above.
You have to check that the VPN private IP network does not overlap with any of your other private IP networks (home/WIFI network, other VPNs you may be connected to...)
your home network is : 192.168.0.0/24 or 192.168.1.0/24
your VPC IPv4 block is 172.31.0.0/16
so you should be fine keeping the default 10.10.10.0/24 for the VPN
But If you need, change the VPN private IP network at line 6, from the default 10.10.10.0/24 to any other private block of 256 IPv4 addresses (this setup supports only /24 subnets).
I suggest using a 10.x.y.0/24 block (with your chosen x and y between 0 and 254).
For our my-fg-ovpn demo, having no issue with the default VPN private IP block, we'll edit our UserData file so that it looks like :
Here is the complete UserData file for a configuration called "sample-conf-name", that you will copy/paste and rename, then edit lines 4-6 so that it matches your own choices/requirements :
Your own resulting userdata script being kept safe, both in your machine and in S3, will come to use when you create a Launch Template, or when you manually Launch your virtual server.
The init script : fgovpn001-init.sh
This has been adapted from other init scripts taken from my VPN servers, I won't dive into the details there because that would be 100%...
Tech blurb !
For those interested, the actual port forwarding is setup at lines 85-86 with iptables PREROUTING and POSTROUTING rules
Just copy/paste this (200+ lines) shell script to the init sub-folder and give it the proper name : it must be the same as what you see at line 34 of the userdata script above : fgovpn001-init.sh
fgovpn001-init.sh
#!/bin/bash#set -xecho`date`"+++ $0 starting with $# args : $@"echo`date`"+++ $0 sourcing static config vars from /usr/local/${MY_STARTUP_CONFIG_NAME}/conf-specs"./usr/local/${MY_STARTUP_CONFIG_NAME}/conf-specs# From now on, the MY_FGVPN_xxxx variables will be used - we won't need the MY_STARTUP_xxxx anymoreecho`date`"+++ $0 UPDATING packages"yum-yupdateecho`date`"+++ $0 INSTALLING Midnight Commander"yum-yinstallmcecho`date`"+++ $0 Creating AWS metadata vars sourcing script"echo"#!/bin/bash">/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho"# set -x">>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_INSTANCE_ID=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/instance-id`'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_AZ=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/placement/availability-zone`'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_REGION=${MY_FGVPN_AZ:0:${#MY_FGVPN_AZ}-1}'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_PUBLIC_IP=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/public-ipv4`'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_PRIVATE_IP=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/local-ipv4`'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_MAC_ETH0=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/mac`'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_VPC_CIDR_URL="http://169.254.169.254/latest/meta-data/network/interfaces/macs/${MY_FGVPN_MAC_ETH0}/vpc-ipv4-cidr-block"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'export MY_FGVPN_VPC_CIDR_RANGE=`/usr/bin/curl --silent ${MY_FGVPN_VPC_CIDR_URL}`'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'echo `date` "MY_FGVPN --- InstanceId : ${MY_FGVPN_INSTANCE_ID} - Region : ${MY_FGVPN_REGION} - AZ : ${MY_FGVPN_AZ}"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'echo `date` "MY_FGVPN --- Public IP : ${MY_FGVPN_PUBLIC_IP} - Private IP : ${MY_FGVPN_PRIVATE_IP}"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'echo `date` "MY_FGVPN --- ETH0 MAC : ${MY_FGVPN_MAC_ETH0}"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho'echo `date` "MY_FGVPN --- VPC CIDR : ${MY_FGVPN_VPC_CIDR_RANGE}"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-vars# no need to chmod for sourcing# chmod u+x /usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-varsecho`date`"+++ $0 sourcing AWS metadata vars"./usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-vars# From now on, we have the full set of MY_FGVPN_xxxx variables# ----------------------------- EC2 SOURCE/DEST CHECK -------------------------echo`date`"+++ $0 REMOVING EC2 SOURCE/DEST CHECK FOR NAT/PAT"echo`date`"+++ $0 THIS REQUIRES AN ASSIGNED EC2 ROLE"awsec2modify-instance-attribute--region ${MY_FGVPN_REGION} --instance-id ${MY_FGVPN_INSTANCE_ID} --no-source-dest-check# ----------------------------- IP FORWARDING + PAT, CRL REFRESH, FG PUBLISHING => boot script -------------------------echo`date`"+++ $0 Creating IP Forwarding and PAT script - boot/ipfw_pat.sh"echo"#!/bin/bash">/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho". /usr/local/${MY_STARTUP_CONFIG_NAME}/conf-specs">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho". /usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-vars">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'echo 1 > /proc/sys/net/ipv4/ip_forward && echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'echo "Activating MASQUERADE"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'# That enables proxying just for our own private IP'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'/sbin/iptables -t nat -A POSTROUTING -o eth0 -s ${MY_FGVPN_PRIVATE_IP}/32 -j MASQUERADE'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'# That would enable proxying to the Internet for the whole VPC CIDR'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'# /sbin/iptables -t nat -A POSTROUTING -o eth0 -s ${MY_FGVPN_VPC_CIDR_RANGE} -j MASQUERADE'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'# That would enalbe proxying to the Internet for VPN clients'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'# /sbin/iptables -t nat -A POSTROUTING -o eth0 -s ${MY_FGVPN_VPNSubNet} -j MASQUERADE'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho'echo "Finished ipfw_pat.sh script"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.shecho`date`"+++ $0 Creating ipfw_publish boot script - boot/ipfw_publish.sh"# FG "transparent" redirect for 1 GM : TCP 1802# This is the magic of iptablesecho"#!/bin/bash">/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho"# set -x">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho". /usr/local/${MY_STARTUP_CONFIG_NAME}/conf-specs">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho". /usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-vars">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho'/sbin/iptables -t nat -A PREROUTING -i eth0 -p tcp --dst ${MY_FGVPN_PRIVATE_IP} --dport 1802 -j DNAT --to-destination ${MY_FGVPN_VPNSubNet:0:${#MY_FGVPN_VPNSubNet}-5}.6:1802'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho'/sbin/iptables -t nat -A POSTROUTING -p tcp --dst ${MY_FGVPN_VPNSubNet:0:${#MY_FGVPN_VPNSubNet}-5}.6 --dport 1802 -j SNAT --to-source ${MY_FGVPN_PRIVATE_IP}'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho'echo "Finished ipfw_publish.sh script"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shecho`date`"+++ $0 Creating boot/${MY_FGVPN_CONFIG_NAME}-boot.sh script with calls to ipfw_pat.sh, crl_refresh.sh and ipfw_publish.sh"cat<<OVPNBOOTEOF>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/${MY_FGVPN_CONFIG_NAME}-boot.sh#!/bin/bash. /usr/local/${MY_STARTUP_CONFIG_NAME}/conf-specs. /usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-vars/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_pat.sh/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.sh/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/ipfw_publish.shOVPNBOOTEOFchmodu+x/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/*.shecho`date`"+++ $0 Adding call to the /usr/local/${MY_FGVPN_CONFIG_NAME}/boot/${MY_FGVPN_CONFIG_NAME}-boot.sh script at the end of /etc/rc.d/rc.local"echo"">>/etc/rc.d/rc.localecho"# added by $0 :">>/etc/rc.d/rc.localecho"/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/${MY_FGVPN_CONFIG_NAME}-boot.sh $@">>/etc/rc.d/rc.localecho"">>/etc/rc.d/rc.local# ---------------------------- INSTALL OPENVPN + OVPN SERVER CONFIGS ---------------------------------echo`date`"+++ $0 INSTALLING conntrack-tools"yum-yinstallconntrack-toolsecho`date`"+++ $0 INSTALLING openvpn"yum-yinstallopenvpnOVPNROOT="/etc/openvpn"OVPN_CA="${MY_FGVPN_CONFIG_NAME}-ca"OVPNCAPATH="${OVPNROOT}/${OVPN_CA}"OVPNSRVNAME="${MY_FGVPN_CONFIG_NAME}-srv"OVPNSRVPATH="${OVPNROOT}/server-${OVPNSRVNAME}"echo`date`"+++ $0 Building server dir : ${OVPNSRVPATH}"mkdir ${OVPNCAPATH}/mkdir ${OVPNSRVPATH}/mkdir ${OVPNSRVPATH}/${OVPNSRVNAME}-ccd/awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/conf/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-ca.crt ${OVPNCAPATH}/awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/conf/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-ta.key ${OVPNCAPATH}/awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/conf/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-dh2048.pem ${OVPNCAPATH}/awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/conf/${MY_FGVPN_CONFIG_NAME}/${OVPNSRVNAME}.crt ${OVPNSRVPATH}/awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/conf/${MY_FGVPN_CONFIG_NAME}/${OVPNSRVNAME}.csr ${OVPNSRVPATH}/awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/conf/${MY_FGVPN_CONFIG_NAME}/${OVPNSRVNAME}.key ${OVPNSRVPATH}/# The CRL must be regularly refreshed !awss3cp ${MY_FGVPN_S3_DEPLOY_URL}/refresh/${MY_FGVPN_CONFIG_NAME}/crl/${MY_FGVPN_CONFIG_NAME}-crl.pem ${OVPNCAPATH}/chownroot:root ${OVPNSRVPATH}/* ${OVPNCAPATH}/*chmod600 ${OVPNSRVPATH}/${OVPNSRVNAME}.keychmod600 ${OVPNCAPATH}/${MY_FGVPN_CONFIG_NAME}-dh2048.pemecho`date`"+++ $0 Creating CRL Refresh boot script - boot/crl_refresh.sh"# This must be done only when we have the OVPNxxx local variablesecho"#!/bin/bash">/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho"">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho". /usr/local/${MY_STARTUP_CONFIG_NAME}/conf-specs">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho". /usr/local/${MY_FGVPN_CONFIG_NAME}/${MY_FGVPN_CONFIG_NAME}-source-aws-vars">>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho'aws s3 cp ${MY_FGVPN_S3_DEPLOY_URL}/refresh/${MY_FGVPN_CONFIG_NAME}/crl/${MY_FGVPN_CONFIG_NAME}-crl.pem ${OVPNCAPATH}/'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho'service openvpn restart'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho'echo "Finished crl_refresh.sh script"'>>/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shchmodu+x/usr/local/${MY_FGVPN_CONFIG_NAME}/boot/crl_refresh.shecho`date`"+++ $0 Creating server-${OVPNSRVNAME}.conf"cat<<SRVEOF> ${OVPNSRVPATH}.conf; local ${MY_FGVPN_PRIVATE_IP}port 1194proto udpdev tunca ${OVPNCAPATH}/${MY_FGVPN_CONFIG_NAME}-ca.crtdh ${OVPNCAPATH}/${MY_FGVPN_CONFIG_NAME}-dh2048.pemcrl-verify ${OVPNCAPATH}/${MY_FGVPN_CONFIG_NAME}-crl.pemcert ${OVPNSRVPATH}/${OVPNSRVNAME}.crtkey ${OVPNSRVPATH}/${OVPNSRVNAME}.key; topology subnetserver ${MY_FGVPN_VPNSubNet:0:${#MY_FGVPN_VPNSubNet}-3} 255.255.255.0ifconfig-pool-persist ${OVPNSRVPATH}/${OVPNSRVNAME}-ipp.txtclient-config-dir ${OVPNSRVPATH}/${OVPNSRVNAME}-ccd# CIDR for this VPNpush "route ${MY_FGVPN_VPNSubNet:0:${#MY_FGVPN_VPNSubNet}-3} 255.255.255.0"# CIDR for this server private IP in the VPC (not the full VPC since is not needed for FG)push "route ${MY_FGVPN_PRIVATE_IP} 255.255.255.255"route ${MY_FGVPN_PRIVATE_IP} 255.255.255.255# CIDRs for other networks available through this server; route X.Y.0.0 255.255.0.0; push "redirect-gateway def1 bypass-dhcp"; push "dhcp-option DNS a.b.c.d"; client-to-clientkeepalive 10 60; inactive 120tls-servertls-auth ${OVPNCAPATH}/${MY_FGVPN_CONFIG_NAME}-ta.key 0; cipher BF-CBCcomp-lzo; max-clients 50user nobodygroup nobodypersist-keypersist-tunstatus ${OVPNSRVPATH}-status.log 5log ${OVPNSRVPATH}-log.log; log-append ${OVPNSRVPATH}-log.logverb 5; mute 20SRVEOF# Initialize ccd ???# Naaahhhhh, not for a single session...echo`date`"+++ $0 Setting autostart for Openvpn and server-${OVPNSRVNAME} startup"serviceopenvpnstartchkconfigopenvpnonecho`date`"+++ $0 OPENVPN INSTALLED AND STARTED"# --------------------------------------------------------------------------------------------------echo`date`"+++ $0 finished - Args : $@"exit0
Files for OpenVPN go into conf
Now we have to put the things OpenVPN itself will need.
We'll create a sub-folder with our "configuration name" in the conf folder :
In this folder, we need 6 properly named (config.name -prefixed) files :
the CA public certificate (.crt)
the TLS-Auth key (.key)
the DH file (.pem)
the OpenVPN Server public certificate (.crt) + private key (.key) + certificate signing request (.csr)
For our demo, these will be :
my-fg-ovpn-ca.crt
my-fg-ovpn-ta.key
my-fg-ovpn-dh2048.pem
my-fg-ovpn-srv.crt
my-fg-ovpn-srv.key
my-fg-ovpn-srv.csr
We're just missing one item : the CRL to filter out revoked certificates.
refresh is for the CRL
The CRL (Certificate Revocation List) is one of those things that can evolve over time, as you revoke certificates to prevent them from connecting to the server...
We'll create a sub-folder with our "configuration name" in the refresh folder...
...and a sub-folder called crl inside this one, where we'll drop our latest CRL file :
Our OpenVPN server will always search for his CRL file in that folder : we only need the properly named (config.name -prefixed) CRL file (.pem) there.
For our demo, it is my-fg-ovpn-crl.pem :
Ready to transfer !
...and with that, our S3 bucket "image" is now ready for transfer to S3 :
We'll do the actual transfer in Step 5, but before that let's build a Launch Template
userdata NEEDS You !
It's likely you won't create/revoke many certificates, but we have to make sure everything is in the right place, just in case - and anyway our setup requires it !