diff --git a/README.md b/README.md index 7e652500..3d823eea 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,14 @@ These variables can be changed in `group_vars/` e.g.: ```yaml # Directory to store WireGuard configuration on the remote hosts -wireguard_remote_directory: "/etc/wireguard" # On Linux -# wireguard_remote_directory: "/opt/local/etc/wireguard" # On MacOS +wireguard_remote_directory: >- + {%- if wireguard_ubuntu_use_netplan -%} + /etc/netplan + {%- elif ansible_os_family == 'Darwin' -%} + /opt/local/etc/wireguard + {%- else -%} + /etc/wireguard + {%- endif %} # The default port WireGuard will listen if not specified otherwise. wireguard_port: "51820" @@ -197,6 +203,9 @@ wireguard_service_state: "started" # If you have a more dynamic routing setup then setting this to "true" might be # the safest way to go. Also if you want to avoid the possibility creating some # hard to detect side effects this option should be considered. +# If using netplan to configure WireGuard interfaces this option should be set +# to "true" if netplan configuration should be applied, otherwise it will +# just be generated. wireguard_interface_restart: false # Normally the role automatically creates a private key the very first time @@ -233,6 +242,9 @@ wireguard_ubuntu_update_cache: "{{ wireguard_update_cache }}" # Set package cache valid time wireguard_ubuntu_cache_valid_time: "3600" +# Set to "true" if netplan should be used to configure WireGuard interfaces +wireguard_ubuntu_use_netplan: false + ####################################### # Settings only relevant for CentOS 7 ####################################### diff --git a/defaults/main.yml b/defaults/main.yml index 4557b7b2..1f942f7e 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -7,7 +7,14 @@ ####################################### # Directory to store WireGuard configuration on the remote hosts -wireguard_remote_directory: "{{ '/etc/wireguard' if not ansible_os_family == 'Darwin' else '/opt/local/etc/wireguard' }}" +wireguard_remote_directory: >- + {%- if wireguard_ubuntu_use_netplan -%} + /etc/netplan + {%- elif ansible_os_family == 'Darwin' -%} + /opt/local/etc/wireguard + {%- else -%} + /etc/wireguard + {%- endif %} # The default port WireGuard will listen if not specified otherwise. wireguard_port: "51820" @@ -18,6 +25,12 @@ wireguard_interface: "wg0" # The default owner of the wg.conf file wireguard_conf_owner: root +# By default a WireGuard configuration file in "wireguard_remote_directory" +# directory will be created that is called like the value of "wireguard_interface" +# plus ".conf". If "wireguard_ubuntu_use_netplan" is set to "true" this should +# be changed to "70-{{ wireguard_interface }}.yaml" e.g. +wireguard_conf_filename: "{{ wireguard_interface }}.conf" + # The default group of the wg.conf file wireguard_conf_group: "{{ 'root' if not ansible_os_family == 'Darwin' else 'wheel' }}" @@ -93,6 +106,9 @@ wireguard_ubuntu_update_cache: "{{ wireguard_update_cache }}" # Set package cache valid time wireguard_ubuntu_cache_valid_time: "3600" +# Set to "true" if you want to use netplan to configure WireGuard. +wireguard_ubuntu_use_netplan: false + ####################################### # Settings only relevant for CentOS 7 ####################################### diff --git a/handlers/main.yml b/handlers/main.yml index afb49601..ce155480 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -33,3 +33,32 @@ - not ansible_os_family == 'Darwin' - wireguard_service_enabled == "yes" listen: "reconfigure wireguard" + +- name: Generating Netplan Configuration + ansible.builtin.command: netplan generate + listen: reconfigure netplan + notify: netplan apply config + changed_when: true + become: true + when: + - wireguard_ubuntu_use_netplan + +- name: Applying Netplan Configuration + ansible.builtin.command: netplan apply + listen: netplan apply config + notify: restart systemd-networkd + changed_when: true + become: true + when: + - wireguard_ubuntu_use_netplan + - wireguard_interface_restart + +- name: Restart systemd-networkd + ansible.builtin.systemd: + name: systemd-networkd + state: restarted + listen: restart systemd-networkd + become: true + when: + - wireguard_ubuntu_use_netplan + - wireguard_interface_restart diff --git a/molecule/netplan/converge.yml b/molecule/netplan/converge.yml new file mode 100644 index 00000000..65ebfab8 --- /dev/null +++ b/molecule/netplan/converge.yml @@ -0,0 +1,13 @@ +--- +# Copyright (C) 2020-2023 Robert Wimmer +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Setup WireGuard + hosts: all + remote_user: vagrant + become: true + gather_facts: true + tasks: + - name: Include WireGuard role + ansible.builtin.include_role: + name: githubixx.ansible_role_wireguard diff --git a/molecule/netplan/molecule.yml b/molecule/netplan/molecule.yml new file mode 100644 index 00000000..f288ab33 --- /dev/null +++ b/molecule/netplan/molecule.yml @@ -0,0 +1,90 @@ +--- +# Copyright (C) 2020-2023 Robert Wimmer +# Copyright (C) 2020 Pierre Ozoux +# SPDX-License-Identifier: GPL-3.0-or-later + +dependency: + name: galaxy + +driver: + name: vagrant + provider: + name: libvirt + type: libvirt + +platforms: + - name: test-wg-ubuntu2004 + box: alvistack/ubuntu-20.04 + memory: 1536 + cpus: 2 + interfaces: + - auto_config: true + network_name: private_network + type: static + ip: 172.16.10.10 + groups: + - vpn + - ubuntu + - name: test-wg-ubuntu2204 + box: alvistack/ubuntu-22.04 + memory: 1536 + cpus: 2 + interfaces: + - auto_config: true + network_name: private_network + type: static + ip: 172.16.10.20 + groups: + - vpn + - ubuntu + - name: test-wg-ubuntu2404 + box: alvistack/ubuntu-24.04 + memory: 1536 + cpus: 2 + interfaces: + - auto_config: true + network_name: private_network + type: static + ip: 172.16.10.30 + groups: + - vpn + - ubuntu + +provisioner: + name: ansible + connection_options: + ansible_ssh_user: vagrant + ansible_become: true + log: true + lint: + name: ansible-lint + inventory: + host_vars: + test-wg-ubuntu2004: + wireguard_address: "10.10.10.10/24" + wireguard_port: 51820 + wireguard_persistent_keepalive: "30" + wireguard_endpoint: "172.16.10.10" + test-wg-ubuntu2204: + wireguard_address: "10.10.10.20/24" + wireguard_port: 51820 + wireguard_persistent_keepalive: "30" + wireguard_endpoint: "172.16.10.20" + wireguard_conf_backup: true + test-wg-ubuntu2404: + wireguard_address: "10.10.10.30/24" + wireguard_port: 51820 + wireguard_persistent_keepalive: "30" + wireguard_endpoint: "172.16.10.30" + wireguard_ubuntu_use_netplan: true + wireguard_conf_filename: "70-wg0.yaml" + wireguard_interface_restart: true + +scenario: + name: netplan + test_sequence: + - prepare + - converge + +verifier: + name: ansible diff --git a/molecule/netplan/prepare.yml b/molecule/netplan/prepare.yml new file mode 100644 index 00000000..93ff46c7 --- /dev/null +++ b/molecule/netplan/prepare.yml @@ -0,0 +1,14 @@ +--- +# Copyright (C) 2024 Robert Wimmer +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Prepare Ubuntu hosts + hosts: ubuntu + remote_user: vagrant + become: true + gather_facts: true + tasks: + - name: Update APT package cache + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 diff --git a/molecule/netplan/verify.yml b/molecule/netplan/verify.yml new file mode 100644 index 00000000..066d4404 --- /dev/null +++ b/molecule/netplan/verify.yml @@ -0,0 +1,33 @@ +--- +# Copyright (C) 2023 Robert Wimmer +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Verify setup + hosts: all + vars: + hosts_count: "{{ groups['vpn'] | length }}" + tasks: + - name: Count WireGuard interfaces + ansible.builtin.shell: | + set -o errexit + set -o pipefail + set -o nounset + wg | grep "peer: " | wc -l + exit 0 + args: + executable: "/bin/bash" + register: wireguard__interfaces_count + changed_when: false + + - name: Print WireGuard interface count + ansible.builtin.debug: + var: wireguard__interfaces_count.stdout + + - name: Print hosts count in vpn group + ansible.builtin.debug: + var: hosts_count + + - name: There should be as much WireGuard interfaces as hosts in vpn group minus one + ansible.builtin.assert: + that: + - "hosts_count|int -1 == wireguard__interfaces_count.stdout|int" diff --git a/tasks/main.yml b/tasks/main.yml index 35dd31db..0bc2723a 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -50,6 +50,7 @@ - name: Make sure wg syncconf option is available when: - not wireguard_interface_restart + - not wireguard_ubuntu_use_netplan tags: - wg-config block: @@ -87,7 +88,7 @@ - name: Register if config/private key already exists on target host ansible.builtin.stat: - path: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + path: "{{ wireguard_remote_directory }}/{{ wireguard_conf_filename }}" register: wireguard__register_config_file tags: - wg-generate-keys @@ -103,6 +104,7 @@ register: wireguard__register_private_key changed_when: false no_log: '{{ ansible_verbosity < 3 }}' + check_mode: false tags: - wg-generate-keys @@ -120,7 +122,7 @@ block: - name: Read WireGuard config file ansible.builtin.slurp: - src: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + src: "{{ wireguard_remote_directory }}/{{ wireguard_conf_filename }}" register: wireguard__register_config no_log: '{{ ansible_verbosity < 3 }}' tags: @@ -128,7 +130,11 @@ - name: Set private key fact ansible.builtin.set_fact: - wireguard_private_key: "{{ wireguard__register_config['content'] | b64decode | regex_findall('PrivateKey = (.*)') | first }}" + wireguard_private_key: >- + {{ wireguard__register_config['content'] | b64decode | + regex_findall(wireguard_ubuntu_use_netplan | + ternary('key:\s*(.*)$', 'PrivateKey\s*=\s*(.*)$'), multiline=True) | + first }} no_log: '{{ ansible_verbosity < 3 }}' tags: - wg-config @@ -157,11 +163,12 @@ mode: 0700 tags: - wg-config + when: not wireguard_ubuntu_use_netplan - name: Generate WireGuard configuration file ansible.builtin.template: - src: etc/wireguard/wg.conf.j2 - dest: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + src: "etc/{{ 'wireguard/wg.conf.j2' if not wireguard_ubuntu_use_netplan else 'netplan/wg.yaml.j2' }}" + dest: "{{ wireguard_remote_directory }}/{{ wireguard_conf_filename }}" owner: "{{ wireguard_conf_owner }}" group: "{{ wireguard_conf_group }}" mode: "{{ wireguard_conf_mode }}" @@ -170,7 +177,7 @@ tags: - wg-config notify: - - reconfigure wireguard + - "reconfigure {{ 'wireguard' if not wireguard_ubuntu_use_netplan else 'netplan' }}" - name: Ensure legacy reload-module-on-update is absent ansible.builtin.file: @@ -184,4 +191,6 @@ name: "wg-quick@{{ wireguard_interface }}" state: "{{ wireguard_service_state }}" enabled: "{{ wireguard_service_enabled }}" - when: not ansible_os_family == 'Darwin' + when: + - not ansible_os_family == 'Darwin' + - not wireguard_ubuntu_use_netplan diff --git a/tasks/setup-ubuntu.yml b/tasks/setup-ubuntu.yml index 57a40676..436a34c0 100644 --- a/tasks/setup-ubuntu.yml +++ b/tasks/setup-ubuntu.yml @@ -2,6 +2,15 @@ # Copyright (C) 2018-2023 Robert Wimmer # SPDX-License-Identifier: GPL-3.0-or-later +- name: Check if Netplan is supported + ansible.builtin.assert: + that: + - ansible_distribution == "Ubuntu" + - ansible_distribution_version is version('17.10', '>=') + fail_msg: "Netplan is only supported on Ubuntu 17.10 and later versions" + success_msg: "Netplan is supported on this system" + when: wireguard_ubuntu_use_netplan + - name: (Ubuntu) Update APT package cache ansible.builtin.apt: update_cache: "{{ wireguard_ubuntu_update_cache }}" diff --git a/templates/etc/netplan/wg.yaml.j2 b/templates/etc/netplan/wg.yaml.j2 new file mode 100644 index 00000000..e5b5be68 --- /dev/null +++ b/templates/etc/netplan/wg.yaml.j2 @@ -0,0 +1,102 @@ +# {{ ansible_managed }} +network: + version: 2 + renderer: networkd + tunnels: + {{ wireguard_interface }}: + mode: wireguard + # {{ inventory_hostname }} +{% if wireguard_address is defined %} + addresses: + - {{ wireguard_address }} +{% endif %} +{% if wireguard_addresses is defined %} + addresses: +{% for wg_addr in wireguard_addresses %} + - {{ wg_addr }} +{% endfor %} +{% endif %} + key: {{ wireguard_private_key }} +{% if wireguard_endpoint is not defined or wireguard_endpoint != "" %} + port: {{ wireguard_port }} +{% endif %} +{% if wireguard_mtu is defined %} + mtu: {{ wireguard_mtu }} +{% endif %} +{% if wireguard_fwmark is defined %} + mark: {{ wireguard_fwmark }} +{% endif %} +{% if wireguard_table is defined %} + routing-table: {{ wireguard_table }} +{% endif %} + peers: +{% for host in ansible_play_hosts %} +{% if host != inventory_hostname and ((hostvars[host].wireguard_endpoint is not defined or hostvars[host].wireguard_endpoint != "") or (wireguard_endpoint is not defined or wireguard_endpoint != "")) %} + - # Name = {{ host }} + keys: + public: {{ hostvars[host].wireguard__fact_public_key }} +{% if hostvars[host].wireguard_preshared_key is defined %} + shared: {{ hostvars[host].wireguard_preshared_key }} +{% endif %} +{% if hostvars[host].wireguard_allowed_ips is defined %} + allowed-ips: + - {{ hostvars[host].wireguard_allowed_ips }} +{% else %} +{% if wireguard_address is defined %} + allowed-ips: + - {{ hostvars[host].wireguard_address.split('/')[0] }}/32 +{% endif %} +{% if wireguard_addresses is defined %} + allowed-ips: +{% for wg_addr in hostvars[host].wireguard_addresses %} +{% if (wg_addr | ansible.utils.ipv4) %} + - {{ wg_addr.split('/')[0] }}/32 +{% elif (wg_addr | ansible.utils.ipv6) %} + - {{ wg_addr.split('/')[0] }}/128 +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{% if hostvars[host].wireguard_persistent_keepalive is defined and (hostvars[host].wireguard_endpoint is not defined or hostvars[host].wireguard_endpoint != "") %} + keepalive: {{ hostvars[host].wireguard_persistent_keepalive }} +{% endif %} +{% if (hostvars[host].wireguard_dc is defined and wireguard_dc is defined and wireguard_dc['name'] != hostvars[host].wireguard_dc['name']) %} + endpoint: {{ hostvars[host].wireguard_dc['endpoint'] }}:{{ hostvars[host].wireguard_dc['port'] }} +{% elif hostvars[host].wireguard_port is defined %} +{% 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 %} +{% if hostvars[host].wireguard_endpoint != "" %} + endpoint: {{ hostvars[host].wireguard_endpoint }}:{{ wireguard_port }} +{% else %} + # No endpoint defined for this peer +{% endif %} +{% else %} + endpoint: {{ host }}:{{ wireguard_port }} +{% endif %} +{% endif %} +{% endfor %} +{% if wireguard_unmanaged_peers is defined %} + # Peers not managed by Ansible from "wireguard_unmanaged_peers" variable +{% for peer in wireguard_unmanaged_peers.keys() %} + - # Name = {{ peer }} + keys: + public: {{ wireguard_unmanaged_peers[peer].public_key }} +{% if wireguard_unmanaged_peers[peer].preshared_key is defined %} + shared: {{ wireguard_unmanaged_peers[peer].preshared_key }} +{% endif %} +{% if wireguard_unmanaged_peers[peer].allowed_ips is defined %} + allowed-ips: + - {{ wireguard_unmanaged_peers[peer].allowed_ips }} +{% endif %} +{% if wireguard_unmanaged_peers[peer].endpoint is defined %} + endpoint: {{ wireguard_unmanaged_peers[peer].endpoint }} +{% endif %} +{% if wireguard_unmanaged_peers[peer].persistent_keepalive is defined %} + keepalive: {{ wireguard_unmanaged_peers[peer].persistent_keepalive }} +{% endif %} +{% endfor %} +{% endif %}