An Ansible playbook to create a hardened OpenVPN server instance
ansible-openvpn-hardened is an Ansible playbook written to create and manage a hardened OpenVPN server instance in the cloud or locally. The created instance is configured for use as a tunnel for internet traffic allowing more secure internet access when using public WiFi or other untrusted networks. With this setup you don't have to trust or pay a shady VPN service; you control your data and the security of the VPN.
Other Ansible playbooks and roles exist to automate the installation of OpenVPN but few, if any, thoroughly harden the OpenVPN server. ansible-openvpn-hardened configures the OpenVPN server to: - run entirely as an unprivileged user as described in the OpenVPN docs - use systemd to further sandbox the OpenVPN server process. - use only TLS ciphers that implement perfect forward secrecy - leverage easyrsa for PKI with a CRL and ansible playbooks for easy key management - produce thorough security audits using independent tools - And more; see the Hardening section
ansible-openvpn-hardened includes a playbook to run audits against the created server that independently verify the steps taken to harden the server. The supported auditing tools include OpenSCAP (with assistance from ubuntu-scap on Debian/Ubuntu), lynis and tiger. Example output from these tools can be reviewed on the project wiki.
To learn more about the server hardening read on; to get started immediately, jump to Quick Start.
The intended target is a fresh instantiation of an image running on a cloud provider like Digital Ocean (fair warning: referral link) or Microsoft's Azure. Physical boxes or local VMs should work as well assuming they are accessible over SSH, but haven't been tested.
The following Linux distros are supported:
Other distros and versions may work but no promises. If support for another distro is desired, submit an issue ticket. Pull requests are always welcome.
Some of the steps taken to harden the server:
openvpnuser instead of starting as
rootand dropping privileges
/etc/ssh/sshd_configfor hardening. See
aide --initjust before the playbook finishes. See
CapabilityBoundingSetis used to bound the Linux capabilities available to the OpenVPN server process, allowing only
CAP_NET_BIND_SERVICE- The systemd unit options
ProtectHomeare used to restrict the OpenVPN server process' access to the filesystem. - These aren't perfect as noted in the systemd docs, but better to have them than not:
> Note that restricting access with these options does not extend to submounts of a directory that are created later on.
tls-authaids in mitigating risk of denial-of-service attacks. Additionally, when combined with usage of UDP at the transport layer (the default configuration used by ansible-openvpn-hardened), it complicates attempts to port scan the OpenVPN server because any unsigned packets can be immediately dropped without sending anything back to the scanner. - From the OpenVPN hardening guide:
> The tls-auth option uses a static pre-shared key (PSK) that must be generated in advance and shared among all peers. This features adds "extra protection" to the TLS channel by requiring that incoming packets have a valid signature generated using the PSK key... The primary benefit is that an unauthenticated client cannot cause the same CPU/crypto load against a server as the junk traffic can be dropped much sooner. This can aid in mitigating denial-of-service attempts.
push block-outside-dnsused by OpenVPN server to fix a potential dns leak on Windows 10
tls-cipherlimits allowable TLS ciphers to a subset that supports perfect forward secrecy
Forward secrecy protects past sessions against future compromises of secret keys or passwords. If forward secrecy is used, encrypted communications and sessions recorded in the past cannot be retrieved and decrypted should long-term secret keys or passwords be compromised in the future, even if the adversary actively interfered.
2048bit RSA key size by default.
defaults/main.ymlif you don't mind extra processing time. Consensus seems to be that 2048 is sufficient for all but the most sensitive data.
verify-x509-nameprevents MitM attacks by verifying the server name in the supplied certificate matches the clients configuration. -
persist-tunprevents the traffic from leaking out over the default interface during interruptions and reconnection attempts by keeping the tun device up until connectivity is restored.
The bullets above are just an overview. See the task definitions with filenames in the form
harden_.ymlto review the complete set of steps.
None of the audit tools used by the
audit.ymlplaybook give the server a perfect score, here are some brief notes on audit findings
nodevfor /tmp, /dev/shm, etc.
Warning: Potential to lock yourself out of the target box. One of the hardening steps configures SSH to only listen on the VPN interfaces. Make sure you have a backup method to access the server in case the VPN doesn't come up. For example, Digital Ocean provides console access through their admin panel.
Requirement: Currently a static IP is required This may change in future releases with support for dynamic IP addresses. Static IPs are available on both Digital Ocean and Azure.
Suggestion: Use a fresh install or image as a target This playbook should work well, but issues will be less likely and more easily resolved if you can start over easily. Also, given the potential for locking yourself out of the box, you don't want to lose access to import things that may be on an existing server.
Install the required packages if you don't have them already. On Ubuntu or Debian use the commands below. On other OSes, sub-in the appropriate package manager.
sudo apt-get install python-pip git sudo pip install ansible
git clone https://github.com/bau-sec/ansible-openvpn-hardened.git
Create a target machine using your cloud provider of choice. The CentOS 7.2, Ubuntu 16.04 and Debian 8.7 images on Digital Ocean and Microsoft's Azure have been tested and should work well. Cloud providers are ideal because you can easily spin up a test box to try things out on and delete the instance when you're done or when you no longer need the VM. Other cloud providers, a local VM or box should work fine as well but haven't been tested.
Make sure you can ssh into the target machine that will become your OpenVPN box. If using a cloud provider they should provide you with login credentials and instructions. For example, to log into the
rootaccount on a box with the ip
Copy the example Ansible inventory to edit for your setup.
inventory.examplehas example values for different ssh configurations. If vim isn't your editor of choice, substitute a different editor.
cd ansible-openvpn-hardened/ cp inventory.example inventory vim inventory
Run the install playbook
The playbook should run for 5-30 minutes depending on how good your target box is at hashing and crypto operations. Assuming the above steps were successful, you should now have directory called
fetched_creds. This contains the openvpn configuration files and private keys that can be distributed to your clients.
Try connecting to the newly created OpenVPN server
cd fetched_creds/[server ip]/[client name]/ openvpn [client name]@[random domain]-pki-embedded.ovpn
You'll be prompted for the private key passphrase, this is stored in a file ending in
.txtin the client directory you just entered in the step above.
ansible-openvpn-hardened provides three different OpenVPN configuration files because OpenVPN clients on different platforms have different requirements for how the PKI information is referenced by the .ovpn file. This is just for convenience. All the configuration information and PKI info is the same, it's just formatted differently to support different OpenVPN clients.
X-pki-files.ovpn- OpenVPN configuration
ca.pem- CA certificate
X.key- client private key
X.pem- client certificate
All private keys (embedded in config, pkcs, and .key) are encrypted with a passphrase to facilitate secure distribution to client devices.
For maximum security when copying the PKI files and configs to client devices don't copy the .txt file containing the randomly generated passphrase. Enter the passphrase manually onto the device after the key has been transferred.
Entering a pass phrase every time the client is started can be annoying. There are a few options to make this less burdensome after the keys have been securely distributed to the client devices.
openvpn --config [config] --askpass [pass.txt]if you don't want to enter the password for the private key
From the OpenVPN man page:
If file is specified, read the password from the first line of file. Keep in mind that storing your password in a file to a certain extent invalidates the extra security provided by using an encrypted key.
Remove or change the passphrase on the private key
openssl rsa -in enc.key -out not_enc.key
Credentials are generated during the install process and are saved as yml formatted files in the Ansible file hierarchy so they can be used without requiring the playbook caller to take any action. The locations are below.
After the install.yml playbook has successfully been run, you'll only be able to SSH into the box when connected to the VPN using the account defined in
By default only two clients are created:
phone. (These defaults can be changed by editing
Connect to the VPN before running the playbook. For example, to create a client named
ansible-playbook playbooks/add_clients.yml -e clients_to_add=cool_client
Clients can also be added using a certificate signing request, CSR. This is useful if you intend to use keys generated and stored in a TPM. Generating the CSR will depend on your hardware, OS, TPM software, etc. If you're interested in this feature, you can probably figure this out (thoughblog post shows how to create private key stored in a TPM and generate a CSR on Windows.
csr_pathspecifies the local path to the CSR.
cnspecifies the common name specified when the CSR was created.
ansible-playbook -e "csr_path=~/test.csr [email protected]" playbooks/add_clients.yml
This will generate the client's signed certificate and put it in
fetched_creds/[server ip]/[cn]/as well as a nearly complete
.ovpnclient configuration file. You'll need to add references to or embed your private key and signed certificate. This will vary based on how your private key is stored. If your following the guide in the blog post mentioned above you'd do this using the OpenVPN option
First connect to the VPN. To revoke
ansible-playbook playbooks/revoke_client -e client=cool_client
First connect to the VPN. Run
The reports will be placed in
Contributions via pull request, feedback, bug reports are all welcome.