IPsec Gateway Operator¶
Project/module identity: bnerd-vpn-operator (formerly bnerd-ipsec-operator).
v0.3.0 — unified vpn.bnerd.net model
v0.3.0 adds a new, protocol-neutral API group — vpn.bnerd.net/v1alpha1
(VPNGateway + VPNPeer) — covering both IPsec and WireGuard
gateways under one model, with per-peer authorization (see The unified
model and WireGuard
gateways). It is served alongside, not
instead of, the frozen ipsec.bnerd.net/v1alpha1 group documented
throughout the rest of this site — see Migrating from
IPsecGateway if you're moving an
existing client over. Every reconciler code path is covered by the
unit/envtest suites. See Known
Limitations for the complete, current
list before onboarding anything that depends on either group's still-open
items.
v0.2.0 (ipsec.bnerd.net group)
Every reconciler code path is covered by unit/envtest suites against a fake OpenStack client and a fake VICI session. v0.2.0 adds the ClusterLink CRD — inter-cluster fabric plumbing as a reconciled resource. 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-vpn-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. Feature-frozen, bugfix-only as of v0.3.0. |
| ClusterLink | Cluster-scoped | ipsec.bnerd.net/v1alpha1 |
One CR = one service cluster plumbed into the transit fabric. |
| VPNGateway | Namespaced | vpn.bnerd.net/v1alpha1 |
One CR = one client's protocol-neutral (ipsec or wireguard) gateway pod. New in v0.3.0, pre-release. |
| VPNPeer | Namespaced | vpn.bnerd.net/v1alpha1 |
One CR = one remote party of a VPNGateway, with per-peer authorization. New in v0.3.0, pre-release. |
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
- The unified model —
VPNGateway/VPNPeerand per-peer authorization (v0.3.0, pre-release) - WireGuard gateways — kernel prerequisite, SG semantics, key handling (
v0.3.0, pre-release) - Migrating from IPsecGateway — per-client cutover runbook to the unified model (
v0.3.0, pre-release) - CRD Reference — every spec/status field, both API groups
- Known Limitations — what's spike-pending, deferred, or verified by inspection only