Skip to content

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 IPsecGateway gets its own Deployment (strongswan + init-nat + status-agent + ipsec-exporter containers), 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 mode v0.1.0 supports — mode: routed is accepted by the CRD's enum but rejected by Validate() as not-implemented, see Networking).
  • Virtual service networks carved from 100.64.0.0/10. Every gateway is allocated a /24 out of the operator's configured CGNAT pool (100.64.0.0/16 by 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 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 IPsecGateway spec/status field
  • Known Limitations — what's spike-pending, deferred, or verified by inspection only