POST /api/v1/air/provision regenerates the WireGuard keypair every time it is called, even for peers that already exist in mesh.yaml. Combined with auto_apply: true, this silently breaks WireGuard connectivity for existing peers.
This caused repeated key mismatches during the Brussels hub migration — every hub apply or hydracluster provision call triggered a new keypair.
Make the endpoint truly idempotent: if the peer already exists in mesh.yaml, return the existing config (with the existing private key) instead of generating a new keypair. Only generate a new keypair when the peer is genuinely new.
Fixed in hydraguard v2.3.0, deployed and verified on the hub
/api/v1/air/provisionand/api/v1/headipad/provisiongenerated a keypair before checking whether the peer existed, then overwrote its stored public key. Withauto_apply: truethe new hub config applied immediately, so a call that read like a lookup silently disconnected a peer still holding the old private key.The fix proposed here was not implementable
The issue asks to "return the existing config (with the existing private key)". That cannot be done:
mesh.Airstores onlyPublicKey. The private key is shown once at creation and never persisted — by design — so there is nothing to hand back.The closest correct behaviour is to refuse, and say how to proceed deliberately:
"force": true, logged as a warning since the device is disconnected until updatedSame change applied to the head-iPad endpoint, which had the identical bug.
Verified on the live hub
The API service no longer needs to be kept stopped for this reason
The runbook's standing instruction to keep
hydraguardstopped existed because of this bug. That warning has been replaced.Two things found while fixing it
keygen.Generatewas untestable. It shells out towg genkey, so provisioning could not be tested anywhere without wireguard-tools installed. Now overridable viaSetGenerator/ResetGenerator, matching the existingwireguard.SetApplierconvention. The regression tests fail against the old behaviour with "the peer's key changed on a call that should have been a no-op".The runbook's mesh table was wrong and caused a real mistake. It listed peers as
air-001,air-tvl-oneand so on; the actual IDs are bare (001,tvl-one). Trusting it, I provisionedair-001against the live hub while testing this fix — which correctly found no such peer and created a new one at10.10.100.17. Removed and reapplied; the mesh is back to 24 peers. The table has been regenerated from the live mesh, with the command to regenerate it so the next update is a paste rather than a transcription.