diff options
-rw-r--r-- | inventory.yaml.example | 2 | ||||
-rw-r--r-- | main.yaml | 1 | ||||
-rw-r--r-- | tasks/pve_setup.yaml | 9 | ||||
-rw-r--r-- | tasks/wg_setup.yaml | 72 | ||||
-rw-r--r-- | templates/manage_wg_peers.sh.j2 | 186 | ||||
-rw-r--r-- | templates/wg0.conf.j2 | 12 | ||||
-rw-r--r-- | vars/main.yaml | 17 |
7 files changed, 296 insertions, 3 deletions
diff --git a/inventory.yaml.example b/inventory.yaml.example index 6f27497..115fb2b 100644 --- a/inventory.yaml.example +++ b/inventory.yaml.example @@ -7,6 +7,8 @@ all: hostname: proxmox-ve-test nat_subnet: 10.10.10.0/24 nat_bridge_ip: 10.10.10.1 + wg_subnet: 10.13.37.0/24 + wg_port: 31337 children: servers: hosts: @@ -8,3 +8,4 @@ - import_tasks: tasks/pve_setup.yaml - import_tasks: tasks/pve_configure.yaml - import_tasks: tasks/harden.yaml + - import_tasks: tasks/wg_setup.yaml diff --git a/tasks/pve_setup.yaml b/tasks/pve_setup.yaml index 7d04ff2..9fcea47 100644 --- a/tasks/pve_setup.yaml +++ b/tasks/pve_setup.yaml @@ -7,7 +7,7 @@ mode: '0644' - name: create /etc/apt/sources.list.d directory - ansible.builtin.file: + file: path: /etc/apt/sources.list.d state: directory mode: '0755' @@ -51,6 +51,8 @@ name: "{{ apt_packages }}" state: present update_cache: true + environment: + DEBIAN_FRONTEND: noninteractive - name: reboot to activate proxmox ve kernel reboot: @@ -91,3 +93,8 @@ apt: name: "{{ apt_packages_to_remove }}" state: absent + +- name: remove pve-enterprise apt source + file: + path: /etc/apt/sources.list.d/pve-enterprise.list + state: absent diff --git a/tasks/wg_setup.yaml b/tasks/wg_setup.yaml new file mode 100644 index 0000000..9557a79 --- /dev/null +++ b/tasks/wg_setup.yaml @@ -0,0 +1,72 @@ +- name: install wireguard and dependencies + apt: + name: "{{ wireguard_packages }}" + state: present + update_cache: yes + +- name: update apt packages + apt: + update_cache: true + +- name: install apt packages + apt: + name: "{{ apt_packages }}" + state: present + update_cache: true + environment: + DEBIAN_FRONTEND: noninteractive + +- name: create wireguard server directory + file: + path: "{{ wireguard_server_home }}" + state: directory + mode: "0700" + +- name: create wireguard peers directory + file: + path: "{{ wireguard_peers_home }}" + state: directory + mode: "0700" + +- name: generate wireguard server keys + shell: + cmd: | + wg genpsk > "{{ wireguard_server_home }}/psk.key" + wg genkey > "{{ wireguard_server_home }}/server.key" + creates: "{{ wireguard_server_home }}/server.key" + args: + chdir: "{{ wireguard_server_home }}" + +- name: get server public key + shell: + cmd: wg pubkey < "{{ wireguard_server_home }}/server.key" + register: server_pubkey + changed_when: false + +- name: read wireguard server.key from remote host + slurp: + src: "{{ wireguard_server_home }}/server.key" + register: wg_key + +- name: set private key from remote file + set_fact: + private_key: "{{ wg_key.content | b64decode }}" + +- name: deploy {{ wireguard_server_home }}/wg0.conf + template: + src: wg0.conf.j2 + dest: "{{ wireguard_server_home }}/wg0.conf" + mode: "0600" + +- name: deploy manage_wg_peers.sh + template: + src: manage_wg_peers.sh.j2 + dest: /root/manage_wg_peers.sh + mode: "0600" + +- name: restart wireguard + systemd: + name: wg-quick@wg0.service + state: restarted + enabled: true + when: ansible_service_mgr == 'systemd' diff --git a/templates/manage_wg_peers.sh.j2 b/templates/manage_wg_peers.sh.j2 new file mode 100644 index 0000000..ed2f800 --- /dev/null +++ b/templates/manage_wg_peers.sh.j2 @@ -0,0 +1,186 @@ +#!/bin/bash +set -e + +WG_SERVER_HOME="{{ wireguard_server_home }}" +WG_PEERS_HOME="${WG_SERVER_HOME}/peers.d" +IP_FILE="${WG_SERVER_HOME}/ips.txt" +SUBNET_PREFIX="{{ wireguard_subnet_prefix }}" +DEFAULT_PORT="{{ wireguard_port }}" +DEFAULT_DNS="8.8.8.8" + +test "${EUID}" -ne 0 && printf "%s\n" "run as root" && exit 1 +umask 077 + +if ! command -v wg &>/dev/null; then + printf "%s\n" "[err] wireguard not installed" + exit 1 +fi + +function usage() { + printf "%s\n" \ + "" \ + "wireguard peer management script" \ + "usage: $(basename ${0}) <action> [-h] <options>" \ + "" \ + "add-peer add a peer to the wg network" \ + "options:" \ + "-s <server> required: wg server endpoint" \ + "-n <name> optional: peer name, default random" \ + "-p <port> optional: wg server port, default ${DEFAULT_PORT}" \ + "-d <dns> optional: dns server, default ${DEFAULT_DNS}" \ + "" \ + "remove-peer remove peer from the wg network" \ + "options:" \ + "-n <name> required: peer name" \ + "" \ + "usage print this help message and exit" \ + "" \ + "configuration files:" \ + "wg server config dir: ${WG_SERVER_HOME}" \ + "wg peers configs dir: ${WG_PEERS_HOME}" + exit 1 +} + +function validate_ip() { + local ip="${1}" + if ! [[ "${ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + printf "%s\n" "[err] invalid IP address: ${ip}" + exit 1 + fi +} + +function validate_port() { + local port="${1}" + if ! [[ "${port}" =~ ^[0-9]+$ ]] || [[ "${port}" -lt 1 ]] || [[ "${port}" -gt 65535 ]]; then + printf "%s\n" "[err] invalid port: ${port}" + exit 1 + fi +} + +function get_next_available_ip() { + ( + flock -x 200 + touch "${IP_FILE}" + for i in {2..254}; do + ip="${SUBNET_PREFIX}.${i}" + if ! grep -q "${ip}" "${IP_FILE}"; then + printf "%s\n" "${ip}" + printf "%s\n" "${ip}" >> "${IP_FILE}" + exit 0 + fi + done + printf "%s\n" "[err] no available ips in range ${SUBNET_PREFIX}.2 - ${SUBNET_PREFIX}.254" + exit 1 + ) 200>"${IP_FILE}.lock" +} + +function add_peer() { + if ! test -d "${WG_SERVER_HOME}" || ! test -f "${WG_SERVER_HOME}/wg0.conf"; then + printf "%s\n" "[err] no wg server config found; install the server first" + exit 1 + fi + + if ! test "${server}"; then + printf "%s\n" "[err] missing -s <server>" + exit 1 + fi + + mkdir -p "${WG_PEERS_HOME}/${name}" &>/dev/null + assigned_ip=$(get_next_available_ip) + ( + cd "${WG_PEERS_HOME}/${name}" + wg genkey | tee "${name}.key" | wg pubkey > "${name}.pub" + + cat > "${name}.conf" << EOF +# peer ${name} +[Interface] +PrivateKey = $(cat "${name}.key") +Address = ${assigned_ip}/24 +DNS = ${dns} + +[Peer] +PublicKey = $(wg pubkey < "${WG_SERVER_HOME}/server.key") +PresharedKey = $(cat "${WG_SERVER_HOME}/psk.key") +Endpoint = ${server}:${port} +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 25 +EOF + printf "%s\n" \ + "[inf] generated peer configuration ${name} to server ${server}" \ + "[inf] config: ${WG_PEERS_HOME}/${name}/${name}.conf" + ) + + ( + peer_public_key=$(wg pubkey < "${WG_PEERS_HOME}/${name}/${name}.key") + cat >> "${WG_SERVER_HOME}/wg0.conf" << EOF + +# peer ${name} +[Peer] +PublicKey = ${peer_public_key} +PresharedKey = $(cat "${WG_SERVER_HOME}/psk.key") +AllowedIPs = ${assigned_ip}/32 +# peer ${name} +EOF + systemctl restart wg-quick@wg0.service + printf "%s\n" "[inf] restarted wg-quick@wg0.service" + ) +} + +function remove_peer() { + if ! test "${name}"; then + printf "%s\n" "[err] missing -n <name>" + exit 1 + fi + + if grep -qi "^# peer ${name}" "${WG_SERVER_HOME}/wg0.conf"; then + sed -i "/# peer ${name}/,/# peer ${name}/ s/^/# /" "${WG_SERVER_HOME}/wg0.conf" + systemctl restart wg-quick@wg0.service + printf "%s\n" "[inf] removed peer ${name} and restarted wg server" + else + printf "%s\n" "[err] no such peer ${name} in ${WG_SERVER_HOME}/wg0.conf" + exit 1 + fi +} + +port="${DEFAULT_PORT}" +dns="${DEFAULT_DNS}" + +if test "${1}"; then + action="${1}" + shift + case "${action}" in + add-peer) + while getopts "s:n:p:d:h" opts; do + case "${opts}" in + s) server="${OPTARG}"; validate_ip "${server}";; + n) name="${OPTARG}";; + p) port="${OPTARG}"; validate_port "${port}";; + d) dns="${OPTARG}"; validate_ip "${dns}";; + h) usage;; + *) usage;; + esac + done + rand=$(printf "%s\n" "${RANDOM}" | md5sum | fold -w4 | head -1) + name="${name:-${rand}}" + add_peer + ;; + remove-peer) + while getopts "n:h" opts; do + case "${opts}" in + n) name="${OPTARG}";; + h) usage;; + *) usage;; + esac + done + remove_peer + ;; + usage) + usage + ;; + *) + usage + ;; + esac +else + usage +fi diff --git a/templates/wg0.conf.j2 b/templates/wg0.conf.j2 new file mode 100644 index 0000000..6b0aa34 --- /dev/null +++ b/templates/wg0.conf.j2 @@ -0,0 +1,12 @@ +[Interface] +PrivateKey = {{ private_key }} +Address = {{ wireguard_subnet_prefix}}.1/24 +ListenPort = {{ wireguard_port }} +PostUp = sysctl -w net.ipv4.ip_forward=1 +PostUp = iptables -A FORWARD -i {{ wireguard_interface }} -o %i -j ACCEPT +PostUp = iptables -A FORWARD -i %i -j ACCEPT +PostUp = iptables -t nat -A POSTROUTING -o {{ wireguard_interface }} -j MASQUERADE +PostDown = sysctl -w net.ipv4.ip_forward=0 +PostDown = iptables -D FORWARD -i {{ wireguard_interface }} -o %i -j ACCEPT +PostDown = iptables -D FORWARD -i %i -j ACCEPT +PostDown = iptables -t nat -D POSTROUTING -o {{ wireguard_interface }} -j MASQUERADE diff --git a/vars/main.yaml b/vars/main.yaml index 568e185..bd079ca 100644 --- a/vars/main.yaml +++ b/vars/main.yaml @@ -1,5 +1,3 @@ -fail2ban_jail_dir: /etc/fail2ban/jail.d - apt_packages: - curl - ca-certificates @@ -17,3 +15,18 @@ pve_packages: apt_packages_to_remove: - os-prober + +wireguard_packages: + - wireguard + - wireguard-tools + - iptables + - iproute2 + +fail2ban_jail_dir: /etc/fail2ban/jail.d +wireguard_server_home: /etc/wireguard +wireguard_peers_home: "{{ wireguard_server_home }}/peers.d" +wireguard_ip_file: "{{ wireguard_server_home }}/ips.txt" +wireguard_subnet: "{{ wg_subnet }}" +wireguard_subnet_prefix: "{{ wg_subnet.split('.')[0:3] | join('.') }}" +wireguard_port: "{{ wg_port }}" +wireguard_interface: "{{ ansible_default_ipv4.interface }}" |