Migrating from IPsecGateway to VPNGateway/VPNPeer¶
Pre-release, v0.3.0 — not yet e2e-validated
This runbook follows directly from the reconciler's documented FIP/
serviceCIDR pinning contracts (the same mechanism the
VM-to-Operator Migration runbook already relies on,
live-validated in v0.1.0) but has not itself been exercised against
a live IPsecGateway → VPNGateway/VPNPeer cutover. Treat this as
the plan, not a track record. See the index page and
Known Limitations.
v0.3.0 ships the vpn.bnerd.net/v1alpha1 group (VPNGateway/VPNPeer,
see The unified model) alongside, not instead of,
the frozen ipsec.bnerd.net/v1alpha1 group (IPsecGateway). Both are
served by the same operator/manager binary at once (cmd/main.go registers
both reconcilers) — an existing IPsecGateway keeps reconciling exactly as
it does today; nothing about it changes just because v0.3.0 is installed.
There is no conversion webhook and no in-place upgrade path between the
two kinds: moving a client from IPsecGateway to VPNGateway/VPNPeer is
a manual, per-CR recreate, following the same pinned-FIP discipline the
VM-to-Operator Migration runbook already established.
Why per-CR, not a bulk cutover¶
Every IPsecGateway has its own floating IP, its own service CIDR, and its
own tunnel/PSK configuration — none of that is shared infrastructure that
could be cut over once for every client. Migrate one client's CR pair at a
time, at a maintenance window that suits that client, using the same
pinned-value contract the VM migration runbook uses: the values that must
stay byte-for-byte identical across the recreate are exactly the ones the
client's own firewall/parameter sheet references.
What must stay pinned¶
Old (IPsecGateway) |
New (VPNGateway/VPNPeer) |
Why it must match |
|---|---|---|
spec.endpoint.floatingIP (the associated FIP) |
VPNGateway.spec.endpoint.floatingIP |
The client's firewall dials this IP — an unpinned VPNGateway allocates a new FIP from the pool, which is a client-side config change you don't want mid-migration. |
status.serviceCIDR |
VPNGateway.spec.serviceCIDR |
Any exposures[]/reverseExposures[] virtual IPs the client's side already has hardcoded (firewall rules, DNS) only keep working if the same /24 is re-pinned; an unpinned VPNGateway allocates the next free /24, not necessarily the same one. |
spec.tunnels[].peer.address / .id |
VPNPeer.spec.ipsec.peer.address / .id |
The IKE peer identity/address the client's own gateway expects. |
spec.tunnels[].remoteSubnets[] |
VPNPeer.spec.ipsec.remoteSubnets[] |
Traffic selectors — a mismatch here fails IKE Phase 2 negotiation, not just a cosmetic drift. |
spec.auth.pskSecretRef (content, not necessarily the Secret name) |
VPNPeer.spec.ipsec.pskSecretRef |
Same pre-shared key value — the new model moves the PSK reference from gateway-wide (IPsecGateway.spec.auth) to per-peer (VPNPeer.spec.ipsec.pskSecretRef), but the value the client authenticates with must be unchanged. |
spec.ike/spec.tunnels[].esp proposals |
VPNGateway.spec.ipsec.ike / VPNPeer.spec.ipsec.esp |
Must still be in the approved allowlist (unchanged between groups) and should match what the client peer is configured to propose, to avoid a re-negotiation surprise. |
Everything else (exposure names, target addresses/ports, DPD timers) can
be copied straight across — the ExposureSpec/TargetSpec shapes are
identical between the two groups.
Runbook¶
1. Draft the new CRs alongside the old one¶
Write the equivalent VPNGateway (protocol: ipsec) and one VPNPeer
per spec.tunnels[] entry the old IPsecGateway had, with the pinned
values from the table above. Do not apply them yet — the old
IPsecGateway still owns the floating IP, so applying now would just
fail at FIPAttached (already associated with a different port), the
same safe pre-stage state the VM-migration runbook's step 1 describes.
Since this is a single-tunnel-per-client migration in the common case,
the resulting VPNPeer will have exactly one peer per gateway and no
authorization block needed — it lands in the
default-allow
shape automatically, reaching every exposure the old IPsecGateway
exposed, with no grant list to write. Only add an authorization block if
you're intentionally adding a second peer to the same gateway as part of
this migration (which flips the gateway to default-deny — see the
unified model).
2. At the maintenance window: delete the old CR, apply the new ones¶
Deleting the IPsecGateway runs its full finalizer unwind (owned
Kubernetes children first, then OpenStack cleanup) — critically, this
disassociates but does not release a pinned FIP (Cleanup never
releases a pinned FIP back to the pool, same contract the VM-migration
runbook relies on), so the same address is immediately available for the
new VPNGateway's own EnsureFIP to pin.
The client's tunnel is down for the span between the delete and the new
gateway pod reaching ESTABLISHED — budget for a brief outage, same as
any other maintenance-window cutover. Based on the pinned-FIP mechanism's
v0.1.0 live validation (association + strongSwan initiation), expect the
tunnel to re-establish within roughly a minute of the new VPNGateway
reaching FIPAttached=True, assuming the client's peer retries on its own
DPD/retry window without any action on their end.
3. Verify¶
kubectl get vpngateway <name> -o jsonpath='{.status.conditions}' | jq .
kubectl get vpnpeer <name> -o jsonpath='{.status.conditions}' | jq .
kubectl exec -n <tenant-ns> deploy/<name> -c strongswan -- swanctl --list-sas
Confirm the peer's Authorized condition (see the unified
model) as well as the
tunnel state — a peer can be ESTABLISHED at the IKE layer while still
dark on authorization if the authorization block was mistyped, which is
easy to miss if you only check swanctl --list-sas.
Rollback¶
Re-apply the old IPsecGateway CR:
The same pinned-FIP contract applies in reverse — deleting the
VPNGateway disassociates (never releases) the FIP, and the old
IPsecGateway's EnsureFIP picks it back up. The client's firewall never
needed to change in either direction, exactly as in the VM-to-Operator
Migration runbook.
What this migration does not do¶
- It does not retire the
IPsecGatewayCRD. Both groups are served in parallel indefinitely as ofv0.3.0; dropping the servedipsec.bnerd.netkind is a separate, Bernd-gated decision, earliestv0.5.0, and is out of scope for this migration entirely. - It is not the same project as the old
bnerd-vpn-gatewayrepo's WireGuard road-warrior migration. That standalone repo is archived only afterv0.4.0ships full road-warrior parity in this operator — also out of scope here. - It does not batch-convert every
IPsecGatewayat once. Each client is its own maintenance window, its own pinned values, and its own verification pass.