OPNsense VM Integration Example
Fully automated end-to-end integration test: boots an OPNsense VM via KVM, bootstraps SSH and API access without any manual browser interaction, installs packages, runs 11 Harmony Scores, and verifies idempotency (runs all Scores twice, asserts zero duplicates). CI-friendly.
Quick start
# 1. One-time setup (libvirt, Docker compatibility)
./examples/opnsense_vm_integration/setup-libvirt.sh
# 2. Verify prerequisites
cargo run -p opnsense-vm-integration -- --check
# 3. Boot + bootstrap + integration test (fully unattended)
cargo run -p opnsense-vm-integration -- --full
# 4. Clean up
cargo run -p opnsense-vm-integration -- --clean
Or use the build script:
./build/opnsense-e2e.sh # check + boot + test
./build/opnsense-e2e.sh --download # download image first
./build/opnsense-e2e.sh --clean # tear down
That's it. No browser clicks, no manual SSH setup, no wizard interaction.
What happens during --full
- Downloads OPNsense 26.1 nano image (~350MB, cached after first download)
- Injects
config.xmlwith virtio interface assignments (vtnet0=LAN, vtnet1=WAN) - Creates a 4 GiB qcow2 disk and boots via KVM (1 vCPU, 1GB RAM, 4 NICs)
- Waits for web UI to respond (~20s)
- Automated bootstrap via
OPNsenseBootstrap:- Logs in (root/opnsense) with CSRF token handling
- Aborts the initial setup wizard
- Enables SSH with root login and password auth
- Changes web GUI port to 9443 (avoids HAProxy conflicts)
- Restarts lighttpd via SSH to apply the port change
- Creates OPNsense API key via SSH (PHP script)
- Installs
os-haproxyvia firmware API - Runs 11 Scores configuring the entire firewall
- Verifies all configurations via REST API assertions
- Idempotency test: runs all 11 Scores again, asserts entity counts are unchanged
Step-by-step mode
If you prefer to separate boot and test:
# Boot + bootstrap (creates VM, enables SSH, sets port)
cargo run -p opnsense-vm-integration -- --boot
# Run integration test (assumes VM is bootstrapped)
cargo run -p opnsense-vm-integration
# Check VM status at any time
cargo run -p opnsense-vm-integration -- --status
Prerequisites
System requirements
- Linux with KVM support (Intel VT-x/AMD-V)
- ~10 GB free disk space
- ~15 minutes for first run (image download + firmware update)
- Subsequent runs: ~2 minutes
Required packages
Arch/Manjaro:
sudo pacman -S libvirt qemu-full dnsmasq
Fedora:
sudo dnf install libvirt qemu-kvm dnsmasq
Ubuntu/Debian:
sudo apt install libvirt-daemon-system qemu-kvm dnsmasq
Automated setup
./examples/opnsense_vm_integration/setup-libvirt.sh
This handles: user group membership, libvirtd startup, default storage pool, Docker FORWARD policy conflict.
After running setup, apply group membership:
newgrp libvirt
Docker + libvirt compatibility
Docker sets the iptables FORWARD policy to DROP, which blocks libvirt's NAT networking. The setup script detects this and switches libvirt to the iptables firewall backend so both coexist.
Scores applied
| # | Score | What it configures |
|---|---|---|
| 1 | LoadBalancerScore |
HAProxy with 2 frontends, backends with TCP health checks |
| 2 | DhcpScore |
DHCP range, 2 static host bindings, PXE boot options |
| 3 | TftpScore |
TFTP server serving boot files |
| 4 | NodeExporterScore |
Prometheus node exporter |
| 5 | VlanScore |
2 VLANs (tags 100, 200) on vtnet0 |
| 6 | FirewallRuleScore |
Firewall filter rules with logging |
| 7 | OutboundNatScore |
Source NAT for outbound traffic |
| 8 | BinatScore |
Bidirectional 1:1 NAT |
| 9 | VipScore |
Virtual IPs (IP aliases) |
| 10 | DnatScore |
Port forwarding rules |
| 11 | LaggScore |
Link aggregation (vtnet2+vtnet3) |
All Scores are idempotent: the test runs them twice and asserts entity counts are unchanged. This catches duplicate creation bugs that mock tests cannot detect.
Network architecture
Host (192.168.1.10) --- virbr-opn bridge --- OPNsense LAN (192.168.1.1)
192.168.1.0/24 vtnet0
NAT to internet
--- virbr0 (default) --- OPNsense WAN (DHCP)
192.168.122.0/24 vtnet1
NAT to internet
Environment variables
| Variable | Default | Description |
|---|---|---|
RUST_LOG |
(unset) | Log level: info, debug, trace |
HARMONY_KVM_URI |
qemu:///system |
Libvirt connection URI |
HARMONY_KVM_IMAGE_DIR |
~/.local/share/harmony/kvm/images |
Cached disk images |
Troubleshooting
VM won't start / permission denied
Ensure your user is in the libvirt group and that the image directory is traversable by the qemu user. The setup script handles this.
192.168.1.0/24 conflict
If your host network already uses this subnet, the VM will be unreachable. Edit the constants in src/main.rs to use a different subnet.
HAProxy install fails OPNsense may need a firmware update first. The integration test attempts this automatically. If it fails, connect to the web UI at https://192.168.1.1:9443 and update manually.
Serial console access
virsh -c qemu:///system console opn-integration
# Press Ctrl+] to exit