diff --git a/README.md b/README.md index 27c0041..e98177c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ The playbook assumes fresh Arch Linux ARM images installed on machines in your L They should start off with default credentials (i.e. `alarm:alarm`, `root:root`). This repo takes care of everything else. The intended topology is a bastion host facing the Internet, with reverse proxies forwarding traffic to a service host inside the firewall. +The servers are all on a WireGuard network. +This network also serves as the typical "encrypted tunnel" for devices on the go. - Flash all your machines with Arch Linux ARM. - Copy `inventory.example.yml` to `inventory.yml`. diff --git a/group_vars/all/00-secret_template.yml b/group_vars/all/00-secret_template.yml index 94925fa..b348ac6 100644 --- a/group_vars/all/00-secret_template.yml +++ b/group_vars/all/00-secret_template.yml @@ -28,14 +28,20 @@ form_secret: "" paperless_secret: "" wireguard_secret: - # server secret + # server secrets # generate with `wg genkey`, available in the 'wireguard-tools' package - server_key: "" - # pipe the secret key (see secret_template in group_vars/) into `wg pubkey` to get this - server_pub_key: "" + servers: + your_bastion_host: + # see inventory.yml to set the vpn address + priv: "" + # pipe the secret key (see secret_template in group_vars/) into `wg pubkey` to get this + pub: "" + your_fleet_host: + priv: "" + pub: "" - # list of clients to generate configs for - peers: + # list of clients to generate configs for on the bastion host + clients: # name of the client - name: test_client addr: "10.66.77.2" diff --git a/group_vars/all/50-vars.yml b/group_vars/all/50-vars.yml index f3c2a3c..9e31dbe 100644 --- a/group_vars/all/50-vars.yml +++ b/group_vars/all/50-vars.yml @@ -43,3 +43,6 @@ escalation_method: doas # set up static IP enable_connection: yes + +# use a wireguard network between bastion and fleet host for the reverse proxy +wireguard_services: true diff --git a/inventory.example.yml b/inventory.example.yml index c203c6d..bfce2dd 100644 --- a/inventory.example.yml +++ b/inventory.example.yml @@ -3,7 +3,10 @@ # fallback_host is only used during setup before the static IP (local_ip) is configured. # Set fallback_host using `nmap 192.168.0.0/24 -p 22` to find the dynamic IP of your Pi +# # local_ip is used after first setup. +# +# vpn_ip is used for the WireGuard network. # Make entries in your .ssh/config for ease of use # Example: @@ -19,6 +22,7 @@ all: your_bastion_host: fallback_host: 192.168.0.123 local_ip: 192.168.0.3 + vpn_ip: 10.66.77.1 ansible_port: 2500 ansible_connection: ssh ansible_ssh_private_key_file: ~/.ssh/keys/your_bastion_host @@ -26,6 +30,7 @@ all: your_fleet_host: fallback_host: 192.168.0.124 local_ip: 192.168.0.86 + vpn_ip: 10.66.77.86 ansible_port: 2500 ansible_connection: ssh ansible_ssh_private_key_file: ~/.ssh/keys/your_fleet_host @@ -65,6 +70,7 @@ all: wireguard: hosts: your_bastion_host: + your_fleet_host: sshd: hosts: your_bastion_host: diff --git a/roles/containers/tasks/main.yml b/roles/containers/tasks/main.yml index 86b9397..d496811 100644 --- a/roles/containers/tasks/main.yml +++ b/roles/containers/tasks/main.yml @@ -126,6 +126,10 @@ register: user_synapse when: '"synapse" in groups' +- name: Figure out local IP address + set_fact: + docker_ip: "{{ vpn_ip if wireguard_services else local_ip }}" + - name: Generate docker-compose.yml template: src: "docker-compose.yml.j2" diff --git a/roles/containers/templates/docker-compose.yml.j2 b/roles/containers/templates/docker-compose.yml.j2 index 8808881..d17a93a 100644 --- a/roles/containers/templates/docker-compose.yml.j2 +++ b/roles/containers/templates/docker-compose.yml.j2 @@ -1,8 +1,6 @@ # vim: ft=yaml --- -version: "3" - networks: gitea: driver: bridge @@ -22,8 +20,8 @@ services: - GITEA__server__DOMAIN={{ gitea_domain }} - GITEA__server__SSH_DOMAIN={{ gitea_domain }} ports: - - "3000:3000" - - "2498:22" + - "{{ docker_ip }}:3000:3000" + - "{{ docker_ip }}:2498:22" restart: unless-stopped volumes: - {{ dataroot }}/gitea:/data @@ -65,7 +63,7 @@ services: networks: - navidrome ports: - - "4533:4533" + - "{{ docker_ip }}:4533:4533" {% endif %} {% if "synapse" in group_names %} @@ -82,7 +80,7 @@ services: networks: - navidrome ports: - - "8008:8008/tcp" + - "{{ docker_ip }}:8008:8008/tcp" {% endif %} @@ -101,7 +99,7 @@ services: depends_on: - paperless-broker ports: - - "8000:8000" + - "{{ docker_ip }}:8000:8000" healthcheck: test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"] interval: 30s diff --git a/roles/firewall/defaults/main.yml b/roles/firewall/defaults/main.yml index f2938ff..1876ae1 100644 --- a/roles/firewall/defaults/main.yml +++ b/roles/firewall/defaults/main.yml @@ -8,3 +8,4 @@ local_subnet: 192.168.0.0/24 sshd_port: 2500 bastion_ip: "{{ hostvars[groups['bastion'][0]]['local_ip'] }}" +bastion_vpn_ip: "{{ hostvars[groups['bastion'][0]]['vpn_ip'] }}" diff --git a/roles/firewall/tasks/main.yml b/roles/firewall/tasks/main.yml index 890eda7..7c6fb6e 100644 --- a/roles/firewall/tasks/main.yml +++ b/roles/firewall/tasks/main.yml @@ -14,9 +14,10 @@ - name: Set default sources (fleet server) set_fact: - default_firewall_src: "{{ bastion_ip }}" + default_firewall_src: "{{ bastion_vpn_ip if wireguard_services else bastion_ip }}" when: '"fleet" in group_names' +# this is actually kind of useless because docker bypasses this - name: Allow service ports community.general.ufw: rule: allow diff --git a/roles/networking/hosts/templates/hosts.j2 b/roles/networking/hosts/templates/hosts.j2 index aa44469..43133e6 100644 --- a/roles/networking/hosts/templates/hosts.j2 +++ b/roles/networking/hosts/templates/hosts.j2 @@ -5,5 +5,9 @@ # Modifications will be lost! {% for host in groups["all"] %} +{% if wireguard_services %} +{{ hostvars[host]["vpn_ip"] }} {{ host }} +{% else %} {{ hostvars[host]["local_ip"] }} {{ host }} +{% endif %} {% endfor %} diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index ff3d187..25e0e08 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -11,8 +11,6 @@ wireguard: - "{{ dns_forward }}" interface: "wg0" ip: - # address for the server - address: "10.66.77.1/32" # cidr range in tunnel cidr: "10.66.77.0/24" diff --git a/roles/wireguard/tasks/main.yml b/roles/wireguard/tasks/main.yml index fe478b0..b34277c 100644 --- a/roles/wireguard/tasks/main.yml +++ b/roles/wireguard/tasks/main.yml @@ -15,6 +15,7 @@ value: 1 state: present reload: yes + when: '"bastion" in group_names' - name: Setup UFW rules to accept VPN traffic community.general.ufw: @@ -22,6 +23,7 @@ direction: in src: "{{ wireguard.ip.cidr }}" dest: any + when: '"bastion" in group_names' - name: Deploy wireguard server config template: @@ -42,6 +44,7 @@ group: root mode: 0700 state: directory + when: '"bastion" in group_names' - name: Create wireguard client configs template: @@ -52,6 +55,7 @@ mode: 0600 lstrip_blocks: true no_log: true - with_items: "{{ wireguard_secret.peers }}" + with_items: "{{ wireguard_secret.clients }}" notify: - Start wireguard + when: '"bastion" in group_names' diff --git a/roles/wireguard/templates/client.conf.j2 b/roles/wireguard/templates/client.conf.j2 index ba911b4..d36d9bc 100644 --- a/roles/wireguard/templates/client.conf.j2 +++ b/roles/wireguard/templates/client.conf.j2 @@ -3,14 +3,12 @@ Address = {{ item.addr }} # device privkey PrivateKey = {{ item.priv_key }} -DNS = {{ wireguard.ip.address }} +DNS = {{ hostvars[groups["bastion"][0]].vpn_ip }} [Peer] # server stuff -PublicKey = {{ wireguard_secret.server_pub_key }} +PublicKey = {{ wireguard_secret.servers[groups["bastion"][0]].pub }} Endpoint = {{ wireguard.ip.server_public }}:{{ wireguard.ip.port }} # allow traffic for all subnets into the VPN AllowedIPs = 0.0.0.0/0 - -PersistentKeepalive = 25 diff --git a/roles/wireguard/templates/server.conf.j2 b/roles/wireguard/templates/server.conf.j2 index c458c88..ea3b466 100644 --- a/roles/wireguard/templates/server.conf.j2 +++ b/roles/wireguard/templates/server.conf.j2 @@ -1,14 +1,27 @@ [Interface] -Address = {{ wireguard.ip.address }} -PrivateKey = {{ wireguard_secret.server_key }} +Address = {{ hostvars[inventory_hostname]["vpn_ip"] }}/32 +PrivateKey = {{ wireguard_secret.servers[inventory_hostname].priv }} ListenPort = {{ wireguard.ip.port }} +{% if "bastion" in group_names %} PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ net_interface }} -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ net_interface }} -j MASQUERADE +{% endif %} SaveConfig = false -{% for peer in wireguard_secret.peers %} +{% for server_peer in wireguard_secret.servers %} +{% if not server_peer == inventory_hostname %} +[Peer] +PublicKey = {{ wireguard_secret.servers[server_peer].pub }} +AllowedIPs = {{ hostvars[server_peer]["vpn_ip"] }} +Endpoint = {{ hostvars[server_peer]["local_ip"] }}:{{ wireguard.ip.port }} +{% endif %} +{% endfor %} + +{% if "bastion" in group_names %} +{% for peer in wireguard_secret.clients %} [Peer] PublicKey = {{ peer.pub_key }} AllowedIPs = {{ peer.addr }} {% endfor %} +{% endif %}