What is Ansible?
In addition to the article about the PeerVPN installation and configuration, I will now show you a more advanced and quite ‘modern’ way to provision several servers and get your VPN client up really fast. You’ve probably heard of Ansible already. Well, one of its use cases is exactly what we need here: Configuration Manager. Many of us have experienced The Headache when we need to install, configure and then administer a whole environment. Yes, to repeat the same steps on hundreds of servers, where you have different OS distributions, application versions, and all kinds of dependencies, and all of that certainly leads to some problems.
Learn about Ansible
Read our comprehensive beginner guide on Ansible and learn all the basics!
Well, Ansible is here to help you with all that stuff. You can choose, set and customize anything that is required for a specific environment and suit its needs. So, let us start with an introduction to Ansible, its structure, and its components.
In my opinion, there are two approaches when you first start with Ansible. The first one is to read the official introduction to Ansible, which explains a lot about its structure, and then start with a simple playbook which you then extend to a role. Or the second one, where you make use of the Ansible Galaxy, which has a lot of community-provided roles open for use. Well, not every role is that much scalable and flexible as you want so you can simply combine both approaches, take an already-built role, and expand its functionalities. If you learn that quickly and all of that is boring, you can start building your own Ansible modules.
Now, let’s have a look at my PeerVPN role.
The directory structure:
roles/ PeerVPN/ # this hierarchy represents a "role" tasks/ # main.yml # <-- the main. yml file task file includes install.yml + configure.yml install.yml # configure.yml # templates/ # <-- files for use with the template resource peervpn.service.j2 # <---templates end in .j2, moved in a certain location peervpn.conf.j2 # defaults/ # main.yml # <-- default lower priority variables for this
Tasks/main.yml:
- include: install.yml
- include: configure.yml
- service: name=peervpn state=started
Tasks/install.yml (NOTE the blankspaces at the start of the line):
- name: install dependencies
yum:
name: "{{ item }}"
state: latest
with_items:
- gcc
- openssl-devel
- openssl
- python-urllib3
- pyOpenSSL
when: ansible_os_family == "RedHat"
- name: downloading PeerVPN
unarchive: src=https://www.peervpn.net/files/peervpn-0-044-linux-x86.tar.gz dest=/tmp/ remote_src=yes
- name: moving extracted files [binary + config]
command: /usr/bin/mv /tmp/peervpn-0-044/peervpn /usr/sbin/peervpn
when: ansible_os_family == "RedHat"
Tasks/configure.yml (NOTE the blankspaces at the start of the line):
- name: create configuration directory
file:
path: /etc/peervpn
state: directory
- name: write the peervpn config file
template: src=../templates/peervpn.conf.j2 dest=/etc/peervpn/peervpn.conf
- name: create peervpn service
template: src=../templates/peervpn.service.j2 dest=/usr/lib/systemd/system/peervpn.service
when: ansible_os_family == "RedHat"
Templates/peervpn.service.j2:
[Unit]
Description={{ unit_description }}
{% if vpn_initpeers -%}
Requires={{ unit_requires }}
{% endif -%}
After={{ unit_after }}
ConditionPathExists={{ unit_condition_path }}
[Service]
Type={{ service_type }}
ExecStart={{ service_exec }}
PIDFile={{ service_pidfile }}
[Install]
WantedBy={{ install_wantedby }}
Templates/peervpn.conf.j2:
port {{ peervpn_port }}
networkname {{ peervpn_networkname }}
psk {{ peervpn_password }}
enabletunneling {{ peevpn_enabletunneling }}
interface {{ peervpn_interface }}
ifconfig4 {{ peervpn_ip }}
{% if vpn_initpeers -%}
initpeers {{ vpn_initpeers }}
{% endif -%}
Defaults/main.yml:
peervpn_port: 7000
peervpn_networkname: MyPeerVPN
peervpn_password: VqmWsJje18/qqIOEN2+pLHugDtAcN5So03J0TL9wIQomAkQwcNR23SzM+m7cuJyCeyXXuHDLoKbzVbk176eDVLPS8wLNoWSiwPF2yQn57Q4vjRz3qjI51nhE3yEyKmHZgotQga4Uz0vyvTGjJRxtPJqZ/igDN0YmI25nwhsYsyi34c6pIcqPCOHYlILndCUh8AYk3hGNPc0lSnkxW/sY+Uo+5BU0K6nB1LYcMlXA9Ij0deU+eyRTfoVggKnpXdl5FikrELbAOyoo71F0PEjL73k5fmyGmsoEE1f4yvgUXOKOzsPUaf567SP4Hm+h/EpqkWMR7JGc5jBDsOlY52LTp5XaZNn+l+VEInCsFQiKgTW3zvzSmUcrPB3GqQ1KXknwfHmtCJe4SRiRZLKtSeTiPdPvXC0HBMk+KK9TNk45dDUi3/ougCIwavMPEedH7Gh5fvG5iNXjZ24tAAOTle2oBHVgO2Wq7llh2VwKkrzyYATuchusVLbHa98D0YR2wA==
peevpn_enabletunneling: "yes"
peervpn_interface: pvpn0
peervpn_ip: 0.0.0.0
vpn_initpeers: false
unit_description: PeerVPN
unit_requires: network.target
unit_after: syslog.target network.target
unit_condition_path: /etc/peervpn/peervpn.conf
service_type: simple
service_exec: /usr/sbin/peervpn {{ unit_condition_path }}
service_pidfile: /var/run/peervpn/peervpn.pid
install_wantedby: multi-user.target
So, now we have the role. The only thing left is the playbook and the inventory file. In fact the inventory file can be skipped(you can parse the hosts as arguments when you run the command) or your target hosts can be placed in the default Ansible inventory file in /etc/ansible/hosts. Here is an example playbook, which is provisioning 2 hosts:
playbook.yml:
- name: Configuring and installing PeerVPN init peer
hosts: VPN_init_peer
remote_user: root
vars:
peervpn_ip: 10.1.0.1/24
roles:
- PeerVPN
- name: Configuring and installing PeerVPN other peers
hosts: Host_1
remote_user: root
vars:
peervpn_ip: 10.1.0.2/24
vpn_initpeers: "{{ groups.VPN_init_peer[0] }} {{ peervpn_port }}"
roles:
- PeerVPN
Note that I am using an inventory file for keeping the hosts:
Inventory.yml:
[VPN_init_peer]
165.165.165.165
[Host_1]
170.170.170.170
To provision more hosts you need to simply add [Host_<2/3/4..etc>] and an IP in your inventory file and add accordingly paragraph(s) to the playbook file, with different peervpn_ip and hosts.
To run the above example, you need to have your public keys distributed on the target hosts because Ansible works over SSH by default. Now simply execute the following command in the dir where your playbook is:
$ ansible-playbook playbook.yml -i inventory.yml