IPsec Gateway Operator¶
v0.2.0 — validated end-to-end 2026-08-20
Every reconciler code path is covered by unit/envtest suites against a fake OpenStack client and a fake VICI session, and the full stack has also been proven on a live reference deployment: tunnel establishment, exposures (including cross-cluster), the reverse-exposure path, PSK rotation, and teardown all ran end-to-end against a real OpenStack project, a real Multus attachment, and a real strongSwan peer. v0.2.0 adds the ClusterLink CRD — inter-cluster fabric plumbing as a reconciled resource — live-verified by adopting a pre-existing hand-made transit leg with zero traffic loss on the standing tunnel. It ships with a few known, deliberate constraints for this release — single gateway pod per tunnel with no HA/spreading, IKEv2 with pre-shared-key authentication only, and one documented egress-policy deviation. See the Known Limitations page for the full, current list before onboarding a tunnel that depends on any of those.
A Kubernetes operator that runs multi-tenant IPsec site-to-site gateways as
one pod per client, declaratively, via a single IPsecGateway CRD — replacing
the current fleet of hand-provisioned VMs (one Ansible-managed VM per client
tunnel) with reconciled, self-describing Kubernetes objects.
What it does¶
- One pod per client. Each
IPsecGatewaygets its own Deployment (strongswan+init-nat+status-agent+ipsec-exportercontainers), its own Neutron port + security group, and its own floating IP — the same isolation model as the VM fleet, running on Kubernetes instead. - SNAT by default; a routed mode is planned but not yet implemented. A
tunnel's traffic is source-NATed through the gateway's own virtual service
network by default (
mode: snat, the only modev0.1.0supports —mode: routedis accepted by the CRD's enum but rejected byValidate()as not-implemented, see Networking). - Virtual service networks carved from
100.64.0.0/10. Every gateway is allocated a/24out of the operator's configured CGNAT pool (100.64.0.0/16by default) — its own private addressing plane for exposures, independent of any tenant's real network. - A dedicated floating IP per client, either allocated fresh from a pool or pinned to an existing one (the mechanism the migration runbook uses to move a client off its VM without a client-side firewall change).
Who this is for¶
Platform/network engineers provisioning a client's IPsec tunnel
You're filling out a client's IPsec parameter sheet (Fortigate, pfSense,
whatever they run) and need to translate it into an IPsecGateway CR.
Start with the Quick Start, then keep the
Parameter Sheet Mapping page open while you work.
Engineers migrating a client off a hand-provisioned VM
You're cutting a client over from an existing, hand-provisioned IPsec VM to this operator. Go straight to the VM-to-Operator Migration runbook.
On-call, triaging a tunnel-down alert
Go straight to Operations.
Architecture at a glance¶
flowchart TB
CR["IPsecGateway CR\n(one per client)"] --> Ctrl["bnerd-ipsec-operator\nreconcile loop"]
Ctrl -->|"EnsureSecurityGroup\nEnsurePort\nEnsureFIP"| Neutron[(Neutron\nOpenStack)]
Ctrl -->|renders + owns| NAD["NetworkAttachmentDefinition\n(Multus, 2nd NIC)"]
Ctrl -->|renders + owns| CM1["ConfigMap\nswanctl.conf"]
Ctrl -->|renders + owns| CM2["ConfigMap\nnat.rules"]
Ctrl -->|renders + owns| SA["ServiceAccount +\nRoleBinding (per tenant ns)"]
Ctrl -->|renders + owns| NP["NetworkPolicy\n(NIC1 ingress only)"]
Ctrl -->|renders + owns| Deploy["Deployment\n(gateway pod)"]
Deploy --> Pod["Gateway Pod"]
Pod --> C1["init-nat\napplies nat.rules"]
Pod --> C2["strongswan\nIKE/IPsec daemon"]
Pod --> C3["status-agent\nvici load-shared +\nconfig reload +\nstatus.tunnels patch"]
Pod --> C4["ipsec-exporter\nprometheus /metrics"]
C3 -.->|"get/patch\nstatus subresource"| CR
NAD -.->|"NIC2, Neutron port"| Neutron
Pod -->|"IKE/ESP over NAT-T\n(FIP = local_id)"| Peer(["Client peer gateway"])
style Ctrl fill:#6366f1,stroke:#4f46e5,color:#fff
style Pod fill:#16a34a,stroke:#15803d,color:#fff
The operator itself never touches the tunnel dataplane — it only ever
provisions OpenStack resources, renders Kubernetes children, and reads status
back over a shared VICI socket via status-agent. The gateway pod's
strongswan container does the actual IKE/ESP work.
CRD at a glance¶
| CRD | Scope | Group/Version | Purpose |
|---|---|---|---|
| IPsecGateway | Namespaced | ipsec.bnerd.net/v1alpha1 |
One CR = one client's IPsec site-to-site gateway pod. The only CRD this operator ships. |
Quick links¶
- Quick Start — install the chart, apply your first
IPsecGateway - Parameter Sheet Mapping — client sheet field → CR field, for filling in a real client's tunnel
- Networking — the on-the-wire tiers, virtual IP conventions, NAT-T/FIP behavior, NetworkPolicy posture
- Operations — status conditions, metrics/alerts, tunnel-down triage, PSK rotation
- VM-to-Operator Migration — cutover runbook with a pinned FIP
- CRD Reference — every
IPsecGatewayspec/status field - Known Limitations — what's spike-pending, deferred, or verified by inspection only