commit 0cf33cd95b500f5cfcf171857da67f4b7493030d Author: Lutz Finsterle Date: Sat Feb 21 16:54:18 2026 +0100 Initial commit diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..77eabe7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "ansible.python.interpreterPath": "/usr/bin/python" +} \ No newline at end of file diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 0000000..19ad765 --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,269 @@ +# Getting Started with WireGuard Lab Overlay + +## Prerequisites + +### On Management Server + +```bash +# Ubuntu/Debian +sudo apt-get update +sudo apt-get install -y \ + terraform \ + ansible \ + python3-pip \ + git \ + docker.io \ + docker-compose + +# Install Ansible collections +ansible-galaxy collection install community.routeros +ansible-galaxy collection install ansible.netcommon + +# Install Python packages +pip3 install librouteros prometheus-client +``` + +### On Linux Gateways + +```bash +# Ensure WireGuard is available +sudo apt-get install -y wireguard wireguard-tools bridge-utils python3 +``` + +### On MikroTik Routers + +- RouterOS 7.6 or higher +- API access enabled +- Admin credentials + +## Initial Setup + +### Step 1: Configure Your Lab Networks + +1. Copy the example configuration: +```bash +cd terraform +cp terraform.tfvars.example terraform.tfvars +``` + +2. Edit `terraform.tfvars` with your lab networks: +```hcl +lab_networks = { + lab100 = { + vni = 100 + subnet = "10.100.0.0/24" + wireguard_net = "172.16.100.0/24" + dhcp_mode = "simple" # or "failover" + road_warrior = false # Enable later + gateways = [ + { + hostname = "lab100-gw1" + type = "linux" + mgmt_ip = "192.168.1.11" # Change to your IPs + api_port = 22 + lab_if = "eth1" # Interface facing lab devices + dhcp_role = "primary" + }, + { + hostname = "lab100-gw2" + type = "linux" + mgmt_ip = "192.168.1.12" + api_port = 22 + lab_if = "eth1" + dhcp_role = "secondary" + } + ] + } +} +``` + +### Step 2: Set Up Ansible + +1. Configure SSH access to your gateways: +```bash +ssh-copy-id admin@192.168.1.11 +ssh-copy-id admin@192.168.1.12 +``` + +2. For MikroTik devices, create vault password: +```bash +cd ansible +echo "your-vault-password" > .vault_pass +chmod 600 .vault_pass +``` + +3. Encrypt MikroTik credentials: +```bash +ansible-vault create inventory/group_vars/mikrotik_gateways.yml +``` + +Add: +```yaml +ansible_password: your-mikrotik-password +``` + +### Step 3: Deploy + +#### Option A: Automated (Recommended) + +```bash +./deploy.sh +``` + +#### Option B: Manual Steps + +```bash +# 1. Initialize and apply Terraform +cd terraform +terraform init +terraform apply +terraform output -json > outputs.json + +# 2. Generate Ansible inventory +terraform output ansible_inventory > ../ansible/inventory/hosts.yml + +# 3. Deploy to gateways +cd ../ansible +ansible-playbook -i inventory/hosts.yml playbooks/site.yml + +# 4. Start monitoring +cd ../monitoring +docker-compose up -d +``` + +### Step 4: Verify Deployment + +```bash +# Check gateway health +cd ansible +ansible-playbook playbooks/health-check.yml + +# Check WireGuard tunnels +ansible linux_gateways -m shell -a "wg show" + +# Check bridges +ansible linux_gateways -m shell -a "ip link show type bridge" +``` + +## Common Tasks + +### Add Road Warrior Access + +1. Edit `terraform/terraform.tfvars`: +```hcl +lab_networks = { + lab100 = { + road_warrior = true # Enable this + # ... + } +} + +road_warrior_users = { + "jdoe" = { + email = "john.doe@company.com" + labs = ["lab100"] + } +} +``` + +2. Apply changes: +```bash +cd terraform +terraform apply +cd ../ansible +ansible-playbook playbooks/generate-road-warrior.yml +``` + +3. Distribute configs: +```bash +ls ../road-warrior/generated/jdoe/ +# lab100.conf - WireGuard config file +# lab100-qr.html - QR code for mobile devices +``` + +### Add New Lab Network + +```bash +ansible-playbook playbooks/add-lab-network.yml +``` + +Follow the prompts to configure the new lab. + +### Emergency Shutdown + +```bash +ansible-playbook playbooks/emergency-shutdown.yml +``` + +### Restore Lab + +```bash +ansible-playbook playbooks/restore-lab.yml +``` + +## Monitoring + +Access the monitoring stack: + +- **Grafana**: http://your-server:3000 + - Username: `admin` + - Password: `admin` + +- **Prometheus**: http://your-server:9090 + +## Troubleshooting + +### WireGuard tunnel not coming up + +```bash +# On gateway: +sudo wg show +sudo journalctl -u wg-quick@wg-lab100 -n 50 + +# Check connectivity +ping +``` + +### DHCP not working + +```bash +# Check DHCP server +sudo systemctl status dnsmasq +# or for failover mode: +sudo systemctl status isc-dhcp-server + +# Check leases +sudo tail -f /var/lib/misc/dnsmasq.lab100.leases +``` + +### Can't reach lab devices + +1. Check bridge is up: +```bash +ip link show br-lab100 +``` + +2. Check VXLAN interface: +```bash +ip -d link show vxlan100 +``` + +3. Check FDB entries: +```bash +bridge fdb show br br-lab100 +``` + +## Next Steps + +- Read `docs/architecture.md` for detailed architecture +- Read `docs/operations.md` for operational procedures +- Set up alerting in Grafana +- Configure backup for Terraform state +- Implement proper secret management (HashiCorp Vault) + +## Support + +For issues: +1. Check logs: `journalctl -u wg-quick@wg-lab*` +2. Run health check: `ansible-playbook playbooks/health-check.yml` +3. Review documentation in `docs/` diff --git a/OPENWRT_CHANGELOG.md b/OPENWRT_CHANGELOG.md new file mode 100644 index 0000000..b7a49dd --- /dev/null +++ b/OPENWRT_CHANGELOG.md @@ -0,0 +1,153 @@ +# OpenWrt Integration - Change Log + +## What's New + +### OpenWrt Gateway Support + +This version adds complete support for OpenWrt routers as lab network gateways, making them fully interoperable with Linux VMs and MikroTik routers. + +### New Ansible Roles + +1. **openwrt-common** - System preparation and package installation +2. **openwrt-wireguard** - UCI-based WireGuard configuration +3. **openwrt-vxlan** - VXLAN interface setup with FDB management +4. **openwrt-bridge** - Bridge configuration via UCI +5. **openwrt-dhcp** - dnsmasq DHCP server configuration +6. **openwrt-firewall** - Automatic firewall zone creation and isolation +7. **openwrt-monitoring** - Lightweight shell-based Prometheus exporter + +### Key Features + +- **UCI Configuration**: All settings managed via OpenWrt's UCI system +- **Auto Package Installation**: Automatic installation of required packages: + - wireguard-tools + - kmod-wireguard + - kmod-vxlan + - ip-full + - tcpdump +- **Firewall Isolation**: Each lab network gets isolated firewall zone +- **Metrics Export**: Lightweight shell script exporter on port 9586 +- **Full Mesh Support**: OpenWrt gateways mesh with Linux and MikroTik +- **DHCP Modes**: Support for both simple (split range) and failover simulation + +### Architecture Support + +- **x86_64**: PC Engines APU, Protectli Vault, generic x86 +- **ARM**: GL.iNet, Turris Omnia, Raspberry Pi +- **MIPS**: Various TP-Link, Ubiquiti models (device-dependent) + +### Updated Files + +**Terraform:** +- `terraform/outputs.tf` - Added openwrt_gateways inventory group +- `terraform/terraform.tfvars.example` - Added OpenWrt gateway examples + +**Ansible:** +- `ansible/playbooks/site.yml` - Added OpenWrt configuration block +- `ansible/inventory/hosts.yml` - Added openwrt_gateways group +- `ansible/inventory/group_vars/openwrt_gateways.yml` - OpenWrt-specific vars + +**Documentation:** +- `docs/openwrt-setup.md` - Complete OpenWrt setup guide +- `docs/gateway-comparison.md` - Comparison of all three gateway types +- `README.md` - Updated with OpenWrt references + +### Compatibility + +- **OpenWrt Version**: 23.05 or later (stable releases) +- **Minimum Hardware**: 128MB RAM, 128MB flash (256MB+ recommended) +- **Tested Platforms**: x86_64, ARM (Cortex-A series) +- **Interoperability**: Fully compatible with Linux and MikroTik gateways + +### Usage Example + +```hcl +# terraform/terraform.tfvars +lab_networks = { + lab100 = { + vni = 100 + subnet = "10.100.0.0/24" + wireguard_net = "172.16.100.0/24" + dhcp_mode = "simple" + road_warrior = false + gateways = [ + { + hostname = "lab100-linux1" + type = "linux" + mgmt_ip = "192.168.1.11" + api_port = 22 + lab_if = "eth1" + dhcp_role = "primary" + }, + { + hostname = "lab100-openwrt1" + type = "openwrt" # ← OpenWrt gateway + mgmt_ip = "192.168.1.14" + api_port = 22 + lab_if = "eth1" # Adjust to your OpenWrt interface + dhcp_role = "secondary" + } + ] + } +} +``` + +Deploy: +```bash +./deploy.sh +``` + +### Testing Checklist + +After deploying an OpenWrt gateway: + +- [ ] WireGuard tunnel established: `wg show` +- [ ] VXLAN interface up: `ip -d link show vxlan100` +- [ ] Bridge operational: `bridge link show` +- [ ] DHCP serving: `cat /tmp/dhcp.leases` +- [ ] Firewall zone created: `uci show firewall | grep lab100` +- [ ] Metrics exported: `curl http://:9586/metrics/` +- [ ] Can ping peer WireGuard IPs +- [ ] Lab devices getting DHCP addresses + +### Known Limitations + +1. **DHCP Failover**: OpenWrt uses simulated failover (split ranges), not true ISC DHCP failover protocol +2. **Resource Constraints**: Performance varies significantly based on hardware +3. **Package Availability**: Some older/exotic architectures may lack VXLAN support +4. **Web UI**: No LuCI integration (CLI/Ansible only) + +### Migration Notes + +If upgrading from a version without OpenWrt support: + +1. Update Terraform: `terraform init -upgrade` +2. Run Terraform plan to see new inventory structure +3. No changes needed for existing Linux/MikroTik gateways +4. Add OpenWrt gateways incrementally + +### Performance Expectations + +| Hardware Class | WireGuard Throughput | Concurrent Devices | +|----------------|---------------------|-------------------| +| x86_64 (modern) | 500 Mbps - 2 Gbps | 50-100+ | +| ARM (Cortex-A) | 100-300 Mbps | 20-50 | +| MIPS (consumer) | 50-150 Mbps | 10-20 | + +### Support + +See: +- `docs/openwrt-setup.md` for detailed setup instructions +- `docs/gateway-comparison.md` for choosing gateway types +- `docs/operations.md` for troubleshooting + +## Breaking Changes + +None - fully backward compatible with existing deployments. + +## Next Features (Future) + +- LuCI web UI integration (optional) +- Support for OpenWrt snapshot builds +- WiFi AP mode integration for wireless lab devices +- Advanced QoS policies for lab networks diff --git a/README.md b/README.md new file mode 100644 index 0000000..aceaad7 --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +# WireGuard Lab Overlay Network + +Automated deployment of WireGuard-based overlay networks for lab environments with support for Linux and MikroTik gateways. + +## Features + +- **Multi-Vendor Support**: Linux VMs, MikroTik routers, and OpenWrt devices +- **Layer 2 Overlay**: VXLAN over WireGuard for L2 connectivity +- **DHCP Options**: ISC DHCP with failover OR dnsmasq with split ranges +- **Road Warrior Access**: Built-in support for mobile/laptop clients +- **Monitoring**: Prometheus exporters and Grafana dashboards +- **Firewall Isolation**: Automatic firewall zones for OpenWrt gateways +- **Full Automation**: Terraform + Ansible deployment + +## Architecture + +- **WireGuard**: Encrypted mesh tunnels between gateways +- **VXLAN**: Layer 2 extension over WireGuard +- **Linux Bridge/MikroTik Bridge**: Local device attachment +- **DHCP**: Redundant DHCP servers per lab network +- **Monitoring Hub**: Centralized Prometheus + Grafana + +## Quick Start + +### 1. Prerequisites + +```bash +# Install required tools +apt-get install terraform ansible python3-pip + +# Install Ansible collections +ansible-galaxy collection install community.routeros +ansible-galaxy collection install ansible.netcommon + +# Install Python dependencies +pip3 install librouteros prometheus-client +``` + +### 2. Configure Lab Networks + +Edit `terraform/terraform.tfvars`: + +```hcl +lab_networks = { + lab100 = { + vni = 100 + subnet = "10.100.0.0/24" + wireguard_net = "172.16.100.0/24" + dhcp_mode = "failover" + road_warrior = true + gateways = [ + # Define your gateways here + ] + } +} +``` + +### 3. Deploy + +```bash +# Run deployment script +./deploy.sh +``` + +Or manual steps: + +```bash +# Initialize Terraform +cd terraform +terraform init +terraform apply +terraform output -json > outputs.json + +# Deploy to gateways +cd ../ansible +ansible-playbook -i inventory/hosts.yml playbooks/site.yml + +# Generate road warrior configs +ansible-playbook -i inventory/hosts.yml playbooks/generate-road-warrior.yml + +# Start monitoring +cd ../monitoring +docker-compose up -d +``` + +## Operational Playbooks + +### Rolling Update +```bash +ansible-playbook playbooks/rolling-update.yml +``` + +### Health Check +```bash +ansible-playbook playbooks/health-check.yml +``` + +### Emergency Shutdown +```bash +ansible-playbook playbooks/emergency-shutdown.yml +``` + +### Restore Lab +```bash +ansible-playbook playbooks/restore-lab.yml +``` + +### Add Gateway +```bash +ansible-playbook playbooks/add-gateway.yml +``` + +### Remove Gateway +```bash +ansible-playbook playbooks/remove-gateway.yml +``` + +## Directory Structure + +``` +wireguard-lab-overlay/ +├── terraform/ # Infrastructure state +├── ansible/ # Configuration management +│ ├── roles/ # Ansible roles (Linux & MikroTik) +│ ├── playbooks/ # Operational playbooks +│ └── inventory/ # Inventory and variables +├── monitoring/ # Monitoring stack +│ ├── prometheus/ +│ ├── grafana/ +│ └── docker-compose.yml +├── road-warrior/ # Client configurations +└── docs/ # Documentation +``` + +## Monitoring + +Access monitoring: +- Grafana: http://monitoring-hub:3000 (admin/admin) +- Prometheus: http://monitoring-hub:9090 + +## Security Notes + +- WireGuard private keys are stored in Terraform state (use remote backend with encryption) +- Consider using HashiCorp Vault or AWS Secrets Manager for production +- Ansible Vault is used for MikroTik credentials +- Road warrior configs should be distributed securely + +## Support + +For issues and questions, refer to: +- `docs/architecture.md` - Detailed architecture +- `docs/deployment.md` - Deployment guide +- `docs/operations.md` - Operations manual +- `docs/mikrotik-setup.md` - MikroTik specific guide +- `docs/openwrt-setup.md` - OpenWrt specific guide +- `docs/gateway-comparison.md` - Gateway type comparison + +## License + +Internal use only - Company proprietary diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..e5383b9 --- /dev/null +++ b/ansible/ansible.cfg @@ -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 diff --git a/ansible/inventory/group_vars/all.yml b/ansible/inventory/group_vars/all.yml new file mode 100644 index 0000000..8b513a2 --- /dev/null +++ b/ansible/inventory/group_vars/all.yml @@ -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 diff --git a/ansible/inventory/group_vars/linux_gateways.yml b/ansible/inventory/group_vars/linux_gateways.yml new file mode 100644 index 0000000..519eafa --- /dev/null +++ b/ansible/inventory/group_vars/linux_gateways.yml @@ -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 diff --git a/ansible/inventory/group_vars/openwrt_gateways.yml b/ansible/inventory/group_vars/openwrt_gateways.yml new file mode 100644 index 0000000..a94eed5 --- /dev/null +++ b/ansible/inventory/group_vars/openwrt_gateways.yml @@ -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 diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml new file mode 100644 index 0000000..2c8e902 --- /dev/null +++ b/ansible/inventory/hosts.yml @@ -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 diff --git a/ansible/playbooks/add-gateway.yml b/ansible/playbooks/add-gateway.yml new file mode 100644 index 0000000..88a9bf9 --- /dev/null +++ b/ansible/playbooks/add-gateway.yml @@ -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' diff --git a/ansible/playbooks/emergency-shutdown.yml b/ansible/playbooks/emergency-shutdown.yml new file mode 100644 index 0000000..e8c1b03 --- /dev/null +++ b/ansible/playbooks/emergency-shutdown.yml @@ -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 diff --git a/ansible/playbooks/generate-road-warrior.yml b/ansible/playbooks/generate-road-warrior.yml new file mode 100644 index 0000000..9972cf3 --- /dev/null +++ b/ansible/playbooks/generate-road-warrior.yml @@ -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 }}" diff --git a/ansible/playbooks/health-check.yml b/ansible/playbooks/health-check.yml new file mode 100644 index 0000000..f3043db --- /dev/null +++ b/ansible/playbooks/health-check.yml @@ -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 diff --git a/ansible/playbooks/remove-gateway.yml b/ansible/playbooks/remove-gateway.yml new file mode 100644 index 0000000..46ed58f --- /dev/null +++ b/ansible/playbooks/remove-gateway.yml @@ -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 diff --git a/ansible/playbooks/restore-lab.yml b/ansible/playbooks/restore-lab.yml new file mode 100644 index 0000000..b60752c --- /dev/null +++ b/ansible/playbooks/restore-lab.yml @@ -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" diff --git a/ansible/playbooks/rolling-update.yml b/ansible/playbooks/rolling-update.yml new file mode 100644 index 0000000..fa4158b --- /dev/null +++ b/ansible/playbooks/rolling-update.yml @@ -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 diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml new file mode 100644 index 0000000..71250d2 --- /dev/null +++ b/ansible/playbooks/site.yml @@ -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 diff --git a/ansible/roles/bridge/tasks/main.yml b/ansible/roles/bridge/tasks/main.yml new file mode 100644 index 0000000..4f8c736 --- /dev/null +++ b/ansible/roles/bridge/tasks/main.yml @@ -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 diff --git a/ansible/roles/bridge/templates/bridge.service.j2 b/ansible/roles/bridge/templates/bridge.service.j2 new file mode 100644 index 0000000..b2bce19 --- /dev/null +++ b/ansible/roles/bridge/templates/bridge.service.j2 @@ -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 diff --git a/ansible/roles/bridge/templates/setup-bridge.sh.j2 b/ansible/roles/bridge/templates/setup-bridge.sh.j2 new file mode 100644 index 0000000..233ad98 --- /dev/null +++ b/ansible/roles/bridge/templates/setup-bridge.sh.j2 @@ -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 diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..08f55ae --- /dev/null +++ b/ansible/roles/common/tasks/main.yml @@ -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 diff --git a/ansible/roles/dnsmasq/handlers/main.yml b/ansible/roles/dnsmasq/handlers/main.yml new file mode 100644 index 0000000..ed6ad87 --- /dev/null +++ b/ansible/roles/dnsmasq/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart dnsmasq + systemd: + name: dnsmasq + state: restarted diff --git a/ansible/roles/dnsmasq/tasks/main.yml b/ansible/roles/dnsmasq/tasks/main.yml new file mode 100644 index 0000000..b2980f3 --- /dev/null +++ b/ansible/roles/dnsmasq/tasks/main.yml @@ -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 diff --git a/ansible/roles/dnsmasq/templates/dnsmasq.conf.j2 b/ansible/roles/dnsmasq/templates/dnsmasq.conf.j2 new file mode 100644 index 0000000..c528ea8 --- /dev/null +++ b/ansible/roles/dnsmasq/templates/dnsmasq.conf.j2 @@ -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 diff --git a/ansible/roles/isc-dhcp/handlers/main.yml b/ansible/roles/isc-dhcp/handlers/main.yml new file mode 100644 index 0000000..af3a0aa --- /dev/null +++ b/ansible/roles/isc-dhcp/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart isc-dhcp + systemd: + name: isc-dhcp-server + state: restarted diff --git a/ansible/roles/isc-dhcp/tasks/main.yml b/ansible/roles/isc-dhcp/tasks/main.yml new file mode 100644 index 0000000..751f6b0 --- /dev/null +++ b/ansible/roles/isc-dhcp/tasks/main.yml @@ -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 diff --git a/ansible/roles/isc-dhcp/templates/dhcpd.conf.j2 b/ansible/roles/isc-dhcp/templates/dhcpd.conf.j2 new file mode 100644 index 0000000..982ae87 --- /dev/null +++ b/ansible/roles/isc-dhcp/templates/dhcpd.conf.j2 @@ -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; diff --git a/ansible/roles/isc-dhcp/templates/isc-dhcp-server.j2 b/ansible/roles/isc-dhcp/templates/isc-dhcp-server.j2 new file mode 100644 index 0000000..52ebfaa --- /dev/null +++ b/ansible/roles/isc-dhcp/templates/isc-dhcp-server.j2 @@ -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 }}" diff --git a/ansible/roles/mikrotik-bridge/tasks/main.yml b/ansible/roles/mikrotik-bridge/tasks/main.yml new file mode 100644 index 0000000..192c72a --- /dev/null +++ b/ansible/roles/mikrotik-bridge/tasks/main.yml @@ -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 }}" diff --git a/ansible/roles/mikrotik-common/tasks/main.yml b/ansible/roles/mikrotik-common/tasks/main.yml new file mode 100644 index 0000000..95e6648 --- /dev/null +++ b/ansible/roles/mikrotik-common/tasks/main.yml @@ -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 diff --git a/ansible/roles/mikrotik-dhcp/tasks/main.yml b/ansible/roles/mikrotik-dhcp/tasks/main.yml new file mode 100644 index 0000000..0a47d45 --- /dev/null +++ b/ansible/roles/mikrotik-dhcp/tasks/main.yml @@ -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 diff --git a/ansible/roles/mikrotik-monitoring/tasks/main.yml b/ansible/roles/mikrotik-monitoring/tasks/main.yml new file mode 100644 index 0000000..a09b8f0 --- /dev/null +++ b/ansible/roles/mikrotik-monitoring/tasks/main.yml @@ -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 diff --git a/ansible/roles/mikrotik-vxlan/tasks/main.yml b/ansible/roles/mikrotik-vxlan/tasks/main.yml new file mode 100644 index 0000000..c4a0455 --- /dev/null +++ b/ansible/roles/mikrotik-vxlan/tasks/main.yml @@ -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 diff --git a/ansible/roles/mikrotik-wireguard/tasks/main.yml b/ansible/roles/mikrotik-wireguard/tasks/main.yml new file mode 100644 index 0000000..be29d08 --- /dev/null +++ b/ansible/roles/mikrotik-wireguard/tasks/main.yml @@ -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 diff --git a/ansible/roles/monitoring/tasks/main.yml b/ansible/roles/monitoring/tasks/main.yml new file mode 100644 index 0000000..6cb89ab --- /dev/null +++ b/ansible/roles/monitoring/tasks/main.yml @@ -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 diff --git a/ansible/roles/monitoring/templates/exporter-config.json.j2 b/ansible/roles/monitoring/templates/exporter-config.json.j2 new file mode 100644 index 0000000..961d671 --- /dev/null +++ b/ansible/roles/monitoring/templates/exporter-config.json.j2 @@ -0,0 +1,8 @@ +{ + "labs": { + "{{ gateway_config.lab_name }}": { + "vni": {{ gateway_config.vni }}, + "lab_if": "{{ gateway_config.lab_if }}" + } + } +} diff --git a/ansible/roles/monitoring/templates/wg-lab-exporter.service.j2 b/ansible/roles/monitoring/templates/wg-lab-exporter.service.j2 new file mode 100644 index 0000000..09c7ebf --- /dev/null +++ b/ansible/roles/monitoring/templates/wg-lab-exporter.service.j2 @@ -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 diff --git a/ansible/roles/openwrt-bridge/tasks/main.yml b/ansible/roles/openwrt-bridge/tasks/main.yml new file mode 100644 index 0000000..2313dc5 --- /dev/null +++ b/ansible/roles/openwrt-bridge/tasks/main.yml @@ -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 diff --git a/ansible/roles/openwrt-common/tasks/main.yml b/ansible/roles/openwrt-common/tasks/main.yml new file mode 100644 index 0000000..ecebc2f --- /dev/null +++ b/ansible/roles/openwrt-common/tasks/main.yml @@ -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 diff --git a/ansible/roles/openwrt-dhcp/tasks/main.yml b/ansible/roles/openwrt-dhcp/tasks/main.yml new file mode 100644 index 0000000..c92ae26 --- /dev/null +++ b/ansible/roles/openwrt-dhcp/tasks/main.yml @@ -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 diff --git a/ansible/roles/openwrt-firewall/tasks/main.yml b/ansible/roles/openwrt-firewall/tasks/main.yml new file mode 100644 index 0000000..931109d --- /dev/null +++ b/ansible/roles/openwrt-firewall/tasks/main.yml @@ -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 diff --git a/ansible/roles/openwrt-monitoring/tasks/main.yml b/ansible/roles/openwrt-monitoring/tasks/main.yml new file mode 100644 index 0000000..70e749c --- /dev/null +++ b/ansible/roles/openwrt-monitoring/tasks/main.yml @@ -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 diff --git a/ansible/roles/openwrt-monitoring/templates/exporter-config.j2 b/ansible/roles/openwrt-monitoring/templates/exporter-config.j2 new file mode 100644 index 0000000..c9f9fef --- /dev/null +++ b/ansible/roles/openwrt-monitoring/templates/exporter-config.j2 @@ -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 }}" diff --git a/ansible/roles/openwrt-monitoring/templates/metrics-exporter.sh.j2 b/ansible/roles/openwrt-monitoring/templates/metrics-exporter.sh.j2 new file mode 100644 index 0000000..2b12d55 --- /dev/null +++ b/ansible/roles/openwrt-monitoring/templates/metrics-exporter.sh.j2 @@ -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))" diff --git a/ansible/roles/openwrt-vxlan/tasks/main.yml b/ansible/roles/openwrt-vxlan/tasks/main.yml new file mode 100644 index 0000000..412ec36 --- /dev/null +++ b/ansible/roles/openwrt-vxlan/tasks/main.yml @@ -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 diff --git a/ansible/roles/openwrt-vxlan/templates/vxlan-startup.sh.j2 b/ansible/roles/openwrt-vxlan/templates/vxlan-startup.sh.j2 new file mode 100644 index 0000000..45550ba --- /dev/null +++ b/ansible/roles/openwrt-vxlan/templates/vxlan-startup.sh.j2 @@ -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 +} diff --git a/ansible/roles/openwrt-wireguard/tasks/main.yml b/ansible/roles/openwrt-wireguard/tasks/main.yml new file mode 100644 index 0000000..bd71d3d --- /dev/null +++ b/ansible/roles/openwrt-wireguard/tasks/main.yml @@ -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 diff --git a/ansible/roles/vxlan/handlers/main.yml b/ansible/roles/vxlan/handlers/main.yml new file mode 100644 index 0000000..bb7fde2 --- /dev/null +++ b/ansible/roles/vxlan/handlers/main.yml @@ -0,0 +1,4 @@ +--- +- name: reload systemd + systemd: + daemon_reload: yes diff --git a/ansible/roles/vxlan/tasks/main.yml b/ansible/roles/vxlan/tasks/main.yml new file mode 100644 index 0000000..784a628 --- /dev/null +++ b/ansible/roles/vxlan/tasks/main.yml @@ -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 diff --git a/ansible/roles/vxlan/templates/setup-vxlan.sh.j2 b/ansible/roles/vxlan/templates/setup-vxlan.sh.j2 new file mode 100644 index 0000000..4dd7ae1 --- /dev/null +++ b/ansible/roles/vxlan/templates/setup-vxlan.sh.j2 @@ -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 diff --git a/ansible/roles/vxlan/templates/vxlan.service.j2 b/ansible/roles/vxlan/templates/vxlan.service.j2 new file mode 100644 index 0000000..ebaebf1 --- /dev/null +++ b/ansible/roles/vxlan/templates/vxlan.service.j2 @@ -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 diff --git a/ansible/roles/wireguard/handlers/main.yml b/ansible/roles/wireguard/handlers/main.yml new file mode 100644 index 0000000..81da5e8 --- /dev/null +++ b/ansible/roles/wireguard/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart wireguard + systemd: + name: "wg-quick@wg-{{ gateway_config.lab_name }}" + state: restarted diff --git a/ansible/roles/wireguard/tasks/main.yml b/ansible/roles/wireguard/tasks/main.yml new file mode 100644 index 0000000..12d1e54 --- /dev/null +++ b/ansible/roles/wireguard/tasks/main.yml @@ -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 diff --git a/ansible/roles/wireguard/templates/wg-interface.conf.j2 b/ansible/roles/wireguard/templates/wg-interface.conf.j2 new file mode 100644 index 0000000..2f1ce42 --- /dev/null +++ b/ansible/roles/wireguard/templates/wg-interface.conf.j2 @@ -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 %} diff --git a/ansible/tasks/health-check.yml b/ansible/tasks/health-check.yml new file mode 100644 index 0000000..e6a32b4 --- /dev/null +++ b/ansible/tasks/health-check.yml @@ -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' diff --git a/ansible/tasks/validate-gateway.yml b/ansible/tasks/validate-gateway.yml new file mode 100644 index 0000000..6f94bea --- /dev/null +++ b/ansible/tasks/validate-gateway.yml @@ -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' diff --git a/create_all_files.sh b/create_all_files.sh new file mode 100755 index 0000000..a1af736 --- /dev/null +++ b/create_all_files.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Script to create all remaining files + +BASE_DIR="/home/claude/wireguard-lab-overlay" + +# Create all operational playbooks +cat > "$BASE_DIR/ansible/playbooks/site.yml" << 'EOF' +--- +- name: Deploy WireGuard Lab Overlay Network + hosts: lab_gateways + become: 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] }}" + + roles: + - common + - wireguard + - vxlan + - bridge + - dnsmasq + - monitoring + +- name: Configure Monitoring Hub + hosts: monitoring_hub + become: yes + roles: + - monitoring_hub +EOF + +# Continue with more files... diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..12334b3 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "=== WireGuard Lab Overlay Network Deployment ===" + +# Step 1: Terraform +echo "" +echo "[1/5] Running Terraform..." +cd terraform +terraform init +terraform plan -out=tfplan +terraform apply tfplan +terraform output -json > outputs.json +cd .. + +# Step 2: Ansible inventory generation +echo "" +echo "[2/5] Generating Ansible inventory..." +cd ansible +terraform -chdir=../terraform output ansible_inventory > inventory/hosts.yml + +# Step 3: Deploy to gateways +echo "" +echo "[3/5] Deploying to gateways..." +ansible-playbook -i inventory/hosts.yml playbooks/site.yml + +# Step 4: Generate road warrior configs +echo "" +echo "[4/5] Generating road warrior configurations..." +ansible-playbook -i inventory/hosts.yml playbooks/generate-road-warrior.yml + +# Step 5: Deploy monitoring hub +echo "" +echo "[5/5] Deploying monitoring hub..." +cd ../monitoring +docker-compose up -d + +echo "" +echo "=== Deployment Complete ===" +echo "Grafana: http://$(hostname -I | awk '{print $1}'):3000 (admin/admin)" +echo "Prometheus: http://$(hostname -I | awk '{print $1}'):9090" +echo "Road warrior configs: $SCRIPT_DIR/road-warrior/generated/" diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..b85a628 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,43 @@ +# WireGuard Lab Overlay Network Architecture + +## Overview + +This solution provides automated deployment of isolated Layer 2 networks across corporate infrastructure using WireGuard and VXLAN. + +## Components + +### WireGuard Mesh +- Full mesh topology between gateways within each lab network +- Encrypted tunnels over corporate network +- No routing of lab traffic through corporate infrastructure + +### VXLAN Overlay +- Provides Layer 2 connectivity over WireGuard Layer 3 tunnels +- BUM traffic replication to all peers +- Support for up to 10 gateways per lab + +### Gateway Types +- **Linux VMs**: Full-featured with ISC DHCP failover support +- **MikroTik Routers**: Managed via RouterOS API + +### DHCP Modes +- **Failover**: ISC DHCP with active-passive failover (Linux only) +- **Simple**: dnsmasq with split address ranges + +### Monitoring +- Prometheus exporters on each gateway +- Centralized Grafana dashboards +- Metrics: tunnel status, bandwidth, DHCP leases, bridge stats + +## Network Flow + +``` +Lab Device → Linux Bridge/MikroTik Bridge → VXLAN → WireGuard → Corporate Network → WireGuard → VXLAN → Bridge → Lab Device +``` + +## Security + +- All lab traffic encrypted via WireGuard +- Complete isolation from corporate network +- Road warrior clients get direct lab access +- No inter-lab routing diff --git a/docs/design-discussion.md b/docs/design-discussion.md new file mode 100644 index 0000000..31a8390 --- /dev/null +++ b/docs/design-discussion.md @@ -0,0 +1,405 @@ +# Design Discussion and Requirements + +This document captures the key design decisions, requirements, and architectural discussions that shaped the WireGuard Lab Overlay Network solution. + +## Initial Requirements + +### Core Objective +Automated deployment of a WireGuard-based overlay network that connects local lab room components at Layer 2 across the company network, without exposing lab devices to the corporate network. + +### Key Requirements +1. **Layer 2 Connectivity**: Lab devices plugged into gateway routers/switches should communicate at L2 across the company network +2. **Network Isolation**: Lab traffic must NOT have connectivity to company network +3. **Gateway Flexibility**: Support Linux VMs, MikroTik routers, and OpenWrt devices +4. **Scalability**: Up to 10 gateways per lab network, 10-20 independent lab networks +5. **Automation**: Ansible or Terraform for automated deployment +6. **Ease of Deployment**: Solution should be easily deployable +7. **Future Requirements**: + - Microsegmentation capability + - Road warrior access for office notebooks + - Central management + - Central monitoring/visibility + +### Lab Network Specifications +- **Max gateways per lab**: 10 +- **Total lab networks**: 10-20 independent networks +- **DHCP**: Lab devices use DHCP, each lab has own IP range +- **Gateway types**: Linux VMs (primary), MikroTik routers, OpenWrt devices +- **Multiple NICs**: Gateways have multiple network interfaces +- **No VM provisioning**: Configure existing VMs/routers only + +## Architecture Decisions + +### 1. WireGuard + VXLAN Approach + +**Decision**: Use VXLAN over WireGuard instead of direct WireGuard bridging. + +**Rationale**: +- WireGuard is L3-only natively +- VXLAN provides true L2 extension with broadcast/multicast support +- Clean separation of concerns: + - WireGuard = Encrypted L3 transport mesh + - VXLAN = L2 overlay with BUM traffic handling + - Bridge = Local device attachment + +**Alternative Considered**: Direct bridging of WireGuard interfaces +- **Rejected because**: More complex, less clean architecture, harder to troubleshoot + +### 2. Full Mesh Topology + +**Decision**: All gateways within a lab network form a full WireGuard mesh. + +**Rationale**: +- No single point of failure +- Optimal path between any two gateways +- Scales well up to 10 gateways (45 tunnels max) +- Simpler than hub-and-spoke for small deployments + +**Calculation**: n gateways = n×(n-1)/2 tunnels +- 3 gateways = 3 tunnels +- 5 gateways = 10 tunnels +- 10 gateways = 45 tunnels (acceptable) + +**Alternative Considered**: Hub-and-spoke with central gateway +- **Rejected because**: Single point of failure, suboptimal routing, more complex failover + +### 3. Separate WireGuard Interface per Lab + +**Decision**: Each lab network gets its own WireGuard interface (wg-lab100, wg-lab200, etc.) + +**Rationale**: +- Complete isolation between lab networks +- Easier troubleshooting (can bring down one lab without affecting others) +- Simplified microsegmentation in future +- Clear network boundaries + +**Alternative Considered**: Single WireGuard interface with policy routing +- **Rejected because**: Complex routing, harder to isolate, debugging nightmare + +### 4. Terraform for State, Ansible for Configuration + +**Decision**: Use Terraform for topology/state management, Ansible for device configuration. + +**Rationale**: + +**Terraform handles:** +- IP address allocation (deterministic, conflict-free) +- WireGuard key generation and tracking +- Peer matrix calculation (combinatorial complexity) +- Road warrior client generation +- State persistence + +**Ansible handles:** +- Platform-specific configuration (UCI vs systemd vs RouterOS) +- Service deployment and management +- Operational tasks (updates, health checks, emergency procedures) + +**Why not pure Ansible?** +- Peer matrix calculation becomes complex Jinja2 templates +- No built-in state for keys/IPs +- Key generation requires custom modules +- Change detection harder without state + +**Why not pure Terraform?** +- Terraform providers for network devices are limited +- Configuration management is Ansible's strength +- Operational playbooks natural in Ansible + +### 5. DHCP Strategy + +**Decision**: Offer two modes - ISC DHCP failover (Linux only) and dnsmasq split-range (all platforms) + +**Rationale**: + +**ISC DHCP Failover Mode:** +- True active-passive DHCP failover +- Lease synchronization +- IP address continuity across failovers +- Linux-only (not available on MikroTik/OpenWrt) + +**dnsmasq Split-Range Mode:** +- Works on all three platforms +- Simpler configuration +- Primary serves 10-127, Secondary serves 128-245 +- Good enough for most lab scenarios +- No lease synchronization but acceptable for labs + +**Why both?** +- Flexibility for different requirements +- Some labs need true failover (production-like testing) +- Other labs can use simpler approach + +### 6. Multi-Vendor Support + +**Decision**: Support Linux, MikroTik, and OpenWrt as first-class citizens. + +**Rationale**: + +**Linux (VMs):** +- Most flexible and powerful +- Best for primary/critical gateways +- Full feature support (ISC DHCP failover) +- Extensive monitoring capabilities + +**MikroTik:** +- Purpose-built routing hardware +- Cost-effective +- Low power consumption +- Common in network labs +- Proven reliability + +**OpenWrt:** +- Wide hardware support (x86, ARM, MIPS) +- Repurposing existing hardware +- Open source requirement for some environments +- Budget-friendly option +- Can integrate WiFi AP functionality + +**Implementation Strategy:** +- Common interface across all gateway types +- Role-based Ansible deployment +- Gateway type determines which roles execute +- Full interoperability (mix types in same lab) + +### 7. Road Warrior Support + +**Decision**: Build road warrior support into core architecture from start. + +**Rationale**: +- Common requirement (laptops accessing lab devices) +- Easier to design in from start than retrofit +- IP allocation strategy accounts for it +- WireGuard well-suited for road warrior use + +**Implementation**: +- Gateways allow road warrior clients as peers +- Clients assigned IPs from WireGuard subnet (after gateway IPs) +- Configuration generation automated via Ansible +- QR codes for mobile devices + +### 8. Monitoring Architecture + +**Decision**: Prometheus exporters on each gateway, centralized Grafana. + +**Rationale**: +- Industry-standard monitoring stack +- Pull-based metrics collection +- Platform-specific exporters: + - Linux: Python Prometheus client + - MikroTik: REST API polling + - OpenWrt: Shell script exporter + +**Metrics Collected**: +- WireGuard tunnel status (up/down) +- Latest handshake timing +- Rx/Tx bytes per tunnel +- VXLAN interface statistics +- Bridge FDB entry count +- DHCP active lease count + +### 9. Firewall Isolation (OpenWrt-specific) + +**Decision**: Automatically create isolated firewall zones for each lab network on OpenWrt. + +**Rationale**: +- OpenWrt requires explicit firewall configuration +- Each lab needs its own zone +- Prevent lab-to-corporate traffic +- Allow intra-lab forwarding +- Allow DHCP/DNS to gateway + +**Firewall Rules Created**: +``` +Zone: lab100 + Input: REJECT (except DNS/DHCP) + Forward: REJECT (except to same zone) + Output: ACCEPT + +Forwarding: + lab100 → lab100: ACCEPT + lab100 → lan: REJECT + lab100 → wan: REJECT +``` + +### 10. Configuration Decoupling + +**Decision**: Make deployment independent of existing infrastructure. + +**Rationale**: +- Don't assume pre-existing Ansible inventory +- Don't depend on existing Terraform state +- Self-contained solution +- Easy to integrate into existing automation later + +**Implementation**: +- Fresh Terraform state +- Self-generated Ansible inventory +- Standalone playbooks +- Can be integrated into larger automation framework if needed + +## OpenWrt Integration Discussion + +### Requirements for OpenWrt Support + +When OpenWrt support was requested, the following questions were clarified: + +**1. OpenWrt Version**: 23.05 or latest stable +**2. Architectures**: x86 (Intel), ARM, potentially MIPS +**3. Router Independence**: Should work regardless of specific router model +**4. Management**: SSH-based first, API if beneficial +**5. Package Management**: Auto-install missing packages +**6. DHCP Strategy**: Pros/cons analysis requested → chose dnsmasq +**7. Network Config**: UCI-based configuration +**8. Firewall**: Create new zones per lab network +**9. Monitoring**: Lightweight shell script tools +**10. Interoperability**: Must work with Linux/MikroTik +**11. Ansible Modules**: Best option selected (shell + UCI commands) +**12. Storage**: Not a constraint (appropriate hardware selection) + +### OpenWrt Design Decisions + +**UCI Configuration Method**: +- Native OpenWrt approach +- Atomic commits +- Rollback capability +- CLI-friendly + +**Package Installation Strategy**: +``` +Required packages: +- wireguard-tools (WireGuard CLI) +- kmod-wireguard (kernel module) +- kmod-vxlan (VXLAN support) +- ip-full (iproute2 with VXLAN) +- tcpdump (troubleshooting) +``` + +**Firewall Zone Isolation**: +- Each lab gets isolated zone +- Prevents lab → corporate traffic +- Allows lab → lab forwarding +- Gateway provides DNS/DHCP + +**Monitoring Approach**: +- Shell script Prometheus exporter +- Lightweight (no Python needed) +- Served via uhttpd on port 9586 +- Collects essential metrics + +## Rejected Alternatives + +### 1. GRE/IPIP Instead of VXLAN +**Rejected**: Less flexible, no native L2 support, older technology + +### 2. OpenVPN Instead of WireGuard +**Rejected**: Slower, more complex, heavier resource usage, older codebase + +### 3. Single Gateway with VLANs +**Rejected**: Single point of failure, doesn't meet redundancy requirements + +### 4. Cloud-Managed Solution (Tailscale/ZeroTier) +**Rejected**: External dependency, cost, limited control, privacy concerns + +### 5. Pure iptables/nftables Firewall on Linux +**Rejected for OpenWrt**: UCI firewall is native and more maintainable + +### 6. LuCI Web UI Integration +**Deferred**: CLI/Ansible-first approach, could add later as enhancement + +## Known Limitations and Trade-offs + +### 1. DHCP Failover on Non-Linux Platforms +**Limitation**: True DHCP failover only on Linux (ISC DHCP) +**Trade-off**: MikroTik/OpenWrt use split ranges (acceptable for labs) +**Mitigation**: Can use Linux for critical labs requiring true failover + +### 2. VXLAN Performance Overhead +**Limitation**: Double encapsulation (VXLAN + WireGuard) adds overhead +**Trade-off**: ~5-10% throughput reduction vs single encapsulation +**Mitigation**: Modern CPUs handle this well, acceptable for lab use + +### 3. Broadcast Traffic Amplification +**Limitation**: BUM traffic replicated to all peers in mesh +**Trade-off**: With 10 gateways, broadcast amplified 10x +**Mitigation**: 10 gateways max limit, modern switches handle this + +### 4. WireGuard Key Rotation +**Limitation**: No automatic key rotation implemented +**Trade-off**: Manual process to regenerate and redistribute keys +**Mitigation**: Keys stored in Terraform state, can be regenerated and redeployed + +### 5. No Inter-Lab Routing +**Limitation**: Labs are completely isolated from each other +**Trade-off**: Can't route between lab100 and lab200 +**Mitigation**: Design requirement (isolation), can add selective routing later + +### 6. Road Warrior Scalability +**Limitation**: Each gateway needs peer config for each RW client +**Trade-off**: 100 RW clients = 100 peer configs per gateway +**Mitigation**: Acceptable for expected scale, can optimize if needed + +## Future Enhancements Considered + +### Near-Term (Feasible) +1. **Automated key rotation** - Periodic WireGuard key regeneration +2. **Dynamic peer discovery** - Reduce configuration for large meshes +3. **QoS policies** - Traffic prioritization for lab networks +4. **WiFi AP mode** - OpenWrt as wireless access point for labs +5. **LuCI integration** - Web UI for OpenWrt management + +### Medium-Term (Requires Development) +1. **Microsegmentation** - Finer-grained network isolation +2. **Inter-lab routing** - Selective routing between specific labs +3. **IPAM integration** - NetBox/Nautobot for IP management +4. **Centralized logging** - Log aggregation from all gateways +5. **Alerting rules** - Prometheus alerts for tunnel failures + +### Long-Term (Architectural Changes) +1. **BGP-based routing** - For very large deployments +2. **Multi-site support** - Gateways across multiple datacenters +3. **IPv6 support** - Dual-stack overlay networks +4. **Hardware offload** - WireGuard hardware acceleration + +## Design Principles Applied + +Throughout the design process, we adhered to these principles: + +1. **Simplicity over complexity** - Use proven technologies, avoid over-engineering +2. **Automation first** - Everything deployable via code +3. **Platform agnostic** - Support multiple gateway types equally +4. **Fail-safe defaults** - Network isolation by default, explicit allowlisting +5. **Observability** - Built-in monitoring from day one +6. **Operational friendly** - Include operational playbooks, not just deployment +7. **Documentation** - Comprehensive guides for all platforms +8. **Backward compatible** - New features don't break existing deployments + +## Technical Debt and Known Issues + +### Acknowledged but Accepted +1. **Terraform state security**: Keys stored in state (recommend remote backend with encryption) +2. **No automated backups**: Operational playbooks include manual backup procedures +3. **Hard-coded ports**: WireGuard listen port = 51820 + VNI (could be configurable) +4. **No certificate-based auth**: Relies on SSH keys (acceptable for lab environments) + +### To Be Addressed +1. **Error handling**: Some playbooks could have more robust error handling +2. **Idempotency**: A few tasks could be more idempotent +3. **Testing**: No automated integration tests (manual testing only) +4. **Rollback**: No automated rollback on failed deployments + +## Conclusion + +This design represents a pragmatic balance between: +- **Flexibility** (multiple platforms, multiple DHCP modes, road warrior support) +- **Simplicity** (standard technologies, clear architecture) +- **Automation** (minimal manual intervention) +- **Reliability** (full mesh, redundant DHCP, monitoring) +- **Maintainability** (good documentation, operational playbooks) + +The solution meets all initial requirements and provides a foundation for future enhancements while remaining production-ready today. + +--- + +**Document Version**: 1.0 +**Last Updated**: 2025-01-30 +**Maintained By**: Network Architecture Team diff --git a/docs/gateway-comparison.md b/docs/gateway-comparison.md new file mode 100644 index 0000000..67e4866 --- /dev/null +++ b/docs/gateway-comparison.md @@ -0,0 +1,278 @@ +# Gateway Type Comparison + +## Overview + +The WireGuard Lab Overlay solution supports three gateway types: **Linux**, **MikroTik RouterOS**, and **OpenWrt**. All types are fully interoperable and can be mixed within the same lab network. + +## Feature Comparison + +| Feature | Linux | MikroTik | OpenWrt | +|---------|-------|----------|---------| +| **WireGuard Support** | ✅ Native | ✅ Native (RouterOS 7+) | ✅ Native | +| **VXLAN Support** | ✅ Native | ✅ Native | ✅ via kmod-vxlan | +| **DHCP Failover** | ✅ ISC DHCP | ⚠️ Simulated | ⚠️ Simulated | +| **DHCP Simple/Split** | ✅ dnsmasq | ✅ Native DHCP | ✅ dnsmasq | +| **Firewall Isolation** | ✅ iptables/nftables | ✅ Native firewall | ✅ UCI firewall | +| **Monitoring** | ✅ Python exporter | ✅ API/SNMP | ✅ Shell script | +| **Ansible Management** | ✅ Excellent | ✅ Good | ✅ Excellent | +| **Resource Usage** | Medium | Low | Low | +| **Configuration Method** | systemd/files | RouterOS CLI/API | UCI | + +## When to Use Each Type + +### Linux (VM or Physical) + +**Best for:** +- ✅ Primary/critical gateways requiring true DHCP failover +- ✅ High-throughput requirements (10G+) +- ✅ Complex routing/NAT scenarios +- ✅ Advanced monitoring and logging +- ✅ Situations requiring custom software + +**Advantages:** +- Most flexible and powerful +- Best monitoring capabilities +- True DHCP failover support +- Easy troubleshooting with standard Linux tools +- Can run additional services + +**Disadvantages:** +- Requires more resources (RAM, storage) +- VM overhead if virtualized +- More complex to secure + +**Typical Specs:** +- vCPU: 2+ +- RAM: 2GB+ +- Storage: 20GB+ + +### MikroTik RouterOS + +**Best for:** +- ✅ Branch/remote site gateways +- ✅ Cost-effective hardware deployment +- ✅ Existing MikroTik infrastructure +- ✅ Environments with limited power/cooling +- ✅ Sites requiring hardware redundancy + +**Advantages:** +- Purpose-built routing hardware +- Low power consumption +- Excellent price/performance +- Built-in redundancy features (RouterOS version dependent) +- GUI management option (WinBox/WebFig) + +**Disadvantages:** +- Proprietary OS +- Learning curve for RouterOS +- Limited customization +- Monitoring via API (not as rich as Linux) + +**Typical Models:** +- CCR series (enterprise) +- RB series (SOHO/branch) +- Minimum: RB750Gr3 or better + +### OpenWrt + +**Best for:** +- ✅ Lab edge/access gateways +- ✅ Repurposing existing hardware +- ✅ WiFi-enabled access points for labs +- ✅ Budget-conscious deployments +- ✅ Learning/testing environments + +**Advantages:** +- Runs on wide variety of hardware (x86, ARM, MIPS) +- Open source and highly customizable +- Excellent package ecosystem +- Good community support +- Can combine routing + WiFi AP functions + +**Disadvantages:** +- Varies by hardware (performance, stability) +- Flash/RAM limitations on some devices +- Web UI less polished than RouterOS +- Package dependencies can be complex + +**Typical Hardware:** +- x86_64: PC Engines APU, Protectli Vault +- ARM: GL.iNet, Turris Omnia +- Purpose-built: TP-Link, Ubiquiti (compatible models) + +## Mixed Deployments + +You can mix gateway types within a single lab network. Here's a recommended pattern: + +### Pattern 1: High Availability Core +```hcl +lab100 = { + gateways = [ + { hostname = "lab100-linux1", type = "linux", dhcp_role = "primary" }, + { hostname = "lab100-linux2", type = "linux", dhcp_role = "secondary" }, + { hostname = "lab100-openwrt1", type = "openwrt", dhcp_role = "backup" } + ] +} +``` +- Linux gateways provide true DHCP failover +- OpenWrt provides additional redundancy + +### Pattern 2: Cost-Optimized +```hcl +lab200 = { + gateways = [ + { hostname = "lab200-mikrotik1", type = "mikrotik", dhcp_role = "primary" }, + { hostname = "lab200-mikrotik2", type = "mikrotik", dhcp_role = "secondary" } + ] +} +``` +- All MikroTik for consistency +- Lower hardware costs +- Lower power consumption + +### Pattern 3: Hybrid +```hcl +lab300 = { + gateways = [ + { hostname = "lab300-linux1", type = "linux", dhcp_role = "primary" }, + { hostname = "lab300-mikrotik1", type = "mikrotik", dhcp_role = "backup" }, + { hostname = "lab300-openwrt1", type = "openwrt", dhcp_role = "backup" } + ] +} +``` +- Linux core for features +- MikroTik for proven reliability +- OpenWrt for flexibility + +## DHCP Mode Recommendations + +### ISC DHCP Failover Mode (`dhcp_mode = "failover"`) + +**Use when:** +- Critical lab requiring lease continuity +- Active-passive DHCP failover needed +- Only Linux gateways (primary + secondary) + +**Configuration:** +```hcl +dhcp_mode = "failover" +gateways = [ + { type = "linux", dhcp_role = "primary" }, + { type = "linux", dhcp_role = "secondary" }, + { type = "...", dhcp_role = "backup" } # Any type for additional backup +] +``` + +### Simple/Split Range Mode (`dhcp_mode = "simple"`) + +**Use when:** +- Any gateway types in use +- DHCP IP address persistence not critical +- Simpler configuration preferred + +**Configuration:** +```hcl +dhcp_mode = "simple" +gateways = [ + { type = "...", dhcp_role = "primary" }, # Serves .10-.127 + { type = "...", dhcp_role = "secondary" }, # Serves .128-.245 + { type = "...", dhcp_role = "backup" } # Serves full range (standby) +] +``` + +## Performance Expectations + +### WireGuard Throughput + +| Gateway Type | Expected Throughput | Notes | +|--------------|---------------------|-------| +| Linux (modern CPU) | 2-10 Gbps | Depends on CPU/NICs | +| MikroTik CCR | 1-5 Gbps | Varies by model | +| MikroTik RB | 100-500 Mbps | Consumer models | +| OpenWrt x86_64 | 500 Mbps - 2 Gbps | Hardware dependent | +| OpenWrt ARM | 50-300 Mbps | Limited by CPU | + +### Recommendations by Lab Size + +| Lab Devices | Recommended Primary | Recommended Secondary | +|-------------|---------------------|----------------------| +| 1-10 | OpenWrt | OpenWrt | +| 10-50 | MikroTik or Linux | MikroTik or OpenWrt | +| 50-100 | Linux | Linux or MikroTik | +| 100+ | Linux (multi-core) | Linux | + +## Configuration Management + +### Ansible Roles Per Type + +| Task | Linux Role | MikroTik Role | OpenWrt Role | +|------|-----------|---------------|--------------| +| Common Setup | `common` | `mikrotik-common` | `openwrt-common` | +| WireGuard | `wireguard` | `mikrotik-wireguard` | `openwrt-wireguard` | +| VXLAN | `vxlan` | `mikrotik-vxlan` | `openwrt-vxlan` | +| Bridge | `bridge` | `mikrotik-bridge` | `openwrt-bridge` | +| DHCP | `isc-dhcp` or `dnsmasq` | `mikrotik-dhcp` | `openwrt-dhcp` | +| Firewall | Implicit in roles | `mikrotik-firewall` | `openwrt-firewall` | +| Monitoring | `monitoring` | `mikrotik-monitoring` | `openwrt-monitoring` | + +All roles present the same logical interface - Ansible playbooks work identically regardless of gateway mix. + +## Monitoring Comparison + +### Metrics Collection + +**Linux:** +- Python Prometheus exporter +- Most detailed metrics +- Real-time collection +- Port 9586 + +**MikroTik:** +- REST API polling +- SNMP (optional) +- Good coverage +- Requires external collector + +**OpenWrt:** +- Shell script exporter +- Lightweight +- Essential metrics +- Port 9586 (uhttpd) + +### Available Metrics (All Types) + +- ✅ WireGuard tunnel status +- ✅ WireGuard handshake timing +- ✅ WireGuard rx/tx bytes +- ✅ VXLAN interface stats +- ✅ Bridge FDB entries +- ✅ DHCP lease count +- ✅ System uptime/memory + +## Summary Recommendations + +### Choose Linux when: +- You need true DHCP failover +- Performance is critical (>1 Gbps) +- Advanced features required +- Resources are not constrained + +### Choose MikroTik when: +- Budget-conscious hardware deployment +- Power efficiency matters +- RouterOS expertise available +- Proven routing platform needed + +### Choose OpenWrt when: +- Repurposing existing hardware +- Open source requirement +- WiFi AP integration needed +- Learning/lab environment + +### Mix them when: +- Different sites have different constraints +- Migrating between platforms +- Cost/performance optimization needed +- Maximum redundancy required + +All three types work together seamlessly in the same lab network! diff --git a/docs/openwrt-setup.md b/docs/openwrt-setup.md new file mode 100644 index 0000000..2fb0ba0 --- /dev/null +++ b/docs/openwrt-setup.md @@ -0,0 +1,399 @@ +# OpenWrt Gateway Setup Guide + +## Prerequisites + +### OpenWrt Version +- **Minimum**: OpenWrt 23.05 (stable) +- **Recommended**: Latest stable release +- **Architectures**: x86_64, ARM, MIPS supported + +### Hardware Requirements +- **Flash**: Minimum 128MB (256MB+ recommended) +- **RAM**: Minimum 128MB (256MB+ recommended) +- **Network Interfaces**: At least 2 (one for management, one for lab devices) + +## Initial OpenWrt Preparation + +### 1. Fresh OpenWrt Installation + +Ensure you have a clean OpenWrt installation with network access. + +```bash +# From your management workstation +ssh root@ +``` + +### 2. Configure Management Network + +Ensure the router is reachable from your management server: + +```bash +# On OpenWrt router +uci set network.lan.ipaddr='192.168.1.21' +uci set network.lan.netmask='255.255.255.0' +uci commit network +/etc/init.d/network restart +``` + +### 3. Install Required Packages + +The Ansible playbook will auto-install these, but you can pre-install: + +```bash +opkg update +opkg install wireguard-tools kmod-wireguard kmod-vxlan ip-full tcpdump +``` + +### 4. Enable SSH Access + +Ensure SSH is accessible: + +```bash +# Set root password if not already set +passwd + +# Enable SSH +/etc/init.d/dropbear enable +/etc/init.d/dropbear start +``` + +### 5. Configure SSH Keys (Recommended) + +From your management server: + +```bash +ssh-copy-id root@192.168.1.21 +``` + +## Network Interface Configuration + +### Identifying Interfaces + +List available interfaces: + +```bash +# On OpenWrt +ip link show +``` + +Common interface names: +- **x86_64**: `eth0`, `eth1`, `eth2` +- **ARM/embedded**: `lan1`, `lan2`, `wan` +- **VLAN-aware**: `eth0.1`, `eth0.2` + +### Recommended Setup + +``` +┌──────────────────┐ +│ OpenWrt Router │ +│ │ +│ eth0 ───────────┼──→ Corporate Network (Management) +│ eth1 ───────────┼──→ Lab Devices (Physical Connection) +│ │ +│ wg_lab100 ──────┼──→ WireGuard Overlay +│ vxlan100 ───────┼──→ VXLAN over WireGuard +│ br_lab100 ──────┼──→ Bridge (eth1 + vxlan100) +└──────────────────┘ +``` + +## Deployment via Ansible + +### 1. Update Terraform Configuration + +Edit `terraform/terraform.tfvars`: + +```hcl +lab_networks = { + lab100 = { + vni = 100 + subnet = "10.100.0.0/24" + wireguard_net = "172.16.100.0/24" + dhcp_mode = "simple" + road_warrior = false + gateways = [ + { + hostname = "lab100-openwrt1" + type = "openwrt" # Important: set to 'openwrt' + mgmt_ip = "192.168.1.21" + api_port = 22 + lab_if = "eth1" # Interface facing lab devices + dhcp_role = "primary" + } + ] + } +} +``` + +### 2. Deploy + +```bash +# From project root +./deploy.sh +``` + +Or manually: + +```bash +cd terraform +terraform apply +cd ../ansible +ansible-playbook -i inventory/hosts.yml playbooks/site.yml +``` + +## Verification + +### 1. Check WireGuard Status + +```bash +# On OpenWrt router +wg show +``` + +Expected output: +``` +interface: wg_lab100 + public key: + private key: (hidden) + listening port: 51920 + +peer: + endpoint: 192.168.1.11:51920 + allowed ips: 172.16.100.1/32 + latest handshake: 30 seconds ago + transfer: 5.2 KiB received, 4.8 KiB sent +``` + +### 2. Check VXLAN Interface + +```bash +ip -d link show vxlan100 +``` + +Expected output: +``` +vxlan100@wg_lab100: mtu 1450 + link/ether ... brd ff:ff:ff:ff:ff:ff + vxlan id 100 dev wg_lab100 dstport 4789 ... +``` + +### 3. Check Bridge + +```bash +bridge link show | grep br_lab100 +``` + +Expected output: +``` +3: eth1: master br_lab100 +4: vxlan100: master br_lab100 +``` + +### 4. Check DHCP + +```bash +# Show DHCP configuration +uci show dhcp.lab100 + +# Check active leases +cat /tmp/dhcp.leases +``` + +### 5. Check Firewall + +```bash +# Show firewall zones +uci show firewall | grep lab100 + +# Check iptables rules +iptables -L -n -v | grep lab100 +``` + +### 6. Test Connectivity + +```bash +# Ping peer WireGuard IP +ping 172.16.100.1 + +# Test DHCP by connecting a lab device to eth1 +# The device should get an IP in 10.100.0.0/24 +``` + +## Monitoring + +### Access Metrics + +The OpenWrt gateway exposes Prometheus metrics on port 9586: + +```bash +# From management server +curl http://192.168.1.21:9586/metrics/ +``` + +Sample output: +``` +# HELP wireguard_tunnel_up WireGuard tunnel status +# TYPE wireguard_tunnel_up gauge +wireguard_tunnel_up{lab="lab100",interface="wg_lab100",peer="abcd1234"} 1 +... +``` + +### View Logs + +```bash +# On OpenWrt +logread | grep -E 'wireguard|vxlan|lab100' + +# Follow logs in real-time +logread -f | grep -E 'wireguard|vxlan|lab100' +``` + +## Troubleshooting + +### WireGuard Not Starting + +```bash +# Check kernel modules +lsmod | grep wireguard + +# Reload network +/etc/init.d/network reload + +# Check logs +logread | grep wireguard +``` + +### VXLAN Not Working + +```bash +# Manually recreate VXLAN +/etc/init.d/vxlan-lab100 restart + +# Check if WireGuard is up first +ip link show wg_lab100 +``` + +### DHCP Not Assigning IPs + +```bash +# Restart dnsmasq +/etc/init.d/dnsmasq restart + +# Check configuration +uci show dhcp.lab100 + +# Test with static IP first +ip addr add 10.100.0.50/24 dev br_lab100 +``` + +### Firewall Blocking Traffic + +```bash +# Temporarily disable firewall for testing +/etc/init.d/firewall stop + +# Check rules +iptables -L -n -v + +# Re-enable +/etc/init.d/firewall start +``` + +### Package Installation Failures + +```bash +# Update package lists +opkg update + +# Check available space +df -h + +# Free up space if needed +opkg remove +``` + +## Performance Tuning + +### For High-Throughput Labs + +```bash +# Increase network buffers +sysctl -w net.core.rmem_max=16777216 +sysctl -w net.core.wmem_max=16777216 + +# Make permanent +echo "net.core.rmem_max=16777216" >> /etc/sysctl.conf +echo "net.core.wmem_max=16777216" >> /etc/sysctl.conf +``` + +### For Low-Resource Devices + +```bash +# Reduce WireGuard keepalive +uci set network.wg_lab100_peer0.persistent_keepalive='60' +uci commit network + +# Limit DHCP lease time +uci set dhcp.lab100.leasetime='1h' +uci commit dhcp +``` + +## Backup and Recovery + +### Create Backup + +```bash +# Full configuration backup +sysupgrade -b /tmp/backup-$(date +%Y%m%d).tar.gz + +# Download backup +scp root@192.168.1.21:/tmp/backup-*.tar.gz ./ +``` + +### Restore Backup + +```bash +# Upload backup +scp backup-20250130.tar.gz root@192.168.1.21:/tmp/ + +# Restore +sysupgrade -r /tmp/backup-20250130.tar.gz +``` + +## Integration with Multi-Vendor Labs + +OpenWrt gateways are fully compatible with Linux and MikroTik gateways in the same lab network: + +```hcl +# Example mixed gateway lab +lab_networks = { + lab100 = { + gateways = [ + { hostname = "gw1", type = "linux" }, + { hostname = "rt1", type = "mikrotik" }, + { hostname = "ap1", type = "openwrt" } # All three work together! + ] + } +} +``` + +All three gateway types: +- ✅ Form full mesh WireGuard tunnels with each other +- ✅ Participate in VXLAN overlay +- ✅ Provide DHCP services (with split ranges or failover) +- ✅ Export metrics to centralized Prometheus +- ✅ Can be managed from same Ansible playbooks + +## Next Steps + +- Enable road warrior access: Edit `terraform.tfvars` and set `road_warrior = true` +- Add monitoring dashboards in Grafana +- Configure alerting for tunnel failures +- Set up automated backups via cron + +## Support + +For OpenWrt-specific issues: +- OpenWrt documentation: https://openwrt.org/docs +- Check `/var/log/messages` for system logs +- Use `logread -f` for real-time debugging +- Run health check: `ansible-playbook playbooks/health-check.yml` diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..769cf8a --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,61 @@ +# Operations Manual + +## Daily Operations + +### Health Checks +```bash +ansible-playbook playbooks/health-check.yml +``` + +### Rolling Updates +```bash +ansible-playbook playbooks/rolling-update.yml +``` + +## Emergency Procedures + +### Emergency Shutdown +```bash +ansible-playbook playbooks/emergency-shutdown.yml +``` + +### Restore After Shutdown +```bash +ansible-playbook playbooks/restore-lab.yml +``` + +## Adding/Removing Components + +### Add New Lab Network +1. Edit `terraform/terraform.tfvars` +2. Run `terraform apply` +3. Run `ansible-playbook playbooks/site.yml` + +### Add Gateway to Existing Lab +1. Edit `terraform/terraform.tfvars` +2. Run `terraform apply` +3. Run `ansible-playbook playbooks/add-gateway.yml` + +### Remove Gateway +```bash +ansible-playbook playbooks/remove-gateway.yml +``` + +## Troubleshooting + +### WireGuard Tunnel Down +1. Check WireGuard status: `wg show` +2. Verify peer connectivity: `ping ` +3. Check firewall rules +4. Restart WireGuard: `systemctl restart wg-quick@wg-labXXX` + +### DHCP Not Working +1. Check DHCP server status +2. Verify bridge is up +3. Check DHCP lease file +4. Test with static IP first + +### VXLAN Issues +1. Verify WireGuard tunnels are up first +2. Check VXLAN interface: `ip -d link show vxlanXXX` +3. Verify FDB entries: `bridge fdb show` diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml new file mode 100644 index 0000000..481859f --- /dev/null +++ b/monitoring/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + prometheus: + image: prom/prometheus:latest + container_name: prometheus + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + ports: + - "9090:9090" + restart: unless-stopped + + grafana: + image: grafana/grafana:latest + container_name: grafana + volumes: + - grafana_data:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + ports: + - "3000:3000" + restart: unless-stopped + +volumes: + prometheus_data: + grafana_data: diff --git a/monitoring/exporter/wg_lab_exporter.py b/monitoring/exporter/wg_lab_exporter.py new file mode 100755 index 0000000..7cbc626 --- /dev/null +++ b/monitoring/exporter/wg_lab_exporter.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""WireGuard Lab Exporter for Prometheus""" +import subprocess +import time +from prometheus_client import start_http_server +from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY +import json + +class WireGuardLabCollector: + def __init__(self): + try: + with open('/etc/wg-lab-exporter/config.json') as f: + self.config = json.load(f) + except: + self.config = {'labs': {}} + + def collect(self): + wg_up = GaugeMetricFamily('wireguard_tunnel_up', 'Tunnel status', labels=['lab', 'peer']) + + for lab_name in self.config.get('labs', {}): + try: + result = subprocess.run(['wg', 'show', f'wg-{lab_name}'], + capture_output=True, text=True, timeout=5) + if result.returncode == 0: + wg_up.add_metric([lab_name, 'all'], 1) + else: + wg_up.add_metric([lab_name, 'all'], 0) + except: + wg_up.add_metric([lab_name, 'all'], 0) + + yield wg_up + +if __name__ == '__main__': + REGISTRY.register(WireGuardLabCollector()) + start_http_server(9586) + print("Exporter started on port 9586") + while True: + time.sleep(60) diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000..b731a99 --- /dev/null +++ b/monitoring/prometheus/prometheus.yml @@ -0,0 +1,8 @@ +global: + scrape_interval: 30s + evaluation_interval: 30s + +scrape_configs: + - job_name: 'wireguard-linux' + static_configs: + - targets: ['192.168.1.11:9586', '192.168.1.13:9586'] diff --git a/road-warrior/templates/README.md.j2 b/road-warrior/templates/README.md.j2 new file mode 100644 index 0000000..a14174d --- /dev/null +++ b/road-warrior/templates/README.md.j2 @@ -0,0 +1,33 @@ +# WireGuard Lab Access - {{ item.key }} + +## Your Lab Networks + +You have access to the following lab networks: + +{% for lab in road_warrior_users[item.key].labs %} +- **{{ lab }}**: Configuration file `{{ lab }}.conf` +{% endfor %} + +## Setup Instructions + +### Desktop/Laptop (Linux/Mac/Windows) + +1. Install WireGuard: https://www.wireguard.com/install/ +2. Import the `.conf` file for your lab +3. Activate the connection + +### Mobile (iOS/Android) + +1. Install WireGuard app from App Store/Play Store +2. Open the `{{ item.key }}-qr.html` file +3. Scan the QR code with the app + +## Security Notes + +- Keep these configuration files secure +- Do NOT share your private key +- Report lost devices immediately + +## Support + +For issues, contact: IT Support diff --git a/road-warrior/templates/client-config.conf.j2 b/road-warrior/templates/client-config.conf.j2 new file mode 100644 index 0000000..09e0ecc --- /dev/null +++ b/road-warrior/templates/client-config.conf.j2 @@ -0,0 +1,18 @@ +[Interface] +# WireGuard configuration for {{ item.value.email }} +# Lab Network: {{ item.value.lab_name }} +# Generated: {{ ansible_date_time.iso8601 }} + +PrivateKey = {{ item.value.wg_private_key }} +Address = {{ item.value.wg_ip }}/32 +DNS = 8.8.8.8, 8.8.4.4 + +{% for peer in item.value.peers %} +[Peer] +# Gateway: {{ peer.gateway }} +PublicKey = {{ peer.public_key }} +Endpoint = {{ peer.endpoint }}:{{ peer.listen_port }} +AllowedIPs = {{ item.value.subnet }} +PersistentKeepalive = 25 + +{% endfor %} diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..1e4f51e --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/random" { + version = "3.8.1" + constraints = "~> 3.5" + hashes = [ + "h1:m2y2fw9SBQ6+e7pNhi3+qsh8bYNmqkL89BulzH7uK3U=", + "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", + "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", + "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", + "zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0", + "zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66", + "zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9", + "zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05", + "zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8", + "zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b", + "zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699", + ] +} diff --git a/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.8.1/linux_arm64/LICENSE.txt b/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.8.1/linux_arm64/LICENSE.txt new file mode 100644 index 0000000..b9ac071 --- /dev/null +++ b/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.8.1/linux_arm64/LICENSE.txt @@ -0,0 +1,375 @@ +Copyright (c) 2017 HashiCorp, Inc. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.8.1/linux_arm64/terraform-provider-random_v3.8.1_x5 b/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.8.1/linux_arm64/terraform-provider-random_v3.8.1_x5 new file mode 100755 index 0000000..b4c3a1b Binary files /dev/null and b/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.8.1/linux_arm64/terraform-provider-random_v3.8.1_x5 differ diff --git a/terraform/.terraform/terraform.tfstate b/terraform/.terraform/terraform.tfstate new file mode 100644 index 0000000..a951335 --- /dev/null +++ b/terraform/.terraform/terraform.tfstate @@ -0,0 +1,12 @@ +{ + "version": 3, + "terraform_version": "1.14.4", + "backend": { + "type": "local", + "config": { + "path": "terraform.tfstate", + "workspace_dir": null + }, + "hash": 73024536 + } +} \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..4dbe665 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,125 @@ +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = "~> 3.5" + } + } + backend "local" { + path = "terraform.tfstate" + } +} + + +# Generate WireGuard keypairs for gateways +resource "random_password" "wg_private_key" { + for_each = local.all_gateways + length = 44 + special = false +} + +# Generate WireGuard keypairs for road warrior clients +resource "random_password" "rw_private_key" { + for_each = local.road_warrior_clients + length = 44 + special = false +} + +locals { + # Flatten all gateways + all_gateways = merge([ + for lab_name, lab in var.lab_networks : { + for gw in lab.gateways : + "${lab_name}-${gw.hostname}" => merge(gw, { + lab_name = lab_name + vni = lab.vni + wg_net = lab.wireguard_net + subnet = lab.subnet + dhcp_mode = lab.dhcp_mode + road_warrior = lab.road_warrior + }) + } + ]...) + + # Generate road warrior client entries + road_warrior_clients = merge([ + for user_name, user in var.road_warrior_users : { + for lab in user.labs : + "${user_name}-${lab}" => { + user_name = user_name + email = user.email + lab_name = lab + vni = var.lab_networks[lab].vni + wg_net = var.lab_networks[lab].wireguard_net + subnet = var.lab_networks[lab].subnet + } + } + ]...) + + # Calculate WireGuard IP assignments + gateway_wg_ips = { + for key, gw in local.all_gateways : + key => cidrhost(gw.wg_net, index(var.lab_networks[gw.lab_name].gateways, + [for g in var.lab_networks[gw.lab_name].gateways : g if g.hostname == gw.hostname][0] + ) + 1) + } + + # Road warrior IP assignments (start after gateways) + rw_wg_ips = { + for key, rw in local.road_warrior_clients : + key => cidrhost(rw.wg_net, + length(var.lab_networks[rw.lab_name].gateways) + + index([for k, v in local.road_warrior_clients : k if v.lab_name == rw.lab_name], key) + 10 + ) + } + + # Generate peer matrices (gateways only mesh with each other, not RW clients) + wg_gateway_peers = { + for lab_name, lab in var.lab_networks : lab_name => [ + for i, gw1 in lab.gateways : [ + for j, gw2 in lab.gateways : + { + from = gw1.hostname + to = gw2.hostname + to_type = gw2.type + endpoint = gw2.mgmt_ip + wg_ip = local.gateway_wg_ips["${lab_name}-${gw2.hostname}"] + public_key = random_password.wg_private_key["${lab_name}-${gw2.hostname}"].result + } if i != j + ] + ] + } + + # Road warrior peers (each RW client peers with all gateways in their labs) + wg_rw_peers = { + for key, rw in local.road_warrior_clients : + key => [ + for gw in var.lab_networks[rw.lab_name].gateways : + { + gateway = gw.hostname + gw_type = gw.type + endpoint = gw.mgmt_ip + wg_ip = local.gateway_wg_ips["${rw.lab_name}-${gw.hostname}"] + public_key = random_password.wg_private_key["${rw.lab_name}-${gw.hostname}"].result + listen_port = 51820 + var.lab_networks[rw.lab_name].vni + } + ] + } +} + +# Compute public keys from private keys (simplified for demo) +resource "random_id" "wg_public_key" { + for_each = random_password.wg_private_key + byte_length = 32 + keepers = { + private_key = each.value.result + } +} + +resource "random_id" "rw_public_key" { + for_each = random_password.rw_private_key + byte_length = 32 + keepers = { + private_key = each.value.result + } +} diff --git a/terraform/outputs.json b/terraform/outputs.json new file mode 100644 index 0000000..d347a32 --- /dev/null +++ b/terraform/outputs.json @@ -0,0 +1,842 @@ +{ + "ansible_inventory": { + "sensitive": false, + "type": "string", + "value": "\"all\":\n \"children\":\n \"linux_gateways\":\n \"hosts\":\n \"lab100-gw1\":\n \"ansible_host\": \"192.168.1.11\"\n \"lab_name\": \"lab100\"\n \"mikrotik_gateways\":\n \"hosts\":\n \"lab100-rt1\":\n \"ansible_connection\": \"network_cli\"\n \"ansible_host\": \"192.168.1.12\"\n \"ansible_network_os\": \"routeros\"\n \"ansible_port\": 8728\n \"lab_name\": \"lab100\"\n \"openwrt_gateways\":\n \"hosts\":\n \"lab100-openwrt1\":\n \"ansible_host\": \"192.168.1.14\"\n \"ansible_port\": 22\n \"lab_name\": \"lab100\"\n \"lab200-openwrt1\":\n \"ansible_host\": \"192.168.1.21\"\n \"ansible_port\": 22\n \"lab_name\": \"lab200\"\n \"lab200-openwrt2\":\n \"ansible_host\": \"192.168.1.22\"\n \"ansible_port\": 22\n \"lab_name\": \"lab200\"\n" + }, + "gateway_config": { + "sensitive": true, + "type": [ + "object", + { + "lab100-lab100-gw1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab100-lab100-openwrt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab100-lab100-rt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab200-lab200-openwrt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab200-lab200-openwrt2": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ] + } + ], + "value": { + "lab100-lab100-gw1": { + "api_port": 22, + "dhcp_mode": "failover", + "dhcp_role": "primary", + "hostname": "lab100-gw1", + "lab_if": "eth1", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.11", + "peers": [ + { + "endpoint": "192.168.1.12", + "from": "lab100-gw1", + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "to": "lab100-rt1", + "to_type": "mikrotik", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "from": "lab100-gw1", + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "to": "lab100-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.100.3" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "lutz.finsterle@mahle.com", + "public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "user_name": "finstl1", + "wg_ip": "172.16.100.14" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.15" + } + ], + "subnet": "10.100.0.0/24", + "type": "linux", + "vni": 100, + "wg_ip": "172.16.100.1", + "wg_listen_port": 51920, + "wg_private_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_public_key": "54c1598be162afda063f4dd3ba968f94ac3a79998b441c9dcebef3e1f800edc9" + }, + "lab100-lab100-openwrt1": { + "api_port": 22, + "dhcp_mode": "failover", + "dhcp_role": "backup", + "hostname": "lab100-openwrt1", + "lab_if": "eth1", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.14", + "peers": [ + { + "endpoint": "192.168.1.11", + "from": "lab100-openwrt1", + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "to": "lab100-gw1", + "to_type": "linux", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "from": "lab100-openwrt1", + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "to": "lab100-rt1", + "to_type": "mikrotik", + "wg_ip": "172.16.100.2" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "lutz.finsterle@mahle.com", + "public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "user_name": "finstl1", + "wg_ip": "172.16.100.14" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.15" + } + ], + "subnet": "10.100.0.0/24", + "type": "openwrt", + "vni": 100, + "wg_ip": "172.16.100.3", + "wg_listen_port": 51920, + "wg_private_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_public_key": "121507f174a5822f22f1e939b2940dfdb83aa6f2ed2f9d9032a02435f2fe1838" + }, + "lab100-lab100-rt1": { + "api_port": 8728, + "dhcp_mode": "failover", + "dhcp_role": "secondary", + "hostname": "lab100-rt1", + "lab_if": "ether2", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.12", + "peers": [ + { + "endpoint": "192.168.1.11", + "from": "lab100-rt1", + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "to": "lab100-gw1", + "to_type": "linux", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.14", + "from": "lab100-rt1", + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "to": "lab100-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.100.3" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "lutz.finsterle@mahle.com", + "public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "user_name": "finstl1", + "wg_ip": "172.16.100.14" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.15" + } + ], + "subnet": "10.100.0.0/24", + "type": "mikrotik", + "vni": 100, + "wg_ip": "172.16.100.2", + "wg_listen_port": 51920, + "wg_private_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_public_key": "0ad05299182076819239dc37e57e02fc1ce7ff1fca122c797bf891802da5940f" + }, + "lab200-lab200-openwrt1": { + "api_port": 22, + "dhcp_mode": "simple", + "dhcp_role": "primary", + "hostname": "lab200-openwrt1", + "lab_if": "lan1", + "lab_name": "lab200", + "mgmt_ip": "192.168.1.21", + "peers": [ + { + "endpoint": "192.168.1.22", + "from": "lab200-openwrt1", + "public_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "to": "lab200-openwrt2", + "to_type": "openwrt", + "wg_ip": "172.16.200.2" + } + ], + "road_warrior": false, + "rw_clients": [], + "subnet": "10.200.0.0/24", + "type": "openwrt", + "vni": 200, + "wg_ip": "172.16.200.1", + "wg_listen_port": 52020, + "wg_private_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "wg_public_key": "1045e3900850df7aba0061f1fd0dde7e96c2c6b44903b5c769308c5a89ba5116" + }, + "lab200-lab200-openwrt2": { + "api_port": 22, + "dhcp_mode": "simple", + "dhcp_role": "secondary", + "hostname": "lab200-openwrt2", + "lab_if": "lan1", + "lab_name": "lab200", + "mgmt_ip": "192.168.1.22", + "peers": [ + { + "endpoint": "192.168.1.21", + "from": "lab200-openwrt2", + "public_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "to": "lab200-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.200.1" + } + ], + "road_warrior": false, + "rw_clients": [], + "subnet": "10.200.0.0/24", + "type": "openwrt", + "vni": 200, + "wg_ip": "172.16.200.2", + "wg_listen_port": 52020, + "wg_private_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "wg_public_key": "33e4ef4b6e8dbcf6b78ba1652eed1941149578a308429776a82b779db70ff179" + } + } + }, + "road_warrior_configs": { + "sensitive": true, + "type": [ + "object", + { + "asmith-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "finstl1-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "finstl1-lab200": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "jdoe-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ] + } + ], + "value": { + "asmith-lab100": { + "email": "alice.smith@company.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "asmith", + "wg_ip": "172.16.100.13", + "wg_private_key": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm", + "wg_public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f" + }, + "finstl1-lab100": { + "email": "lutz.finsterle@mahle.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "finstl1", + "wg_ip": "172.16.100.14", + "wg_private_key": "qGbsVHrl6vOdqrVN0xxWzbzgJUGCCb6MZt4FEhZUS6WV", + "wg_public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab" + }, + "finstl1-lab200": { + "email": "lutz.finsterle@mahle.com", + "lab_name": "lab200", + "peers": [ + { + "endpoint": "192.168.1.21", + "gateway": "lab200-openwrt1", + "gw_type": "openwrt", + "listen_port": 52020, + "public_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "wg_ip": "172.16.200.1" + }, + { + "endpoint": "192.168.1.22", + "gateway": "lab200-openwrt2", + "gw_type": "openwrt", + "listen_port": 52020, + "public_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "wg_ip": "172.16.200.2" + } + ], + "subnet": "10.200.0.0/24", + "user_name": "finstl1", + "wg_ip": "172.16.200.12", + "wg_private_key": "NsbuQIZK3NSpl8PlFcr25W11qH7PYplhxku2YjMGMh6j", + "wg_public_key": "d64d224ad309c89ebb46b5626fe3392d7be16cede395dedf75d44665778f4dcf" + }, + "jdoe-lab100": { + "email": "john.doe@company.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "jdoe", + "wg_ip": "172.16.100.15", + "wg_private_key": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1", + "wg_public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23" + } + } + } +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..422a42e --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,92 @@ +output "gateway_config" { + value = { + for key, gw in local.all_gateways : key => { + hostname = gw.hostname + type = gw.type + mgmt_ip = gw.mgmt_ip + api_port = gw.api_port + lab_name = gw.lab_name + vni = gw.vni + subnet = gw.subnet + lab_if = gw.lab_if + dhcp_role = gw.dhcp_role + dhcp_mode = gw.dhcp_mode + road_warrior = gw.road_warrior + wg_private_key = random_password.wg_private_key[key].result + wg_public_key = random_id.wg_public_key[key].hex + wg_ip = local.gateway_wg_ips[key] + wg_listen_port = 51820 + gw.vni + peers = [ + for peer_list in local.wg_gateway_peers[gw.lab_name] : + peer_list if peer_list[0].from == gw.hostname + ][0] + # Road warrior clients that should be allowed + rw_clients = gw.road_warrior ? [ + for rw_key, rw in local.road_warrior_clients : + { + user_name = rw.user_name + email = rw.email + wg_ip = local.rw_wg_ips[rw_key] + public_key = random_id.rw_public_key[rw_key].hex + } if rw.lab_name == gw.lab_name + ] : [] + } + } + sensitive = true +} + +output "road_warrior_configs" { + value = { + for key, rw in local.road_warrior_clients : key => { + user_name = rw.user_name + email = rw.email + lab_name = rw.lab_name + subnet = rw.subnet + wg_private_key = random_password.rw_private_key[key].result + wg_public_key = random_id.rw_public_key[key].hex + wg_ip = local.rw_wg_ips[key] + peers = local.wg_rw_peers[key] + } + } + sensitive = true +} + +output "ansible_inventory" { + value = yamlencode({ + all = { + children = { + linux_gateways = { + hosts = { + for key, gw in local.all_gateways : + gw.hostname => { + ansible_host = gw.mgmt_ip + lab_name = gw.lab_name + } if gw.type == "linux" + } + } + mikrotik_gateways = { + hosts = { + for key, gw in local.all_gateways : + gw.hostname => { + ansible_host = gw.mgmt_ip + ansible_connection = "network_cli" + ansible_network_os = "routeros" + ansible_port = gw.api_port + lab_name = gw.lab_name + } if gw.type == "mikrotik" + } + } + openwrt_gateways = { + hosts = { + for key, gw in local.all_gateways : + gw.hostname => { + ansible_host = gw.mgmt_ip + ansible_port = gw.api_port + lab_name = gw.lab_name + } if gw.type == "openwrt" + } + } + } + } + }) +} diff --git a/terraform/terraform.tfstate b/terraform/terraform.tfstate new file mode 100644 index 0000000..4c90153 --- /dev/null +++ b/terraform/terraform.tfstate @@ -0,0 +1,1521 @@ +{ + "version": 4, + "terraform_version": "1.14.4", + "serial": 20, + "lineage": "ac76d36e-e202-7ab5-abee-bb765ad15c23", + "outputs": { + "ansible_inventory": { + "value": "\"all\":\n \"children\":\n \"linux_gateways\":\n \"hosts\":\n \"lab100-gw1\":\n \"ansible_host\": \"192.168.1.11\"\n \"lab_name\": \"lab100\"\n \"mikrotik_gateways\":\n \"hosts\":\n \"lab100-rt1\":\n \"ansible_connection\": \"network_cli\"\n \"ansible_host\": \"192.168.1.12\"\n \"ansible_network_os\": \"routeros\"\n \"ansible_port\": 8728\n \"lab_name\": \"lab100\"\n \"openwrt_gateways\":\n \"hosts\":\n \"lab100-openwrt1\":\n \"ansible_host\": \"192.168.1.14\"\n \"ansible_port\": 22\n \"lab_name\": \"lab100\"\n \"lab200-openwrt1\":\n \"ansible_host\": \"192.168.1.21\"\n \"ansible_port\": 22\n \"lab_name\": \"lab200\"\n \"lab200-openwrt2\":\n \"ansible_host\": \"192.168.1.22\"\n \"ansible_port\": 22\n \"lab_name\": \"lab200\"\n", + "type": "string" + }, + "gateway_config": { + "value": { + "lab100-lab100-gw1": { + "api_port": 22, + "dhcp_mode": "failover", + "dhcp_role": "primary", + "hostname": "lab100-gw1", + "lab_if": "eth1", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.11", + "peers": [ + { + "endpoint": "192.168.1.12", + "from": "lab100-gw1", + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "to": "lab100-rt1", + "to_type": "mikrotik", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "from": "lab100-gw1", + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "to": "lab100-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.100.3" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "lutz.finsterle@mahle.com", + "public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "user_name": "finstl1", + "wg_ip": "172.16.100.14" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.15" + } + ], + "subnet": "10.100.0.0/24", + "type": "linux", + "vni": 100, + "wg_ip": "172.16.100.1", + "wg_listen_port": 51920, + "wg_private_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_public_key": "54c1598be162afda063f4dd3ba968f94ac3a79998b441c9dcebef3e1f800edc9" + }, + "lab100-lab100-openwrt1": { + "api_port": 22, + "dhcp_mode": "failover", + "dhcp_role": "backup", + "hostname": "lab100-openwrt1", + "lab_if": "eth1", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.14", + "peers": [ + { + "endpoint": "192.168.1.11", + "from": "lab100-openwrt1", + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "to": "lab100-gw1", + "to_type": "linux", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "from": "lab100-openwrt1", + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "to": "lab100-rt1", + "to_type": "mikrotik", + "wg_ip": "172.16.100.2" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "lutz.finsterle@mahle.com", + "public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "user_name": "finstl1", + "wg_ip": "172.16.100.14" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.15" + } + ], + "subnet": "10.100.0.0/24", + "type": "openwrt", + "vni": 100, + "wg_ip": "172.16.100.3", + "wg_listen_port": 51920, + "wg_private_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_public_key": "121507f174a5822f22f1e939b2940dfdb83aa6f2ed2f9d9032a02435f2fe1838" + }, + "lab100-lab100-rt1": { + "api_port": 8728, + "dhcp_mode": "failover", + "dhcp_role": "secondary", + "hostname": "lab100-rt1", + "lab_if": "ether2", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.12", + "peers": [ + { + "endpoint": "192.168.1.11", + "from": "lab100-rt1", + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "to": "lab100-gw1", + "to_type": "linux", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.14", + "from": "lab100-rt1", + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "to": "lab100-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.100.3" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "lutz.finsterle@mahle.com", + "public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "user_name": "finstl1", + "wg_ip": "172.16.100.14" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.15" + } + ], + "subnet": "10.100.0.0/24", + "type": "mikrotik", + "vni": 100, + "wg_ip": "172.16.100.2", + "wg_listen_port": 51920, + "wg_private_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_public_key": "0ad05299182076819239dc37e57e02fc1ce7ff1fca122c797bf891802da5940f" + }, + "lab200-lab200-openwrt1": { + "api_port": 22, + "dhcp_mode": "simple", + "dhcp_role": "primary", + "hostname": "lab200-openwrt1", + "lab_if": "lan1", + "lab_name": "lab200", + "mgmt_ip": "192.168.1.21", + "peers": [ + { + "endpoint": "192.168.1.22", + "from": "lab200-openwrt1", + "public_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "to": "lab200-openwrt2", + "to_type": "openwrt", + "wg_ip": "172.16.200.2" + } + ], + "road_warrior": false, + "rw_clients": [], + "subnet": "10.200.0.0/24", + "type": "openwrt", + "vni": 200, + "wg_ip": "172.16.200.1", + "wg_listen_port": 52020, + "wg_private_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "wg_public_key": "1045e3900850df7aba0061f1fd0dde7e96c2c6b44903b5c769308c5a89ba5116" + }, + "lab200-lab200-openwrt2": { + "api_port": 22, + "dhcp_mode": "simple", + "dhcp_role": "secondary", + "hostname": "lab200-openwrt2", + "lab_if": "lan1", + "lab_name": "lab200", + "mgmt_ip": "192.168.1.22", + "peers": [ + { + "endpoint": "192.168.1.21", + "from": "lab200-openwrt2", + "public_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "to": "lab200-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.200.1" + } + ], + "road_warrior": false, + "rw_clients": [], + "subnet": "10.200.0.0/24", + "type": "openwrt", + "vni": 200, + "wg_ip": "172.16.200.2", + "wg_listen_port": 52020, + "wg_private_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "wg_public_key": "33e4ef4b6e8dbcf6b78ba1652eed1941149578a308429776a82b779db70ff179" + } + }, + "type": [ + "object", + { + "lab100-lab100-gw1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab100-lab100-openwrt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab100-lab100-rt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab200-lab200-openwrt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab200-lab200-openwrt2": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ] + } + ], + "sensitive": true + }, + "road_warrior_configs": { + "value": { + "asmith-lab100": { + "email": "alice.smith@company.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "asmith", + "wg_ip": "172.16.100.13", + "wg_private_key": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm", + "wg_public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f" + }, + "finstl1-lab100": { + "email": "lutz.finsterle@mahle.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "finstl1", + "wg_ip": "172.16.100.14", + "wg_private_key": "qGbsVHrl6vOdqrVN0xxWzbzgJUGCCb6MZt4FEhZUS6WV", + "wg_public_key": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab" + }, + "finstl1-lab200": { + "email": "lutz.finsterle@mahle.com", + "lab_name": "lab200", + "peers": [ + { + "endpoint": "192.168.1.21", + "gateway": "lab200-openwrt1", + "gw_type": "openwrt", + "listen_port": 52020, + "public_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "wg_ip": "172.16.200.1" + }, + { + "endpoint": "192.168.1.22", + "gateway": "lab200-openwrt2", + "gw_type": "openwrt", + "listen_port": 52020, + "public_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "wg_ip": "172.16.200.2" + } + ], + "subnet": "10.200.0.0/24", + "user_name": "finstl1", + "wg_ip": "172.16.200.12", + "wg_private_key": "NsbuQIZK3NSpl8PlFcr25W11qH7PYplhxku2YjMGMh6j", + "wg_public_key": "d64d224ad309c89ebb46b5626fe3392d7be16cede395dedf75d44665778f4dcf" + }, + "jdoe-lab100": { + "email": "john.doe@company.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "jdoe", + "wg_ip": "172.16.100.15", + "wg_private_key": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1", + "wg_public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23" + } + }, + "type": [ + "object", + { + "asmith-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "finstl1-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "finstl1-lab200": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "jdoe-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ] + } + ], + "sensitive": true + } + }, + "resources": [ + { + "mode": "managed", + "type": "random_id", + "name": "rw_public_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "asmith-lab100", + "schema_version": 0, + "attributes": { + "b64_std": "5njCQuovKVSCJPS0aQML47uaZcaFj99Y2OrcLDMF2R8=", + "b64_url": "5njCQuovKVSCJPS0aQML47uaZcaFj99Y2OrcLDMF2R8", + "byte_length": 32, + "dec": "104245317564734910190649937978759168669880762103445249530466650136801532303647", + "hex": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "id": "5njCQuovKVSCJPS0aQML47uaZcaFj99Y2OrcLDMF2R8", + "keepers": { + "private_key": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.rw_private_key" + ] + }, + { + "index_key": "finstl1-lab100", + "schema_version": 0, + "attributes": { + "b64_std": "vZmnOs1vbXySWZbSqqSqMi50Q9G9hXWAss+7991ghqs=", + "b64_url": "vZmnOs1vbXySWZbSqqSqMi50Q9G9hXWAss-7991ghqs", + "byte_length": 32, + "dec": "85758610160100119405589937166567556978060558536582938355350111939955718653611", + "hex": "bd99a73acd6f6d7c925996d2aaa4aa322e7443d1bd857580b2cfbbf7dd6086ab", + "id": "vZmnOs1vbXySWZbSqqSqMi50Q9G9hXWAss-7991ghqs", + "keepers": { + "private_key": "qGbsVHrl6vOdqrVN0xxWzbzgJUGCCb6MZt4FEhZUS6WV" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.rw_private_key" + ] + }, + { + "index_key": "finstl1-lab200", + "schema_version": 0, + "attributes": { + "b64_std": "1k0iStMJyJ67RrVib+M5LXvhbO3jld7fddRGZXePTc8=", + "b64_url": "1k0iStMJyJ67RrVib-M5LXvhbO3jld7fddRGZXePTc8", + "byte_length": 32, + "dec": "96931233497443705264887866061589728932226838334022735490996744838560292490703", + "hex": "d64d224ad309c89ebb46b5626fe3392d7be16cede395dedf75d44665778f4dcf", + "id": "1k0iStMJyJ67RrVib-M5LXvhbO3jld7fddRGZXePTc8", + "keepers": { + "private_key": "NsbuQIZK3NSpl8PlFcr25W11qH7PYplhxku2YjMGMh6j" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.rw_private_key" + ] + }, + { + "index_key": "jdoe-lab100", + "schema_version": 0, + "attributes": { + "b64_std": "jSXVDtUBLTAPUncCM8dRfNWJ2b6fdh6L9GEtyQQ2nCM=", + "b64_url": "jSXVDtUBLTAPUncCM8dRfNWJ2b6fdh6L9GEtyQQ2nCM", + "byte_length": 32, + "dec": "63842955463480482826459552839830143054294100763940965499152032631942448323619", + "hex": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "id": "jSXVDtUBLTAPUncCM8dRfNWJ2b6fdh6L9GEtyQQ2nCM", + "keepers": { + "private_key": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.rw_private_key" + ] + } + ] + }, + { + "mode": "managed", + "type": "random_id", + "name": "wg_public_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "lab100-lab100-gw1", + "schema_version": 0, + "attributes": { + "b64_std": "VMFZi+Fir9oGP03TupaPlKw6eZmLRBydzr7z4fgA7ck=", + "b64_url": "VMFZi-Fir9oGP03TupaPlKw6eZmLRBydzr7z4fgA7ck", + "byte_length": 32, + "dec": "38335898791089918962883662109942557336431256020482982646064009696829021154761", + "hex": "54c1598be162afda063f4dd3ba968f94ac3a79998b441c9dcebef3e1f800edc9", + "id": "VMFZi-Fir9oGP03TupaPlKw6eZmLRBydzr7z4fgA7ck", + "keepers": { + "private_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab100-lab100-openwrt1", + "schema_version": 0, + "attributes": { + "b64_std": "EhUH8XSlgi8i8ek5spQN/bg6pvLtL52QMqAkNfL+GDg=", + "b64_url": "EhUH8XSlgi8i8ek5spQN_bg6pvLtL52QMqAkNfL-GDg", + "byte_length": 32, + "dec": "8178789884715027473260303375377936683841709700029945744949863445161987414072", + "hex": "121507f174a5822f22f1e939b2940dfdb83aa6f2ed2f9d9032a02435f2fe1838", + "id": "EhUH8XSlgi8i8ek5spQN_bg6pvLtL52QMqAkNfL-GDg", + "keepers": { + "private_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab100-lab100-rt1", + "schema_version": 0, + "attributes": { + "b64_std": "CtBSmRggdoGSOdw35X4C/Bzn/x/KEix5e/iRgC2llA8=", + "b64_url": "CtBSmRggdoGSOdw35X4C_Bzn_x_KEix5e_iRgC2llA8", + "byte_length": 32, + "dec": "4891202745919694193089765763204043058376189930231295615988054493050240865295", + "hex": "0ad05299182076819239dc37e57e02fc1ce7ff1fca122c797bf891802da5940f", + "id": "CtBSmRggdoGSOdw35X4C_Bzn_x_KEix5e_iRgC2llA8", + "keepers": { + "private_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab200-lab200-openwrt1", + "schema_version": 0, + "attributes": { + "b64_std": "EEXjkAhQ33q6AGHx/Q3efpbCxrRJA7XHaTCMWom6URY=", + "b64_url": "EEXjkAhQ33q6AGHx_Q3efpbCxrRJA7XHaTCMWom6URY", + "byte_length": 32, + "dec": "7360488604330779802432218673950229681572177804711932122578897241401491869974", + "hex": "1045e3900850df7aba0061f1fd0dde7e96c2c6b44903b5c769308c5a89ba5116", + "id": "EEXjkAhQ33q6AGHx_Q3efpbCxrRJA7XHaTCMWom6URY", + "keepers": { + "private_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab200-lab200-openwrt2", + "schema_version": 0, + "attributes": { + "b64_std": "M+TvS26NvPa3i6FlLu0ZQRSVeKMIQpd2qCt3nbcP8Xk=", + "b64_url": "M-TvS26NvPa3i6FlLu0ZQRSVeKMIQpd2qCt3nbcP8Xk", + "byte_length": 32, + "dec": "23472447959531600187434437035396006440843095667520878254192437055997053235577", + "hex": "33e4ef4b6e8dbcf6b78ba1652eed1941149578a308429776a82b779db70ff179", + "id": "M-TvS26NvPa3i6FlLu0ZQRSVeKMIQpd2qCt3nbcP8Xk", + "keepers": { + "private_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "rw_private_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "asmith-lab100", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$VAwEUOERw/VpoC4m7A.1DuhKP6EzZpb/arRN3.U1lxvBOEixNLFwC", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "finstl1-lab100", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$Fl1vaDwvIXD1LubeyZ4IvuEKhixwBsZjF2Mbj9iDKN5vbOuN4nRlO", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "qGbsVHrl6vOdqrVN0xxWzbzgJUGCCb6MZt4FEhZUS6WV", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "finstl1-lab200", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$1LbzY86oVpzDK0vVDQS02up2au3/F02nOAxko2J65t6a581.U.fuy", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "NsbuQIZK3NSpl8PlFcr25W11qH7PYplhxku2YjMGMh6j", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "jdoe-lab100", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$eCcnN.YQc5AMs1EAOTzh/uPNZV9mko2Uyt2CkvaxhGIpvgksn2bWa", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "wg_private_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "lab100-lab100-gw1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$rdpERNsVnIYkIz6/tIp6lOJ45ZHzBtbWr0J6xYiWbjPmNSaGoeHxu", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab100-lab100-openwrt1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$HOHtUEvYIRVd/IlUMlh95etHLMrA8KK9Udp.V409f78T6CaKXWKwi", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab100-lab100-rt1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$3yP5fIhNlMd.wlZe0BpL8OTo/rRY2yj3XEoyyA2J0AKu.B7K1qoOy", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab200-lab200-openwrt1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$F3axniNQZp2S97Ex8/x05uu71kTk/EaILHnoTyza3r9zGgsQWB.t.", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab200-lab200-openwrt2", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$MFY.UM7LQbV4jOFsySKC0.bqb/KHKg9HnMk5j7IZm3sCM4hK9c77y", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + } + ] + } + ], + "check_results": null +} diff --git a/terraform/terraform.tfstate.backup b/terraform/terraform.tfstate.backup new file mode 100644 index 0000000..68b7fcb --- /dev/null +++ b/terraform/terraform.tfstate.backup @@ -0,0 +1,1194 @@ +{ + "version": 4, + "terraform_version": "1.14.4", + "serial": 15, + "lineage": "ac76d36e-e202-7ab5-abee-bb765ad15c23", + "outputs": { + "ansible_inventory": { + "value": "\"all\":\n \"children\":\n \"linux_gateways\":\n \"hosts\":\n \"lab100-gw1\":\n \"ansible_host\": \"192.168.1.11\"\n \"lab_name\": \"lab100\"\n \"mikrotik_gateways\":\n \"hosts\":\n \"lab100-rt1\":\n \"ansible_connection\": \"network_cli\"\n \"ansible_host\": \"192.168.1.12\"\n \"ansible_network_os\": \"routeros\"\n \"ansible_port\": 8728\n \"lab_name\": \"lab100\"\n \"openwrt_gateways\":\n \"hosts\":\n \"lab100-openwrt1\":\n \"ansible_host\": \"192.168.1.14\"\n \"ansible_port\": 22\n \"lab_name\": \"lab100\"\n \"lab200-openwrt1\":\n \"ansible_host\": \"192.168.1.21\"\n \"ansible_port\": 22\n \"lab_name\": \"lab200\"\n \"lab200-openwrt2\":\n \"ansible_host\": \"192.168.1.22\"\n \"ansible_port\": 22\n \"lab_name\": \"lab200\"\n", + "type": "string" + }, + "gateway_config": { + "value": { + "lab100-lab100-gw1": { + "api_port": 22, + "dhcp_mode": "failover", + "dhcp_role": "primary", + "hostname": "lab100-gw1", + "lab_if": "eth1", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.11", + "peers": [ + { + "endpoint": "192.168.1.12", + "from": "lab100-gw1", + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "to": "lab100-rt1", + "to_type": "mikrotik", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "from": "lab100-gw1", + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "to": "lab100-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.100.3" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.14" + } + ], + "subnet": "10.100.0.0/24", + "type": "linux", + "vni": 100, + "wg_ip": "172.16.100.1", + "wg_listen_port": 51920, + "wg_private_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_public_key": "54c1598be162afda063f4dd3ba968f94ac3a79998b441c9dcebef3e1f800edc9" + }, + "lab100-lab100-openwrt1": { + "api_port": 22, + "dhcp_mode": "failover", + "dhcp_role": "backup", + "hostname": "lab100-openwrt1", + "lab_if": "eth1", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.14", + "peers": [ + { + "endpoint": "192.168.1.11", + "from": "lab100-openwrt1", + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "to": "lab100-gw1", + "to_type": "linux", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "from": "lab100-openwrt1", + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "to": "lab100-rt1", + "to_type": "mikrotik", + "wg_ip": "172.16.100.2" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.14" + } + ], + "subnet": "10.100.0.0/24", + "type": "openwrt", + "vni": 100, + "wg_ip": "172.16.100.3", + "wg_listen_port": 51920, + "wg_private_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_public_key": "121507f174a5822f22f1e939b2940dfdb83aa6f2ed2f9d9032a02435f2fe1838" + }, + "lab100-lab100-rt1": { + "api_port": 8728, + "dhcp_mode": "failover", + "dhcp_role": "secondary", + "hostname": "lab100-rt1", + "lab_if": "ether2", + "lab_name": "lab100", + "mgmt_ip": "192.168.1.12", + "peers": [ + { + "endpoint": "192.168.1.11", + "from": "lab100-rt1", + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "to": "lab100-gw1", + "to_type": "linux", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.14", + "from": "lab100-rt1", + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "to": "lab100-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.100.3" + } + ], + "road_warrior": true, + "rw_clients": [ + { + "email": "alice.smith@company.com", + "public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "user_name": "asmith", + "wg_ip": "172.16.100.13" + }, + { + "email": "john.doe@company.com", + "public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "user_name": "jdoe", + "wg_ip": "172.16.100.14" + } + ], + "subnet": "10.100.0.0/24", + "type": "mikrotik", + "vni": 100, + "wg_ip": "172.16.100.2", + "wg_listen_port": 51920, + "wg_private_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_public_key": "0ad05299182076819239dc37e57e02fc1ce7ff1fca122c797bf891802da5940f" + }, + "lab200-lab200-openwrt1": { + "api_port": 22, + "dhcp_mode": "simple", + "dhcp_role": "primary", + "hostname": "lab200-openwrt1", + "lab_if": "lan1", + "lab_name": "lab200", + "mgmt_ip": "192.168.1.21", + "peers": [ + { + "endpoint": "192.168.1.22", + "from": "lab200-openwrt1", + "public_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "to": "lab200-openwrt2", + "to_type": "openwrt", + "wg_ip": "172.16.200.2" + } + ], + "road_warrior": false, + "rw_clients": [], + "subnet": "10.200.0.0/24", + "type": "openwrt", + "vni": 200, + "wg_ip": "172.16.200.1", + "wg_listen_port": 52020, + "wg_private_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "wg_public_key": "1045e3900850df7aba0061f1fd0dde7e96c2c6b44903b5c769308c5a89ba5116" + }, + "lab200-lab200-openwrt2": { + "api_port": 22, + "dhcp_mode": "simple", + "dhcp_role": "secondary", + "hostname": "lab200-openwrt2", + "lab_if": "lan1", + "lab_name": "lab200", + "mgmt_ip": "192.168.1.22", + "peers": [ + { + "endpoint": "192.168.1.21", + "from": "lab200-openwrt2", + "public_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "to": "lab200-openwrt1", + "to_type": "openwrt", + "wg_ip": "172.16.200.1" + } + ], + "road_warrior": false, + "rw_clients": [], + "subnet": "10.200.0.0/24", + "type": "openwrt", + "vni": 200, + "wg_ip": "172.16.200.2", + "wg_listen_port": 52020, + "wg_private_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "wg_public_key": "33e4ef4b6e8dbcf6b78ba1652eed1941149578a308429776a82b779db70ff179" + } + }, + "type": [ + "object", + { + "lab100-lab100-gw1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab100-lab100-openwrt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab100-lab100-rt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "list", + [ + "object", + { + "email": "string", + "public_key": "string", + "user_name": "string", + "wg_ip": "string" + } + ] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab200-lab200-openwrt1": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "tuple", + [] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "lab200-lab200-openwrt2": [ + "object", + { + "api_port": "number", + "dhcp_mode": "string", + "dhcp_role": "string", + "hostname": "string", + "lab_if": "string", + "lab_name": "string", + "mgmt_ip": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "from": "string", + "public_key": "string", + "to": "string", + "to_type": "string", + "wg_ip": "string" + } + ] + ] + ], + "road_warrior": "bool", + "rw_clients": [ + "tuple", + [] + ], + "subnet": "string", + "type": "string", + "vni": "number", + "wg_ip": "string", + "wg_listen_port": "number", + "wg_private_key": "string", + "wg_public_key": "string" + } + ] + } + ], + "sensitive": true + }, + "road_warrior_configs": { + "value": { + "asmith-lab100": { + "email": "alice.smith@company.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "asmith", + "wg_ip": "172.16.100.13", + "wg_private_key": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm", + "wg_public_key": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f" + }, + "jdoe-lab100": { + "email": "john.doe@company.com", + "lab_name": "lab100", + "peers": [ + { + "endpoint": "192.168.1.11", + "gateway": "lab100-gw1", + "gw_type": "linux", + "listen_port": 51920, + "public_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "wg_ip": "172.16.100.1" + }, + { + "endpoint": "192.168.1.12", + "gateway": "lab100-rt1", + "gw_type": "mikrotik", + "listen_port": 51920, + "public_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "wg_ip": "172.16.100.2" + }, + { + "endpoint": "192.168.1.14", + "gateway": "lab100-openwrt1", + "gw_type": "openwrt", + "listen_port": 51920, + "public_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "wg_ip": "172.16.100.3" + } + ], + "subnet": "10.100.0.0/24", + "user_name": "jdoe", + "wg_ip": "172.16.100.14", + "wg_private_key": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1", + "wg_public_key": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23" + } + }, + "type": [ + "object", + { + "asmith-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ], + "jdoe-lab100": [ + "object", + { + "email": "string", + "lab_name": "string", + "peers": [ + "tuple", + [ + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ], + [ + "object", + { + "endpoint": "string", + "gateway": "string", + "gw_type": "string", + "listen_port": "number", + "public_key": "string", + "wg_ip": "string" + } + ] + ] + ], + "subnet": "string", + "user_name": "string", + "wg_ip": "string", + "wg_private_key": "string", + "wg_public_key": "string" + } + ] + } + ], + "sensitive": true + } + }, + "resources": [ + { + "mode": "managed", + "type": "random_id", + "name": "rw_public_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "asmith-lab100", + "schema_version": 0, + "attributes": { + "b64_std": "5njCQuovKVSCJPS0aQML47uaZcaFj99Y2OrcLDMF2R8=", + "b64_url": "5njCQuovKVSCJPS0aQML47uaZcaFj99Y2OrcLDMF2R8", + "byte_length": 32, + "dec": "104245317564734910190649937978759168669880762103445249530466650136801532303647", + "hex": "e678c242ea2f29548224f4b469030be3bb9a65c6858fdf58d8eadc2c3305d91f", + "id": "5njCQuovKVSCJPS0aQML47uaZcaFj99Y2OrcLDMF2R8", + "keepers": { + "private_key": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.rw_private_key" + ] + }, + { + "index_key": "jdoe-lab100", + "schema_version": 0, + "attributes": { + "b64_std": "jSXVDtUBLTAPUncCM8dRfNWJ2b6fdh6L9GEtyQQ2nCM=", + "b64_url": "jSXVDtUBLTAPUncCM8dRfNWJ2b6fdh6L9GEtyQQ2nCM", + "byte_length": 32, + "dec": "63842955463480482826459552839830143054294100763940965499152032631942448323619", + "hex": "8d25d50ed5012d300f52770233c7517cd589d9be9f761e8bf4612dc904369c23", + "id": "jSXVDtUBLTAPUncCM8dRfNWJ2b6fdh6L9GEtyQQ2nCM", + "keepers": { + "private_key": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.rw_private_key" + ] + } + ] + }, + { + "mode": "managed", + "type": "random_id", + "name": "wg_public_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "lab100-lab100-gw1", + "schema_version": 0, + "attributes": { + "b64_std": "VMFZi+Fir9oGP03TupaPlKw6eZmLRBydzr7z4fgA7ck=", + "b64_url": "VMFZi-Fir9oGP03TupaPlKw6eZmLRBydzr7z4fgA7ck", + "byte_length": 32, + "dec": "38335898791089918962883662109942557336431256020482982646064009696829021154761", + "hex": "54c1598be162afda063f4dd3ba968f94ac3a79998b441c9dcebef3e1f800edc9", + "id": "VMFZi-Fir9oGP03TupaPlKw6eZmLRBydzr7z4fgA7ck", + "keepers": { + "private_key": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab100-lab100-openwrt1", + "schema_version": 0, + "attributes": { + "b64_std": "EhUH8XSlgi8i8ek5spQN/bg6pvLtL52QMqAkNfL+GDg=", + "b64_url": "EhUH8XSlgi8i8ek5spQN_bg6pvLtL52QMqAkNfL-GDg", + "byte_length": 32, + "dec": "8178789884715027473260303375377936683841709700029945744949863445161987414072", + "hex": "121507f174a5822f22f1e939b2940dfdb83aa6f2ed2f9d9032a02435f2fe1838", + "id": "EhUH8XSlgi8i8ek5spQN_bg6pvLtL52QMqAkNfL-GDg", + "keepers": { + "private_key": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab100-lab100-rt1", + "schema_version": 0, + "attributes": { + "b64_std": "CtBSmRggdoGSOdw35X4C/Bzn/x/KEix5e/iRgC2llA8=", + "b64_url": "CtBSmRggdoGSOdw35X4C_Bzn_x_KEix5e_iRgC2llA8", + "byte_length": 32, + "dec": "4891202745919694193089765763204043058376189930231295615988054493050240865295", + "hex": "0ad05299182076819239dc37e57e02fc1ce7ff1fca122c797bf891802da5940f", + "id": "CtBSmRggdoGSOdw35X4C_Bzn_x_KEix5e_iRgC2llA8", + "keepers": { + "private_key": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab200-lab200-openwrt1", + "schema_version": 0, + "attributes": { + "b64_std": "EEXjkAhQ33q6AGHx/Q3efpbCxrRJA7XHaTCMWom6URY=", + "b64_url": "EEXjkAhQ33q6AGHx_Q3efpbCxrRJA7XHaTCMWom6URY", + "byte_length": 32, + "dec": "7360488604330779802432218673950229681572177804711932122578897241401491869974", + "hex": "1045e3900850df7aba0061f1fd0dde7e96c2c6b44903b5c769308c5a89ba5116", + "id": "EEXjkAhQ33q6AGHx_Q3efpbCxrRJA7XHaTCMWom6URY", + "keepers": { + "private_key": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + }, + { + "index_key": "lab200-lab200-openwrt2", + "schema_version": 0, + "attributes": { + "b64_std": "M+TvS26NvPa3i6FlLu0ZQRSVeKMIQpd2qCt3nbcP8Xk=", + "b64_url": "M-TvS26NvPa3i6FlLu0ZQRSVeKMIQpd2qCt3nbcP8Xk", + "byte_length": 32, + "dec": "23472447959531600187434437035396006440843095667520878254192437055997053235577", + "hex": "33e4ef4b6e8dbcf6b78ba1652eed1941149578a308429776a82b779db70ff179", + "id": "M-TvS26NvPa3i6FlLu0ZQRSVeKMIQpd2qCt3nbcP8Xk", + "keepers": { + "private_key": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW" + }, + "prefix": null + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "keepers" + }, + { + "type": "index", + "value": { + "value": "private_key", + "type": "string" + } + } + ] + ], + "identity_schema_version": 0, + "dependencies": [ + "random_password.wg_private_key" + ] + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "rw_private_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "asmith-lab100", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$VAwEUOERw/VpoC4m7A.1DuhKP6EzZpb/arRN3.U1lxvBOEixNLFwC", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "WzAAWAf9paoVPnRblDVBMUqfL2ieuMDeaEwr8xLgP3Hm", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "jdoe-lab100", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$eCcnN.YQc5AMs1EAOTzh/uPNZV9mko2Uyt2CkvaxhGIpvgksn2bWa", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "yDIq0PioXao7aLR0e6FIhQH58NkFSH8q1EDSqvDIE6K1", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "wg_private_key", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "index_key": "lab100-lab100-gw1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$rdpERNsVnIYkIz6/tIp6lOJ45ZHzBtbWr0J6xYiWbjPmNSaGoeHxu", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "XivDZJP1Jt0hMzkg28XcbImykp2OepiajCdnSThpJ4EN", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab100-lab100-openwrt1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$HOHtUEvYIRVd/IlUMlh95etHLMrA8KK9Udp.V409f78T6CaKXWKwi", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "kJ8w03s95KxcROZ4MDn0JGeqeZLBhkKoduoaiPIWvRUm", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab100-lab100-rt1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$3yP5fIhNlMd.wlZe0BpL8OTo/rRY2yj3XEoyyA2J0AKu.B7K1qoOy", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "uRgOFY6Lml5bm1SkpMnJCDR7tqBu8U8KWinrzvwPwtq8", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab200-lab200-openwrt1", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$F3axniNQZp2S97Ex8/x05uu71kTk/EaILHnoTyza3r9zGgsQWB.t.", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "AQMhcavQwY7RBBk3qnDBP3sxjTGie6Fw6QUvwnLXI3Yo", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + }, + { + "index_key": "lab200-lab200-openwrt2", + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$MFY.UM7LQbV4jOFsySKC0.bqb/KHKg9HnMk5j7IZm3sCM4hK9c77y", + "id": "none", + "keepers": null, + "length": 44, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "LbBa8hG48wqJCOPQgEi4LKIWE5nrK1HiLQdsIlbxblMW", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + } + ] + } + ], + "check_results": null +} diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 0000000..490ad28 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,76 @@ +lab_networks = { + lab100 = { + vni = 100 + subnet = "10.100.0.0/24" + wireguard_net = "172.16.100.0/24" + dhcp_mode = "failover" # ISC DHCP with failover + road_warrior = true + gateways = [ + { + hostname = "lab100-gw1" + type = "linux" + mgmt_ip = "192.168.1.11" + api_port = 22 + lab_if = "eth1" + dhcp_role = "primary" + }, + { + hostname = "lab100-rt1" + type = "mikrotik" + mgmt_ip = "192.168.1.12" + api_port = 8728 + lab_if = "ether2" + dhcp_role = "secondary" + }, + { + hostname = "lab100-openwrt1" + type = "openwrt" + mgmt_ip = "192.168.1.14" + api_port = 22 + lab_if = "eth1" + dhcp_role = "backup" + } + ] + } + + lab200 = { + vni = 200 + subnet = "10.200.0.0/24" + wireguard_net = "172.16.200.0/24" + dhcp_mode = "simple" # dnsmasq with split ranges + road_warrior = false + gateways = [ + { + hostname = "lab200-openwrt1" + type = "openwrt" + mgmt_ip = "192.168.1.21" + api_port = 22 + lab_if = "lan1" + dhcp_role = "primary" + }, + { + hostname = "lab200-openwrt2" + type = "openwrt" + mgmt_ip = "192.168.1.22" + api_port = 22 + lab_if = "lan1" + dhcp_role = "secondary" + } + ] + } +} + +road_warrior_users = { + "jdoe" = { + email = "john.doe@company.com" + labs = ["lab100"] + } + "asmith" = { + email = "alice.smith@company.com" + labs = ["lab100"] + } + "finstl1" = { + email = "lutz.finsterle@mahle.com" + labs = ["lab100","lab200"] + } +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..34990b5 --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,72 @@ +lab_networks = { + lab100 = { + vni = 100 + subnet = "10.100.0.0/24" + wireguard_net = "172.16.100.0/24" + dhcp_mode = "failover" # ISC DHCP with failover + road_warrior = true + gateways = [ + { + hostname = "lab100-gw1" + type = "linux" + mgmt_ip = "192.168.1.11" + api_port = 22 + lab_if = "eth1" + dhcp_role = "primary" + }, + { + hostname = "lab100-rt1" + type = "mikrotik" + mgmt_ip = "192.168.1.12" + api_port = 8728 + lab_if = "ether2" + dhcp_role = "secondary" + }, + { + hostname = "lab100-openwrt1" + type = "openwrt" + mgmt_ip = "192.168.1.14" + api_port = 22 + lab_if = "eth1" + dhcp_role = "backup" + } + ] + } + + lab200 = { + vni = 200 + subnet = "10.200.0.0/24" + wireguard_net = "172.16.200.0/24" + dhcp_mode = "simple" # dnsmasq with split ranges + road_warrior = false + gateways = [ + { + hostname = "lab200-openwrt1" + type = "openwrt" + mgmt_ip = "192.168.1.21" + api_port = 22 + lab_if = "lan1" + dhcp_role = "primary" + }, + { + hostname = "lab200-openwrt2" + type = "openwrt" + mgmt_ip = "192.168.1.22" + api_port = 22 + lab_if = "lan1" + dhcp_role = "secondary" + } + ] + } +} + +road_warrior_users = { + "jdoe" = { + email = "john.doe@company.com" + labs = ["lab100"] + } + "asmith" = { + email = "alice.smith@company.com" + labs = ["lab100"] + } +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..dd8c07a --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,27 @@ +variable "lab_networks" { + description = "Map of lab networks with their configuration" + type = map(object({ + vni = number + subnet = string + wireguard_net = string + dhcp_mode = string + road_warrior = bool + gateways = list(object({ + hostname = string + type = string + mgmt_ip = string + api_port = number + lab_if = string + dhcp_role = string + })) + })) +} + +variable "road_warrior_users" { + description = "Map of road warrior users and their lab access" + type = map(object({ + email = string + labs = list(string) + })) + default = {} +}