diff --git a/.DS_Store b/.DS_Store index 6a066ce..cb8f714 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.git_ignore b/.gitignore similarity index 100% rename from .git_ignore rename to .gitignore diff --git a/templates/wg0.conf.j2 b/templates/wg0.conf.j2 index adc2f34..6c9482c 100644 --- a/templates/wg0.conf.j2 +++ b/templates/wg0.conf.j2 @@ -8,7 +8,11 @@ ListenPort = {{ wg_listen_port }} MTU = {{ wg_mtu }} PrivateKey = {{ wg_relay_effective_private_key }} -{% for peer in wg_peers %} +{% if wg_active_peers | length == 0 %} +# No peer has a real public key yet. The interface comes up with no peers so +# this side can generate and print its key; add the peer's key and re-run. +{% endif %} +{% for peer in wg_active_peers %} # {{ peer.name }} [Peer] PublicKey = {{ peer.public_key }} diff --git a/wg-relay.yml b/wg-relay.yml index 2e46b7c..3517308 100644 --- a/wg-relay.yml +++ b/wg-relay.yml @@ -8,10 +8,12 @@ # ansible-playbook wg-relay.yml -e @vars/site-a.yml --check --diff # ansible-playbook wg-relay.yml -e @vars/site-a.yml # -# No inventory file needed. Set relay_host to localhost or leave it unset +# No inventory file needed. Set relay_host to localhost (or leave it unset) # to configure the machine you're running on. Defaults come from -# defaults.yml. -# The restrictions are enforced locally at each site. +# defaults.yml; anything in the vars file wins, since -e outranks it. +# +# The restrictions are enforced locally at each site, so neither side can +# widen the other's exposure. - name: Resolve which box to configure hosts: localhost @@ -180,6 +182,50 @@ # -- tunnel ------------------------------------------------------------- + # A peer whose public key is still a placeholder would make wg-quick + # refuse the whole config, so the first run could never get far enough to + # print the key you need in order to fill it in. Skip those peers instead: + # the interface comes up bare, prints its key, and picks up the peer on + # the next run once the key is in the vars file. + - name: Select peers whose public key is a real key + ansible.builtin.set_fact: + wg_active_peers: >- + {{ wg_peers | selectattr('public_key', 'match', '^[A-Za-z0-9+/]{43}=$') | list }} + + - name: Note any peers being skipped + ansible.builtin.debug: + msg: >- + Skipping peer(s) {{ wg_peers | map(attribute='name') | difference( + wg_active_peers | map(attribute='name') | list) | join(', ') }} + — public key not filled in yet. The tunnel will come up with no peer. + Put the key in your vars file and run again. + when: wg_active_peers | length < wg_peers | length + + # wg-quick resolves Endpoint at startup and fails the unit if it can't, + # which surfaces as an opaque systemd error. Catch it here instead. + - name: Check each peer endpoint resolves + ansible.builtin.command: + cmd: "getent hosts {{ item.endpoint | regex_replace(':[0-9]+$', '') }}" + loop: "{{ wg_active_peers | selectattr('endpoint', 'defined') | list }}" + loop_control: + label: "{{ item.name }}" + register: endpoint_check + changed_when: false + failed_when: false + when: item.endpoint | regex_replace(':[0-9]+$', '') is not match('^[0-9.]+$') + + - name: Fail clearly on an endpoint that does not resolve + ansible.builtin.assert: + that: item.rc == 0 + fail_msg: >- + Peer '{{ item.item.name }}' endpoint '{{ item.item.endpoint }}' does + not resolve. Fix it in your vars file, or drop the endpoint line if + this side should wait for the other to dial in. + quiet: true + loop: "{{ endpoint_check.results | default([]) | rejectattr('skipped', 'defined') | list }}" + loop_control: + label: "{{ item.item.name }}" + - name: Write the WireGuard config ansible.builtin.template: src: wg0.conf.j2 @@ -319,4 +365,4 @@ - name: Show tunnel status ansible.builtin.debug: - var: wg_status.stdout_lines \ No newline at end of file + var: wg_status.stdout_lines