On the previous post I wrote about deploying a AWS Network Stack with Terraform and how to use Terraform to deploy a Linux Instance with LibreSwan installed.
I’ve been wanting to learn Ansible for a while now, so on this post we are going to use it to make it easy to deploy VPN Configuration into new VPN Instances. I won’t get into details of how Ansible works because they have a great Documentation, its easy to understand the basics and start playing with it!
On this setup we will be using the two VPCs in eu-central-1 and eu-west-1 that we built on the previous posts, with the difference that a second vpn-instance was added per vpc, so we can have 2 vpc-instance for redundancy, and also the Security Groups were changed because I notice I had left it allowing everything (Oops!).
The full thing can be found here on my Git Repo.
Playbook Tree
This is the File structure used by our vpn-instances Playbook:
└── vpn-instances ├── hosts ├── libreswan.j2 ├── libreswan_secrets.j2 ├── vpn-playbook.yml └── vpn_vars ├── vault.yml ├── vpn01-euc1-euw1.yml └── vpn02-euc1-euw1.yml
Hosts is our Ansible Inventory File, it should list all of our VPN Instances:
[euc1] vpn01.euc1.netoops.net ansible_host=18.196.67.47 vpn02.euc1.netoops.net ansible_host=52.58.236.243 [euw1] vpn01.euw1.netoops.net ansible_host=34.243.28.111 vpn02.euw1.netoops.net ansible_host=52.211.207.206
Libreswan.j2 and libreswan_secrets.j2 are our Jinja2 Template Files, these holds the LibreSwan Template, and the Playbook will pass our defined variables to it and deploy it to our vpn instances.
{% if ( left_side in inventory_hostname) %} conn {{ conn_name }} left=%defaultroute leftid={{ left_id }} right={{ right_peer }} authby=secret leftsubnet=0.0.0.0/0 rightsubnet=0.0.0.0/0 auto=start # route-based VPN requires marking and an interface mark=5/0xffffffff vti-interface={{ vti_left }} # do not setup routing because we don't want to send 0.0.0.0/0 over the tunnel vti-routing=no {% endif %} {% if ( right_side in inventory_hostname) %} conn {{ conn_name }} right=%defaultroute rightid={{ right_id }} left={{left_peer}} authby=secret leftsubnet=0.0.0.0/0 rightsubnet=0.0.0.0/0 auto=start # route-based VPN requires marking and an interface mark=5/0xffffffff vti-interface={{ vti_right }} # do not setup routing because we don't want to send 0.0.0.0/0 over the tunnel vti-routing=no {% endif %}%
{{ left_peer }} {{right_peer}} : PSK "{{ vpn_psk }}"%
vpn-playbook.yml Is the Ansible Playbook. Here’s where we tell Ansible what to do and how to do it. The Playbook has a YAML format, where you define Tasks and defines hosts where the Tasks needs to be applied.
--- - hosts: vpn0x.xxx1.*,vpn0y.other-yyy1.* gather_facts: no vars_files: - ./vpn_vars/vault.yml - ./vpn_vars/vpn0x-xxx1-yyy1.yml tasks: - name: write the vpn config file template: src=libreswan.j2 dest=/etc/ipsec.d/{{ conn_name }}.conf become: true register: tunnel - name: write the vpn secrets file template: src=libreswan_secrets.j2 dest=/etc/ipsec.d/{{ conn_name }}.secrets become: true - name: Activate the tunnel shell: "{{ item }}" with_items: - ipsec auto --rereadsecrets - ipsec auto --add {{ conn_name }} - ipsec auto --up {{ conn_name }} become: true when: tunnel.changed - name: Install Routes left shell: ip route add {{ right_subnet }} dev {{ vti_left }} when: (left_side in inventory_hostname) and tunnel.changed become: true - name: Install Routes Right shell: ip route add {{ left_subnet }} dev {{ vti_right }} when: (right_side in inventory_hostname) and tunnel.changed become: true - name: ensure ipsec is running service: name=ipsec state=started become: true
The vault.yml is an Ansible Vault, here is where we are going to Store our PSKs as we don’t want them to be available in Clear-Text in our configuration Files, and specially not in clear-text on a Git Repo, so do remember to also add the vault.yml to your .gitignore 🙂
Finally we have the vpn01-euc1-euw1.yml and vpn02-euc1-euw1.yml file. These files holds our Variables, with all the Parameters that we need to setup each one of our IPSEC VPN Pairs. Here is the example of one of them:
--- #Name for the VPN Connetion conn_name: euc1-euw1 #EUW1, Left Side left_id: 34.243.28.111 left_peer: 34.243.28.111 left_side: vpn01.euw1.netoops.net vti_left: vti0 left_subnet: 10.250.0.0/24 #EUC1, right Side right_peer: 18.196.67.47 right_id: 18.196.67.47 right_side: vpn01.euc1.netoops.net vti_right: vti0 right_subnet: 10.240.0.0/24 #PSK to be used. Note: PSK is stored on vault-psk vpn_psk: "{{ vault_vpn01_psk }}"%
For each new P2P IPSEC That we want to setup, we will create a new file with a suggestive name, we are going to set the variables with the common VPN params as Local Peer IP and ID, Remote Peer IP and ID, Subnet of Each Side, the VTI Tunnel Number (one VTI per Tunnel), and last but not least, our Pre-Shared Key. If you pay attention on the vpn_psk variable, you will see that it points to another variable with a prefix of vault_, this variable is stored on our Ansible Vault, and for each new Tunnel we should define a new PSK on the Vault. Try not to use the same PSK everywhere!
With your variables done, all we need to do now is go back to our Playbook and change the Variables File, pointing to the var_files we defined above and filter the hosts to apply the configuration to:
- hosts: vpn01.euc1.*,vpn01.euw1.* gather_facts: no vars_files: - ./vpn_vars/vault.yml - ./vpn_vars/vpn01-euc1-euw1.yml
That’s it. The playbook will run against the inventory file, will match the host-entries vpn01.euc1* and vpn01.euw1* (wildcards allowed, take a look on Ansible Patterns), we will source our encrypted-variables from vault.yml and our vpn variables from vpn01-euc1-euw1.yml. We only need now to execute the Playbook and the 2 VPN Instances in EUC1 and EUW1 should have their IPSEC Tunnels established and proper routes set to talk to each-other.
AWS Cross-Region Talk in no time 🙂 So far we got the 2 VPN Instances to establish the Tunnel and setup the Static Routes, the next step is to make the AWS Route Tables itself to route traffic from the AWS Subnets into the proper VPN Instances. Lets do that in a different post.
I hope this post helps to see that a Network Engineer doesn’t need to be a Programmer or a Systems Wizard to be able to start writing some Network Automation tools that will help us on our day-by-day tasks, we just need to go a little bit out of our comfort zone and start learning new and interesting skills, its fun, try it!
This is a brilliant piece of Ansible, precisely what I was looking for. I have got a few Libreswan based tunnels in AWS and didn’t have them in Ansible, now I can do it. Thanks to you!
LikeLike
I am glad that this post helped 🙂
LikeLike