From d509df731a6691b3e83c1115f5b2f41b4c099d81 Mon Sep 17 00:00:00 2001 From: joshsiv-mulga Date: Fri, 24 Apr 2026 16:10:55 +1000 Subject: [PATCH] fix: portToBr uses Output() to skip sudo PAM audit noise vpcd.service sets AmbientCapabilities which trips sudo's pam_limits into emitting 'sudo: unable to send audit message: Operation not permitted' on stderr. CombinedOutput merged that into stdout and poisoned the port-to-br result, causing verifyBridgeMode to fail with a bogus bridge name on service start after reinstall. Switch to Output() (stdout only) + exec.ExitError.Stderr on error. --- spinifex/services/vpcd/vpcd.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spinifex/services/vpcd/vpcd.go b/spinifex/services/vpcd/vpcd.go index b15c24c6b..630c29761 100644 --- a/spinifex/services/vpcd/vpcd.go +++ b/spinifex/services/vpcd/vpcd.go @@ -465,10 +465,19 @@ func detectBridgeMode(externalIface string) string { // portToBr returns the OVS bridge that owns `port`. Returns "" when the port // is not in OVSDB. Used by the post-detect sanity checks. +// +// Uses Output() (stdout only) because vpcd.service runs with AmbientCapabilities +// set, which causes sudo's PAM to emit "sudo: unable to send audit message" +// warnings on stderr. CombinedOutput would merge those into stdout and poison +// the bridge-name compare. var portToBr = func(port string) (string, error) { - out, err := sudoCommand("ovs-vsctl", "port-to-br", port).CombinedOutput() + out, err := sudoCommand("ovs-vsctl", "port-to-br", port).Output() if err != nil { - return "", fmt.Errorf("ovs-vsctl port-to-br %s: %s: %w", port, strings.TrimSpace(string(out)), err) + var stderr string + if exitErr, ok := err.(*exec.ExitError); ok { + stderr = strings.TrimSpace(string(exitErr.Stderr)) + } + return "", fmt.Errorf("ovs-vsctl port-to-br %s: %s: %w", port, stderr, err) } return strings.TrimSpace(string(out)), nil }