Initial commit

This commit is contained in:
2026-02-21 16:54:18 +01:00
commit 0cf33cd95b
80 changed files with 8042 additions and 0 deletions

20
ansible/ansible.cfg Normal file
View File

@@ -0,0 +1,20 @@
[defaults]
inventory = inventory/hosts.yml
roles_path = roles
host_key_checking = False
vault_password_file = .vault_pass
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

View File

@@ -0,0 +1,7 @@
---
terraform_output_file: "{{ playbook_dir }}/../../terraform/outputs.json"
wireguard_persistent_keepalive: 25
vxlan_ttl: 64
vxlan_port: 4789
exporter_port: 9586
monitoring_hub_ip: 192.168.1.100

View File

@@ -0,0 +1,28 @@
---
# Linux-specific settings
# Sysctl settings
sysctl_settings:
net.ipv4.ip_forward: 1
net.bridge.bridge-nf-call-iptables: 0
net.bridge.bridge-nf-call-ip6tables: 0
net.ipv4.conf.all.rp_filter: 0
net.ipv4.conf.default.rp_filter: 0
# WireGuard
wireguard_config_dir: /etc/wireguard
# Bridge settings
bridge_stp: no
bridge_forward_delay: 0
# ISC DHCP failover settings
isc_dhcp_failover:
max_response_delay: 30
max_unacked_updates: 10
mclt: 3600
split: 128
load_balance_max_seconds: 3
# Monitoring
exporter_install_dir: /opt/wg-lab-exporter

View File

@@ -0,0 +1,33 @@
---
# OpenWrt-specific settings
# SSH connection
ansible_user: root
ansible_python_interpreter: /usr/bin/python3
# OpenWrt package requirements
openwrt_required_packages:
- wireguard-tools
- kmod-wireguard
- kmod-vxlan
- ip-full
- tcpdump
# UCI settings
uci_commit_delay: 2
# Network reload wait time
network_reload_wait: 10
# Firewall zone settings
firewall_default_input: REJECT
firewall_default_forward: REJECT
firewall_default_output: ACCEPT
# Monitoring
exporter_port: 9586
exporter_path: /usr/lib/wg-lab-exporter
# Logging
syslog_enabled: yes
log_level: info

View File

@@ -0,0 +1,24 @@
all:
vars:
ansible_user: admin
ansible_python_interpreter: /usr/bin/python3
children:
lab_gateways:
children:
linux_gateways:
vars:
ansible_become: yes
mikrotik_gateways:
vars:
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: community.routeros.routeros
openwrt_gateways:
vars:
ansible_user: root
ansible_become: no
monitoring_hub:
hosts:
monitoring-hub.example.com:
ansible_host: 192.168.1.100

View File

@@ -0,0 +1,64 @@
---
- name: Add Gateway to Existing Lab Network
hosts: localhost
gather_facts: no
vars_prompt:
- name: lab_name
prompt: "Lab network name (e.g., lab100)"
private: no
- name: gateway_hostname
prompt: "New gateway hostname"
private: no
tasks:
- name: Load Terraform outputs
set_fact:
tf_config: "{{ lookup('file', terraform_output_file) | from_json }}"
- name: Verify gateway exists in Terraform
fail:
msg: "Gateway {{ gateway_hostname }} not found. Add to terraform.tfvars first."
when: gateway_hostname not in tf_config.gateway_config.value.keys()
- name: Display next steps
debug:
msg:
- "Gateway {{ gateway_hostname }} found in configuration"
- "Deploying to gateway..."
- name: Deploy to new gateway
hosts: "{{ gateway_hostname }}"
become: yes
tasks:
- name: Load gateway config
set_fact:
gateway_config: "{{ hostvars['localhost']['tf_config'].gateway_config.value[inventory_hostname] }}"
- name: Deploy based on gateway type
include_role:
name: "{{ item }}"
loop: "{{ roles_to_deploy }}"
vars:
roles_to_deploy: "{{ ['openwrt-common', 'openwrt-wireguard', 'openwrt-vxlan', 'openwrt-bridge', 'openwrt-dhcp', 'openwrt-firewall', 'openwrt-monitoring'] if gateway_config.type == 'openwrt' else (['mikrotik-common', 'mikrotik-wireguard', 'mikrotik-vxlan', 'mikrotik-bridge', 'mikrotik-dhcp', 'mikrotik-monitoring'] if gateway_config.type == 'mikrotik' else ['common', 'wireguard', 'vxlan', 'bridge', ('isc-dhcp' if gateway_config.dhcp_mode == 'failover' else 'dnsmasq'), 'monitoring']) }}"
- name: Update existing gateways with new peer
hosts: lab_gateways
become: yes
tasks:
- name: Skip if not in same lab
meta: end_host
when: gateway_config.lab_name != hostvars['localhost']['lab_name']
- name: Skip new gateway itself
meta: end_host
when: inventory_hostname == hostvars['localhost']['gateway_hostname']
- name: Reload WireGuard configuration
systemd:
name: "wg-quick@wg-{{ gateway_config.lab_name }}"
state: reloaded
when: gateway_config.type == 'linux'

View File

@@ -0,0 +1,30 @@
---
- name: Emergency Shutdown of Lab Network
hosts: localhost
gather_facts: no
vars_prompt:
- name: lab_name
prompt: "Which lab network to shutdown? (e.g., lab100, or 'all')"
private: no
- name: confirm_shutdown
prompt: "CONFIRM: Type 'YES' to proceed with emergency shutdown"
private: no
pre_tasks:
- name: Validate confirmation
fail:
msg: "Shutdown cancelled"
when: confirm_shutdown != "YES"
- name: Execute shutdown on Linux gateways
hosts: linux_gateways
become: yes
tasks:
- name: Stop WireGuard
systemd:
name: "wg-quick@wg-{{ gateway_config.lab_name }}"
state: stopped
ignore_errors: yes

View File

@@ -0,0 +1,40 @@
---
- name: Generate Road Warrior Configurations
hosts: localhost
gather_facts: yes
tasks:
- name: Load Terraform outputs
set_fact:
tf_config: "{{ lookup('file', terraform_output_file) | from_json }}"
- name: Create output directories
file:
path: "{{ rw_config_output_dir }}/{{ item.value.user_name }}"
state: directory
mode: '0755'
loop: "{{ tf_config.road_warrior_configs.value | dict2items }}"
loop_control:
label: "{{ item.value.user_name }}"
- name: Generate client configs
template:
src: ../road-warrior/templates/client-config.conf.j2
dest: "{{ rw_config_output_dir }}/{{ item.value.user_name }}/{{ item.value.lab_name }}.conf"
mode: '0600'
loop: "{{ tf_config.road_warrior_configs.value | dict2items }}"
loop_control:
label: "{{ item.value.user_name }}"
- name: Generate README
template:
src: ../road-warrior/templates/README.md.j2
dest: "{{ rw_config_output_dir }}/{{ item.key }}/README.md"
mode: '0644'
loop: "{{ tf_config.road_warrior_configs.value | dict2items | groupby('value.user_name') | list }}"
loop_control:
label: "{{ item.0 }}"
- name: Display output location
debug:
msg: "Road warrior configs generated in {{ rw_config_output_dir }}"

View File

@@ -0,0 +1,14 @@
---
- name: Comprehensive Health Check
hosts: lab_gateways
become: yes
tasks:
- name: Check WireGuard status
shell: "wg show"
register: wg_status
when: ansible_os_family == "Debian"
- name: Display results
debug:
var: wg_status.stdout_lines

View File

@@ -0,0 +1,35 @@
---
- name: Remove Gateway from Lab Network
hosts: localhost
gather_facts: no
vars_prompt:
- name: gateway_hostname
prompt: "Gateway hostname to remove"
private: no
- name: confirm_removal
prompt: "Type 'REMOVE' to confirm"
private: no
pre_tasks:
- name: Validate confirmation
fail:
msg: "Removal cancelled"
when: confirm_removal != "REMOVE"
- name: Shutdown gateway
hosts: "{{ gateway_hostname }}"
become: yes
tasks:
- name: Stop all services
systemd:
name: "{{ item }}"
state: stopped
loop:
- "wg-quick@wg-{{ gateway_config.lab_name }}"
- "vxlan-{{ gateway_config.lab_name }}"
- "bridge-{{ gateway_config.lab_name }}"
when: gateway_config.type == 'linux'
ignore_errors: yes

View File

@@ -0,0 +1,12 @@
---
- name: Restore Lab Network
hosts: linux_gateways
become: yes
serial: 1
tasks:
- name: Start WireGuard
systemd:
name: "wg-quick@wg-{{ gateway_config.lab_name }}"
state: started
when: ansible_os_family == "Debian"

View File

@@ -0,0 +1,37 @@
---
- name: Rolling Update of WireGuard Lab Gateways
hosts: lab_gateways
serial: 1
become: yes
vars:
health_check_retries: 10
health_check_delay: 10
pre_tasks:
- name: Load Terraform outputs
set_fact:
tf_config: "{{ lookup('file', terraform_output_file) | from_json }}"
delegate_to: localhost
run_once: yes
- name: Extract gateway-specific config
set_fact:
gateway_config: "{{ tf_config.gateway_config.value[inventory_hostname] }}"
tasks:
- name: Update system packages (Linux)
apt:
update_cache: yes
upgrade: dist
when: gateway_config.type == "linux"
- name: Restart WireGuard
systemd:
name: "wg-quick@wg-{{ gateway_config.lab_name }}"
state: restarted
when: gateway_config.type == "linux"
- name: Wait and validate
pause:
seconds: 30

View File

@@ -0,0 +1,90 @@
---
- name: Deploy WireGuard Lab Overlay Network
hosts: lab_gateways
become: yes
gather_facts: yes
pre_tasks:
- name: Load Terraform outputs
set_fact:
tf_config: "{{ lookup('file', '../../terraform/outputs.json') | from_json }}"
delegate_to: localhost
run_once: yes
- name: Extract gateway-specific config
set_fact:
gateway_config: "{{ tf_config.gateway_config.value[inventory_hostname] }}"
- name: Display gateway type
debug:
msg: "Configuring {{ inventory_hostname }} ({{ gateway_config.type }}) for {{ gateway_config.lab_name }}"
tasks:
- name: Configure Linux gateways
block:
- include_role:
name: common
- include_role:
name: wireguard
- include_role:
name: vxlan
- include_role:
name: bridge
- include_role:
name: "{{ 'isc-dhcp' if gateway_config.dhcp_mode == 'failover' else 'dnsmasq' }}"
- include_role:
name: monitoring
when: gateway_config.type == 'linux'
- name: Configure MikroTik gateways
block:
- include_role:
name: mikrotik-common
- include_role:
name: mikrotik-wireguard
- include_role:
name: mikrotik-vxlan
- include_role:
name: mikrotik-bridge
- include_role:
name: mikrotik-dhcp
- include_role:
name: mikrotik-monitoring
when: gateway_config.type == 'mikrotik'
- name: Configure OpenWrt gateways
block:
- include_role:
name: openwrt-common
- include_role:
name: openwrt-wireguard
- include_role:
name: openwrt-vxlan
- include_role:
name: openwrt-bridge
- include_role:
name: openwrt-dhcp
- include_role:
name: openwrt-firewall
- include_role:
name: openwrt-monitoring
when: gateway_config.type == 'openwrt'
post_tasks:
- name: Deployment summary
debug:
msg:
- "Gateway: {{ inventory_hostname }}"
- "Type: {{ gateway_config.type }}"
- "Lab: {{ gateway_config.lab_name }}"
- "VNI: {{ gateway_config.vni }}"
- "WireGuard IP: {{ gateway_config.wg_ip }}"
- "Subnet: {{ gateway_config.subnet }}"
- "DHCP Mode: {{ gateway_config.dhcp_mode }}"
- "DHCP Role: {{ gateway_config.dhcp_role }}"
- name: Configure Monitoring Hub
hosts: monitoring_hub
become: yes
roles:
- monitoring_hub

View File

@@ -0,0 +1,18 @@
---
- name: Create bridge setup script
template:
src: setup-bridge.sh.j2
dest: /usr/local/bin/setup-bridge-{{ gateway_config.lab_name }}.sh
mode: '0755'
- name: Create systemd service for bridge
template:
src: bridge.service.j2
dest: /etc/systemd/system/bridge-{{ gateway_config.lab_name }}.service
notify: reload systemd
- name: Enable and start bridge service
systemd:
name: bridge-{{ gateway_config.lab_name }}
enabled: yes
state: started

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Bridge for {{ gateway_config.lab_name }}
After=vxlan-{{ gateway_config.lab_name }}.service
Requires=vxlan-{{ gateway_config.lab_name }}.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/setup-bridge-{{ gateway_config.lab_name }}.sh
ExecStop=/sbin/ip link delete br-{{ gateway_config.lab_name }}
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
LAB_NAME="{{ gateway_config.lab_name }}"
VNI="{{ gateway_config.vni }}"
BR_IF="br-${LAB_NAME}"
VXLAN_IF="vxlan${VNI}"
LAB_IF="{{ gateway_config.lab_if }}"
# Wait for VXLAN interface
timeout=30
while [ $timeout -gt 0 ]; do
if ip link show ${VXLAN_IF} >/dev/null 2>&1; then
break
fi
sleep 1
timeout=$((timeout-1))
done
# Create bridge
if ! ip link show ${BR_IF} &>/dev/null; then
ip link add ${BR_IF} type bridge
ip link set ${BR_IF} type bridge stp_state {{ bridge_stp | ternary('1', '0') }}
ip link set ${BR_IF} type bridge forward_delay {{ bridge_forward_delay }}
fi
# Add interfaces to bridge
ip link set ${VXLAN_IF} master ${BR_IF}
ip link set ${LAB_IF} master ${BR_IF}
# Configure bridge IP (for DHCP server)
ip addr flush dev ${BR_IF}
ip addr add {{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }}/{{ gateway_config.subnet | ipaddr('prefix') }} dev ${BR_IF}
# Bring everything up
ip link set ${LAB_IF} up
ip link set ${VXLAN_IF} up
ip link set ${BR_IF} up

View File

@@ -0,0 +1,26 @@
---
- name: Install required packages
apt:
name:
- wireguard
- wireguard-tools
- bridge-utils
- python3-prometheus-client
- vlan
state: present
update_cache: yes
- name: Configure sysctl settings
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: yes
loop: "{{ sysctl_settings | dict2items }}"
- name: Ensure systemd-networkd is disabled
systemd:
name: systemd-networkd
enabled: no
state: stopped
ignore_errors: yes

View File

@@ -0,0 +1,5 @@
---
- name: restart dnsmasq
systemd:
name: dnsmasq
state: restarted

View File

@@ -0,0 +1,18 @@
---
- name: Install dnsmasq
apt:
name: dnsmasq
state: present
- name: Create dnsmasq configuration
template:
src: dnsmasq.conf.j2
dest: /etc/dnsmasq.d/{{ gateway_config.lab_name }}.conf
mode: '0644'
notify: restart dnsmasq
- name: Enable and start dnsmasq
systemd:
name: dnsmasq
enabled: yes
state: started

View File

@@ -0,0 +1,28 @@
# {{ gateway_config.lab_name }} DHCP configuration
interface=br-{{ gateway_config.lab_name }}
bind-interfaces
{% if gateway_config.dhcp_role == 'primary' %}
# Primary DHCP server
dhcp-range={{ gateway_config.subnet | ipaddr('10') | ipaddr('address') }},{{ gateway_config.subnet | ipaddr('127') | ipaddr('address') }},{{ gateway_config.subnet | ipaddr('netmask') }},12h
{% elif gateway_config.dhcp_role == 'secondary' %}
# Secondary DHCP server (split range)
dhcp-range={{ gateway_config.subnet | ipaddr('128') | ipaddr('address') }},{{ gateway_config.subnet | ipaddr('245') | ipaddr('address') }},{{ gateway_config.subnet | ipaddr('netmask') }},12h
{% else %}
# Backup server (only responds if others fail)
dhcp-range={{ gateway_config.subnet | ipaddr('10') | ipaddr('address') }},{{ gateway_config.subnet | ipaddr('245') | ipaddr('address') }},{{ gateway_config.subnet | ipaddr('netmask') }},12h
{% endif %}
dhcp-option=option:router,{{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }}
dhcp-option=option:dns-server,{{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }}
# Local DNS domain
domain={{ gateway_config.lab_name }}.lab
local=/{{ gateway_config.lab_name }}.lab/
# Lease file
dhcp-leasefile=/var/lib/misc/dnsmasq.{{ gateway_config.lab_name }}.leases
# Logging
log-dhcp
log-queries

View File

@@ -0,0 +1,5 @@
---
- name: restart isc-dhcp
systemd:
name: isc-dhcp-server
state: restarted

View File

@@ -0,0 +1,48 @@
---
- name: Install ISC DHCP server
apt:
name: isc-dhcp-server
state: present
- name: Stop and disable dnsmasq (conflicts with ISC DHCP)
systemd:
name: dnsmasq
state: stopped
enabled: no
ignore_errors: yes
- name: Configure ISC DHCP server defaults
template:
src: isc-dhcp-server.j2
dest: /etc/default/isc-dhcp-server
mode: '0644'
notify: restart isc-dhcp
- name: Get failover peer IP
set_fact:
failover_peer_ip: "{{ hostvars[item].gateway_config.wg_ip }}"
loop: "{{ groups['lab_gateways'] }}"
when:
- hostvars[item].gateway_config.lab_name == gateway_config.lab_name
- hostvars[item].gateway_config.dhcp_role in ['primary', 'secondary']
- hostvars[item].gateway_config.dhcp_role != gateway_config.dhcp_role
register: peer_search
- name: Set failover peer IP from search
set_fact:
failover_peer_ip: "{{ peer_search.results | selectattr('ansible_facts', 'defined') | map(attribute='ansible_facts.failover_peer_ip') | first }}"
when: peer_search.results | selectattr('ansible_facts', 'defined') | list | length > 0
- name: Configure DHCP failover
template:
src: dhcpd.conf.j2
dest: /etc/dhcp/dhcpd.conf
mode: '0644'
validate: 'dhcpd -t -cf %s'
notify: restart isc-dhcp
- name: Ensure ISC DHCP is enabled and started
systemd:
name: isc-dhcp-server
enabled: yes
state: started

View File

@@ -0,0 +1,56 @@
# ISC DHCP Configuration for {{ gateway_config.lab_name }}
# Generated by Ansible
authoritative;
ddns-update-style none;
default-lease-time 43200; # 12 hours
max-lease-time 86400; # 24 hours
# Failover configuration
{% if gateway_config.dhcp_role == 'primary' %}
failover peer "{{ gateway_config.lab_name }}-failover" {
primary;
address {{ gateway_config.wg_ip }};
port 647;
peer address {{ failover_peer_ip }};
peer port 647;
max-response-delay {{ isc_dhcp_failover.max_response_delay }};
max-unacked-updates {{ isc_dhcp_failover.max_unacked_updates }};
mclt {{ isc_dhcp_failover.mclt }};
split {{ isc_dhcp_failover.split }};
load balance max seconds {{ isc_dhcp_failover.load_balance_max_seconds }};
}
{% elif gateway_config.dhcp_role == 'secondary' %}
failover peer "{{ gateway_config.lab_name }}-failover" {
secondary;
address {{ gateway_config.wg_ip }};
port 647;
peer address {{ failover_peer_ip }};
peer port 647;
max-response-delay {{ isc_dhcp_failover.max_response_delay }};
max-unacked-updates {{ isc_dhcp_failover.max_unacked_updates }};
load balance max seconds {{ isc_dhcp_failover.load_balance_max_seconds }};
}
{% endif %}
# Subnet declaration
subnet {{ gateway_config.subnet | ipaddr('network') }} netmask {{ gateway_config.subnet | ipaddr('netmask') }} {
option routers {{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }};
option domain-name-servers {{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }};
option domain-name "{{ gateway_config.lab_name }}.lab";
{% if gateway_config.dhcp_role in ['primary', 'secondary'] %}
pool {
failover peer "{{ gateway_config.lab_name }}-failover";
range {{ gateway_config.subnet | ipaddr('10') | ipaddr('address') }} {{ gateway_config.subnet | ipaddr('245') | ipaddr('address') }};
}
{% else %}
# Backup server - only serves when primary/secondary unavailable
pool {
range {{ gateway_config.subnet | ipaddr('10') | ipaddr('address') }} {{ gateway_config.subnet | ipaddr('245') | ipaddr('address') }};
}
{% endif %}
}
# Logging
log-facility local7;

View File

@@ -0,0 +1,10 @@
# Defaults for isc-dhcp-server (sourced by /etc/init.d/isc-dhcp-server)
# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
DHCPDv4_CONF=/etc/dhcp/dhcpd.conf
# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
DHCPDv4_PID=/var/run/dhcpd.pid
# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
INTERFACESv4="br-{{ gateway_config.lab_name }}"

View File

@@ -0,0 +1,16 @@
---
- name: Create bridge
community.routeros.command:
commands:
- /interface bridge add name="bridge-{{ gateway_config.lab_name }}" protocol-mode=none
- name: Add interfaces to bridge
community.routeros.command:
commands:
- /interface bridge port add bridge="bridge-{{ gateway_config.lab_name }}" interface="vxlan{{ gateway_config.vni }}"
- /interface bridge port add bridge="bridge-{{ gateway_config.lab_name }}" interface="{{ gateway_config.lab_if }}"
- name: Configure bridge IP
community.routeros.command:
commands:
- /ip address add address={{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }}/{{ gateway_config.subnet | ipaddr('prefix') }} interface="bridge-{{ gateway_config.lab_name }}"

View File

@@ -0,0 +1,16 @@
---
- name: Check RouterOS version
community.routeros.command:
commands:
- /system resource print
register: ros_version
- name: Set system identity
community.routeros.command:
commands:
- /system identity set name={{ inventory_hostname }}
- name: Configure NTP client
community.routeros.command:
commands:
- /system ntp client set enabled=yes servers=pool.ntp.org

View File

@@ -0,0 +1,18 @@
---
- name: Create DHCP pool
community.routeros.command:
commands:
- /ip pool add name="pool-{{ gateway_config.lab_name }}" ranges={{ dhcp_start }}-{{ dhcp_end }}
vars:
dhcp_start: "{{ gateway_config.subnet | ipaddr('10') | ipaddr('address') if gateway_config.dhcp_role == 'primary' else gateway_config.subnet | ipaddr('128') | ipaddr('address') }}"
dhcp_end: "{{ gateway_config.subnet | ipaddr('127') | ipaddr('address') if gateway_config.dhcp_role == 'primary' else gateway_config.subnet | ipaddr('245') | ipaddr('address') }}"
- name: Configure DHCP network
community.routeros.command:
commands:
- /ip dhcp-server network add address={{ gateway_config.subnet }} gateway={{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }} dns-server={{ gateway_config.subnet | ipaddr('1') | ipaddr('address') }}
- name: Create DHCP server
community.routeros.command:
commands:
- /ip dhcp-server add name="dhcp-{{ gateway_config.lab_name }}" interface="bridge-{{ gateway_config.lab_name }}" address-pool="pool-{{ gateway_config.lab_name }}" lease-time=12h disabled=no

View File

@@ -0,0 +1,11 @@
---
- name: Enable REST API
community.routeros.command:
commands:
- /ip service set api-ssl disabled=no port=8729
- name: Create monitoring user
community.routeros.command:
commands:
- /user add name=monitoring group=read comment="Prometheus monitoring"
ignore_errors: yes

View File

@@ -0,0 +1,13 @@
---
- name: Create VXLAN interface
community.routeros.command:
commands:
- /interface vxlan add name="vxlan{{ gateway_config.vni }}" vni={{ gateway_config.vni }} interface="wg-{{ gateway_config.lab_name }}" port=4789
- name: Configure VXLAN vteps
community.routeros.command:
commands:
- /interface vxlan vteps add interface="vxlan{{ gateway_config.vni }}" remote-ip={{ peer.wg_ip }}
loop: "{{ gateway_config.peers }}"
loop_control:
loop_var: peer

View File

@@ -0,0 +1,20 @@
---
- name: Remove existing WireGuard interface
community.routeros.command:
commands:
- /interface wireguard remove [find name="wg-{{ gateway_config.lab_name }}"]
ignore_errors: yes
- name: Create WireGuard interface
community.routeros.command:
commands:
- /interface wireguard add name="wg-{{ gateway_config.lab_name }}" listen-port={{ gateway_config.wg_listen_port }} private-key="{{ gateway_config.wg_private_key }}"
- /ip address add address={{ gateway_config.wg_ip }}/{{ gateway_config.wg_net | ipaddr("prefix") }} interface="wg-{{ gateway_config.lab_name }}"
- name: Configure WireGuard peers
community.routeros.command:
commands:
- /interface wireguard peers add interface="wg-{{ gateway_config.lab_name }}" public-key="{{ peer.public_key }}" endpoint-address={{ peer.endpoint }} endpoint-port={{ gateway_config.wg_listen_port }} allowed-address={{ peer.wg_ip }}/32 persistent-keepalive=25s
loop: "{{ gateway_config.peers }}"
loop_control:
loop_var: peer

View File

@@ -0,0 +1,24 @@
---
- name: Create exporter directory
file:
path: "{{ exporter_install_dir }}"
state: directory
mode: '0755'
- name: Deploy exporter configuration
template:
src: exporter-config.json.j2
dest: /etc/wg-lab-exporter/config.json
mode: '0644'
- name: Create systemd service
template:
src: wg-lab-exporter.service.j2
dest: /etc/systemd/system/wg-lab-exporter.service
- name: Enable and start exporter
systemd:
name: wg-lab-exporter
enabled: yes
state: started
daemon_reload: yes

View File

@@ -0,0 +1,8 @@
{
"labs": {
"{{ gateway_config.lab_name }}": {
"vni": {{ gateway_config.vni }},
"lab_if": "{{ gateway_config.lab_if }}"
}
}
}

View File

@@ -0,0 +1,11 @@
[Unit]
Description=WireGuard Lab Exporter
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/wg-lab-exporter/wg_lab_exporter.py
Restart=always
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,63 @@
---
- name: Set bridge interface name
set_fact:
br_if: "br_{{ gateway_config.lab_name }}"
vxlan_if: "vxlan{{ gateway_config.vni }}"
lab_if: "{{ gateway_config.lab_if }}"
- name: Remove existing bridge configuration
shell: |
uci delete network.{{ br_if }} 2>/dev/null || true
uci delete network.{{ br_if }}_dev 2>/dev/null || true
uci commit network
changed_when: true
- name: Create bridge device
shell: |
uci set network.{{ br_if }}_dev='device'
uci set network.{{ br_if }}_dev.name='{{ br_if }}'
uci set network.{{ br_if }}_dev.type='bridge'
uci set network.{{ br_if }}_dev.stp='0'
uci add_list network.{{ br_if }}_dev.ports='{{ vxlan_if }}'
uci add_list network.{{ br_if }}_dev.ports='{{ lab_if }}'
uci commit network
changed_when: true
- name: Create bridge interface
shell: |
uci set network.{{ br_if }}='interface'
uci set network.{{ br_if }}.proto='static'
uci set network.{{ br_if }}.device='{{ br_if }}'
uci set network.{{ br_if }}.ipaddr='{{ gateway_config.subnet | ipaddr("1") | ipaddr("address") }}'
uci set network.{{ br_if }}.netmask='{{ gateway_config.subnet | ipaddr("netmask") }}'
uci commit network
changed_when: true
- name: Reload network configuration
shell: /etc/init.d/network reload
changed_when: true
- name: Wait for bridge to come up
shell: ip link show {{ br_if }}
register: bridge_check
retries: 10
delay: 2
until: bridge_check.rc == 0
changed_when: false
- name: Verify bridge configuration
shell: |
echo "=== Bridge Interface ==="
ip link show {{ br_if }}
echo ""
echo "=== Bridge Members ==="
bridge link show | grep {{ br_if }}
echo ""
echo "=== IP Address ==="
ip addr show {{ br_if }}
register: bridge_status
changed_when: false
- name: Display bridge status
debug:
var: bridge_status.stdout_lines

View File

@@ -0,0 +1,62 @@
---
- name: Gather OpenWrt facts
setup:
- name: Check OpenWrt version
shell: ". /etc/os-release && echo $VERSION"
register: openwrt_version
changed_when: false
- name: Display OpenWrt version
debug:
msg: "OpenWrt version: {{ openwrt_version.stdout }}"
- name: Update package lists
shell: opkg update
changed_when: false
- name: Check if packages are installed
shell: opkg list-installed | grep -E "^(wireguard-tools|kmod-wireguard|kmod-vxlan|ip-full|tcpdump) " || true
register: installed_packages
changed_when: false
- name: Install required packages
shell: opkg install {{ item }}
loop:
- wireguard-tools
- kmod-wireguard
- kmod-vxlan
- ip-full
- tcpdump
when: item not in installed_packages.stdout
register: package_install
failed_when: package_install.rc != 0 and 'already installed' not in package_install.stderr
- name: Enable IP forwarding
shell: |
uci set network.@globals[0].ipv6_forwarding='1' 2>/dev/null || uci add network globals
uci set network.@globals[0].ipv6_forwarding='1'
uci commit network
changed_when: true
- name: Configure sysctl settings
blockinfile:
path: /etc/sysctl.conf
create: yes
block: |
net.ipv4.ip_forward=1
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-ip6tables=0
marker: "# {mark} ANSIBLE MANAGED - Wireguard Lab"
- name: Apply sysctl settings
shell: sysctl -p
changed_when: false
- name: Create configuration backup
shell: sysupgrade -b /tmp/backup-pre-wireguard-lab-$(date +%Y%m%d-%H%M%S).tar.gz
args:
creates: /tmp/backup-pre-wireguard-lab-*.tar.gz
ignore_errors: yes

View File

@@ -0,0 +1,78 @@
---
- name: Set DHCP configuration variables
set_fact:
br_if: "br_{{ gateway_config.lab_name }}"
dhcp_tag: "{{ gateway_config.lab_name }}"
- name: Calculate DHCP range based on role
set_fact:
dhcp_start: "{{ gateway_config.subnet | ipaddr('10') | ipaddr('address') if gateway_config.dhcp_role == 'primary' else gateway_config.subnet | ipaddr('128') | ipaddr('address') }}"
dhcp_limit: "{{ 118 if gateway_config.dhcp_role == 'primary' else 118 }}"
when: gateway_config.dhcp_mode == "simple"
- name: Calculate DHCP range for failover simulation
set_fact:
dhcp_start: "{{ gateway_config.subnet | ipaddr('10') | ipaddr('address') }}"
dhcp_limit: 236
when: gateway_config.dhcp_mode == "failover"
- name: Remove existing DHCP configuration for this lab
shell: |
uci delete dhcp.{{ dhcp_tag }} 2>/dev/null || true
uci commit dhcp
changed_when: true
- name: Configure DHCP server
shell: |
uci set dhcp.{{ dhcp_tag }}='dhcp'
uci set dhcp.{{ dhcp_tag }}.interface='{{ br_if }}'
uci set dhcp.{{ dhcp_tag }}.start='{{ dhcp_start | ipaddr('host') }}'
uci set dhcp.{{ dhcp_tag }}.limit='{{ dhcp_limit }}'
uci set dhcp.{{ dhcp_tag }}.leasetime='12h'
uci add_list dhcp.{{ dhcp_tag }}.dhcp_option='3,{{ gateway_config.subnet | ipaddr("1") | ipaddr("address") }}'
uci add_list dhcp.{{ dhcp_tag }}.dhcp_option='6,{{ gateway_config.subnet | ipaddr("1") | ipaddr("address") }}'
uci commit dhcp
changed_when: true
- name: Configure DNS domain for lab network
shell: |
uci set dhcp.{{ dhcp_tag }}.domain='{{ gateway_config.lab_name }}.lab'
uci set dhcp.{{ dhcp_tag }}.local='/{{ gateway_config.lab_name }}.lab/'
uci commit dhcp
changed_when: true
- name: Set DHCP authoritative mode based on role
shell: |
uci set dhcp.{{ dhcp_tag }}.authoritative='{{ "1" if gateway_config.dhcp_role == "primary" else "0" }}'
uci commit dhcp
changed_when: true
when: gateway_config.dhcp_mode == "failover"
- name: Restart dnsmasq
shell: /etc/init.d/dnsmasq restart
changed_when: true
- name: Wait for dnsmasq to start
shell: pgrep dnsmasq
register: dnsmasq_check
retries: 5
delay: 2
until: dnsmasq_check.rc == 0
changed_when: false
- name: Verify DHCP configuration
shell: |
echo "=== DHCP Configuration ==="
uci show dhcp.{{ dhcp_tag }}
echo ""
echo "=== dnsmasq Process ==="
ps | grep dnsmasq | grep -v grep
echo ""
echo "=== DHCP Leases ==="
cat /tmp/dhcp.leases 2>/dev/null || echo "No leases yet"
register: dhcp_status
changed_when: false
- name: Display DHCP status
debug:
var: dhcp_status.stdout_lines

View File

@@ -0,0 +1,98 @@
---
- name: Set firewall zone name
set_fact:
fw_zone: "{{ gateway_config.lab_name }}"
br_if: "br_{{ gateway_config.lab_name }}"
- name: Remove existing firewall zone
shell: |
# Find and remove existing zone
zone_idx=$(uci show firewall | grep "=zone" | grep "name='{{ fw_zone }}'" | cut -d. -f2 | cut -d= -f1)
if [ -n "$zone_idx" ]; then
uci delete firewall.$zone_idx
fi
# Remove associated forwarding rules
for fwd in $(uci show firewall | grep "={{ fw_zone }}" | cut -d. -f2 | cut -d= -f1); do
uci delete firewall.$fwd 2>/dev/null || true
done
uci commit firewall
changed_when: true
- name: Create isolated firewall zone for lab network
shell: |
# Create new zone
uci add firewall zone
uci set firewall.@zone[-1].name='{{ fw_zone }}'
uci set firewall.@zone[-1].input='REJECT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='REJECT'
uci set firewall.@zone[-1].network='{{ br_if }}'
uci commit firewall
changed_when: true
- name: Allow DNS and DHCP from lab to gateway
shell: |
# Allow DNS
uci add firewall rule
uci set firewall.@rule[-1].name='Allow-{{ fw_zone }}-DNS'
uci set firewall.@rule[-1].src='{{ fw_zone }}'
uci set firewall.@rule[-1].proto='tcp udp'
uci set firewall.@rule[-1].dest_port='53'
uci set firewall.@rule[-1].target='ACCEPT'
# Allow DHCP
uci add firewall rule
uci set firewall.@rule[-1].name='Allow-{{ fw_zone }}-DHCP'
uci set firewall.@rule[-1].src='{{ fw_zone }}'
uci set firewall.@rule[-1].proto='udp'
uci set firewall.@rule[-1].src_port='67:68'
uci set firewall.@rule[-1].dest_port='67:68'
uci set firewall.@rule[-1].target='ACCEPT'
uci commit firewall
changed_when: true
- name: Allow intra-lab forwarding
shell: |
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='{{ fw_zone }}'
uci set firewall.@forwarding[-1].dest='{{ fw_zone }}'
uci commit firewall
changed_when: true
- name: Block lab to WAN/LAN
shell: |
# Explicit reject to WAN
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='{{ fw_zone }}'
uci set firewall.@forwarding[-1].dest='wan'
uci set firewall.@forwarding[-1].enabled='0'
# Explicit reject to LAN
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='{{ fw_zone }}'
uci set firewall.@forwarding[-1].dest='lan'
uci set firewall.@forwarding[-1].enabled='0'
uci commit firewall
changed_when: true
- name: Reload firewall
shell: /etc/init.d/firewall reload
changed_when: true
- name: Verify firewall configuration
shell: |
echo "=== Firewall Zones ==="
uci show firewall | grep "={{ fw_zone }}"
echo ""
echo "=== Firewall Rules ==="
iptables -L -n -v | grep -A 5 "{{ fw_zone }}" || echo "No iptables rules yet"
register: firewall_status
changed_when: false
- name: Display firewall status
debug:
var: firewall_status.stdout_lines

View File

@@ -0,0 +1,80 @@
---
- name: Create metrics exporter directory
file:
path: /usr/lib/wg-lab-exporter
state: directory
mode: '0755'
- name: Deploy metrics exporter script
template:
src: metrics-exporter.sh.j2
dest: /usr/lib/wg-lab-exporter/exporter.sh
mode: '0755'
- name: Create exporter configuration
template:
src: exporter-config.j2
dest: /etc/wg-lab-exporter.conf
mode: '0644'
- name: Create uhttpd configuration for metrics endpoint
blockinfile:
path: /etc/uhttpd.conf
create: yes
marker: "# {mark} ANSIBLE MANAGED - Metrics Exporter"
block: |
config uhttpd 'metrics'
list listen_http '0.0.0.0:9586'
option home '/www-metrics'
option cgi_prefix '/metrics'
option script_timeout '10'
option network_timeout '30'
option tcp_keepalive '1'
- name: Create metrics web directory
file:
path: /www-metrics/metrics
state: directory
mode: '0755'
- name: Create CGI wrapper for metrics
copy:
dest: /www-metrics/metrics/index.cgi
mode: '0755'
content: |
#!/bin/sh
echo "Content-Type: text/plain"
echo ""
/usr/lib/wg-lab-exporter/exporter.sh
- name: Create cron job for metrics collection (fallback)
cron:
name: "WireGuard Lab Metrics Collection"
minute: "*/5"
job: "/usr/lib/wg-lab-exporter/exporter.sh > /tmp/wg-lab-metrics.txt"
user: root
- name: Restart uhttpd
shell: /etc/init.d/uhttpd restart
changed_when: true
- name: Test metrics endpoint
uri:
url: "http://localhost:9586/metrics/"
timeout: 5
register: metrics_test
ignore_errors: yes
changed_when: false
- name: Display metrics test result
debug:
msg: "Metrics endpoint {{ 'OK' if metrics_test.status == 200 else 'FAILED' }}"
- name: Show sample metrics output
shell: /usr/lib/wg-lab-exporter/exporter.sh | head -20
register: metrics_sample
changed_when: false
- name: Display sample metrics
debug:
var: metrics_sample.stdout_lines

View File

@@ -0,0 +1,10 @@
# WireGuard Lab Exporter Configuration
# Lab: {{ gateway_config.lab_name }}
LAB_NAME="{{ gateway_config.lab_name }}"
VNI="{{ gateway_config.vni }}"
# Interfaces to monitor
WG_INTERFACES="wg_{{ gateway_config.lab_name }}"
BRIDGE_INTERFACES="br_{{ gateway_config.lab_name }}"
VXLAN_INTERFACES="vxlan{{ gateway_config.vni }}"

View File

@@ -0,0 +1,105 @@
#!/bin/sh
# Prometheus metrics exporter for WireGuard Lab Overlay
# Generated by Ansible
. /etc/wg-lab-exporter.conf
# Helper function to output metrics
metric() {
echo "$1 $2"
}
metric_label() {
echo "$1{$2} $3"
}
# Current timestamp
TIMESTAMP=$(date +%s)
echo "# HELP wireguard_tunnel_up WireGuard tunnel status (1=up, 0=down)"
echo "# TYPE wireguard_tunnel_up gauge"
# Check WireGuard tunnels
for WG_IF in ${WG_INTERFACES}; do
if ip link show ${WG_IF} >/dev/null 2>&1; then
# Get peer information
wg show ${WG_IF} dump | tail -n +2 | while IFS=$'\t' read pubkey psk endpoint allowed_ip latest_handshake rx_bytes tx_bytes persistent_keepalive; do
# Calculate handshake age
if [ "$latest_handshake" != "0" ]; then
handshake_age=$((TIMESTAMP - latest_handshake))
if [ $handshake_age -lt 180 ]; then
status=1
else
status=0
fi
else
status=0
handshake_age=-1
fi
peer_short=$(echo $pubkey | cut -c1-16)
metric_label "wireguard_tunnel_up" "lab=\"${LAB_NAME}\",interface=\"${WG_IF}\",peer=\"${peer_short}\"" "$status"
metric_label "wireguard_latest_handshake_seconds" "lab=\"${LAB_NAME}\",interface=\"${WG_IF}\",peer=\"${peer_short}\"" "$handshake_age"
metric_label "wireguard_rx_bytes_total" "lab=\"${LAB_NAME}\",interface=\"${WG_IF}\",peer=\"${peer_short}\"" "$rx_bytes"
metric_label "wireguard_tx_bytes_total" "lab=\"${LAB_NAME}\",interface=\"${WG_IF}\",peer=\"${peer_short}\"" "$tx_bytes"
done
else
metric_label "wireguard_tunnel_up" "lab=\"${LAB_NAME}\",interface=\"${WG_IF}\",peer=\"all\"" "0"
fi
done
echo ""
echo "# HELP bridge_fdb_entries Number of FDB entries in bridge"
echo "# TYPE bridge_fdb_entries gauge"
# Bridge FDB entries
for BR_IF in ${BRIDGE_INTERFACES}; do
if ip link show ${BR_IF} >/dev/null 2>&1; then
fdb_count=$(bridge fdb show br ${BR_IF} 2>/dev/null | wc -l)
metric_label "bridge_fdb_entries" "lab=\"${LAB_NAME}\",bridge=\"${BR_IF}\"" "$fdb_count"
fi
done
echo ""
echo "# HELP vxlan_rx_packets_total VXLAN received packets"
echo "# TYPE vxlan_rx_packets_total counter"
echo "# HELP vxlan_tx_packets_total VXLAN transmitted packets"
echo "# TYPE vxlan_tx_packets_total counter"
# VXLAN statistics
for VXLAN_IF in ${VXLAN_INTERFACES}; do
if [ -f "/sys/class/net/${VXLAN_IF}/statistics/rx_packets" ]; then
rx_packets=$(cat /sys/class/net/${VXLAN_IF}/statistics/rx_packets)
tx_packets=$(cat /sys/class/net/${VXLAN_IF}/statistics/tx_packets)
rx_bytes=$(cat /sys/class/net/${VXLAN_IF}/statistics/rx_bytes)
tx_bytes=$(cat /sys/class/net/${VXLAN_IF}/statistics/tx_bytes)
metric_label "vxlan_rx_packets_total" "lab=\"${LAB_NAME}\",vni=\"${VNI}\"" "$rx_packets"
metric_label "vxlan_tx_packets_total" "lab=\"${LAB_NAME}\",vni=\"${VNI}\"" "$tx_packets"
metric_label "vxlan_rx_bytes_total" "lab=\"${LAB_NAME}\",vni=\"${VNI}\"" "$rx_bytes"
metric_label "vxlan_tx_bytes_total" "lab=\"${LAB_NAME}\",vni=\"${VNI}\"" "$tx_bytes"
fi
done
echo ""
echo "# HELP dhcp_leases_active Number of active DHCP leases"
echo "# TYPE dhcp_leases_active gauge"
# DHCP lease count
if [ -f "/tmp/dhcp.leases" ]; then
lease_count=$(wc -l < /tmp/dhcp.leases)
metric_label "dhcp_leases_active" "lab=\"${LAB_NAME}\"" "$lease_count"
fi
echo ""
echo "# HELP openwrt_system_uptime_seconds System uptime in seconds"
echo "# TYPE openwrt_system_uptime_seconds counter"
uptime_seconds=$(cat /proc/uptime | cut -d' ' -f1 | cut -d. -f1)
metric "openwrt_system_uptime_seconds" "$uptime_seconds"
echo ""
echo "# HELP openwrt_memory_free_bytes Free memory in bytes"
echo "# TYPE openwrt_memory_free_bytes gauge"
mem_free=$(free | grep Mem | awk '{print $4}')
metric "openwrt_memory_free_bytes" "$((mem_free * 1024))"

View File

@@ -0,0 +1,62 @@
---
- name: Set VXLAN interface name
set_fact:
vxlan_if: "vxlan{{ gateway_config.vni }}"
wg_if: "wg_{{ gateway_config.lab_name }}"
- name: Remove existing VXLAN interface
shell: |
uci delete network.{{ vxlan_if }} 2>/dev/null || true
uci commit network
changed_when: true
- name: Create VXLAN interface using ip command (OpenWrt native VXLAN)
shell: |
# Remove if exists
ip link delete {{ vxlan_if }} 2>/dev/null || true
# Create VXLAN interface
ip link add {{ vxlan_if }} type vxlan \
id {{ gateway_config.vni }} \
dev {{ wg_if }} \
dstport 4789 \
nolearning \
proxy
# Bring up interface
ip link set {{ vxlan_if }} up
changed_when: true
- name: Add static FDB entries for peers
shell: |
# Clear existing FDB entries for this interface
bridge fdb show dev {{ vxlan_if }} | grep 00:00:00:00:00:00 | while read line; do
bridge fdb del $(echo $line | awk '{print $1}') dev {{ vxlan_if }} 2>/dev/null || true
done
# Add FDB entries for all peers
bridge fdb append 00:00:00:00:00:00 dev {{ vxlan_if }} dst {{ peer.wg_ip }}
loop: "{{ gateway_config.peers }}"
loop_control:
loop_var: peer
label: "{{ peer.to }}"
changed_when: true
- name: Create VXLAN startup script
template:
src: vxlan-startup.sh.j2
dest: /etc/init.d/vxlan-{{ gateway_config.lab_name }}
mode: '0755'
- name: Enable VXLAN startup script
shell: /etc/init.d/vxlan-{{ gateway_config.lab_name }} enable
changed_when: true
- name: Verify VXLAN interface
shell: ip -d link show {{ vxlan_if }}
register: vxlan_status
changed_when: false
- name: Display VXLAN status
debug:
var: vxlan_status.stdout_lines

View File

@@ -0,0 +1,56 @@
#!/bin/sh /etc/rc.common
# VXLAN startup script for {{ gateway_config.lab_name }}
START=99
STOP=10
USE_PROCD=1
LAB_NAME="{{ gateway_config.lab_name }}"
VNI="{{ gateway_config.vni }}"
WG_IF="wg_${LAB_NAME}"
VXLAN_IF="vxlan${VNI}"
start_service() {
# Wait for WireGuard interface
timeout=30
while [ $timeout -gt 0 ]; do
if ip link show ${WG_IF} >/dev/null 2>&1; then
break
fi
sleep 1
timeout=$((timeout-1))
done
# Remove if exists
ip link delete ${VXLAN_IF} 2>/dev/null || true
# Create VXLAN interface
ip link add ${VXLAN_IF} type vxlan \
id ${VNI} \
dev ${WG_IF} \
dstport 4789 \
nolearning \
proxy
# Add FDB entries
{% for peer in gateway_config.peers %}
bridge fdb append 00:00:00:00:00:00 dev ${VXLAN_IF} dst {{ peer.wg_ip }}
{% endfor %}
# Bring up interface
ip link set ${VXLAN_IF} up
logger -t vxlan-${LAB_NAME} "VXLAN ${VXLAN_IF} started"
}
stop_service() {
ip link delete ${VXLAN_IF} 2>/dev/null || true
logger -t vxlan-${LAB_NAME} "VXLAN ${VXLAN_IF} stopped"
}
restart() {
stop_service
sleep 2
start_service
}

View File

@@ -0,0 +1,86 @@
---
- name: Set WireGuard interface name
set_fact:
wg_if: "wg_{{ gateway_config.lab_name }}"
wg_port: "{{ gateway_config.wg_listen_port }}"
- name: Remove existing WireGuard interface configuration
shell: |
uci delete network.{{ wg_if }} 2>/dev/null || true
uci commit network
changed_when: true
- name: Create WireGuard interface
shell: |
uci set network.{{ wg_if }}=interface
uci set network.{{ wg_if }}.proto='wireguard'
uci set network.{{ wg_if }}.private_key='{{ gateway_config.wg_private_key }}'
uci set network.{{ wg_if }}.listen_port='{{ wg_port }}'
uci add_list network.{{ wg_if }}.addresses='{{ gateway_config.wg_ip }}/{{ gateway_config.wg_net | ipaddr("prefix") }}'
uci commit network
changed_when: true
- name: Remove existing WireGuard peers
shell: |
for peer in $(uci show network | grep "={{ wg_if }}_peer" | cut -d. -f2 | cut -d= -f1); do
uci delete network.$peer
done
uci commit network
changed_when: true
ignore_errors: yes
- name: Configure WireGuard gateway peers
shell: |
peer_name="{{ wg_if }}_peer{{ idx }}"
uci set network.$peer_name=wireguard_{{ wg_if }}
uci set network.$peer_name.public_key='{{ peer.public_key }}'
uci set network.$peer_name.endpoint_host='{{ peer.endpoint }}'
uci set network.$peer_name.endpoint_port='{{ wg_port }}'
uci set network.$peer_name.persistent_keepalive='25'
uci add_list network.$peer_name.allowed_ips='{{ peer.wg_ip }}/32'
uci set network.$peer_name.route_allowed_ips='0'
uci commit network
loop: "{{ gateway_config.peers }}"
loop_control:
index_var: idx
loop_var: peer
label: "{{ peer.to }}"
changed_when: true
- name: Configure WireGuard road warrior peers
shell: |
peer_name="{{ wg_if }}_rw{{ idx }}"
uci set network.$peer_name=wireguard_{{ wg_if }}
uci set network.$peer_name.public_key='{{ client.public_key }}'
uci add_list network.$peer_name.allowed_ips='{{ client.wg_ip }}/32'
uci set network.$peer_name.route_allowed_ips='0'
uci set network.$peer_name.description='{{ client.email }}'
uci commit network
loop: "{{ gateway_config.rw_clients }}"
loop_control:
index_var: idx
loop_var: client
label: "{{ client.email }}"
when: gateway_config.road_warrior and gateway_config.rw_clients | length > 0
changed_when: true
- name: Reload network configuration
shell: /etc/init.d/network reload
changed_when: true
- name: Wait for WireGuard interface to come up
shell: ip link show {{ wg_if }}
register: wg_link_check
retries: 10
delay: 2
until: wg_link_check.rc == 0
changed_when: false
- name: Verify WireGuard is running
shell: wg show {{ wg_if }}
register: wg_status
changed_when: false
- name: Display WireGuard status
debug:
var: wg_status.stdout_lines

View File

@@ -0,0 +1,4 @@
---
- name: reload systemd
systemd:
daemon_reload: yes

View File

@@ -0,0 +1,19 @@
---
- name: Create VXLAN interface setup script
template:
src: setup-vxlan.sh.j2
dest: /usr/local/bin/setup-vxlan-{{ gateway_config.lab_name }}.sh
mode: '0755'
- name: Create systemd service for VXLAN
template:
src: vxlan.service.j2
dest: /etc/systemd/system/vxlan-{{ gateway_config.lab_name }}.service
notify: reload systemd
- name: Enable and start VXLAN service
systemd:
name: vxlan-{{ gateway_config.lab_name }}
enabled: yes
state: started
daemon_reload: yes

View File

@@ -0,0 +1,35 @@
#!/bin/bash
set -e
LAB_NAME="{{ gateway_config.lab_name }}"
VNI="{{ gateway_config.vni }}"
WG_IF="wg-${LAB_NAME}"
VXLAN_IF="vxlan${VNI}"
# Wait for WireGuard interface
timeout=30
while [ $timeout -gt 0 ]; do
if ip link show ${WG_IF} &>/dev/null; then
break
fi
sleep 1
((timeout--))
done
# Create VXLAN interface
if ! ip link show ${VXLAN_IF} &>/dev/null; then
ip link add ${VXLAN_IF} type vxlan \
id ${VNI} \
dev ${WG_IF} \
dstport {{ vxlan_port }} \
nolearning \
proxy
fi
# Configure VXLAN peers (FDB entries)
{% for peer in gateway_config.peers %}
bridge fdb append 00:00:00:00:00:00 dev ${VXLAN_IF} dst {{ peer.wg_ip }}
{% endfor %}
# Bring up interface
ip link set ${VXLAN_IF} up

View File

@@ -0,0 +1,13 @@
[Unit]
Description=VXLAN interface for {{ gateway_config.lab_name }}
After=wg-quick@wg-{{ gateway_config.lab_name }}.service
Requires=wg-quick@wg-{{ gateway_config.lab_name }}.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/setup-vxlan-{{ gateway_config.lab_name }}.sh
ExecStop=/sbin/ip link delete vxlan{{ gateway_config.vni }}
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,5 @@
---
- name: restart wireguard
systemd:
name: "wg-quick@wg-{{ gateway_config.lab_name }}"
state: restarted

View File

@@ -0,0 +1,19 @@
---
- name: Create WireGuard config directory
file:
path: "{{ wireguard_config_dir }}"
state: directory
mode: '0700'
- name: Generate WireGuard configuration
template:
src: wg-interface.conf.j2
dest: "{{ wireguard_config_dir }}/wg-{{ gateway_config.lab_name }}.conf"
mode: '0600'
notify: restart wireguard
- name: Enable WireGuard interface
systemd:
name: "wg-quick@wg-{{ gateway_config.lab_name }}"
enabled: yes
state: started

View File

@@ -0,0 +1,31 @@
[Interface]
Address = {{ gateway_config.wg_ip }}/{{ gateway_config.wg_net | ipaddr('prefix') }}
ListenPort = {{ gateway_config.wg_listen_port }}
PrivateKey = {{ gateway_config.wg_private_key }}
# Disable default route
Table = off
# Post-up: Add route for WireGuard mesh network only
PostUp = ip route add {{ gateway_config.wg_net }} dev wg-{{ gateway_config.lab_name }}
{% for peer in gateway_config.peers %}
[Peer]
# {{ peer.to }}
PublicKey = {{ peer.public_key }}
Endpoint = {{ peer.endpoint }}:{{ gateway_config.wg_listen_port }}
AllowedIPs = {{ peer.wg_ip }}/32
PersistentKeepalive = {{ wireguard_persistent_keepalive }}
{% endfor %}
{% if gateway_config.road_warrior and gateway_config.rw_clients | length > 0 %}
# Road Warrior Clients
{% for client in gateway_config.rw_clients %}
[Peer]
# {{ client.email }}
PublicKey = {{ client.public_key }}
AllowedIPs = {{ client.wg_ip }}/32
{% endfor %}
{% endif %}

View File

@@ -0,0 +1,29 @@
---
- name: Check WireGuard status (Linux)
shell: "wg show wg-{{ gateway_config.lab_name }}"
register: wg_status_linux
changed_when: false
failed_when: false
when: gateway_config.type == 'linux'
- name: Check VXLAN interface (Linux)
shell: "ip link show vxlan{{ gateway_config.vni }}"
register: vxlan_status
changed_when: false
failed_when: false
when: gateway_config.type == 'linux'
- name: Check bridge (Linux)
shell: "ip link show br-{{ gateway_config.lab_name }}"
register: bridge_status
changed_when: false
failed_when: false
when: gateway_config.type == 'linux'
- name: Set health status
set_fact:
health_check_results:
wireguard: "{{ 'PASS' if wg_status_linux.rc == 0 else 'FAIL' }}"
vxlan: "{{ 'PASS' if vxlan_status.rc == 0 else 'FAIL' }}"
bridge: "{{ 'PASS' if bridge_status.rc == 0 else 'FAIL' }}"
when: gateway_config.type == 'linux'

View File

@@ -0,0 +1,14 @@
---
- name: Validate gateway configuration
debug:
msg: "Validating {{ inventory_hostname }}"
- name: Check WireGuard peers connectivity
shell: "ping -c 3 -W 2 {{ peer.wg_ip }}"
loop: "{{ gateway_config.peers }}"
loop_control:
loop_var: peer
register: ping_results
failed_when: false
changed_when: false
when: gateway_config.type == 'linux'