wireguard: use vpn for bastion-fleet comms

supposedly fleet will be more secure this way
This commit is contained in:
dogeystamp 2024-06-16 21:32:52 -04:00
parent 1b3e800443
commit 0c8d18dcce
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
13 changed files with 62 additions and 24 deletions

View File

@ -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`). They should start off with default credentials (i.e. `alarm:alarm`, `root:root`).
This repo takes care of everything else. 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 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. - Flash all your machines with Arch Linux ARM.
- Copy `inventory.example.yml` to `inventory.yml`. - Copy `inventory.example.yml` to `inventory.yml`.

View File

@ -28,14 +28,20 @@ form_secret: ""
paperless_secret: "" paperless_secret: ""
wireguard_secret: wireguard_secret:
# server secret # server secrets
# generate with `wg genkey`, available in the 'wireguard-tools' package # generate with `wg genkey`, available in the 'wireguard-tools' package
server_key: "" servers:
# pipe the secret key (see secret_template in group_vars/) into `wg pubkey` to get this your_bastion_host:
server_pub_key: "" # 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 # list of clients to generate configs for on the bastion host
peers: clients:
# name of the client # name of the client
- name: test_client - name: test_client
addr: "10.66.77.2" addr: "10.66.77.2"

View File

@ -43,3 +43,6 @@ escalation_method: doas
# set up static IP # set up static IP
enable_connection: yes enable_connection: yes
# use a wireguard network between bastion and fleet host for the reverse proxy
wireguard_services: true

View File

@ -3,7 +3,10 @@
# fallback_host is only used during setup before the static IP (local_ip) is configured. # 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 # 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. # 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 # Make entries in your .ssh/config for ease of use
# Example: # Example:
@ -19,6 +22,7 @@ all:
your_bastion_host: your_bastion_host:
fallback_host: 192.168.0.123 fallback_host: 192.168.0.123
local_ip: 192.168.0.3 local_ip: 192.168.0.3
vpn_ip: 10.66.77.1
ansible_port: 2500 ansible_port: 2500
ansible_connection: ssh ansible_connection: ssh
ansible_ssh_private_key_file: ~/.ssh/keys/your_bastion_host ansible_ssh_private_key_file: ~/.ssh/keys/your_bastion_host
@ -26,6 +30,7 @@ all:
your_fleet_host: your_fleet_host:
fallback_host: 192.168.0.124 fallback_host: 192.168.0.124
local_ip: 192.168.0.86 local_ip: 192.168.0.86
vpn_ip: 10.66.77.86
ansible_port: 2500 ansible_port: 2500
ansible_connection: ssh ansible_connection: ssh
ansible_ssh_private_key_file: ~/.ssh/keys/your_fleet_host ansible_ssh_private_key_file: ~/.ssh/keys/your_fleet_host
@ -65,6 +70,7 @@ all:
wireguard: wireguard:
hosts: hosts:
your_bastion_host: your_bastion_host:
your_fleet_host:
sshd: sshd:
hosts: hosts:
your_bastion_host: your_bastion_host:

View File

@ -126,6 +126,10 @@
register: user_synapse register: user_synapse
when: '"synapse" in groups' 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 - name: Generate docker-compose.yml
template: template:
src: "docker-compose.yml.j2" src: "docker-compose.yml.j2"

View File

@ -1,8 +1,6 @@
# vim: ft=yaml # vim: ft=yaml
--- ---
version: "3"
networks: networks:
gitea: gitea:
driver: bridge driver: bridge
@ -22,8 +20,8 @@ services:
- GITEA__server__DOMAIN={{ gitea_domain }} - GITEA__server__DOMAIN={{ gitea_domain }}
- GITEA__server__SSH_DOMAIN={{ gitea_domain }} - GITEA__server__SSH_DOMAIN={{ gitea_domain }}
ports: ports:
- "3000:3000" - "{{ docker_ip }}:3000:3000"
- "2498:22" - "{{ docker_ip }}:2498:22"
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- {{ dataroot }}/gitea:/data - {{ dataroot }}/gitea:/data
@ -65,7 +63,7 @@ services:
networks: networks:
- navidrome - navidrome
ports: ports:
- "4533:4533" - "{{ docker_ip }}:4533:4533"
{% endif %} {% endif %}
{% if "synapse" in group_names %} {% if "synapse" in group_names %}
@ -82,7 +80,7 @@ services:
networks: networks:
- navidrome - navidrome
ports: ports:
- "8008:8008/tcp" - "{{ docker_ip }}:8008:8008/tcp"
{% endif %} {% endif %}
@ -101,7 +99,7 @@ services:
depends_on: depends_on:
- paperless-broker - paperless-broker
ports: ports:
- "8000:8000" - "{{ docker_ip }}:8000:8000"
healthcheck: healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"] test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s interval: 30s

View File

@ -8,3 +8,4 @@ local_subnet: 192.168.0.0/24
sshd_port: 2500 sshd_port: 2500
bastion_ip: "{{ hostvars[groups['bastion'][0]]['local_ip'] }}" bastion_ip: "{{ hostvars[groups['bastion'][0]]['local_ip'] }}"
bastion_vpn_ip: "{{ hostvars[groups['bastion'][0]]['vpn_ip'] }}"

View File

@ -14,9 +14,10 @@
- name: Set default sources (fleet server) - name: Set default sources (fleet server)
set_fact: set_fact:
default_firewall_src: "{{ bastion_ip }}" default_firewall_src: "{{ bastion_vpn_ip if wireguard_services else bastion_ip }}"
when: '"fleet" in group_names' when: '"fleet" in group_names'
# this is actually kind of useless because docker bypasses this
- name: Allow service ports - name: Allow service ports
community.general.ufw: community.general.ufw:
rule: allow rule: allow

View File

@ -5,5 +5,9 @@
# Modifications will be lost! # Modifications will be lost!
{% for host in groups["all"] %} {% for host in groups["all"] %}
{% if wireguard_services %}
{{ hostvars[host]["vpn_ip"] }} {{ host }}
{% else %}
{{ hostvars[host]["local_ip"] }} {{ host }} {{ hostvars[host]["local_ip"] }} {{ host }}
{% endif %}
{% endfor %} {% endfor %}

View File

@ -11,8 +11,6 @@ wireguard:
- "{{ dns_forward }}" - "{{ dns_forward }}"
interface: "wg0" interface: "wg0"
ip: ip:
# address for the server
address: "10.66.77.1/32"
# cidr range in tunnel # cidr range in tunnel
cidr: "10.66.77.0/24" cidr: "10.66.77.0/24"

View File

@ -15,6 +15,7 @@
value: 1 value: 1
state: present state: present
reload: yes reload: yes
when: '"bastion" in group_names'
- name: Setup UFW rules to accept VPN traffic - name: Setup UFW rules to accept VPN traffic
community.general.ufw: community.general.ufw:
@ -22,6 +23,7 @@
direction: in direction: in
src: "{{ wireguard.ip.cidr }}" src: "{{ wireguard.ip.cidr }}"
dest: any dest: any
when: '"bastion" in group_names'
- name: Deploy wireguard server config - name: Deploy wireguard server config
template: template:
@ -42,6 +44,7 @@
group: root group: root
mode: 0700 mode: 0700
state: directory state: directory
when: '"bastion" in group_names'
- name: Create wireguard client configs - name: Create wireguard client configs
template: template:
@ -52,6 +55,7 @@
mode: 0600 mode: 0600
lstrip_blocks: true lstrip_blocks: true
no_log: true no_log: true
with_items: "{{ wireguard_secret.peers }}" with_items: "{{ wireguard_secret.clients }}"
notify: notify:
- Start wireguard - Start wireguard
when: '"bastion" in group_names'

View File

@ -3,14 +3,12 @@
Address = {{ item.addr }} Address = {{ item.addr }}
# device privkey # device privkey
PrivateKey = {{ item.priv_key }} PrivateKey = {{ item.priv_key }}
DNS = {{ wireguard.ip.address }} DNS = {{ hostvars[groups["bastion"][0]].vpn_ip }}
[Peer] [Peer]
# server stuff # server stuff
PublicKey = {{ wireguard_secret.server_pub_key }} PublicKey = {{ wireguard_secret.servers[groups["bastion"][0]].pub }}
Endpoint = {{ wireguard.ip.server_public }}:{{ wireguard.ip.port }} Endpoint = {{ wireguard.ip.server_public }}:{{ wireguard.ip.port }}
# allow traffic for all subnets into the VPN # allow traffic for all subnets into the VPN
AllowedIPs = 0.0.0.0/0 AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

View File

@ -1,14 +1,27 @@
[Interface] [Interface]
Address = {{ wireguard.ip.address }} Address = {{ hostvars[inventory_hostname]["vpn_ip"] }}/32
PrivateKey = {{ wireguard_secret.server_key }} PrivateKey = {{ wireguard_secret.servers[inventory_hostname].priv }}
ListenPort = {{ wireguard.ip.port }} 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 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 PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ net_interface }} -j MASQUERADE
{% endif %}
SaveConfig = false 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] [Peer]
PublicKey = {{ peer.pub_key }} PublicKey = {{ peer.pub_key }}
AllowedIPs = {{ peer.addr }} AllowedIPs = {{ peer.addr }}
{% endfor %} {% endfor %}
{% endif %}