Installing OpenVPN on a Raspberry Pi with Ansible

I have to confess that I initially decided to install a VPN, not to secure my connection when using a free Wireless Acces Point in an airport or hotel, but to watch Netflix :-)

I had a VPS in France where I installed sniproxy to access Netflix. Not that I find the french catalogue so great, but as a French guy living in Sweden, it was a good way for my kids to watch some french programs. But Netflix started to block VPS providers...

I have a brother in France who has a Fiber Optic Internet access. That was a good opportunity to setup a private VPN and I bought him a Raspberry Pi.

There are many resources on the web about OpenVPN. A paper worth mentioning is: SOHO Remote Access VPN. Easy as Pie, Raspberry Pi... It's from end of 2013 and describes Esay-RSA 2.0 (that used to be installed with OpenVPN), but it's still an interesting read.

Anyway, most resources describe all the commands to run. I don't really like installing softwares by running a bunch of commands. Propably due to my professional experience, I like things to be reproducible. That's why I love to automate things. I wrote a lot of shell scripts over the years. About two years ago, I discovered Ansible and it quickly became my favorite tool to deploy software.

So let's write a small Ansible playbook to install OpenVPN on a Raspberry Pi.

First the firewall configuration. I like to use ufw which is quite easy to setup:

- name: install dependencies
  apt: name=ufw state=present update_cache=yes cache_valid_time=3600

- name: update ufw default forward policy
  lineinfile: dest=/etc/default/ufw regexp=^DEFAULT_FORWARD_POLICY line=DEFAULT_FORWARD_POLICY="ACCEPT"
  notify: reload ufw

- name: enable ufw ip forward
  lineinfile: dest=/etc/ufw/sysctl.conf regexp=^net/ipv4/ip_forward line=net/ipv4/ip_forward=1
  notify: reload ufw

- name: add NAT rules to ufw
    dest: /etc/ufw/before.rules
    insertbefore: BOF
    block: |
      # Nat table

      # Nat rules
      -A POSTROUTING -s -o eth0 -j SNAT --to-source {{ansible_eth0.ipv4.address}}

      # don't delete the 'COMMIT' line or these nat rules won't be processed
  notify: reload ufw

- name: allow ssh
  ufw: rule=limit port=ssh proto=tcp

- name: allow openvpn
  ufw: rule=allow port={{openvpn_port}} proto={{openvpn_protocol}}

- name: enable ufw
  ufw: logging=on state=enabled

This enables IP forwarding, adds the required NAT rules and allows ssh and openvpn.

The rest of the playbook installs OpenVPN and generates all the keys automatically, except the Diffie-Hellman one that should be generated locally. This is just because it takes for ever on the Pi :-)

- name: install openvpn
  apt: name=openvpn state=present

- name: create /etc/openvpn
  file: path=/etc/openvpn state=directory mode=0755 owner=root group=root

- name: create /etc/openvpn/keys
  file: path=/etc/openvpn/keys state=directory mode=0700 owner=root group=root

- name: create clientside and serverside directories
  file: path="{{item}}" state=directory mode=0755
      - "{{clientside}}/keys"
      - "{{serverside}}"
  become: true
  become_user: "{{user}}"

- name: create openvpn base client.conf
  template: src=client.conf.j2 dest={{clientside}}/client.conf owner=root group=root mode=0644

- name: download EasyRSA
  get_url: url={{easyrsa_url}} dest=/home/{{user}}/openvpn
  become: true
  become_user: "{{user}}"

- name: create scripts
  template: src={{item}}.j2 dest=/home/{{user}}/openvpn/{{item}} owner=root group=root mode=0755
    - create_serverside
    - create_clientside
  tags: client

- name: run serverside script
  command: ./create_serverside
    chdir: /home/{{user}}/openvpn
    creates: "{{easyrsa_server}}/ta.key"
  become: true
  become_user: "{{user}}"

- name: run clientside script
  command: ./create_clientside {{item}}
    chdir: /home/{{user}}/openvpn
    creates: "{{clientside}}/files/{{item}}.ovpn"
  become: true
  become_user: "{{user}}"
  with_items: "{{openvpn_clients}}"
  tags: client

- name: install all server keys
  command: install -o root -g root -m 600 {{}} /etc/openvpn/keys/
    chdir: "{{item.path}}"
    creates: /etc/openvpn/keys/{{}}
    - { name: 'ca.crt', path: "{{easyrsa_server}}/pki" }
    - { name: '{{ansible_hostname}}.crt', path: "{{easyrsa_server}}/pki/issued" }
    - { name: '{{ansible_hostname}}.key', path: "{{easyrsa_server}}/pki/private" }
    - { name: 'ta.key', path: "{{easyrsa_server}}" }

- name: copy Diffie-Hellman key
  copy: src="{{openvpn_dh}}" dest=/etc/openvpn/keys/dh.pem owner=root group=root mode=0600

- name: create openvpn server.conf
  template: src=server.conf.j2 dest=/etc/openvpn/server.conf owner=root group=root mode=0644
  notify: restart openvpn

- name: start openvpn
  service: name=openvpn state=started

The create_clientside script generates all the required client keys and creates an ovpn file that includes them. It makes it very easy to install on any device: just one file to drop.

One thing I stumbled upon is the ns-cert-type server option that I initially used in the server configuration. This prevented the client to connect. As explained here, this option is a deprecated "Netscape" cert attribute. It's not enabled by default with Easy-RSA 3.

Fortunately, the mentioned howto and the Easy-RSA github page are good references for Easy-RSA 3.

One important thing to note is that I create all the keys with no password. That's obviously not the most secure and recommended way. Anyone accessing the CA could sign new requests. But it can be stored offline on an USB stick. I actually think that for my use case it's not even worth keeping the CA. Sure it means I can't easily add a new client or revoke a certificate. But with the playbook, it's super easy to throw all the keys and regenerate everything. That forces to replace all clients configuration but with 2 or 3 clients, this is not a problem.

For sure don't leave all the generated keys on the Pi! After copying the clients ovpn files, remove the /home/pi/openvpn directory (save it somewhere safe if you want to add new clients or revoke a certificate without regenerating everything).

The full playbook can be found on github. The README includes some quick instructions.

I now have a private VPN in France and one at home that I can use to securely access my NAS from anywhere!


Comments powered by Disqus