Skip to content

Commit 29f517c

Browse files
authored
Merge pull request #80 from dhis2/feat/wireguard-vpn
feat: add support for wireguard vpn
2 parents dce3c4a + 1af9426 commit 29f517c

69 files changed

Lines changed: 2166 additions & 107 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ venv/
99
.venv/
1010
.claude/
1111

12-
# Ignore all files in deploy/inventory/host_vars/ and deploy/inventory/group_vars/
12+
# Ignore all files in deploy/inventory/host_vars/ and deploy/inventory/group_vars/
1313
# but allow .template files
1414
deploy/inventory/host_vars/*
15-
deploy/inventory/group_vars/*
1615
!deploy/inventory/host_vars/*.template
16+
deploy/inventory/group_vars/*
1717
!deploy/inventory/group_vars/*.template
18+
!deploy/inventory/group_vars/all/
19+
deploy/inventory/group_vars/all/*
20+
!deploy/inventory/group_vars/all/vars.yml
1821
deploy/filter_plugins/__pycache__/
1922

2023
# Testing artifacts

deploy/ansible.cfg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ host_key_checking = False
66
# log_path=./deploy.log
77
remote_tmp = /tmp/
88
local_tmp = ~/.ansible/tmp
9+
# Explicit so sub-playbooks under playbooks/ resolve roles and custom
10+
# filters/library regardless of which playbook is invoked.
11+
roles_path = ./roles
12+
filter_plugins = ./filter_plugins
13+
library = ./library
914

1015
# callbacks_enabled = timer, profile_tasks
1116
# stdout_callback = yaml

deploy/dhis2.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
roles:
77
- role: pre-install
88

9+
- name: WireGuard VPN bring-up (early — SSH data plane)
10+
ansible.builtin.import_playbook: playbooks/wireguard.yml
11+
when: wireguard_data_plane | default(false) | bool
12+
913
- name: DHIS2 setup
1014
gather_facts: false
1115
force_handlers: true
1216
become: true
13-
hosts: all:!127.0.0.1
17+
hosts: all:!127.0.0.1:!{{ wireguard_hub_inventory_hostname | default('wireguard') }}
1418
vars_files:
1519
- vars/vars.yml
1620
roles:
@@ -44,5 +48,12 @@
4448
roles:
4549
- role: backups
4650

51+
- name: WireGuard VPN bring-up
52+
ansible.builtin.import_playbook: playbooks/wireguard.yml
53+
when: not (wireguard_data_plane | default(false) | bool)
54+
55+
- name: WireGuard VPN service lockdown
56+
ansible.builtin.import_playbook: playbooks/wireguard-lockdown.yml
57+
4758
- import_playbook: playbooks/delete-dhis2-instance.yml
4859
tags: [never, delete-instance]

deploy/inventory/group_vars/all.template

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
service_ip: >-
3+
{{ (wireguard_ip | default(ansible_host))
4+
if (wireguard_data_plane | default(false) | bool)
5+
else ansible_host }}
6+
7+
wireguard_monitoring_locked: >-
8+
{{ (wireguard_enabled | default(false) | bool)
9+
and (wireguard_lockdown_monitoring | default(true) | bool) }}
10+
11+
wireguard_peers:
12+
- name: sysadmin
13+
allowed_ips: "10.0.0.6/32"
14+
# public_key: "<peer-public-key>" # only needed if wireguard_auto_generate_keys: false
15+
# preshared_key: "<peer-preshared-key>" # optional - for post-quantum resistance
16+
pg_access:
17+
- { instance: dhis } # access the 'dhis' instance's database as its own role
18+
# - name: superuser
19+
# allowed_ips: "10.0.0.7/32"
20+
# pg_access:
21+
# - { database: all, user: all } # superuser-equivalent - allows access to all databases as any user

deploy/inventory/hosts.template

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,30 @@
33

44
# proxy
55
[web]
6-
proxy ansible_host=172.19.2.2
6+
proxy ansible_host=172.19.2.2 wireguard_ip=10.0.0.2
77

88

99
# database hosts
1010
# To use SSH for a specific host instead of lxd, add ansible_connection=ssh ansible_user=<user>
1111
# EXAMPLE: postgres ansible_host=192.168.1.50 ansible_connection=ssh ansible_user=ubuntu
1212
[databases]
13-
postgres ansible_host=172.19.2.20
13+
postgres ansible_host=172.19.2.20 wireguard_ip=10.0.0.3
1414

1515
# dhis2 hosts
1616
# EXAMPLE: dhis ansible_host=172.19.2.11 database_host=postgres dhis2_version=2.42 fqdn=dhis.example.org dhis2_auto_upgrade=true proxy_rewrite=True
1717
# NOTE: dhis2_auto_upgrade=true automates upgrading to the versision set in dhis2_version=2.42
1818
[instances]
19-
dhis ansible_host=172.19.2.11 database_host=postgres dhis2_version=2.42 proxy_rewrite=True
19+
dhis ansible_host=172.19.2.11 database_host=postgres dhis2_version=2.42 proxy_rewrite=True wireguard_ip=10.0.0.4
2020

2121

2222
# monitoring hosts
2323
[monitoring]
24-
monitor ansible_host=172.19.2.30
24+
monitor ansible_host=172.19.2.30 wireguard_ip=10.0.0.5
25+
26+
# WireGuard hub container. wireguard_ip must match wireguard_server_ip.
27+
# Group is wireguard_hub (not wireguard) to avoid Ansible's "host and group share name" warning.
28+
[wireguard_hub]
29+
wireguard ansible_host=172.19.2.200 wireguard_ip=10.0.0.1
2530

2631
[backup_servers]
2732
backup ansible_host=172.19.2.100
@@ -53,6 +58,10 @@ postgresql_version=16
5358
server_monitoring=munin
5459
app_monitoring=glowroot
5560

61+
# WireGuard VPN: restricts Grafana, Prometheus, Munin, Glowroot and PostgreSQL to VPN-only access.
62+
wireguard_enabled=false
63+
# wireguard_data_plane=false
64+
5665

5766
# lxd
5867
lxd_network=172.19.2.1/24

deploy/playbooks/apache-doris-setup.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@
435435
- name: Debug
436436
loop: "{{ groups['instances'] }}"
437437
ansible.builtin.debug:
438-
msg: "{{ hostvars[hostvars[item]['apache_doris_db']]['ansible_host'] }}"
438+
msg: "{{ hostvars[hostvars[item]['apache_doris_db']]['service_ip'] | default(hostvars[hostvars[item]['apache_doris_db']]['ansible_host']) }}"
439439
when:
440440
- hostvars[item]['apache_doris_db'] is defined
441441
- hostvars[hostvars[item]['apache_doris_db']] is defined
@@ -450,7 +450,7 @@
450450
# Analytics database management system
451451
analytics.database = doris
452452
analytics.connection.driver_class = com.mysql.cj.jdbc.Driver
453-
analytics.connection.url = jdbc:mysql://{{ hostvars[(hostvars[item]['apache_doris_db'])]['ansible_host'] }}:9030/analytics
453+
analytics.connection.url = jdbc:mysql://{{ hostvars[(hostvars[item]['apache_doris_db'])]['service_ip'] | default(hostvars[(hostvars[item]['apache_doris_db'])]['ansible_host']) }}:9030/analytics
454454
analytics.connection.username = {{ hostvars[item]['apache_doris_db_user'] | default('doris_user') }}
455455
analytics.connection.password = {{ hostvars[item]['apache_doris_db_password'] | default(lookup('password', '/opt/ansible/secrets/apache_doris_db_password')) }}
456456
loop: "{{ groups['instances'] }}"
@@ -494,9 +494,9 @@
494494
index_var: idx
495495
ansible.builtin.lineinfile:
496496
path: /etc/postgresql/{{ postgresql_version_info.results[idx].version.major }}/main/pg_hba.conf
497-
regexp: "^(host|hostssl)\\s+{{ item }}\\s+{{ hostvars[item]['apache_doris_db'] }}\\s+{{ hostvars[ hostvars[item]['apache_doris_db'] ]['ansible_host'] }}(/32)?\\s+.*$"
497+
regexp: "^(host|hostssl)\\s+{{ item }}\\s+{{ hostvars[item]['apache_doris_db'] }}\\s+{{ hostvars[ hostvars[item]['apache_doris_db'] ]['service_ip'] | default(hostvars[ hostvars[item]['apache_doris_db'] ]['ansible_host']) }}(/32)?\\s+.*$"
498498
insertafter: EOF
499-
line: hostssl {{ item }} {{ item }} {{ hostvars[hostvars[item]['apache_doris_db']]['ansible_host'] }}/32 scram-sha-256
499+
line: hostssl {{ item }} {{ item }} {{ hostvars[hostvars[item]['apache_doris_db']]['service_ip'] | default(hostvars[hostvars[item]['apache_doris_db']]['ansible_host']) }}/32 scram-sha-256
500500
when:
501501
- hostvars[item]['apache_doris_db'] is defined
502502
- hostvars[hostvars[item]['apache_doris_db']] is defined
@@ -508,9 +508,9 @@
508508
community.general.ufw:
509509
rule: allow
510510
port: "{{ postgresql_port | default('5432') }}"
511-
src: "{{ hostvars[hostvars[item]['apache_doris_db']]['ansible_host'] }}"
511+
src: "{{ hostvars[hostvars[item]['apache_doris_db']]['service_ip'] | default(hostvars[hostvars[item]['apache_doris_db']]['ansible_host']) }}"
512512
proto: tcp
513-
comment: "Allow access from {{ hostvars[hostvars[item]['apache_doris_db']]['ansible_host'] }} instance "
513+
comment: "Allow access from {{ hostvars[hostvars[item]['apache_doris_db']]['service_ip'] | default(hostvars[hostvars[item]['apache_doris_db']]['ansible_host']) }} instance "
514514
state: enabled
515515
loop: "{{ groups['instances'] }}"
516516
when:

deploy/playbooks/delete-dhis2-instance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
community.general.ufw:
8484
rule: allow
8585
port: "{{ postgresql_port | default('5432') }}"
86-
src: "{{ hostvars[item]['ansible_host'] }}"
86+
src: "{{ hostvars[item]['service_ip'] | default(hostvars[item]['ansible_host']) }}"
8787
proto: tcp
8888
comment: "Allow access from {{ item }} instance "
8989
delete: true
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
- name: WireGuard lockdown | Pre-flight
3+
hosts: 127.0.0.1
4+
connection: local
5+
become: true
6+
gather_facts: false
7+
tags:
8+
- wireguard
9+
- wireguard-lockdown
10+
tasks:
11+
- name: Lockdown | Announce service hardening
12+
ansible.builtin.debug:
13+
msg: >-
14+
WireGuard service lockdown is about to apply: monitoring,
15+
PostgreSQL, and Glowroot will be restricted to the
16+
{{ wireguard_network | default('10.0.0.0/24') }} VPN subnet.
17+
Skip with --skip-tags wireguard-lockdown (whole phase) or
18+
--skip-tags lockdown-proxy,lockdown-monitor,lockdown-postgres,lockdown-instances
19+
(per component).
20+
when: wireguard_enabled | default(false) | bool
21+
22+
- name: WireGuard lockdown | Proxy
23+
hosts: web
24+
become: true
25+
gather_facts: false
26+
tags:
27+
- wireguard
28+
- wireguard-lockdown
29+
- lockdown-proxy
30+
tasks:
31+
- name: Lockdown | Proxy
32+
ansible.builtin.include_role:
33+
name: wireguard
34+
tasks_from: lockdown_proxy.yml
35+
when: wireguard_monitoring_locked | default(false) | bool
36+
37+
- name: WireGuard lockdown | Monitoring
38+
hosts: monitoring
39+
become: true
40+
gather_facts: false
41+
tags:
42+
- wireguard
43+
- wireguard-lockdown
44+
- lockdown-monitor
45+
tasks:
46+
- name: Lockdown | Monitor
47+
ansible.builtin.include_role:
48+
name: wireguard
49+
tasks_from: lockdown_monitor.yml
50+
when: wireguard_monitoring_locked | default(false) | bool
51+
52+
- name: WireGuard lockdown | PostgreSQL
53+
hosts: databases
54+
become: true
55+
gather_facts: false
56+
tags:
57+
- wireguard
58+
- wireguard-lockdown
59+
- lockdown-postgres
60+
tasks:
61+
- name: Lockdown | Postgres
62+
ansible.builtin.include_role:
63+
name: wireguard
64+
tasks_from: lockdown_postgres.yml
65+
when: wireguard_enabled | default(false) | bool
66+
67+
- name: WireGuard lockdown | Instances
68+
hosts: instances
69+
become: true
70+
gather_facts: false
71+
tags:
72+
- wireguard
73+
- wireguard-lockdown
74+
- lockdown-instances
75+
tasks:
76+
- name: Lockdown | Instances
77+
ansible.builtin.include_role:
78+
name: wireguard
79+
tasks_from: lockdown_instances.yml
80+
when: wireguard_enabled | default(false) | bool

deploy/playbooks/wireguard.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
- name: WireGuard | Provision hub on localhost
3+
hosts: 127.0.0.1
4+
become: true
5+
gather_facts: false
6+
tags:
7+
- wireguard
8+
- wireguard-bring-up
9+
tasks:
10+
- name: WireGuard | Capture deployment mode (lxd vs ssh)
11+
vars:
12+
ansible_connection: local
13+
ansible.builtin.set_fact:
14+
wireguard_deploy_mode: >-
15+
{{ hostvars[wireguard_hub_inventory_hostname | default('wireguard')]['ansible_connection']
16+
| default('lxd') }}
17+
18+
- name: WireGuard | Set Ansible connection to local
19+
vars:
20+
ansible_connection: local
21+
ansible.builtin.set_fact:
22+
ansible_connection: local
23+
24+
- name: WireGuard | Gather network facts on host
25+
ansible.builtin.setup:
26+
gather_subset:
27+
- network
28+
- '!min'
29+
- name: WireGuard | Validate configuration
30+
ansible.builtin.include_role:
31+
name: wireguard
32+
tasks_from: validate.yml
33+
when: >-
34+
(wireguard_enabled | default(false) | bool)
35+
or (wireguard_data_plane | default(false) | bool)
36+
37+
- name: WireGuard | Provision hub LXD container (LXD only)
38+
ansible.builtin.include_role:
39+
name: wireguard
40+
tasks_from: lxd_container.yml
41+
when:
42+
- wireguard_enabled | default(false) | bool
43+
- wireguard_deploy_mode == 'lxd'
44+
45+
- name: WireGuard | LXD host port-forward (LXD only)
46+
ansible.builtin.include_role:
47+
name: wireguard
48+
tasks_from: host_portforward.yml
49+
when:
50+
- wireguard_enabled | default(false) | bool
51+
- wireguard_deploy_mode == 'lxd'
52+
53+
- name: WireGuard | Configure hub server
54+
hosts: "{{ wireguard_hub_inventory_hostname | default('wireguard') }}"
55+
become: true
56+
gather_facts: false
57+
tags:
58+
- wireguard
59+
- wireguard-bring-up
60+
tasks:
61+
- name: WireGuard | Hub
62+
ansible.builtin.include_role:
63+
name: wireguard
64+
tasks_from: hub.yml
65+
when: wireguard_enabled | default(false) | bool
66+
67+
- name: WireGuard | Configure peers across app hosts
68+
hosts: "all:!127.0.0.1:!{{ wireguard_hub_inventory_hostname | default('wireguard') }}"
69+
become: true
70+
gather_facts: false
71+
tags:
72+
- wireguard
73+
- wireguard-bring-up
74+
tasks:
75+
- name: WireGuard | Restore peer connection (undo backups ansible_connection leak)
76+
ansible.builtin.set_fact:
77+
ansible_connection: >-
78+
{{ hostvars[wireguard_hub_inventory_hostname | default('wireguard')]['ansible_connection']
79+
| default('lxd') }}
80+
when: wireguard_enabled | default(false) | bool
81+
82+
- name: WireGuard | Peer
83+
ansible.builtin.include_role:
84+
name: wireguard
85+
tasks_from: peer.yml
86+
when:
87+
- wireguard_enabled | default(false) | bool
88+
- hostvars[inventory_hostname].wireguard_ip is defined
89+
- hostvars[inventory_hostname].wireguard_ip | length > 0

0 commit comments

Comments
 (0)