diff --git a/lib/devicetrust/native/device_linux.go b/lib/devicetrust/native/device_linux.go index 35a41731c81a1..b364cdd67ba89 100644 --- a/lib/devicetrust/native/device_linux.go +++ b/lib/devicetrust/native/device_linux.go @@ -271,10 +271,12 @@ func readDMIInfoEscalated() (*linux.DMIInfo, error) { return nil, trace.Wrap(err, "reading current executable") } + sudo := sudoPath() + // Run `sudo -v` first to re-authenticate, then run the actual tsh command // using `sudo --non-interactive`, so we don't risk getting sudo output // mixed with our desired output. - sudoCmd := exec.Command("/usr/bin/sudo", "-v") + sudoCmd := exec.Command(sudo, "-v") sudoCmd.Stdout = os.Stdout sudoCmd.Stderr = os.Stderr sudoCmd.Stdin = os.Stdin @@ -289,7 +291,7 @@ func readDMIInfoEscalated() (*linux.DMIInfo, error) { defer cancel() dmiOut := &bytes.Buffer{} - dmiCmd := exec.CommandContext(ctx, "/usr/bin/sudo", "-n", tshPath, "device", "dmi-read") + dmiCmd := exec.CommandContext(ctx, sudo, "-n", tshPath, "device", "dmi-read") dmiCmd.Stdout = dmiOut if err := dmiCmd.Run(); err != nil { return nil, trace.Wrap(err, "running `sudo tsh device dmi-read`") @@ -330,3 +332,33 @@ func saveDMIInfoToCache(dmiInfo *linux.DMIInfo) error { return nil } + +func sudoPath() string { + const defaultSudoPath = "/usr/bin/sudo" + + for i, path := range []string{ + defaultSudoPath, // preferred + + // Fallbacks are only allowed if /usr/bin/sudo does not exist. + // If it does exist, then it's used regardless of any other errors that may + // happen later. + "/run/wrappers/bin/sudo", // NixOS + } { + if _, err := os.Stat(path); err != nil { + slog.DebugContext( + context.Background(), + "Failed to stat sudo binary", + "error", err, + "path", path, + ) + continue + } + if i > 0 { + slog.DebugContext(context.Background(), "Using alternative sudo path", "path", path) + } + return path + } + + // If everything fails then use the default and hard-fail later. + return defaultSudoPath +}