mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 19:01:42 +00:00
fix(ipsec): enable openvswitch-ipsec at provision time, drop daemon write
Provisioning now runs `systemctl enable --now openvswitch-ipsec.service` alongside openvswitch-switch and ovn-controller. The spinifex-daemon process drops its `enable --now` shell-out and only polls is-active. Sudoers gains one read-only line: spinifex-daemon ALL=(root) NOPASSWD: /usr/bin/systemctl is-active openvswitch-ipsec.service This mirrors the existing vpcd ovn-controller is-active precedent and removes the daemon's need for systemd-write capability. Aligns with least-privilege service account pattern: provision enables services, daemon only reads status. If the unit is inactive at runtime, the daemon refuses to flip ipsec_encapsulation=true (silent-drop guard from mulga-siv-136) and surfaces a clear error pointing at scripts/setup-ovn.sh.
This commit is contained in:
@@ -721,8 +721,14 @@ echo ""
|
||||
echo "Step 10: Enabling OVN auto-start on boot..."
|
||||
sudo systemctl enable openvswitch-switch 2>/dev/null || true
|
||||
sudo systemctl enable ovn-controller 2>/dev/null || true
|
||||
echo " openvswitch-switch: enabled on boot"
|
||||
echo " ovn-controller: enabled on boot"
|
||||
# ovs-monitor-ipsec drives strongSwan from OVS DB cert pointers. The daemon's
|
||||
# enableOVNIPSec() flips ipsec_encapsulation=true at runtime and silently drops
|
||||
# tunnel traffic if this unit isn't already up — enable at provision time so
|
||||
# daemon never needs systemd-write capability (only is-active read).
|
||||
sudo systemctl enable --now openvswitch-ipsec.service 2>/dev/null || true
|
||||
echo " openvswitch-switch: enabled on boot"
|
||||
echo " ovn-controller: enabled on boot"
|
||||
echo " openvswitch-ipsec: enabled on boot"
|
||||
|
||||
# --- Step 11: Health check ---
|
||||
echo ""
|
||||
|
||||
@@ -186,6 +186,7 @@ install_sudoers() {
|
||||
spinifex-daemon ALL=(root) NOPASSWD: /sbin/ip, /usr/sbin/ip
|
||||
spinifex-daemon ALL=(root) NOPASSWD: /usr/bin/ovs-vsctl, /usr/bin/ovs-appctl
|
||||
spinifex-daemon ALL=(root) NOPASSWD: /usr/sbin/dhcpcd
|
||||
spinifex-daemon ALL=(root) NOPASSWD: /usr/bin/systemctl is-active openvswitch-ipsec.service
|
||||
|
||||
# Spinifex VPC daemon: OVN and OVS read/write, OVN controller status check and DHCP
|
||||
spinifex-vpcd ALL=(root) NOPASSWD: /usr/sbin/dhcpcd
|
||||
|
||||
+10
-10
@@ -12,8 +12,10 @@ import (
|
||||
)
|
||||
|
||||
// systemctlActiveTimeout bounds how long enableOVNIPSec waits for
|
||||
// openvswitch-ipsec.service to report active after a `systemctl enable
|
||||
// --now`. Overridden in tests.
|
||||
// openvswitch-ipsec.service to report active. The unit is enabled at
|
||||
// provision time (scripts/setup-ovn.sh) so the daemon only needs read
|
||||
// capability (systemctl is-active); this poll absorbs boot-time races
|
||||
// where the unit hasn't finished starting yet. Overridden in tests.
|
||||
var systemctlActiveTimeout = 5 * time.Second
|
||||
|
||||
// enableOVNIPSec wires the per-node IPsec peer cert into the local OVS DB and
|
||||
@@ -79,14 +81,12 @@ func (d *Daemon) enableOVNIPSec() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureOVSMonitorIPSecActive enables + starts openvswitch-ipsec.service and
|
||||
// waits for it to report active. Without this guarantee, ipsec_encapsulation
|
||||
// silently drops all tunnel traffic because no userspace daemon is reading
|
||||
// the OVS DB cert pointers to drive strongSwan IKE.
|
||||
// ensureOVSMonitorIPSecActive polls openvswitch-ipsec.service for "active".
|
||||
// The unit is enabled at provision time (scripts/setup-ovn.sh); the daemon
|
||||
// has read-only sudoers scope (systemctl is-active). If the unit is inactive
|
||||
// here, operator intervention is required — daemon refuses to flip
|
||||
// ipsec_encapsulation=true and silently drop tunnel traffic.
|
||||
func ensureOVSMonitorIPSecActive() error {
|
||||
if out, err := sudoCommand("systemctl", "enable", "--now", "openvswitch-ipsec.service").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("enable --now: %s: %w", strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
deadline := time.Now().Add(systemctlActiveTimeout)
|
||||
var lastOut string
|
||||
for time.Now().Before(deadline) {
|
||||
@@ -97,5 +97,5 @@ func ensureOVSMonitorIPSecActive() error {
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("not active after %s: %s", systemctlActiveTimeout, lastOut)
|
||||
return fmt.Errorf("not active after %s: %s (provision via scripts/setup-ovn.sh)", systemctlActiveTimeout, lastOut)
|
||||
}
|
||||
|
||||
@@ -65,23 +65,21 @@ func TestEnableOVNIPSec(t *testing.T) {
|
||||
require.NoError(t, d.enableOVNIPSec())
|
||||
|
||||
// Expected sudo invocations in order:
|
||||
// systemctl enable --now openvswitch-ipsec.service
|
||||
// systemctl is-active openvswitch-ipsec.service
|
||||
// systemctl is-active openvswitch-ipsec.service (unit enabled at provision time)
|
||||
// ovs-vsctl set Open_vSwitch . other_config:certificate=... private_key=... ca_cert=...
|
||||
// ovs-vsctl set Open_vSwitch . other_config:ipsec_encapsulation=true
|
||||
require.Len(t, recorder.runs, 4)
|
||||
assert.Equal(t, []string{"systemctl", "enable", "--now", "openvswitch-ipsec.service"}, recorder.runs[0])
|
||||
assert.Equal(t, []string{"systemctl", "is-active", "openvswitch-ipsec.service"}, recorder.runs[1])
|
||||
for _, run := range recorder.runs[2:] {
|
||||
require.Len(t, recorder.runs, 3)
|
||||
assert.Equal(t, []string{"systemctl", "is-active", "openvswitch-ipsec.service"}, recorder.runs[0])
|
||||
for _, run := range recorder.runs[1:] {
|
||||
assert.Equal(t, "ovs-vsctl", run[0])
|
||||
assert.Equal(t, "set", run[1])
|
||||
assert.Equal(t, "Open_vSwitch", run[2])
|
||||
}
|
||||
joined := strings.Join(recorder.runs[2], " ")
|
||||
joined := strings.Join(recorder.runs[1], " ")
|
||||
assert.Contains(t, joined, "other_config:certificate="+filepath.Join(configDir, "ipsec", "peer.pem"))
|
||||
assert.Contains(t, joined, "other_config:private_key="+filepath.Join(configDir, "ipsec", "peer.key"))
|
||||
assert.Contains(t, joined, "other_config:ca_cert="+filepath.Join(configDir, "ca.pem"))
|
||||
assert.Contains(t, strings.Join(recorder.runs[3], " "), "other_config:ipsec_encapsulation=true")
|
||||
assert.Contains(t, strings.Join(recorder.runs[2], " "), "other_config:ipsec_encapsulation=true")
|
||||
}
|
||||
|
||||
func TestEnableOVNIPSec_SingleNodeSkip(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user