From e36b6e079e8e524afafb6be62d45aa0ee00d1def Mon Sep 17 00:00:00 2001 From: Yevhenii Shcherbina Date: Wed, 21 Jan 2026 16:31:54 +0000 Subject: [PATCH 1/2] chore: allow compilation on non-linux platforms --- run/{run.go => run_linux.go} | 3 +++ run/run_stub.go | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) rename run/{run.go => run_linux.go} (96%) create mode 100644 run/run_stub.go diff --git a/run/run.go b/run/run_linux.go similarity index 96% rename from run/run.go rename to run/run_linux.go index 9353090b..b73579ea 100644 --- a/run/run.go +++ b/run/run_linux.go @@ -1,3 +1,5 @@ +//go:build linux + package run import ( @@ -20,3 +22,4 @@ func Run(ctx context.Context, logger *slog.Logger, cfg config.AppConfig) error { return fmt.Errorf("unknown jail type: %s", cfg.JailType) } } + diff --git a/run/run_stub.go b/run/run_stub.go new file mode 100644 index 00000000..895fb5fc --- /dev/null +++ b/run/run_stub.go @@ -0,0 +1,17 @@ +//go:build !linux + +package run + +import ( + "context" + "fmt" + "log/slog" + "runtime" + + "github.com/coder/boundary/config" +) + +func Run(ctx context.Context, logger *slog.Logger, cfg config.AppConfig) error { + return fmt.Errorf("boundary is only supported on Linux, current platform: %s", runtime.GOOS) +} + From 94f5ad128fab05927b0bc1b6f6ae2e8286a96d9b Mon Sep 17 00:00:00 2001 From: Yevhenii Shcherbina Date: Wed, 21 Jan 2026 20:36:19 +0000 Subject: [PATCH 2/2] feat: handle privilege escalation in boundary --- cli/cli.go | 9 ++++ privilege/privilege_linux.go | 98 ++++++++++++++++++++++++++++++++++++ privilege/privilege_stub.go | 14 ++++++ 3 files changed, 121 insertions(+) create mode 100644 privilege/privilege_linux.go create mode 100644 privilege/privilege_stub.go diff --git a/cli/cli.go b/cli/cli.go index 692486af..5e577df8 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -8,6 +8,7 @@ import ( "github.com/coder/boundary/config" "github.com/coder/boundary/log" + "github.com/coder/boundary/privilege" "github.com/coder/boundary/run" "github.com/coder/coder/v2/agent/boundarylogproxy" "github.com/coder/serpent" @@ -173,6 +174,14 @@ func BaseCommand(version string) *serpent.Command { return fmt.Errorf("failed to parse cli config file: %v", err) } + // Ensure we have the necessary privileges only if using nsjail + // (landjail doesn't require the same privileges) + if appConfig.JailType == config.NSJailType { + if err := privilege.EnsurePrivileges(); err != nil { + return fmt.Errorf("failed to ensure privileges: %v", err) + } + } + // Get command arguments if len(appConfig.TargetCMD) == 0 { return fmt.Errorf("no command specified") diff --git a/privilege/privilege_linux.go b/privilege/privilege_linux.go new file mode 100644 index 00000000..f724dd45 --- /dev/null +++ b/privilege/privilege_linux.go @@ -0,0 +1,98 @@ +//go:build linux + +package privilege + +import ( + "fmt" + "os" + "os/exec" + "os/user" + "strconv" + "syscall" +) + +// EnsurePrivileges ensures the process has the necessary privileges (CAP_NET_ADMIN and optionally CAP_SYS_ADMIN). +// If not running with sufficient privileges, it re-executes itself with sudo + setpriv. +// This function should be called early in main() before any privileged operations. +// Assumes the process is always started as a regular user. +func EnsurePrivileges() error { + // Check if we're already in the process of privilege escalation (to prevent infinite loops) + if os.Getenv("BOUNDARY_PRIV_ESCALATED") == "1" { + // We've already escalated, continue + return nil + } + + // If we're already root, something went wrong (we shouldn't be root as a regular user) + // But continue anyway to avoid breaking existing setups + if os.Geteuid() == 0 { + return nil + } + + // Not root, need to re-exec with sudo + setpriv + return reExecWithPrivileges() +} + +// reExecWithPrivileges re-executes the current binary with sudo + setpriv +func reExecWithPrivileges() error { + // Find sudo binary + sudoPath, err := exec.LookPath("sudo") + if err != nil { + return fmt.Errorf("sudo not found in PATH. Please run with sudo or install sudo: %w", err) + } + + // Find setpriv binary + setprivPath, err := exec.LookPath("setpriv") + if err != nil { + return fmt.Errorf("setpriv not found in PATH. Please install util-linux: %w", err) + } + + // Get current user + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("failed to get current user: %w", err) + } + + uid, err := strconv.Atoi(currentUser.Uid) + if err != nil { + return fmt.Errorf("failed to parse UID: %w", err) + } + + gid, err := strconv.Atoi(currentUser.Gid) + if err != nil { + return fmt.Errorf("failed to parse GID: %w", err) + } + + // Get current binary path + binaryPath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %w", err) + } + + // Get current args (skip program name) + args := os.Args[1:] + + // Build sudo command: sudo -E env PATH=$PATH setpriv --reuid=UID --regid=GID --clear-groups --inh-caps=+net_admin,+sys_admin --ambient-caps=+net_admin,+sys_admin binary args... + cmd := exec.Command(sudoPath, + "-E", + "env", + "PATH="+os.Getenv("PATH"), + setprivPath, + "--reuid", strconv.Itoa(uid), + "--regid", strconv.Itoa(gid), + "--clear-groups", + "--inh-caps", "+net_admin,+sys_admin", + "--ambient-caps", "+net_admin,+sys_admin", + binaryPath, + ) + cmd.Args = append(cmd.Args, args...) + env := os.Environ() + env = append(env, "BOUNDARY_PRIV_ESCALATED=1") + cmd.Env = env + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // Execute and replace current process + return syscall.Exec(cmd.Path, cmd.Args, cmd.Env) +} + diff --git a/privilege/privilege_stub.go b/privilege/privilege_stub.go new file mode 100644 index 00000000..54d9d1a5 --- /dev/null +++ b/privilege/privilege_stub.go @@ -0,0 +1,14 @@ +//go:build !linux + +package privilege + +import ( + "fmt" + "runtime" +) + +// EnsurePrivileges is a no-op on non-Linux platforms. +func EnsurePrivileges() error { + return fmt.Errorf("boundary is only supported on Linux, current platform: %s", runtime.GOOS) +} +