6.6 KiB
sys-fips: Single-Node FIPS ProxyVM for Qubes OS
Overview
Simplify the current fips_setup to run one FIPS node in a Qubes ProxyVM called sys-fips. This VM slots into the Qubes network chain just like sys-vpn:
AppVM → sys-fips → sys-firewall → sys-net → Internet
↘ FIPS Mesh (encrypted overlay)
To get a second independent FIPS node, simply clone sys-fips in dom0 — each clone gets its own identity and peer config.
Architecture
graph LR
A[AppVM-1] --> SF[sys-fips]
B[AppVM-2] --> SF
SF -->|regular traffic| FW[sys-firewall]
SF -->|fips0 TUN| MESH[FIPS Mesh]
FW --> NET[sys-net]
NET --> INET[Internet]
What changes from the current plan
| Current plan | Revised plan |
|---|---|
| Two FIPS nodes per VM - fips0 + fips1 | One FIPS node per VM - fips0 only |
| Two configs - node-a.yaml, node-b.yaml | One config - fips.yaml |
| Two keypairs generated | One keypair - persistent identity |
| Localhost peering between nodes | No local peering needed |
| Complex start/stop for two processes | Single systemd service |
| Clone not considered | Clone-friendly design |
Network flow inside sys-fips
graph TD
subgraph sys-fips ProxyVM
VIF[vif* - from AppVMs] -->|fd00::/8 traffic| FW6[ip6tables FORWARD]
FW6 --> FIPS0[fips0 TUN interface]
VIF -->|regular traffic| ETH0[eth0 - to sys-firewall]
DNS53[dnsmasq :53] -->|.fips queries| DNS5354[FIPS DNS :5354]
DNS53 -->|other queries| UPSTREAM[upstream DNS]
end
FIPS0 --> MESH[FIPS Mesh Network]
ETH0 --> SYSFW[sys-firewall]
Revised file structure
fips_setup/
├── README.md # Updated overview and instructions
├── configs/
│ └── fips.yaml # Single node config template
├── scripts/
│ ├── 01-dom0-create-sys-fips.sh # Create sys-fips ProxyVM in dom0
│ ├── 02-install-fips.sh # Install binary + deps in sys-fips
│ ├── 03-configure-identity.sh # Generate or import keypair
│ ├── 04-start-fips.sh # Start single FIPS node + routing
│ ├── 05-stop-fips.sh # Stop FIPS node + cleanup
│ ├── 06-configure-dns.sh # dnsmasq for .fips resolution
│ ├── 07-route-appvms.sh # ip6tables forwarding for AppVMs
│ ├── 08-add-peer.sh # Add an external peer to config
│ └── test-connectivity.sh # Unified test script
└── plans/
└── sys-fips-plan.md # This file
Implementation plan
1. Simplify config to single node
Replace fips-node-a.yaml and fips-node-b.yaml with a single fips.yaml:
node:
identity:
persistent: true # Auto-generates keypair on first run, reuses on restart
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
mtu: 1472
peers: []
Key difference: persistent: true instead of explicit nsec. FIPS auto-generates /etc/fips/fips.key and /etc/fips/fips.pub on first run. When you clone the VM, the clone starts fresh and generates its own identity.
2. Rewrite dom0 script - 01-dom0-create-sys-fips.sh
- Rename VM from
fips-proxytosys-fipsto follow Qubes naming convention - Accept optional
netvmparameter - defaults tosys-firewallbut supports chaining throughsys-vpn - Same
provides_network=Truesetup
3. Simplify install script - 02-install-fips.sh
- Same as current but also installs the upstream systemd service files from FIPS packaging
- Installs
fips.serviceandfips-dns.servicefrom the FIPS repo packaging directory - Copies the single
fips.yamlconfig template
4. New identity script - 03-configure-identity.sh
- If
/etc/fips/fips.keyexists, show current npub and ask to keep or regenerate - If not, let FIPS generate on first start via
persistent: true - Option to import an existing nsec for a known identity
- This is what makes cloning work: after cloning, run this script to get a fresh identity
5. Simplify start/stop - 04-start-fips.sh / 05-stop-fips.sh
-
Start:
systemctl start fips— uses the upstream systemd unit -
Enable IPv6 forwarding
-
Add
fd00::/8 → fips0route -
Wait for
fips0TUN to appear -
Show npub and IPv6 address
-
Stop:
systemctl stop fips+ cleanup routes
6. DNS - 06-configure-dns.sh
- Same dnsmasq approach but simplified — only one FIPS DNS backend on port 5354
- No changes needed from current plan
7. Routing - 07-route-appvms.sh
- Same ip6tables forwarding rules but only for
fips0— nofips1 - Persist via
/rw/config/rc.local
8. New peer management - 08-add-peer.sh
- Interactive script to add a peer to
/etc/fips/fips.yaml - Takes npub, alias, and address as arguments
- Appends to the peers list in the YAML config
- Restarts FIPS to pick up the new peer
9. Unified test script - test-connectivity.sh
- Merge
test-local.sh,test-from-appvm.sh, andtest-full-suite.shinto one - Auto-detects whether running inside sys-fips or an AppVM
- Tests: binary installed, fips0 up, IPv6 address, DNS resolution, peer connectivity
Cloning workflow
Once sys-fips is working:
# In dom0:
qvm-clone sys-fips sys-fips-2
# Start sys-fips-2, open terminal:
sudo bash 03-configure-identity.sh # Generates new keypair
sudo systemctl restart fips
# Now you have two independent FIPS nodes:
# sys-fips → identity A, peers X
# sys-fips-2 → identity B, peers Y
# Point AppVMs at whichever you want:
qvm-prefs work-vm netvm sys-fips
qvm-prefs personal-vm netvm sys-fips-2
Qubes network chaining
sys-fips can chain through a VPN qube:
AppVM → sys-fips → sys-vpn → sys-firewall → sys-net → Internet
Set this up in dom0:
qvm-prefs sys-fips netvm sys-vpn
FIPS UDP traffic exits through the VPN tunnel, adding another layer of transport privacy. The FIPS mesh encryption is independent of the VPN — even if the VPN is compromised, FIPS traffic remains encrypted end-to-end.
What gets deleted
configs/fips-node-a.yaml— replaced by singleconfigs/fips.yamlconfigs/fips-node-b.yaml— no longer neededscripts/03-generate-node-configs.sh— replaced by03-configure-identity.shscripts/test-local.sh— merged intotest-connectivity.shscripts/test-from-appvm.sh— merged intotest-connectivity.shscripts/test-full-suite.sh— merged intotest-connectivity.sh