Self-service WireGuard enrollment ships in hydraheadipad v0.2.156 (#449), but the tunnel's private key is still generated on HydraGuard, sent over the wire, and stored at rest. The device should generate its own keypair and send only the public half.
handleFleetEnrollHead calls provisionHeadIPadWireGuard, HydraGuard generates a keypair and returns the full config including PrivateKey.nodes.yaml on disk indefinitely.GET /api/v1/heads/{id}/wireguard-config and writes it into its App Group.So the private key exists in at least three places, only one of which is the device that needs it. HydraGuard itself is the strict one here: it shows a private key only at creation and never stores it, which is why a lost config means re-keying rather than re-reading. hydracluster is the weak link.
The iPad generates a Curve25519 keypair locally, keeps the private half in its App Group (or the keychain), and sends only the public key at enrollment. HydraGuard registers that public key against a slot and returns the peer half: server public key, endpoint, allowed IPs, assigned address. hydracluster stores none of it.
That removes the class of problem rather than guarding it: there is no private key to leak from nodes.yaml, none in transit, and none in a QR on an admin's screen.
It needs a HydraGuard provisioning API change. POST /api/v1/headipad/provision currently takes {"name": ...} and generates the keypair server-side (hydraguard/pkg/api/handlers.go, around the 409-on-duplicate path). It would need to accept an optional public key and skip generation when one is supplied.
Phase 1 was deliberately the smaller move: wire up what already existed on the device and fix the auth so a head can fetch its own config. The app work is compatible with phase 2 — same "Set up WireGuard" button, with the config assembled locally instead of fetched whole.
provisionHeadIPadWireGuard is still called only from the enrollment handler, so a head whose provisioning failed cannot recover without being deleted and re-enrolled. With device-generated keys this becomes cheap: the device can just re-register a fresh public key.mesh.yaml today.ipad-head-cederik currently has a peer named ipad-head-2. A key-based identity would make the name cosmetic.