Files
fips_setup/plans/sys-fips-plan.md
2026-04-11 14:20:42 -04:00

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-proxy to sys-fips to follow Qubes naming convention
  • Accept optional netvm parameter - defaults to sys-firewall but supports chaining through sys-vpn
  • Same provides_network=True setup

3. Simplify install script - 02-install-fips.sh

  • Same as current but also installs the upstream systemd service files from FIPS packaging
  • Installs fips.service and fips-dns.service from the FIPS repo packaging directory
  • Copies the single fips.yaml config template

4. New identity script - 03-configure-identity.sh

  • If /etc/fips/fips.key exists, 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 → fips0 route

  • Wait for fips0 TUN 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 — no fips1
  • 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, and test-full-suite.sh into 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 single configs/fips.yaml
  • configs/fips-node-b.yaml — no longer needed
  • scripts/03-generate-node-configs.sh — replaced by 03-configure-identity.sh
  • scripts/test-local.sh — merged into test-connectivity.sh
  • scripts/test-from-appvm.sh — merged into test-connectivity.sh
  • scripts/test-full-suite.sh — merged into test-connectivity.sh