From b6c5306d12aa205cc4deac7274c47e73842b6c30 Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Wed, 18 Mar 2026 20:19:27 +0000 Subject: [PATCH 1/9] Wip wireguard role --- .../test/group_vars/wireguard/vars.yml | 2 +- .../wireguard/tests/test_wireguard-clients.py | 19 +++ roles/wireguard/README.md | 24 ++++ roles/wireguard/defaults/main.yml | 7 +- roles/wireguard/handlers/main.yml | 4 + roles/wireguard/tasks/2_server-config.yml | 80 +----------- roles/wireguard/tasks/3_clients.yml | 8 +- roles/wireguard/tasks/4_ufw.yml | 29 +++++ roles/wireguard/tasks/5_service.yml | 6 + .../wireguard/tasks/create-client-config.yml | 121 ++++++++++++++++++ roles/wireguard/tasks/process-server-keys.yml | 39 ++++++ .../templates/client-peer-all.conf.j2 | 13 ++ .../templates/client-peer-lan.conf.j2 | 13 +- .../templates/client-peer-section.conf.j2 | 4 + .../wireguard/templates/wg-interface.conf.j2 | 19 +-- 15 files changed, 284 insertions(+), 104 deletions(-) create mode 100644 molecule/wireguard/tests/test_wireguard-clients.py create mode 100644 roles/wireguard/README.md create mode 100644 roles/wireguard/tasks/4_ufw.yml create mode 100644 roles/wireguard/tasks/5_service.yml create mode 100644 roles/wireguard/tasks/create-client-config.yml create mode 100644 roles/wireguard/tasks/process-server-keys.yml create mode 100644 roles/wireguard/templates/client-peer-section.conf.j2 diff --git a/inventories/test/group_vars/wireguard/vars.yml b/inventories/test/group_vars/wireguard/vars.yml index 29ab310..3c6aafd 100644 --- a/inventories/test/group_vars/wireguard/vars.yml +++ b/inventories/test/group_vars/wireguard/vars.yml @@ -1,7 +1,7 @@ --- wireguard_port: 51821 wireguard_endpoint: "vpn.example.com" -wireguard_allowed_ips: "10.10.0.1/32, 192.168.0.1/24" +wireguard_allowed_lan_ips: "10.10.0.1/32, 192.168.0.1/24" wireguard_client_peers: - name: "alice" ip: "10.10.0.2/32" diff --git a/molecule/wireguard/tests/test_wireguard-clients.py b/molecule/wireguard/tests/test_wireguard-clients.py new file mode 100644 index 0000000..37dfd1a --- /dev/null +++ b/molecule/wireguard/tests/test_wireguard-clients.py @@ -0,0 +1,19 @@ +def test_wireguard_config_file_exists(host): + config_file = host.file("/etc/wireguard/wg0.conf") + + assert config_file.exists + assert config_file.is_file + + +def test_wireguard_config_file_content(host): + """Test that the WireGuard configuration file contains expected content.""" + config_file = host.file("/etc/wireguard/wg0.conf") + + content = config_file.content_string + + # Check for required sections and parameters + assert "[Peer]\n# Alice" in content + # assert "# Ansible managed" in content + # assert "Address = 10.10.0.1/24" in content + # assert "ListenPort = 51820" in content + # assert "PrivateKey = " in content diff --git a/roles/wireguard/README.md b/roles/wireguard/README.md new file mode 100644 index 0000000..4d54c7d --- /dev/null +++ b/roles/wireguard/README.md @@ -0,0 +1,24 @@ +# Wireguard + +this role takes care of installing and configuring wireguard: + +- installs wireguard on the server +- generates the server config +- generates the client config (stored in local inventory) +- sets up ufw config (NAT) for wireguard (ufw rules have to configured in ufw role) + +## Client config + +configure clients in inventory `group_vars/wireguard` like so: + +```yaml +wireguard_client_peers: + - name: "alice" + ip: "10.10.0.2/32" +``` + +configs are stored and encrypted (ansible-vault) locally in `inventories/production/wireguard_configs/clients` + +to generate the QR code for the client config, run: + +`ansible-vault view vpn-acme-all.conf.vault | qrencode -t ansiutf8` diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index 0343d15..193875e 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -1,18 +1,21 @@ --- +wireguard_company: "acme" wireguard_interface: wg0 wireguard_internal_server_ip: "10.10.0.1/24" +wireguard_subnet: "10.10.0.0/24" +wireguard_external_interface: "eth0" wireguard_port: 51820 # Peers configuration wireguard_client_peers: [] # Example: -# wireguard_peers: +# wireguard_client_peers: # - name: "alice" # ip: "10.10.0.2/32" # DNS servers (optional) -wireguard_dns: [] +wireguard_dns: [10.10.0.1] # MTU (optional) wireguard_mtu: "" diff --git a/roles/wireguard/handlers/main.yml b/roles/wireguard/handlers/main.yml index 2f987f1..790723d 100644 --- a/roles/wireguard/handlers/main.yml +++ b/roles/wireguard/handlers/main.yml @@ -3,3 +3,7 @@ ansible.builtin.systemd: name: "wg-quick@{{ wireguard_interface }}" state: restarted + +- name: reload ufw + community.general.ufw: + state: reloaded diff --git a/roles/wireguard/tasks/2_server-config.yml b/roles/wireguard/tasks/2_server-config.yml index cc81400..d35f8c7 100644 --- a/roles/wireguard/tasks/2_server-config.yml +++ b/roles/wireguard/tasks/2_server-config.yml @@ -7,79 +7,12 @@ owner: root group: root -- name: Set wg config file path - ansible.builtin.set_fact: - wireguard_config_path: "/etc/wireguard/{{ wireguard_interface }}.conf" - -- name: Check if WireGuard config exists - stat: - path: "{{ wireguard_config_path }}" - register: wireguard_config_stat - -- name: When WireGuard config exists, extract existing key - when: wireguard_config_stat.stat.exists - block: - - name: Extract WireGuard private key if present - shell: "grep '^PrivateKey' {{ wireguard_config_path }} | awk '{print $3}'" - register: wireguard_private_key - changed_when: false - when: wireguard_config_stat.stat.exists - - name: Set private key fact - ansible.builtin.set_fact: - wireguard_private_key: "{{ wireguard_private_key.stdout }}" - no_log: true - rescue: - - name: there was an error extracting the private key - debug: - msg: "there was an error extracting wireguard private key in existing config on {{ inventory_hostname }}." - - name: Fail explicitly after extracting private key error - fail: - msg: "extracting wireguard private key failed on {{ inventory_hostname }}." - always: - - name: extract wireguard private key block finished (success or failure) - debug: - msg: "extracting wireguard private key block finished on {{ inventory_hostname }}." - -- name: When WireGuard config does not yet exist - when: not wireguard_config_stat.stat.exists - block: - - name: Generate WireGuard private key - ansible.builtin.shell: | - wg genkey - register: wireguard_private_key_generated - changed_when: false - when: not wireguard_config_stat.stat.exists - no_log: true - - name: Set private key fact - ansible.builtin.set_fact: - wireguard_private_key: "{{ wireguard_private_key_generated.stdout }}" - no_log: true - rescue: - - name: there was an error generating the private key - debug: - msg: "there was an error generating wireguard private key on {{ inventory_hostname }}." - - name: Fail explicitly after wireguard private key generation error - fail: - msg: "wireguard private key generation failed failed on {{ inventory_hostname }}." - always: - - name: generating wireguard private key block finished (success or failure) - debug: - msg: "generating wireguard private key block finished on {{ inventory_hostname }}." - -- name: Generate WireGuard public key - ansible.builtin.shell: | - echo "{{ wireguard_private_key }}" | wg pubkey - register: wireguard_public_key - changed_when: false - no_log: true - -- name: Set public key fact - ansible.builtin.set_fact: - wireguard_public_key: "{{ wireguard_public_key.stdout }}" +- name: When WireGuard config exists, extract existing keys + include_tasks: "process-server-keys.yml" - name: Display public key ansible.builtin.debug: - msg: "WireGuard Public Key: {{ wireguard_public_key }}" + msg: "WireGuard Public Key: {{ wireguard_server_public_key }}" - name: Deploy WireGuard interface configuration ansible.builtin.template: @@ -88,10 +21,3 @@ mode: '0600' owner: root group: root - notify: Restart WireGuard - -- name: Enable and start WireGuard interface - ansible.builtin.systemd: - name: "wg-quick@{{ wireguard_interface }}" - enabled: true - state: started diff --git a/roles/wireguard/tasks/3_clients.yml b/roles/wireguard/tasks/3_clients.yml index d5432d0..bdbe0a0 100644 --- a/roles/wireguard/tasks/3_clients.yml +++ b/roles/wireguard/tasks/3_clients.yml @@ -9,7 +9,7 @@ - name: Set local_wireguard_client_config_dir ansible.builtin.set_fact: - local_wireguard_client_config_dir: "{{ inventory_dir }}/wireguard_configs/clients" + local_wireguard_client_config_dir: "{{ local_wireguard_config_dir }}/clients" - name: Make sure client config directory exists for given inventory delegate_to: localhost @@ -20,3 +20,9 @@ owner: "{{ local_user }}" group: "{{ local_user }}" changed_when: false + +- name: Create client config files + include_tasks: "create-client-config.yml" + loop_control: + loop_var: client + loop: "{{ wireguard_client_peers }}" diff --git a/roles/wireguard/tasks/4_ufw.yml b/roles/wireguard/tasks/4_ufw.yml new file mode 100644 index 0000000..c415b14 --- /dev/null +++ b/roles/wireguard/tasks/4_ufw.yml @@ -0,0 +1,29 @@ +--- +- name: Set UFW default forward policy to ACCEPT + lineinfile: + path: "/etc/default/ufw" + regexp: "^DEFAULT_FORWARD_POLICY=" + line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' + backup: yes + notify: reload ufw + +- name: Check if NAT rules already exist in before.rules + command: grep -q "NAT table rules for WireGuard" /etc/ufw/before.rules + register: nat_rules_exist + changed_when: false + failed_when: false + +- name: Add NAT masquerading rules to UFW before.rules + blockinfile: + path: /etc/ufw/before.rules + insertbefore: '^\*filter' + marker: "# {mark} ANSIBLE MANAGED BLOCK - WireGuard NAT" + block: | + # NAT table rules for WireGuard + *nat + :POSTROUTING ACCEPT [0:0] + -A POSTROUTING -s {{ wireguard_subnet }} -o {{ wireguard_external_interface }} -j MASQUERADE + COMMIT + backup: yes + when: nat_rules_exist.rc != 0 + notify: reload ufw diff --git a/roles/wireguard/tasks/5_service.yml b/roles/wireguard/tasks/5_service.yml new file mode 100644 index 0000000..78a2843 --- /dev/null +++ b/roles/wireguard/tasks/5_service.yml @@ -0,0 +1,6 @@ +--- +- name: Enable and start WireGuard interface + ansible.builtin.systemd: + name: "wg-quick@{{ wireguard_interface }}" + enabled: true + state: restarted diff --git a/roles/wireguard/tasks/create-client-config.yml b/roles/wireguard/tasks/create-client-config.yml new file mode 100644 index 0000000..792cee7 --- /dev/null +++ b/roles/wireguard/tasks/create-client-config.yml @@ -0,0 +1,121 @@ +--- +- name: Set client config all path + ansible.builtin.set_fact: + client_config_all_path: "{{ local_wireguard_client_config_dir }}/{{ client.name }}/vpn-{{ wireguard_company }}-all.conf" + +- name: Set client config lan path + ansible.builtin.set_fact: + client_config_lan_path: "{{ local_wireguard_client_config_dir }}/{{ client.name }}/vpn-{{ wireguard_company }}-lan.conf" + +- name: Extract existing server keys + include_tasks: "process-server-keys.yml" + +- name: Make sure client config dir exists for given client + delegate_to: localhost + ansible.builtin.file: + path: "{{ local_wireguard_client_config_dir }}/{{ client.name }}" + state: directory + mode: "0700" + owner: "{{ local_user }}" + group: "{{ local_user }}" + changed_when: false + +- name: Check if client config exists + delegate_to: localhost + stat: + path: "{{ client_config_all_path }}.vault" + register: client_config_stat + +- name: Create client private key + ansible.builtin.shell: | + wg genkey + register: generated_client_private_key + when: not client_config_stat.stat.exists + +- name: Extract client config private key if present + delegate_to: localhost + become: yes + become_user: "{{ local_user }}" + shell: "ansible-vault view {{ client_config_all_path }}.vault | grep '^PrivateKey' | awk '{print $3}'" + register: existing_client_private_key + changed_when: false + when: client_config_stat.stat.exists + +- name: set client_private_key + ansible.builtin.set_fact: + client_private_key: "{{ existing_client_private_key.stdout | default(generated_client_private_key.stdout) }}" + +- name: Extract client config preshared key if present + delegate_to: localhost + become: yes + become_user: "{{ local_user }}" + shell: "ansible-vault view {{ client_config_all_path }}.vault | grep '^PresharedKey' | awk '{print $3}'" + register: existing_client_preshared_key + changed_when: false + when: client_config_stat.stat.exists + +- name: Create client private key + ansible.builtin.shell: | + wg genpsk + register: generated_client_preshared_key + when: not client_config_stat.stat.exists + +- name: set client_preshared_key + ansible.builtin.set_fact: + client_preshared_key: "{{ existing_client_preshared_key.stdout | default(generated_client_preshared_key.stdout) }}" + +- name: Create client config for {{ client.name }} (all) + delegate_to: localhost + ansible.builtin.template: + src: "client-peer-all.conf.j2" + dest: "{{ client_config_all_path }}" + mode: "0600" + owner: "{{ local_user }}" + group: "{{ local_user }}" + +- name: Encrypt client config for {{ client.name }} (all) + delegate_to: localhost + become: yes + become_user: "{{ local_user }}" + command: "ansible-vault encrypt {{ client_config_all_path }} --output {{ client_config_all_path }}.vault" + +- name: Create client config for {{ client.name }} (lan) + delegate_to: localhost + ansible.builtin.template: + src: "client-peer-lan.conf.j2" + dest: "{{ client_config_lan_path }}" + mode: "0600" + owner: "{{ local_user }}" + group: "{{ local_user }}" + +- name: Encrypt client config for {{ client.name }} (lan) + delegate_to: localhost + become: yes + become_user: "{{ local_user }}" + command: "ansible-vault encrypt {{ client_config_lan_path }} --output {{ client_config_lan_path }}.vault" + +- name: Shred and delete unencrypted config files + delegate_to: localhost + become: yes + become_user: "{{ local_user }}" + ansible.builtin.shell: | + shred -u "{{ config }}" + loop: "{{ [client_config_all_path, client_config_lan_path] }}" + loop_control: + loop_var: config + +- name: Extract client public key + shell: "echo '{{ client_private_key }}' | wg pubkey" + register: client_public_key + changed_when: false + +- name: Set client public key fact + ansible.builtin.set_fact: + client_public_key: "{{ client_public_key.stdout }}" + +- name: Append peer config to server config + ansible.builtin.blockinfile: + path: /etc/wireguard/{{ wireguard_interface }}.conf + block: "{{ lookup('template', 'client-peer-section.conf.j2') }}" + marker: "# {mark} {{ client.name }}" + insertafter: EOF diff --git a/roles/wireguard/tasks/process-server-keys.yml b/roles/wireguard/tasks/process-server-keys.yml new file mode 100644 index 0000000..e3c854d --- /dev/null +++ b/roles/wireguard/tasks/process-server-keys.yml @@ -0,0 +1,39 @@ +--- +- name: Set wg config file path + ansible.builtin.set_fact: + wireguard_server_config_path: "/etc/wireguard/{{ wireguard_interface }}.conf" + +- name: Check if WireGuard config exists + stat: + path: "{{ wireguard_server_config_path }}" + register: wireguard_server_config_stat + +- name: Extract WireGuard private key if present + shell: "grep '^PrivateKey' {{ wireguard_server_config_path }} | awk '{print $3}'" + register: existing_wireguard_server_private_key + changed_when: false + when: wireguard_server_config_stat.stat.exists + +- name: Generate WireGuard private key + ansible.builtin.shell: | + wg genkey + register: wireguard_server_private_key_generated + changed_when: false + when: not wireguard_server_config_stat.stat.exists + no_log: true + +- name: Set server private key fact + ansible.builtin.set_fact: + wireguard_server_private_key: "{{ existing_wireguard_server_private_key.stdout | default(wireguard_server_private_key_generated.stdout) }}" + no_log: true + +- name: Extract WireGuard public key if present + shell: "echo '{{ wireguard_server_private_key }}' | wg pubkey" + register: wireguard_server_public_key + changed_when: false + when: wireguard_server_private_key is defined + +- name: Set public key fact + ansible.builtin.set_fact: + wireguard_server_public_key: "{{ wireguard_server_public_key.stdout }}" + when: wireguard_server_private_key is defined diff --git a/roles/wireguard/templates/client-peer-all.conf.j2 b/roles/wireguard/templates/client-peer-all.conf.j2 index e69de29..9b1c5d1 100644 --- a/roles/wireguard/templates/client-peer-all.conf.j2 +++ b/roles/wireguard/templates/client-peer-all.conf.j2 @@ -0,0 +1,13 @@ +[Interface] +PrivateKey = {{ client_private_key }} +Address = {{ client.ip }} +{% if wireguard_dns | length > 0 %} +DNS = {{ wireguard_dns | join(', ') }} +{% endif %} + +[Peer] +PublicKey = {{ wireguard_server_public_key }} +PresharedKey = {{ client_preshared_key }} +Endpoint = {{ wireguard_endpoint }}:{{ wireguard_port }} +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 25 diff --git a/roles/wireguard/templates/client-peer-lan.conf.j2 b/roles/wireguard/templates/client-peer-lan.conf.j2 index 3fcf4f2..752a7e5 100644 --- a/roles/wireguard/templates/client-peer-lan.conf.j2 +++ b/roles/wireguard/templates/client-peer-lan.conf.j2 @@ -1,10 +1,13 @@ [Interface] -PrivateKey = {{ private_key }} -Address = {{ peer.ip }} -DNS = {{ dns1 }} +PrivateKey = {{ client_private_key }} +Address = {{ client.ip }} +{% if wireguard_dns | length > 0 %} +DNS = {{ wireguard_dns | join(', ') }} +{% endif %} [Peer] -PublicKey = {{ wireguard_server_public_key }} +PublicKey = {{ wireguard_server_public_key }} +PresharedKey = {{ client_preshared_key }} Endpoint = {{ wireguard_endpoint }}:{{ wireguard_port }} -AllowedIPs = {{ allowed_ips }} +AllowedIPs = {{ wireguard_allowed_lan_ips }} PersistentKeepalive = 25 diff --git a/roles/wireguard/templates/client-peer-section.conf.j2 b/roles/wireguard/templates/client-peer-section.conf.j2 new file mode 100644 index 0000000..c79a79c --- /dev/null +++ b/roles/wireguard/templates/client-peer-section.conf.j2 @@ -0,0 +1,4 @@ +[Peer] +PublicKey = {{ client_public_key }} +AllowedIPs = {{ client.ip }} +PreSharedKey = {{ client_preshared_key }} diff --git a/roles/wireguard/templates/wg-interface.conf.j2 b/roles/wireguard/templates/wg-interface.conf.j2 index 0aa63f9..24d9846 100644 --- a/roles/wireguard/templates/wg-interface.conf.j2 +++ b/roles/wireguard/templates/wg-interface.conf.j2 @@ -2,7 +2,7 @@ # {{ ansible_managed }} Address = {{ wireguard_internal_server_ip }} ListenPort = {{ wireguard_port }} -PrivateKey = {{ wireguard_private_key }} +PrivateKey = {{ wireguard_server_private_key }} {% if wireguard_mtu %} MTU = {{ wireguard_mtu }} @@ -19,20 +19,3 @@ PostUp = {{ command }} {% for command in wireguard_post_down %} PostDown = {{ command }} {% endfor %} - -{% for peer in wireguard_client_peers %} - -[Peer] -# Peer: {{ peer.name }} -PublicKey = {{ peer.public_key }} -{% if peer.preshared_key is defined %} -PresharedKey = {{ peer.preshared_key }} -{% endif %} -AllowedIPs = {{ peer.allowed_ips }} -{% if peer.endpoint is defined %} -Endpoint = {{ peer.endpoint }} -{% endif %} -{% if peer.persistent_keepalive is defined %} -PersistentKeepalive = {{ peer.persistent_keepalive }} -{% endif %} -{% endfor %} From 2d79b3f44c4ae0c6d5d07ac321cfb8f89f14561c Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Fri, 20 Mar 2026 20:04:05 +0000 Subject: [PATCH 2/9] Make managing ufw config for wireguard optional to make it work within molecule tests --- inventories/test/group_vars/all/vars.yml | 1 + .../wireguard/tests/test_wireguard-clients.py | 19 ------------ roles/wireguard/defaults/main.yml | 2 ++ roles/wireguard/tasks/4_ufw.yml | 31 ++----------------- roles/wireguard/tasks/ufw.yml | 29 +++++++++++++++++ 5 files changed, 35 insertions(+), 47 deletions(-) delete mode 100644 molecule/wireguard/tests/test_wireguard-clients.py create mode 100644 roles/wireguard/tasks/ufw.yml diff --git a/inventories/test/group_vars/all/vars.yml b/inventories/test/group_vars/all/vars.yml index 73f3c45..b8c9b3f 100644 --- a/inventories/test/group_vars/all/vars.yml +++ b/inventories/test/group_vars/all/vars.yml @@ -10,3 +10,4 @@ bootstrap_user: "test-bootstrap" dns1: 8.8.8.8 dns2: 8.8.4.4 disable_ipv6: false +manage_ufw: false diff --git a/molecule/wireguard/tests/test_wireguard-clients.py b/molecule/wireguard/tests/test_wireguard-clients.py deleted file mode 100644 index 37dfd1a..0000000 --- a/molecule/wireguard/tests/test_wireguard-clients.py +++ /dev/null @@ -1,19 +0,0 @@ -def test_wireguard_config_file_exists(host): - config_file = host.file("/etc/wireguard/wg0.conf") - - assert config_file.exists - assert config_file.is_file - - -def test_wireguard_config_file_content(host): - """Test that the WireGuard configuration file contains expected content.""" - config_file = host.file("/etc/wireguard/wg0.conf") - - content = config_file.content_string - - # Check for required sections and parameters - assert "[Peer]\n# Alice" in content - # assert "# Ansible managed" in content - # assert "Address = 10.10.0.1/24" in content - # assert "ListenPort = 51820" in content - # assert "PrivateKey = " in content diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index 193875e..ec8b614 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -23,3 +23,5 @@ wireguard_mtu: "" # PostUp and PostDown commands (optional) wireguard_post_up: [] wireguard_post_down: [] + +manage_ufw: true diff --git a/roles/wireguard/tasks/4_ufw.yml b/roles/wireguard/tasks/4_ufw.yml index c415b14..a9c42fd 100644 --- a/roles/wireguard/tasks/4_ufw.yml +++ b/roles/wireguard/tasks/4_ufw.yml @@ -1,29 +1,4 @@ --- -- name: Set UFW default forward policy to ACCEPT - lineinfile: - path: "/etc/default/ufw" - regexp: "^DEFAULT_FORWARD_POLICY=" - line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' - backup: yes - notify: reload ufw - -- name: Check if NAT rules already exist in before.rules - command: grep -q "NAT table rules for WireGuard" /etc/ufw/before.rules - register: nat_rules_exist - changed_when: false - failed_when: false - -- name: Add NAT masquerading rules to UFW before.rules - blockinfile: - path: /etc/ufw/before.rules - insertbefore: '^\*filter' - marker: "# {mark} ANSIBLE MANAGED BLOCK - WireGuard NAT" - block: | - # NAT table rules for WireGuard - *nat - :POSTROUTING ACCEPT [0:0] - -A POSTROUTING -s {{ wireguard_subnet }} -o {{ wireguard_external_interface }} -j MASQUERADE - COMMIT - backup: yes - when: nat_rules_exist.rc != 0 - notify: reload ufw +- name: Setup wireguard ufw config + include_tasks: "ufw.yml" + when: manage_ufw diff --git a/roles/wireguard/tasks/ufw.yml b/roles/wireguard/tasks/ufw.yml new file mode 100644 index 0000000..c415b14 --- /dev/null +++ b/roles/wireguard/tasks/ufw.yml @@ -0,0 +1,29 @@ +--- +- name: Set UFW default forward policy to ACCEPT + lineinfile: + path: "/etc/default/ufw" + regexp: "^DEFAULT_FORWARD_POLICY=" + line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' + backup: yes + notify: reload ufw + +- name: Check if NAT rules already exist in before.rules + command: grep -q "NAT table rules for WireGuard" /etc/ufw/before.rules + register: nat_rules_exist + changed_when: false + failed_when: false + +- name: Add NAT masquerading rules to UFW before.rules + blockinfile: + path: /etc/ufw/before.rules + insertbefore: '^\*filter' + marker: "# {mark} ANSIBLE MANAGED BLOCK - WireGuard NAT" + block: | + # NAT table rules for WireGuard + *nat + :POSTROUTING ACCEPT [0:0] + -A POSTROUTING -s {{ wireguard_subnet }} -o {{ wireguard_external_interface }} -j MASQUERADE + COMMIT + backup: yes + when: nat_rules_exist.rc != 0 + notify: reload ufw From 41d09f8cb343439b288310f214fb29fff04877f5 Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Sat, 21 Mar 2026 06:20:25 +0000 Subject: [PATCH 3/9] Extend wireguard client config tests --- molecule/wireguard/tests/conftest.py | 14 ++++++++++++++ .../tests/test_wireguard-client-configs.py | 7 +++++++ 2 files changed, 21 insertions(+) create mode 100644 molecule/wireguard/tests/conftest.py create mode 100644 molecule/wireguard/tests/test_wireguard-client-configs.py diff --git a/molecule/wireguard/tests/conftest.py b/molecule/wireguard/tests/conftest.py new file mode 100644 index 0000000..191bb24 --- /dev/null +++ b/molecule/wireguard/tests/conftest.py @@ -0,0 +1,14 @@ +import os +from pathlib import Path +import pytest + +@pytest.fixture(scope="session") +def molecule_inventory_file(): + inv = os.environ.get("MOLECULE_INVENTORY_FILE") + if not inv: + pytest.skip("MOLECULE_INVENTORY_FILE not set (not running under Molecule)") + return Path(inv).resolve() + +@pytest.fixture(scope="session") +def molecule_inventory_dir(molecule_inventory_file): + return molecule_inventory_file.parent diff --git a/molecule/wireguard/tests/test_wireguard-client-configs.py b/molecule/wireguard/tests/test_wireguard-client-configs.py new file mode 100644 index 0000000..1987481 --- /dev/null +++ b/molecule/wireguard/tests/test_wireguard-client-configs.py @@ -0,0 +1,7 @@ +from pathlib import Path + +def test_wireguard_config_dir_exists_locally(molecule_inventory_dir): + client_config_dir: Path = molecule_inventory_dir / "wireguard_configs" / "clients" + + assert client_config_dir.exists(), f"{client_config_dir} missing on controller" + assert client_config_dir.is_dir(), f"{client_config_dir} is not a directory on controller" From 15b4f052a63bbd4cefffe8f132e90cd3c768d6a6 Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Mon, 23 Mar 2026 08:40:53 +0000 Subject: [PATCH 4/9] Wip improving molecule tests for wireguard --- .gitignore | 1 + .../test/group_vars/wireguard/vars.yml | 3 +- inventories/test/hosts | 3 ++ inventories/test/wireguard_configs/.gitkeep | 0 molecule/wireguard/converge.yml | 2 +- molecule/wireguard/molecule.yml | 2 ++ .../tests/test_wireguard-client-configs.py | 12 +++++++ .../wireguard/tests/test_wireguard-server.py | 33 +++++++++++++------ roles/wireguard/defaults/main.yml | 2 +- roles/wireguard/tasks/2_server-config.yml | 1 + .../wireguard/tasks/create-client-config.yml | 9 +++-- .../templates/client-peer-all.conf.j2 | 4 +-- .../templates/client-peer-lan.conf.j2 | 4 +-- .../wireguard/templates/wg-interface.conf.j2 | 4 +-- 14 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 inventories/test/wireguard_configs/.gitkeep diff --git a/.gitignore b/.gitignore index 7193b27..007ec54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ megalinter-reports/ inventories/production .galaxy/ +inventories/test/wireguard_configs diff --git a/inventories/test/group_vars/wireguard/vars.yml b/inventories/test/group_vars/wireguard/vars.yml index 3c6aafd..a4f0645 100644 --- a/inventories/test/group_vars/wireguard/vars.yml +++ b/inventories/test/group_vars/wireguard/vars.yml @@ -1,7 +1,8 @@ --- -wireguard_port: 51821 +wireguard_port: 51842 wireguard_endpoint: "vpn.example.com" wireguard_allowed_lan_ips: "10.10.0.1/32, 192.168.0.1/24" +wireguard_dns_servers: [8.8.8.8] wireguard_client_peers: - name: "alice" ip: "10.10.0.2/32" diff --git a/inventories/test/hosts b/inventories/test/hosts index be906c6..904eaaa 100644 --- a/inventories/test/hosts +++ b/inventories/test/hosts @@ -1,2 +1,5 @@ [example_group] #testhost ansible_host=10.42.42.42 + +[wireguard] +testhost-ubuntu-24.04 diff --git a/inventories/test/wireguard_configs/.gitkeep b/inventories/test/wireguard_configs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/molecule/wireguard/converge.yml b/molecule/wireguard/converge.yml index 8f9f1da..b810c16 100644 --- a/molecule/wireguard/converge.yml +++ b/molecule/wireguard/converge.yml @@ -1,6 +1,6 @@ --- - name: Converge - hosts: all + hosts: wireguard become: yes roles: diff --git a/molecule/wireguard/molecule.yml b/molecule/wireguard/molecule.yml index 59e489e..4c84f39 100644 --- a/molecule/wireguard/molecule.yml +++ b/molecule/wireguard/molecule.yml @@ -19,3 +19,5 @@ provisioner: group_vars: "../../inventories/test/group_vars/" host_vars: "../../inventories/test/host_vars/" hosts: "../../inventories/test/hosts" + wireguard_configs: "../../inventories/test/wireguard_configs" + diff --git a/molecule/wireguard/tests/test_wireguard-client-configs.py b/molecule/wireguard/tests/test_wireguard-client-configs.py index 1987481..4df09ef 100644 --- a/molecule/wireguard/tests/test_wireguard-client-configs.py +++ b/molecule/wireguard/tests/test_wireguard-client-configs.py @@ -5,3 +5,15 @@ def test_wireguard_config_dir_exists_locally(molecule_inventory_dir): assert client_config_dir.exists(), f"{client_config_dir} missing on controller" assert client_config_dir.is_dir(), f"{client_config_dir} is not a directory on controller" + +def test_wireguard_client_config(host, molecule_inventory_dir): + client_config_dir: Path = molecule_inventory_dir / "wireguard_configs" / "clients" / "alice" + + assert client_config_dir.exists(), f"{client_config_dir} missing on controller" + assert client_config_dir.is_dir(), f"{client_config_dir} is not a directory on controller" + + client_all_config_file: Path = client_config_dir / "vpn-acme-all.conf.vault" + assert client_all_config_file.exists(), f"{client_all_config_file} missing in local inventory" + + client_lan_config_file: Path = client_config_dir / "vpn-acme-lan.conf.vault" + assert client_lan_config_file.exists(), f"{client_lan_config_file} missing in local inventory" diff --git a/molecule/wireguard/tests/test_wireguard-server.py b/molecule/wireguard/tests/test_wireguard-server.py index 6d48087..92e0edb 100644 --- a/molecule/wireguard/tests/test_wireguard-server.py +++ b/molecule/wireguard/tests/test_wireguard-server.py @@ -1,3 +1,5 @@ +import re + def test_wireguard_config_file_exists(host): config_file = host.file("/etc/wireguard/wg0.conf") @@ -6,7 +8,6 @@ def test_wireguard_config_file_exists(host): def test_wireguard_config_file_permissions(host): - """Test that the WireGuard configuration file has correct permissions.""" config_file = host.file("/etc/wireguard/wg0.conf") # WireGuard config files should be readable only by root for security @@ -15,15 +16,27 @@ def test_wireguard_config_file_permissions(host): assert config_file.mode == 0o600 -def test_wireguard_config_file_content(host): - """Test that the WireGuard configuration file contains expected content.""" +def test_wireguard_server_conf_content(host): config_file = host.file("/etc/wireguard/wg0.conf") - content = config_file.content_string + wg0_conf_content = config_file.content_string + + # server part + assert "[Interface]" in wg0_conf_content + assert "# Ansible managed" in wg0_conf_content + assert "Address = 10.10.0.1/24" in wg0_conf_content + assert "ListenPort = 51842" in wg0_conf_content + assert "PrivateKey = " in wg0_conf_content + assert "DNS = 8.8.8.8" in wg0_conf_content + + # client peer part + alice_peer = ( + r'# BEGIN peer alice\n' + r'\[Peer\]\n' + r'PublicKey = [A-Za-z0-9+/]{43}=\n' + r'AllowedIPs = 10\.10\.0\.2/32\n' + r'PreSharedKey = [A-Za-z0-9+/]{43}=\n' + r'# END peer alice' + ) - # Check for required sections and parameters - assert "[Interface]" in content - assert "# Ansible managed" in content - assert "Address = 10.10.0.1/24" in content - assert "ListenPort = 51820" in content - assert "PrivateKey = " in content + assert re.search(alice_peer, wg0_conf_content), "alice peer section not found or invalid format" diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index ec8b614..a7a5817 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -15,7 +15,7 @@ wireguard_client_peers: [] # ip: "10.10.0.2/32" # DNS servers (optional) -wireguard_dns: [10.10.0.1] +wireguard_dns_servers: [10.10.0.1] # MTU (optional) wireguard_mtu: "" diff --git a/roles/wireguard/tasks/2_server-config.yml b/roles/wireguard/tasks/2_server-config.yml index d35f8c7..374d2f9 100644 --- a/roles/wireguard/tasks/2_server-config.yml +++ b/roles/wireguard/tasks/2_server-config.yml @@ -21,3 +21,4 @@ mode: '0600' owner: root group: root + changed_when: false diff --git a/roles/wireguard/tasks/create-client-config.yml b/roles/wireguard/tasks/create-client-config.yml index 792cee7..7c01d27 100644 --- a/roles/wireguard/tasks/create-client-config.yml +++ b/roles/wireguard/tasks/create-client-config.yml @@ -18,7 +18,6 @@ mode: "0700" owner: "{{ local_user }}" group: "{{ local_user }}" - changed_when: false - name: Check if client config exists delegate_to: localhost @@ -72,12 +71,14 @@ mode: "0600" owner: "{{ local_user }}" group: "{{ local_user }}" + changed_when: false - name: Encrypt client config for {{ client.name }} (all) delegate_to: localhost become: yes become_user: "{{ local_user }}" command: "ansible-vault encrypt {{ client_config_all_path }} --output {{ client_config_all_path }}.vault" + changed_when: false - name: Create client config for {{ client.name }} (lan) delegate_to: localhost @@ -87,12 +88,14 @@ mode: "0600" owner: "{{ local_user }}" group: "{{ local_user }}" + changed_when: false - name: Encrypt client config for {{ client.name }} (lan) delegate_to: localhost become: yes become_user: "{{ local_user }}" command: "ansible-vault encrypt {{ client_config_lan_path }} --output {{ client_config_lan_path }}.vault" + changed_when: false - name: Shred and delete unencrypted config files delegate_to: localhost @@ -103,6 +106,7 @@ loop: "{{ [client_config_all_path, client_config_lan_path] }}" loop_control: loop_var: config + changed_when: false - name: Extract client public key shell: "echo '{{ client_private_key }}' | wg pubkey" @@ -117,5 +121,6 @@ ansible.builtin.blockinfile: path: /etc/wireguard/{{ wireguard_interface }}.conf block: "{{ lookup('template', 'client-peer-section.conf.j2') }}" - marker: "# {mark} {{ client.name }}" + marker: "# {mark} peer {{ client.name }}" insertafter: EOF + changed_when: false diff --git a/roles/wireguard/templates/client-peer-all.conf.j2 b/roles/wireguard/templates/client-peer-all.conf.j2 index 9b1c5d1..a7db8c0 100644 --- a/roles/wireguard/templates/client-peer-all.conf.j2 +++ b/roles/wireguard/templates/client-peer-all.conf.j2 @@ -1,8 +1,8 @@ [Interface] PrivateKey = {{ client_private_key }} Address = {{ client.ip }} -{% if wireguard_dns | length > 0 %} -DNS = {{ wireguard_dns | join(', ') }} +{% if wireguard_dns_servers | length > 0 %} +DNS = {{ wireguard_dns_servers | join(', ') }} {% endif %} [Peer] diff --git a/roles/wireguard/templates/client-peer-lan.conf.j2 b/roles/wireguard/templates/client-peer-lan.conf.j2 index 752a7e5..ded7e62 100644 --- a/roles/wireguard/templates/client-peer-lan.conf.j2 +++ b/roles/wireguard/templates/client-peer-lan.conf.j2 @@ -1,8 +1,8 @@ [Interface] PrivateKey = {{ client_private_key }} Address = {{ client.ip }} -{% if wireguard_dns | length > 0 %} -DNS = {{ wireguard_dns | join(', ') }} +{% if wireguard_dns_servers | length > 0 %} +DNS = {{ wireguard_dns_servers | join(', ') }} {% endif %} [Peer] diff --git a/roles/wireguard/templates/wg-interface.conf.j2 b/roles/wireguard/templates/wg-interface.conf.j2 index 24d9846..5489f17 100644 --- a/roles/wireguard/templates/wg-interface.conf.j2 +++ b/roles/wireguard/templates/wg-interface.conf.j2 @@ -8,8 +8,8 @@ PrivateKey = {{ wireguard_server_private_key }} MTU = {{ wireguard_mtu }} {% endif %} -{% if wireguard_dns | length > 0 %} -DNS = {{ wireguard_dns | join(', ') }} +{% if wireguard_dns_servers | length > 0 %} +DNS = {{ wireguard_dns_servers | join(', ') }} {% endif %} {% for command in wireguard_post_up %} From 055e1f8df4682f85f7c0da3734596768676eee0f Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Mon, 23 Mar 2026 14:18:23 +0000 Subject: [PATCH 5/9] Document how to import wireguard client config mit nmcli, rewrite local encrypted wireguard config only if changed --- roles/wireguard/README.md | 4 +++ .../wireguard/tasks/9_local-config-backup.yml | 15 ++++++++--- .../wireguard/tasks/create-client-config.yml | 26 ++++++++++++++++--- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/roles/wireguard/README.md b/roles/wireguard/README.md index 4d54c7d..6f01165 100644 --- a/roles/wireguard/README.md +++ b/roles/wireguard/README.md @@ -22,3 +22,7 @@ configs are stored and encrypted (ansible-vault) locally in `inventories/product to generate the QR code for the client config, run: `ansible-vault view vpn-acme-all.conf.vault | qrencode -t ansiutf8` + +or import the config into network manager after decrypting: + +`nmcli connection import type wireguard file vpn-acme-all.conf` diff --git a/roles/wireguard/tasks/9_local-config-backup.yml b/roles/wireguard/tasks/9_local-config-backup.yml index 3041dfa..8ca4888 100644 --- a/roles/wireguard/tasks/9_local-config-backup.yml +++ b/roles/wireguard/tasks/9_local-config-backup.yml @@ -50,13 +50,20 @@ run_once: true changed_when: false -- name: Encrypt the fetched file with Ansible Vault +- name: Encrypt server config for if it has changed delegate_to: localhost become: yes become_user: "{{ local_user }}" - command: "ansible-vault encrypt {{ local_tmp_server_bkp_config_path }} --output {{ inventory_dir }}/wireguard_configs/wg0.conf.vault" - ignore_errors: yes - run_once: true + shell: | + set -euo pipefail + if [ -f "{{ inventory_dir }}/wireguard_configs/wg0.conf.vault" ]; then + if ansible-vault view "{{ inventory_dir }}/wireguard_configs/wg0.conf.vault" | cmp -s - "{{ local_tmp_server_bkp_config_path }}"; then + exit 0 + fi + fi + ansible-vault encrypt "{{ local_tmp_server_bkp_config_path }}" --output "{{ inventory_dir }}/wireguard_configs/wg0.conf.vault" + args: + executable: /bin/bash changed_when: false - name: Remove the unencrypted fetched file diff --git a/roles/wireguard/tasks/create-client-config.yml b/roles/wireguard/tasks/create-client-config.yml index 7c01d27..d37bb10 100644 --- a/roles/wireguard/tasks/create-client-config.yml +++ b/roles/wireguard/tasks/create-client-config.yml @@ -73,11 +73,20 @@ group: "{{ local_user }}" changed_when: false -- name: Encrypt client config for {{ client.name }} (all) +- name: Encrypt client config for {{ client.name }} (all) if it has changed delegate_to: localhost become: yes become_user: "{{ local_user }}" - command: "ansible-vault encrypt {{ client_config_all_path }} --output {{ client_config_all_path }}.vault" + shell: | + set -euo pipefail + if [ -f "{{ client_config_all_path }}.vault" ]; then + if ansible-vault view "{{ client_config_all_path }}.vault" | cmp -s - "{{ client_config_all_path }}"; then + exit 0 + fi + fi + ansible-vault encrypt "{{ client_config_all_path }}" --output "{{ client_config_all_path }}.vault" + args: + executable: /bin/bash changed_when: false - name: Create client config for {{ client.name }} (lan) @@ -90,11 +99,20 @@ group: "{{ local_user }}" changed_when: false -- name: Encrypt client config for {{ client.name }} (lan) +- name: Encrypt client config for {{ client.name }} (lan) if it has changed delegate_to: localhost become: yes become_user: "{{ local_user }}" - command: "ansible-vault encrypt {{ client_config_lan_path }} --output {{ client_config_lan_path }}.vault" + shell: | + set -euo pipefail + if [ -f "{{ client_config_lan_path }}.vault" ]; then + if ansible-vault view "{{ client_config_lan_path }}.vault" | cmp -s - "{{ client_config_lan_path }}"; then + exit 0 + fi + fi + ansible-vault encrypt "{{ client_config_lan_path }}" --output "{{ client_config_lan_path }}.vault" + args: + executable: /bin/bash changed_when: false - name: Shred and delete unencrypted config files From d5894ca0eab4f83de5f3bc5b5e8c7da24402b9c4 Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Tue, 24 Mar 2026 07:10:48 +0000 Subject: [PATCH 6/9] Try to fix wireguard server tests --- .../tests/test_wireguard-client-configs.py | 2 +- .../wireguard/tests/test_wireguard-server.py | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/molecule/wireguard/tests/test_wireguard-client-configs.py b/molecule/wireguard/tests/test_wireguard-client-configs.py index 4df09ef..4ad038c 100644 --- a/molecule/wireguard/tests/test_wireguard-client-configs.py +++ b/molecule/wireguard/tests/test_wireguard-client-configs.py @@ -6,7 +6,7 @@ def test_wireguard_config_dir_exists_locally(molecule_inventory_dir): assert client_config_dir.exists(), f"{client_config_dir} missing on controller" assert client_config_dir.is_dir(), f"{client_config_dir} is not a directory on controller" -def test_wireguard_client_config(host, molecule_inventory_dir): +def test_wireguard_client_config(molecule_inventory_dir): client_config_dir: Path = molecule_inventory_dir / "wireguard_configs" / "clients" / "alice" assert client_config_dir.exists(), f"{client_config_dir} missing on controller" diff --git a/molecule/wireguard/tests/test_wireguard-server.py b/molecule/wireguard/tests/test_wireguard-server.py index 92e0edb..0fe1c9c 100644 --- a/molecule/wireguard/tests/test_wireguard-server.py +++ b/molecule/wireguard/tests/test_wireguard-server.py @@ -1,19 +1,20 @@ import re +import pytest -def test_wireguard_config_file_exists(host): - config_file = host.file("/etc/wireguard/wg0.conf") +# def test_wireguard_config_file_exists(host): + # config_file = host.file("/etc/wireguard/wg0.conf") - assert config_file.exists - assert config_file.is_file + # assert config_file.exists + # assert config_file.is_file -def test_wireguard_config_file_permissions(host): - config_file = host.file("/etc/wireguard/wg0.conf") +# def test_wireguard_config_file_permissions(host): + # config_file = host.file("/etc/wireguard/wg0.conf") - # WireGuard config files should be readable only by root for security - assert config_file.user == "root" - assert config_file.group == "root" - assert config_file.mode == 0o600 + # # WireGuard config files should be readable only by root for security + # assert config_file.user == "root" + # assert config_file.group == "root" + # assert config_file.mode == 0o600 def test_wireguard_server_conf_content(host): From b9ce1f12925142bcdd562444cb0afa99b056e4eb Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Tue, 24 Mar 2026 07:28:50 +0000 Subject: [PATCH 7/9] Fix wireguard config inventory directory permissions --- roles/wireguard/tasks/9_local-config-backup.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/roles/wireguard/tasks/9_local-config-backup.yml b/roles/wireguard/tasks/9_local-config-backup.yml index 8ca4888..d1c50a2 100644 --- a/roles/wireguard/tasks/9_local-config-backup.yml +++ b/roles/wireguard/tasks/9_local-config-backup.yml @@ -23,8 +23,6 @@ - name: Make sure wireguard_configs directory exists for given inventory delegate_to: localhost - become: yes - become_user: "{{ local_user }}" ansible.builtin.file: path: "{{ inventory_dir }}/wireguard_configs" state: directory From dc5e28618795992f714515f8b480c0c799bb690c Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Tue, 24 Mar 2026 15:38:01 +0000 Subject: [PATCH 8/9] Make it possible to store wireguard config locally outside of inventory, fix wireguard tests --- ansible.cfg | 1 + .../test/group_vars/wireguard/vars.yml | 3 +++ molecule/wireguard/molecule.yml | 1 - molecule/wireguard/tests/conftest.py | 14 ------------- .../tests/test_wireguard-client-configs.py | 12 +++++------ .../wireguard/tests/test_wireguard-server.py | 20 +++++++++---------- roles/wireguard/tasks/3_clients.yml | 7 +++---- .../wireguard/tasks/9_local-config-backup.yml | 11 ++++++---- .../wireguard/tasks/create-client-config.yml | 18 ++++++++--------- roles/wireguard/tasks/facts.yml | 5 +++++ 10 files changed, 44 insertions(+), 48 deletions(-) delete mode 100644 molecule/wireguard/tests/conftest.py create mode 100644 roles/wireguard/tasks/facts.yml diff --git a/ansible.cfg b/ansible.cfg index e5bb87f..9393156 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -4,3 +4,4 @@ inventory = inventories/default/hosts interpreter_python = /usr/bin/python3.12 no_log = ${ANSIBLE_NO_LOG:true} inventory_ignore_patterns = ^wireguard_configs/.*$,^netplan_configs/.*$,^ssh_keys/.*$ +inventory_ignore_extensions = .orig,.bak,.swp,.vaulted diff --git a/inventories/test/group_vars/wireguard/vars.yml b/inventories/test/group_vars/wireguard/vars.yml index a4f0645..a045ad0 100644 --- a/inventories/test/group_vars/wireguard/vars.yml +++ b/inventories/test/group_vars/wireguard/vars.yml @@ -8,3 +8,6 @@ wireguard_client_peers: ip: "10.10.0.2/32" - name: "bob" ip: "10.10.0.3/32" +# default is inventory_dir/wireguard_configs +# moved outside of inventory_dir since this breaks molecule tests +local_wireguard_config_dir: "/tmp/molecule/wireguard_configs" diff --git a/molecule/wireguard/molecule.yml b/molecule/wireguard/molecule.yml index 4c84f39..090b5ee 100644 --- a/molecule/wireguard/molecule.yml +++ b/molecule/wireguard/molecule.yml @@ -19,5 +19,4 @@ provisioner: group_vars: "../../inventories/test/group_vars/" host_vars: "../../inventories/test/host_vars/" hosts: "../../inventories/test/hosts" - wireguard_configs: "../../inventories/test/wireguard_configs" diff --git a/molecule/wireguard/tests/conftest.py b/molecule/wireguard/tests/conftest.py deleted file mode 100644 index 191bb24..0000000 --- a/molecule/wireguard/tests/conftest.py +++ /dev/null @@ -1,14 +0,0 @@ -import os -from pathlib import Path -import pytest - -@pytest.fixture(scope="session") -def molecule_inventory_file(): - inv = os.environ.get("MOLECULE_INVENTORY_FILE") - if not inv: - pytest.skip("MOLECULE_INVENTORY_FILE not set (not running under Molecule)") - return Path(inv).resolve() - -@pytest.fixture(scope="session") -def molecule_inventory_dir(molecule_inventory_file): - return molecule_inventory_file.parent diff --git a/molecule/wireguard/tests/test_wireguard-client-configs.py b/molecule/wireguard/tests/test_wireguard-client-configs.py index 4ad038c..98bc95a 100644 --- a/molecule/wireguard/tests/test_wireguard-client-configs.py +++ b/molecule/wireguard/tests/test_wireguard-client-configs.py @@ -1,19 +1,19 @@ from pathlib import Path -def test_wireguard_config_dir_exists_locally(molecule_inventory_dir): - client_config_dir: Path = molecule_inventory_dir / "wireguard_configs" / "clients" +def test_wireguard_config_dir_exists_locally(): + client_config_dir: Path = Path("/tmp/molecule/wireguard_configs/clients/alice") assert client_config_dir.exists(), f"{client_config_dir} missing on controller" assert client_config_dir.is_dir(), f"{client_config_dir} is not a directory on controller" -def test_wireguard_client_config(molecule_inventory_dir): - client_config_dir: Path = molecule_inventory_dir / "wireguard_configs" / "clients" / "alice" +def test_wireguard_client_config(): + client_config_dir: Path = Path("/tmp/molecule/wireguard_configs/clients/alice") assert client_config_dir.exists(), f"{client_config_dir} missing on controller" assert client_config_dir.is_dir(), f"{client_config_dir} is not a directory on controller" - client_all_config_file: Path = client_config_dir / "vpn-acme-all.conf.vault" + client_all_config_file: Path = client_config_dir / "vpn-acme-all.conf.vaulted" assert client_all_config_file.exists(), f"{client_all_config_file} missing in local inventory" - client_lan_config_file: Path = client_config_dir / "vpn-acme-lan.conf.vault" + client_lan_config_file: Path = client_config_dir / "vpn-acme-lan.conf.vaulted" assert client_lan_config_file.exists(), f"{client_lan_config_file} missing in local inventory" diff --git a/molecule/wireguard/tests/test_wireguard-server.py b/molecule/wireguard/tests/test_wireguard-server.py index 0fe1c9c..d109013 100644 --- a/molecule/wireguard/tests/test_wireguard-server.py +++ b/molecule/wireguard/tests/test_wireguard-server.py @@ -1,20 +1,20 @@ import re import pytest -# def test_wireguard_config_file_exists(host): - # config_file = host.file("/etc/wireguard/wg0.conf") +def test_wireguard_config_file_exists(host): + config_file = host.file("/etc/wireguard/wg0.conf") - # assert config_file.exists - # assert config_file.is_file + assert config_file.exists + assert config_file.is_file -# def test_wireguard_config_file_permissions(host): - # config_file = host.file("/etc/wireguard/wg0.conf") +def test_wireguard_config_file_permissions(host): + config_file = host.file("/etc/wireguard/wg0.conf") - # # WireGuard config files should be readable only by root for security - # assert config_file.user == "root" - # assert config_file.group == "root" - # assert config_file.mode == 0o600 + # WireGuard config files should be readable only by root for security + assert config_file.user == "root" + assert config_file.group == "root" + assert config_file.mode == 0o600 def test_wireguard_server_conf_content(host): diff --git a/roles/wireguard/tasks/3_clients.yml b/roles/wireguard/tasks/3_clients.yml index bdbe0a0..da55298 100644 --- a/roles/wireguard/tasks/3_clients.yml +++ b/roles/wireguard/tasks/3_clients.yml @@ -1,12 +1,11 @@ --- +- name: Load common wireguard facts + include_tasks: "facts.yml" + - name: Set local_user ansible.builtin.set_fact: local_user: "{{ lookup('env', 'USER') }}" -- name: Set local_wireguard_config_dir - ansible.builtin.set_fact: - local_wireguard_config_dir: "{{ inventory_dir }}/wireguard_configs" - - name: Set local_wireguard_client_config_dir ansible.builtin.set_fact: local_wireguard_client_config_dir: "{{ local_wireguard_config_dir }}/clients" diff --git a/roles/wireguard/tasks/9_local-config-backup.yml b/roles/wireguard/tasks/9_local-config-backup.yml index d1c50a2..865bf22 100644 --- a/roles/wireguard/tasks/9_local-config-backup.yml +++ b/roles/wireguard/tasks/9_local-config-backup.yml @@ -1,4 +1,7 @@ --- +- name: Load common wireguard facts + include_tasks: "facts.yml" + - name: Set local_user ansible.builtin.set_fact: local_user: "{{ lookup('env', 'USER') }}" @@ -24,7 +27,7 @@ - name: Make sure wireguard_configs directory exists for given inventory delegate_to: localhost ansible.builtin.file: - path: "{{ inventory_dir }}/wireguard_configs" + path: "{{ local_wireguard_config_dir }}" state: directory mode: "0700" owner: "{{ local_user }}" @@ -54,12 +57,12 @@ become_user: "{{ local_user }}" shell: | set -euo pipefail - if [ -f "{{ inventory_dir }}/wireguard_configs/wg0.conf.vault" ]; then - if ansible-vault view "{{ inventory_dir }}/wireguard_configs/wg0.conf.vault" | cmp -s - "{{ local_tmp_server_bkp_config_path }}"; then + if [ -f "{{ local_wireguard_config_dir }}/wg0.conf.vaulted" ]; then + if ansible-vault view "{{ local_wireguard_config_dir }}/wg0.conf.vaulted" | cmp -s - "{{ local_tmp_server_bkp_config_path }}"; then exit 0 fi fi - ansible-vault encrypt "{{ local_tmp_server_bkp_config_path }}" --output "{{ inventory_dir }}/wireguard_configs/wg0.conf.vault" + ansible-vault encrypt "{{ local_tmp_server_bkp_config_path }}" --output "{{ local_wireguard_config_dir }}/wg0.conf.vaulted" args: executable: /bin/bash changed_when: false diff --git a/roles/wireguard/tasks/create-client-config.yml b/roles/wireguard/tasks/create-client-config.yml index d37bb10..33ec8e3 100644 --- a/roles/wireguard/tasks/create-client-config.yml +++ b/roles/wireguard/tasks/create-client-config.yml @@ -22,7 +22,7 @@ - name: Check if client config exists delegate_to: localhost stat: - path: "{{ client_config_all_path }}.vault" + path: "{{ client_config_all_path }}.vaulted" register: client_config_stat - name: Create client private key @@ -35,7 +35,7 @@ delegate_to: localhost become: yes become_user: "{{ local_user }}" - shell: "ansible-vault view {{ client_config_all_path }}.vault | grep '^PrivateKey' | awk '{print $3}'" + shell: "ansible-vault view {{ client_config_all_path }}.vaulted | grep '^PrivateKey' | awk '{print $3}'" register: existing_client_private_key changed_when: false when: client_config_stat.stat.exists @@ -48,7 +48,7 @@ delegate_to: localhost become: yes become_user: "{{ local_user }}" - shell: "ansible-vault view {{ client_config_all_path }}.vault | grep '^PresharedKey' | awk '{print $3}'" + shell: "ansible-vault view {{ client_config_all_path }}.vaulted | grep '^PresharedKey' | awk '{print $3}'" register: existing_client_preshared_key changed_when: false when: client_config_stat.stat.exists @@ -79,12 +79,12 @@ become_user: "{{ local_user }}" shell: | set -euo pipefail - if [ -f "{{ client_config_all_path }}.vault" ]; then - if ansible-vault view "{{ client_config_all_path }}.vault" | cmp -s - "{{ client_config_all_path }}"; then + if [ -f "{{ client_config_all_path }}.vaulted" ]; then + if ansible-vault view "{{ client_config_all_path }}.vaulted" | cmp -s - "{{ client_config_all_path }}"; then exit 0 fi fi - ansible-vault encrypt "{{ client_config_all_path }}" --output "{{ client_config_all_path }}.vault" + ansible-vault encrypt "{{ client_config_all_path }}" --output "{{ client_config_all_path }}.vaulted" args: executable: /bin/bash changed_when: false @@ -105,12 +105,12 @@ become_user: "{{ local_user }}" shell: | set -euo pipefail - if [ -f "{{ client_config_lan_path }}.vault" ]; then - if ansible-vault view "{{ client_config_lan_path }}.vault" | cmp -s - "{{ client_config_lan_path }}"; then + if [ -f "{{ client_config_lan_path }}.vaulted" ]; then + if ansible-vault view "{{ client_config_lan_path }}.vaulted" | cmp -s - "{{ client_config_lan_path }}"; then exit 0 fi fi - ansible-vault encrypt "{{ client_config_lan_path }}" --output "{{ client_config_lan_path }}.vault" + ansible-vault encrypt "{{ client_config_lan_path }}" --output "{{ client_config_lan_path }}.vaulted" args: executable: /bin/bash changed_when: false diff --git a/roles/wireguard/tasks/facts.yml b/roles/wireguard/tasks/facts.yml new file mode 100644 index 0000000..1579fe7 --- /dev/null +++ b/roles/wireguard/tasks/facts.yml @@ -0,0 +1,5 @@ +--- +- name: Set local_wireguard_config_dir + ansible.builtin.set_fact: + local_wireguard_config_dir: "{{ inventory_dir }}/wireguard_configs" + when: local_wireguard_config_dir is not defined From 37d79e55bce92db8577e5e064bb7ccce56dfe661 Mon Sep 17 00:00:00 2001 From: Mountain Star Date: Wed, 25 Mar 2026 16:11:21 +0000 Subject: [PATCH 9/9] Extend wireguard server tests --- molecule/wireguard/tests/test_wireguard-server.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/molecule/wireguard/tests/test_wireguard-server.py b/molecule/wireguard/tests/test_wireguard-server.py index d109013..82812a6 100644 --- a/molecule/wireguard/tests/test_wireguard-server.py +++ b/molecule/wireguard/tests/test_wireguard-server.py @@ -41,3 +41,14 @@ def test_wireguard_server_conf_content(host): ) assert re.search(alice_peer, wg0_conf_content), "alice peer section not found or invalid format" + + bob_peer = ( + r'# BEGIN peer bob\n' + r'\[Peer\]\n' + r'PublicKey = [A-Za-z0-9+/]{43}=\n' + r'AllowedIPs = 10\.10\.0\.3/32\n' + r'PreSharedKey = [A-Za-z0-9+/]{43}=\n' + r'# END peer bob' + ) + + assert re.search(bob_peer, wg0_conf_content), "bob peer section not found or invalid format"