From aa10c19aef58e413b9f445549e51f64428e6bf6f Mon Sep 17 00:00:00 2001 From: joshsiv-mulga Date: Mon, 25 May 2026 20:02:45 +1000 Subject: [PATCH] fix(ovn): pin OVS system-id to hostname for IPsec cert identity match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ovs-monitor-ipsec uses chassis-id as the IKEv2 `@` peer identity in the strongSwan config it generates. Our per-node IPsec peer cert (admin.GenerateIPSecPeerCert) carries the node name as CN + dnsName SAN. With chassis-id left at the package-generated UUID, charon validates `@` against a cert with dnsName= — mismatch returns AUTHENTICATION_FAILED and no xfrm SAs are negotiated. Pin system-id to hostname (which equals the node name on Mulga clusters) by writing both the on-disk source of truth (/etc/openvswitch/system-id.conf, read by ovs-ctl on boot) and the running OVS DB value. Writing only one would drift on the next reboot — the original "do not override" comment was right about that hazard, just wrong about the resolution. Cross-link the invariant in admin/ipsec.go: changing the cert identity without updating setup-ovn.sh (or vice versa) breaks IKE silently. Refs: mulga-siv-136 --- scripts/setup-ovn.sh | 25 ++++++++++++++++++++----- spinifex/admin/ipsec.go | 9 +++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/scripts/setup-ovn.sh b/scripts/setup-ovn.sh index cff918d98..b5b314171 100755 --- a/scripts/setup-ovn.sh +++ b/scripts/setup-ovn.sh @@ -597,11 +597,26 @@ else external_ids:ovn-encap-type="geneve" fi -# system-id is owned by the openvswitch-switch package (persisted in -# /etc/openvswitch/system-id.conf and re-applied on every boot). Read it back -# rather than overriding — overriding here would drift from the on-disk value -# and the next reboot would silently flip the chassis identity (mulga-999). -echo " system-id: $(sudo ovs-vsctl get open . external_ids:system-id)" +# Pin OVS system-id (= OVN chassis-id) to the node's hostname. Two reasons: +# 1. IPsec identity: ovs-monitor-ipsec uses chassis-id as the IKEv2 +# `@` peer identity. Our per-node IPsec peer cert +# (admin.GenerateIPSecPeerCert) carries the node name as CN + dnsName +# SAN — see spx admin init/join, which pass --node as the cert +# identity. Leaving chassis-id as the package-generated UUID would +# cause `received AUTHENTICATION_FAILED` because charon validates +# `@` against a cert dnsName=. +# 2. Ops legibility: `ovn-sbctl show` lists chassis by name, not UUID. +# Both the on-disk file (read by ovs-ctl on boot) and the running OVS DB +# value must be updated atomically — writing only one drifts on the next +# reboot. +NODE_SYSTEM_ID="$(hostname -s)" +if [ -z "$NODE_SYSTEM_ID" ]; then + echo " ERROR: hostname -s returned empty; cannot pin OVS system-id." >&2 + exit 1 +fi +echo "$NODE_SYSTEM_ID" | sudo tee /etc/openvswitch/system-id.conf >/dev/null +sudo ovs-vsctl set Open_vSwitch . external_ids:system-id="$NODE_SYSTEM_ID" +echo " system-id: $NODE_SYSTEM_ID (pinned to hostname for IPsec cert identity)" echo " ovn-remote: $OVN_REMOTE" echo " ovn-encap-ip: $ENCAP_IP" echo " ovn-encap-type: geneve" diff --git a/spinifex/admin/ipsec.go b/spinifex/admin/ipsec.go index 589433b84..ad330872e 100644 --- a/spinifex/admin/ipsec.go +++ b/spinifex/admin/ipsec.go @@ -48,6 +48,15 @@ func IPSecCertPaths(configDir string) (certPath, keyPath string) { // SANs discovered from the local machine's interfaces are not added here — // IPSec peer identity should be the explicit cluster identity only, not // every interface address, to keep peer-identity matching strict. +// +// Cross-cutting invariant: this dnsName SAN must equal the OVS chassis-id +// (Open_vSwitch.external_ids:system-id) on every node, because +// ovs-monitor-ipsec generates strongSwan configs with leftid=@ +// / rightid=@ and charon validates those names against the +// peer cert's dnsName SAN. setup-ovn.sh pins system-id to `hostname -s` +// for exactly this reason — see the comment in setup-ovn.sh near +// `system-id.conf`. Changing the cert identity here without updating +// setup-ovn.sh (or vice versa) breaks IKE with AUTHENTICATION_FAILED. // GenerateIPSecPeerCertIfEnabled is a no-op when enabled is false. Used by // admin init and admin join so the call site reads as a single guarded line // keyed off the cluster's network.ipsec_enabled flag.