--- # Ansible Playbook: WireGuard Host-based VPN Setup # Purpose: Deploy minimalistic WireGuard VPN for admin access # Architecture: Host-based (systemd), no Docker, no DNS - name: Setup WireGuard VPN (Host-based) hosts: all become: yes vars: # WireGuard Configuration wg_interface: wg0 wg_network: 10.8.0.0/24 wg_server_ip: 10.8.0.1 wg_netmask: 24 wg_port: 51820 # Network Configuration wan_interface: eth0 # Change to your WAN interface (eth0, ens3, etc.) # Admin Service Ports (VPN-only access) admin_service_ports: - 8080 # Traefik Dashboard - 9090 # Prometheus - 3001 # Grafana - 9000 # Portainer - 8001 # Redis Insight # Public Service Ports public_service_ports: - 80 # HTTP - 443 # HTTPS - 22 # SSH # Rate Limiting wg_enable_rate_limit: true # Paths wg_config_dir: /etc/wireguard wg_backup_dir: /root/wireguard-backup nft_config_file: /etc/nftables.d/wireguard.nft tasks: # ======================================== # 1. Pre-flight Checks # ======================================== - name: Check if running as root assert: that: ansible_user_id == 'root' fail_msg: "This playbook must be run as root" - name: Detect WAN interface shell: ip route | grep default | awk '{print $5}' | head -n1 register: detected_wan_interface changed_when: false - name: Set WAN interface if not specified set_fact: wan_interface: "{{ detected_wan_interface.stdout }}" when: wan_interface == 'eth0' and detected_wan_interface.stdout != '' - name: Display detected network configuration debug: msg: - "WAN Interface: {{ wan_interface }}" - "VPN Network: {{ wg_network }}" - "VPN Server IP: {{ wg_server_ip }}" # ======================================== # 2. Backup Existing Configuration # ======================================== - name: Create backup directory file: path: "{{ wg_backup_dir }}" state: directory mode: '0700' - name: Backup existing WireGuard config (if exists) shell: | if [ -d {{ wg_config_dir }} ]; then tar -czf {{ wg_backup_dir }}/wireguard-backup-$(date +%Y%m%d-%H%M%S).tar.gz {{ wg_config_dir }} echo "Backup created" else echo "No existing config" fi register: backup_result changed_when: "'Backup created' in backup_result.stdout" # ======================================== # 3. Install WireGuard # ======================================== - name: Update apt cache apt: update_cache: yes cache_valid_time: 3600 when: ansible_os_family == 'Debian' - name: Install WireGuard and dependencies apt: name: - wireguard - wireguard-tools - qrencode # For QR code generation - nftables state: present when: ansible_os_family == 'Debian' - name: Ensure WireGuard kernel module is loaded modprobe: name: wireguard state: present - name: Verify WireGuard module is available shell: lsmod | grep -q wireguard register: wg_module_check failed_when: wg_module_check.rc != 0 changed_when: false # ======================================== # 4. Generate Server Keys (if not exist) # ======================================== - name: Create WireGuard config directory file: path: "{{ wg_config_dir }}" state: directory mode: '0700' - name: Check if server private key exists stat: path: "{{ wg_config_dir }}/server_private.key" register: server_private_key_stat - name: Generate server private key shell: wg genkey > {{ wg_config_dir }}/server_private.key when: not server_private_key_stat.stat.exists - name: Set server private key permissions file: path: "{{ wg_config_dir }}/server_private.key" mode: '0600' - name: Generate server public key shell: cat {{ wg_config_dir }}/server_private.key | wg pubkey > {{ wg_config_dir }}/server_public.key when: not server_private_key_stat.stat.exists - name: Read server private key slurp: src: "{{ wg_config_dir }}/server_private.key" register: server_private_key_content - name: Read server public key slurp: src: "{{ wg_config_dir }}/server_public.key" register: server_public_key_content - name: Set server key facts set_fact: wg_server_private_key: "{{ server_private_key_content.content | b64decode | trim }}" wg_server_public_key: "{{ server_public_key_content.content | b64decode | trim }}" - name: Display server public key debug: msg: "Server Public Key: {{ wg_server_public_key }}" # ======================================== # 5. Configure WireGuard # ======================================== - name: Deploy WireGuard server configuration template: src: ../templates/wg0.conf.j2 dest: "{{ wg_config_dir }}/wg0.conf" mode: '0600' notify: restart wireguard - name: Enable IP forwarding sysctl: name: net.ipv4.ip_forward value: '1' sysctl_set: yes state: present reload: yes # ======================================== # 6. Configure nftables Firewall # ======================================== - name: Create nftables config directory file: path: /etc/nftables.d state: directory mode: '0755' - name: Deploy WireGuard firewall rules template: src: ../templates/wireguard-host-firewall.nft.j2 dest: "{{ nft_config_file }}" mode: '0644' notify: reload nftables - name: Include WireGuard rules in main nftables config lineinfile: path: /etc/nftables.conf line: 'include "{{ nft_config_file }}"' create: yes state: present notify: reload nftables - name: Enable nftables service systemd: name: nftables enabled: yes state: started # ======================================== # 7. Enable and Start WireGuard # ======================================== - name: Enable WireGuard interface systemd: name: wg-quick@wg0 enabled: yes state: started - name: Verify WireGuard is running command: wg show wg0 register: wg_status changed_when: false - name: Display WireGuard status debug: msg: "{{ wg_status.stdout_lines }}" # ======================================== # 8. Health Checks # ======================================== - name: Check WireGuard interface exists command: ip link show wg0 register: wg_interface_check failed_when: wg_interface_check.rc != 0 changed_when: false - name: Check firewall rules applied command: nft list ruleset register: nft_rules failed_when: "'wireguard_firewall' not in nft_rules.stdout" changed_when: false - name: Verify admin ports are blocked from public shell: nft list chain inet wireguard_firewall input | grep -q "admin_service_ports.*drop" register: admin_port_block_check failed_when: admin_port_block_check.rc != 0 changed_when: false # ======================================== # 9. Post-Installation Summary # ======================================== - name: Create post-installation summary debug: msg: - "=========================================" - "WireGuard VPN Setup Complete!" - "=========================================" - "" - "Server Configuration:" - " Interface: wg0" - " Server IP: {{ wg_server_ip }}/{{ wg_netmask }}" - " Listen Port: {{ wg_port }}" - " Public Key: {{ wg_server_public_key }}" - "" - "Network Configuration:" - " VPN Network: {{ wg_network }}" - " WAN Interface: {{ wan_interface }}" - "" - "Admin Service Access (VPN-only):" - " Traefik Dashboard: https://{{ wg_server_ip }}:8080" - " Prometheus: http://{{ wg_server_ip }}:9090" - " Grafana: https://{{ wg_server_ip }}:3001" - " Portainer: http://{{ wg_server_ip }}:9000" - " Redis Insight: http://{{ wg_server_ip }}:8001" - "" - "Next Steps:" - " 1. Generate client config: ./scripts/generate-client-config.sh " - " 2. Import config on client device" - " 3. Connect and verify access" - "" - "Firewall Status: ACTIVE (nftables)" - " - Public ports: 80, 443, 22" - " - VPN port: {{ wg_port }}" - " - Admin services: VPN-only access" - "" - "=========================================" handlers: - name: restart wireguard systemd: name: wg-quick@wg0 state: restarted - name: reload nftables systemd: name: nftables state: reloaded