From a357e5fab17ebd89f1ace1b310776f0f03d86fa7 Mon Sep 17 00:00:00 2001 From: fbourqui Date: Sat, 2 Nov 2019 20:39:47 +0100 Subject: [PATCH] Merge stateless idea with no local storage of public and private keys, support multiple interface per hosts using several groups (#29) * merge stateless with no storage of local priv key * Delete locally stored private key * add reload module on update config file * privatekey template is not used anymore * remove all local keys priv and public * use ansible_play_hosts instead of hardcoded vpn grp should use the group in the play calling the role. works fine when hosts bellong to several groups * Clean tasks names * add tag, and cleanup * fix private key creation * Support for mutliple wireguard vpn on same host add inventory exemple in readme * fix typo, add some comment on inventory * add wg-config tag to Check config: allow run with -t - wg-config * Update tasks/main.yml Co-Authored-By: Robert Wimmer <2039811+githubixx@users.noreply.github.com> * remove trailing whitespace * Update templates/wg.conf.j2 Co-Authored-By: Robert Wimmer <2039811+githubixx@users.noreply.github.com> * Update templates/wg.conf.j2 Co-Authored-By: Robert Wimmer <2039811+githubixx@users.noreply.github.com> * changes after githubixx code review * readd new line to separate peers in config --- CHANGELOG.md | 7 ++ README.md | 65 ++++++++++++++---- defaults/main.yml | 16 +---- tasks/main.yml | 137 ++++++++++++++----------------------- templates/wg-privatekey.j2 | 1 - templates/wg-publickey.j2 | 1 - templates/wg.conf.j2 | 19 +++-- 7 files changed, 124 insertions(+), 122 deletions(-) delete mode 100644 templates/wg-privatekey.j2 delete mode 100644 templates/wg-publickey.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad5178..cb625fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog --------- +**4.0.0** + +- While the changes introduced are backwards compatible in general if you stay with your current settings some variables are no longer needed. So this is partly a breaking change and therefore justifies a new major version. +- Support multiple Wireguard interfaces. See README for examples (contribution by fbourqui) +- Make role stateless: In the previous versions the private and public keys of the Wireguard hosts were stored locally in the directory defined with the `wireguard_cert_directory` variable. This is no longer the case. The variables `wireguard_cert_directory`, `wireguard_cert_owner` and `wireguard_cert_group` are no longer needed and were removed. If you used this role before this release it's safe to remove them from your settings. The directory that was defined with the `wireguard_cert_directory` variable will be kept. While not tested it may enable you to go back to an older version of this role and it should still work (contribution by fbourqui) +- Reminder: `wireguard_cert_directory` default was `~/wireguard/certs`. Public and Private keys where stored on the host running ansible playbook. As a security best practice private keys of all your WireGuard endpoints should not be kept locally. + **3.2.2** - remove unneeded `with_inventory_hostnames` loops (thanks to pierreozoux for initial PR) diff --git a/README.md b/README.md index b6090a2..c8d2a4b 100644 --- a/README.md +++ b/README.md @@ -27,28 +27,16 @@ see [CHANGELOG.md](https://github.com/githubixx/ansible-role-wireguard/blob/mast Role Variables -------------- -This variables can be changed in `group_vars/`: +Those variables can be changed in `group_vars/`: ``` -# The LOCAL directory where the WireGuard certificates are stored after they -# were generated. By default this will expand to user's LOCAL ${HOME} -# (the user that run's "ansible-playbook" command) plus -# "/wireguard/certs". That means if the user's ${HOME} directory is e.g. -# "/home/da_user" then "wireguard_cert_directory" will have a value of -# "/home/da_user/wireguard/certs". If you change this make sure that -# the parent directory is writable by the user that runs "ansible-playbook" -# command. -wireguard_cert_directory: "{{ '~/wireguard/certs' | expanduser }}" -wireguard_cert_owner: "root" -wireguard_cert_group: "root" - # Directory to store WireGuard configuration on the remote hosts wireguard_remote_directory: "/etc/wireguard" -# The port WireGuard will listen on. +# The default port WireGuard will listen if not specified otherwise. wireguard_port: "51820" -# The interface name that wireguard should use. +# The default interface name that wireguard should use if not specified otherwise. wireguard_interface: "wg0" ``` @@ -252,6 +240,53 @@ Example Playbook - wireguard ``` +Example Inventory using 2 different WireGuard interfaces on host multi +---------------------------------------------------------------------- + +This is a complex example using yaml inventory format + +``` +vpn1: + hosts: + multi: + wireguard_address: 10.9.0.1/32 + wireguard_allowed_ips: "10.9.0.1/32, 192.168.2.0/24" + wireguard_endpoint: multi.exemple.com + nated: + wireguard_address: 10.9.0.2/32 + wireguard_allowed_ips: "10.9.0.2/32, 192.168.3.0/24" + wireguard_persistent_keepalive: 15 + wireguard_endpoint: nated.exemple.com + wireguard_postup: "iptables -t nat -A POSTROUTING -o ens12 -j MASQUERADE" + wireguard_postdown: "iptables -t nat -D POSTROUTING -o ens12 -j MASQUERADE" +vpn2: + hosts: + multi-wg1: # use a different name, and define ansible_host, to avoid mixing of vars without needing to prefix vars with interface name + ansible_host: multi + wireguard_interface: wg1 + wireguard_port: 51821 # when using several interface on one host, we must use different ports + wireguard_address: 10.9.1.1/32 + wireguard_endpoint: multi.exemple.com + another: + wireguard_address: 10.9.1.2/32 + wireguard_endpoint: another.exemple.com +``` + +Playbooks +--------- + +``` +- hosts: vpn1 + roles: + - wireguard +``` + +``` +- hosts: vpn2 + roles: + - wireguard +``` + License ------- diff --git a/defaults/main.yml b/defaults/main.yml index 51c4e92..55db08b 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,21 +1,9 @@ --- -# The LOCAL directory where the WireGuard certificates are stored after they -# were generated. By default this will expand to user's LOCAL ${HOME} -# (the user that run's "ansible-playbook" command) plus -# "/wireguard/certs". That means if the user's ${HOME} directory is e.g. -# "/home/da_user" then "wireguard_cert_directory" will have a value of -# "/home/da_user/wireguard/certs". If you change this make sure that -# the parent directory is writable by the user that runs "ansible-playbook" -# command. -wireguard_cert_directory: "{{ '~/wireguard/certs' | expanduser }}" -wireguard_cert_owner: "root" -wireguard_cert_group: "root" - # Directory to store WireGuard configuration on the remote hosts wireguard_remote_directory: "/etc/wireguard" -# The port WireGuard will listen on. +# The default port WireGuard will listen if not specified otherwise. wireguard_port: "51820" -# The interface name that wireguard should use. +# The default interface name that wireguard should use if not specified otherwise. wireguard_interface: "wg0" diff --git a/tasks/main.yml b/tasks/main.yml index dea3068..492f833 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -14,7 +14,6 @@ - wireguard-tools tags: - wg-install - - skip_ansible_lint - name: Enable WireGuard kernel module modprobe: @@ -28,106 +27,59 @@ tags: - wg-install -- name: Create WireGuard certificates directory - file: - dest: "{{ wireguard_cert_directory }}" - state: directory - owner: "{{ wireguard_cert_owner }}" - group: "{{ wireguard_cert_group }}" - mode: 0700 - run_once: true - delegate_to: localhost - tags: - wg-generate-keys - - name: Set WireGuard IP (without mask) set_fact: wireguard_ip: "{{ wireguard_address.split('/')[0] }}" -- name: Set path to private key file - set_fact: - private_key_file_path: "{{ wireguard_cert_directory }}/{{ inventory_hostname }}.private.key" - tags: - wg-generate-keys - -- name: Set path to public key file - set_fact: - public_key_file_path: "{{ wireguard_cert_directory }}/{{ inventory_hostname }}.public.key" - tags: - wg-generate-keys - -- name: Register if private key already exists +- name: Register if config/private key already exists on target host stat: - path: "{{ private_key_file_path }}" - register: private_key_file_stat - delegate_to: localhost + path: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + register: config_file_stat tags: - wg-generate-keys + - wg-config -- name: Generate WireGuard private key - shell: "wg genkey" - register: wg_private_key_result - when: not private_key_file_stat.stat.exists - tags: - - wg-generate-keys - - skip_ansible_lint +- block: + - name: Generate WireGuard private key + shell: "wg genkey" + register: wg_private_key_result + tags: + - wg-generate-keys -- name: Set private key fact - set_fact: - wg_private_key: "{{ wg_private_key_result.stdout }}" - when: not private_key_file_stat.stat.exists - tags: - - wg-generate-keys + - name: Set private key fact + set_fact: + private_key: "{{ wg_private_key_result.stdout }}" + tags: + - wg-generate-keys + when: not config_file_stat.stat.exists -- name: Generate WireGuard public key - shell: "echo '{{ wg_private_key }}' | wg pubkey" +- block: + - name: Read WireGuard config file + slurp: + src: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + register: wg_config + tags: + - wg-config + + - name: Set private key fact + set_fact: + private_key: "{{ wg_config['content'] | b64decode | regex_findall('PrivateKey = (.*)') | first }}" + tags: + - wg-config + when: config_file_stat.stat.exists + +- name: Derive WireGuard public key + shell: "echo '{{ private_key }}' | wg pubkey" # noqa 306 register: wg_public_key_result - when: not private_key_file_stat.stat.exists + changed_when: false tags: - - wg-generate-keys + - wg-config - name: Set public key fact set_fact: - wg_public_key: "{{ wg_public_key_result.stdout }}" - when: not private_key_file_stat.stat.exists + public_key: "{{ wg_public_key_result.stdout }}" tags: - - wg-generate-keys - -- name: Store hosts private key locally - template: - src: "wg-privatekey.j2" - dest: "{{ private_key_file_path }}" - owner: "{{ wireguard_cert_owner }}" - group: "{{ wireguard_cert_group }}" - mode: 0644 - when: not private_key_file_stat.stat.exists - delegate_to: localhost - tags: - - wg-generate-keys - -- name: Store hosts public key locally - template: - src: "wg-publickey.j2" - dest: "{{ public_key_file_path }}" - owner: "{{ wireguard_cert_owner }}" - group: "{{ wireguard_cert_group }}" - mode: 0644 - when: not private_key_file_stat.stat.exists - delegate_to: localhost - tags: - - wg-generate-keys - -- name: Read private key - set_fact: - private_key: "{{ lookup('file', private_key_file_path) }}" - tags: - wg-config - -- name: Read public key - set_fact: - public_key: "{{ lookup('file', public_key_file_path) }}" - tags: - wg-config + - wg-config - name: Create WireGuard configuration directory file: @@ -149,6 +101,21 @@ notify: - restart wireguard +- name: Check if reload-module-on-update is set + stat: + path: "{{ wireguard_remote_directory }}/.reload-module-on-update" + register: reload_module_on_update + tags: + - wg-config + +- name: Set WireGuard reload-module-on-update + file: + dest: "{{ wireguard_remote_directory }}/.reload-module-on-update" + state: touch + when: not reload_module_on_update.stat.exists + tags: + - wg-config + - name: Start and enable WireGuard service service: name: "wg-quick@{{ wireguard_interface }}" diff --git a/templates/wg-privatekey.j2 b/templates/wg-privatekey.j2 deleted file mode 100644 index 84b77fa..0000000 --- a/templates/wg-privatekey.j2 +++ /dev/null @@ -1 +0,0 @@ -{{hostvars[inventory_hostname]['wg_private_key']}} diff --git a/templates/wg-publickey.j2 b/templates/wg-publickey.j2 deleted file mode 100644 index ca2953a..0000000 --- a/templates/wg-publickey.j2 +++ /dev/null @@ -1 +0,0 @@ -{{hostvars[inventory_hostname]['wg_public_key']}} diff --git a/templates/wg.conf.j2 b/templates/wg.conf.j2 index 81e0d48..93f2883 100644 --- a/templates/wg.conf.j2 +++ b/templates/wg.conf.j2 @@ -15,9 +15,9 @@ PostDown = {{hostvars[inventory_hostname].wireguard_postdown}} {% if hostvars[inventory_hostname].wireguard_save_config is defined %} SaveConfig = true {% endif %} - -{% for host in groups["vpn"] %} +{% for host in ansible_play_hosts %} {% if host != inventory_hostname %} + [Peer] PublicKey = {{hostvars[host].public_key}} {% if hostvars[host].wireguard_allowed_ips is defined %} @@ -28,11 +28,18 @@ SaveConfig = true {% if hostvars[host].wireguard_persistent_keepalive is defined %} PersistentKeepalive = {{hostvars[host].wireguard_persistent_keepalive}} {% endif %} - {% if hostvars[host].wireguard_endpoint is not defined %} - Endpoint = {{host}}:{{wireguard_port}} - {% elif hostvars[host].wireguard_endpoint != "" %} + {% if hostvars[host].wireguard_port is defined and hostvars[host].wireguard_port is number %} + {% if hostvars[host].wireguard_endpoint is defined and hostvars[host].wireguard_endpoint != "" %} + Endpoint = {{hostvars[host].wireguard_endpoint}}:{{hostvars[host].wireguard_port}} + {% else %} + Endpoint = {{host}}:{{hostvars[host].wireguard_port}} + {% endif %} + {% elif hostvars[host].wireguard_endpoint is defined and hostvars[host].wireguard_endpoint != "" %} Endpoint = {{hostvars[host].wireguard_endpoint}}:{{wireguard_port}} + {% elif hostvars[host].wireguard_endpoint == "" %} + # No endpoint defined + {% else %} + Endpoint = {{host}}:{{wireguard_port}} {% endif %} - {% endif %} {% endfor %}