From a137ccd897ebcde51abd5a7e2c94c235fbddd19d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 28 Nov 2024 13:46:28 +0000 Subject: [PATCH 01/10] debug --- agent/agentexec/cli_linux.go | 50 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index 9c6568c81811b..63cbc672ac937 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -19,13 +19,6 @@ import ( // CLI runs the agent-exec command. It should only be called by the cli package. func CLI() error { - // We lock the OS thread here to avoid a race condition where the nice priority - // we set gets applied to a different thread than the one we exec the provided - // command on. - runtime.LockOSThread() - // Nop on success but we do it anyway in case of an error. - defer runtime.UnlockOSThread() - var ( fs = flag.NewFlagSet("agent-exec", flag.ExitOnError) nice = fs.Int("coder-nice", unset, "") @@ -37,11 +30,36 @@ func CLI() error { } // Parse everything after "coder agent-exec". - err := fs.Parse(os.Args[2:]) + var err error + err = fs.Parse(os.Args[2:]) if err != nil { return xerrors.Errorf("parse flags: %w", err) } + fmt.Println("oom", *oom) + if *oom == unset { + // If an explicit oom score isn't set, we use the default. + *oom, err = defaultOOMScore() + if err != nil { + return xerrors.Errorf("get default oom score: %w", err) + } + } + + err = writeOOMScoreAdj(*oom) + if err != nil { + // We alert the user instead of failing the command since it can be difficult to debug + // for a template admin otherwise. It's quite possible (and easy) to set an + // inappriopriate value for oom_score_adj. + printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, execArgs(os.Args), err) + } + + // We lock the OS thread here to avoid a race condition where the nice priority + // we set gets applied to a different thread than the one we exec the provided + // command on. + runtime.LockOSThread() + // Nop on success but we do it anyway in case of an error. + defer runtime.UnlockOSThread() + // Get everything after "coder agent-exec --" args := execArgs(os.Args) if len(args) == 0 { @@ -56,14 +74,6 @@ func CLI() error { } } - if *oom == unset { - // If an explicit oom score isn't set, we use the default. - *oom, err = defaultOOMScore() - if err != nil { - return xerrors.Errorf("get default oom score: %w", err) - } - } - err = unix.Setpriority(unix.PRIO_PROCESS, 0, *nice) if err != nil { // We alert the user instead of failing the command since it can be difficult to debug @@ -72,14 +82,6 @@ func CLI() error { printfStdErr("failed to adjust niceness to %d for cmd %+v: %v", *nice, args, err) } - err = writeOOMScoreAdj(*oom) - if err != nil { - // We alert the user instead of failing the command since it can be difficult to debug - // for a template admin otherwise. It's quite possible (and easy) to set an - // inappriopriate value for oom_score_adj. - printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, args, err) - } - path, err := exec.LookPath(args[0]) if err != nil { return xerrors.Errorf("look path: %w", err) From c74d070d5f0dbaf87cc0d6bd4e04a65e92df0c63 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 2 Dec 2024 16:03:24 +0000 Subject: [PATCH 02/10] set dumpable fixes it --- agent/agentexec/cli_linux.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index 63cbc672ac937..a4d500e2b115d 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -45,6 +45,12 @@ func CLI() error { } } + err = unix.Prctl(unix.PR_SET_DUMPABLE, 1, 0, 0, 0) + if err != nil { + printfStdErr("failed to set dumpable: %v", err) + } + fmt.Println("set dumpable") + err = writeOOMScoreAdj(*oom) if err != nil { // We alert the user instead of failing the command since it can be difficult to debug From 158e11722b7dfebb95744ee83aa1df1ac750b7b6 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 2 Dec 2024 21:02:49 +0000 Subject: [PATCH 03/10] lock thread at the top --- agent/agentexec/cli_linux.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index a4d500e2b115d..27a390d026653 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -19,6 +19,13 @@ import ( // CLI runs the agent-exec command. It should only be called by the cli package. func CLI() error { + // We lock the OS thread here to avoid a race condition where the nice priority + // we set gets applied to a different thread than the one we exec the provided + // command on. + runtime.LockOSThread() + // Nop on success but we do it anyway in case of an error. + defer runtime.UnlockOSThread() + var ( fs = flag.NewFlagSet("agent-exec", flag.ExitOnError) nice = fs.Int("coder-nice", unset, "") @@ -36,7 +43,6 @@ func CLI() error { return xerrors.Errorf("parse flags: %w", err) } - fmt.Println("oom", *oom) if *oom == unset { // If an explicit oom score isn't set, we use the default. *oom, err = defaultOOMScore() @@ -49,7 +55,6 @@ func CLI() error { if err != nil { printfStdErr("failed to set dumpable: %v", err) } - fmt.Println("set dumpable") err = writeOOMScoreAdj(*oom) if err != nil { @@ -59,13 +64,6 @@ func CLI() error { printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, execArgs(os.Args), err) } - // We lock the OS thread here to avoid a race condition where the nice priority - // we set gets applied to a different thread than the one we exec the provided - // command on. - runtime.LockOSThread() - // Nop on success but we do it anyway in case of an error. - defer runtime.UnlockOSThread() - // Get everything after "coder agent-exec --" args := execArgs(os.Args) if len(args) == 0 { From 161d3dce0299d792235c93dd249c9a77fd3d7125 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 4 Dec 2024 21:33:42 +0000 Subject: [PATCH 04/10] drop caps --- agent/agentexec/cli_linux.go | 37 ++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index 27a390d026653..42027b7f3a724 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -51,11 +51,16 @@ func CLI() error { } } - err = unix.Prctl(unix.PR_SET_DUMPABLE, 1, 0, 0, 0) + err = dropEffectiveCaps() if err != nil { - printfStdErr("failed to set dumpable: %v", err) + printfStdErr("failed to drop effective caps: %v", err) } + // err = unix.Prctl(unix.PR_SET_DUMPABLE, 1, 0, 0, 0) + // if err != nil { + // printfStdErr("failed to set dumpable: %v", err) + // } + err = writeOOMScoreAdj(*oom) if err != nil { // We alert the user instead of failing the command since it can be difficult to debug @@ -64,6 +69,11 @@ func CLI() error { printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, execArgs(os.Args), err) } + err = unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0) + if err != nil { + printfStdErr("failed to unset dumpable: %v", err) + } + // Get everything after "coder agent-exec --" args := execArgs(os.Args) if len(args) == 0 { @@ -157,3 +167,26 @@ func execArgs(args []string) []string { func printfStdErr(format string, a ...any) { _, _ = fmt.Fprintf(os.Stderr, "coder-agent: %s\n", fmt.Sprintf(format, a...)) } + +func dropEffectiveCaps() error { + // Get the current capabilities + var header unix.CapUserHeader + var data unix.CapUserData + + header.Version = unix.LINUX_CAPABILITY_VERSION_3 + header.Pid = 0 // 0 means current process + + // Get current caps + if err := unix.Capget(&header, &data); err != nil { + return xerrors.Errorf("capget failed: %v", err) + } + + // Clear the effective set by setting it to 0 + data.Effective = 0 + + // Set the new capabilities + if err := unix.Capset(&header, &data); err != nil { + return xerrors.Errorf("capset failed: %v", err) + } + return nil +} From 2bd0372a2ffe8570e4651910adcd5d29be21838b Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 4 Dec 2024 23:24:12 +0000 Subject: [PATCH 05/10] drop caps --- agent/agentexec/cli_linux.go | 60 +++++++++++++++++++----------------- go.mod | 2 ++ go.sum | 4 +++ 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index 42027b7f3a724..f35bdef8b33ea 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -15,8 +15,14 @@ import ( "golang.org/x/sys/unix" "golang.org/x/xerrors" + "kernel.org/pub/linux/libs/security/libcap/cap" ) +func init() { + p := cap.GetProc() + fmt.Println(p.String()) +} + // CLI runs the agent-exec command. It should only be called by the cli package. func CLI() error { // We lock the OS thread here to avoid a race condition where the nice priority @@ -37,12 +43,17 @@ func CLI() error { } // Parse everything after "coder agent-exec". - var err error - err = fs.Parse(os.Args[2:]) + err := fs.Parse(os.Args[2:]) if err != nil { return xerrors.Errorf("parse flags: %w", err) } + // Get everything after "coder agent-exec --" + args := execArgs(os.Args) + if len(args) == 0 { + return xerrors.Errorf("no exec command provided %+v", os.Args) + } + if *oom == unset { // If an explicit oom score isn't set, we use the default. *oom, err = defaultOOMScore() @@ -51,15 +62,21 @@ func CLI() error { } } + // We drop effective caps prior to setting dumpable so that we limit the + // impact of someone attempting to hijack the process (i.e. with a debugger) + // to take advantage of the capabilities of the agent process. err = dropEffectiveCaps() if err != nil { printfStdErr("failed to drop effective caps: %v", err) } - // err = unix.Prctl(unix.PR_SET_DUMPABLE, 1, 0, 0, 0) - // if err != nil { - // printfStdErr("failed to set dumpable: %v", err) - // } + // Set dumpable to 1 so that we can adjust the oom score. If the process + // doesn't have capabilities or has an suid/sgid bit set, this is already + // set. + err = unix.Prctl(unix.PR_SET_DUMPABLE, 1, 0, 0, 0) + if err != nil { + printfStdErr("failed to set dumpable: %v", err) + } err = writeOOMScoreAdj(*oom) if err != nil { @@ -69,17 +86,12 @@ func CLI() error { printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, execArgs(os.Args), err) } + // Set dumpable back to 0 just to be safe. It's not inherited for execve anyways. err = unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0) if err != nil { printfStdErr("failed to unset dumpable: %v", err) } - // Get everything after "coder agent-exec --" - args := execArgs(os.Args) - if len(args) == 0 { - return xerrors.Errorf("no exec command provided %+v", os.Args) - } - if *nice == unset { // If an explicit nice score isn't set, we use the default. *nice, err = defaultNiceScore() @@ -169,24 +181,14 @@ func printfStdErr(format string, a ...any) { } func dropEffectiveCaps() error { - // Get the current capabilities - var header unix.CapUserHeader - var data unix.CapUserData - - header.Version = unix.LINUX_CAPABILITY_VERSION_3 - header.Pid = 0 // 0 means current process - - // Get current caps - if err := unix.Capget(&header, &data); err != nil { - return xerrors.Errorf("capget failed: %v", err) + proc := cap.GetProc() + err := proc.ClearFlag(cap.Effective) + if err != nil { + return xerrors.Errorf("failed to drop effective caps: %w", err) } - - // Clear the effective set by setting it to 0 - data.Effective = 0 - - // Set the new capabilities - if err := unix.Capset(&header, &data); err != nil { - return xerrors.Errorf("capset failed: %v", err) + err = proc.SetProc() + if err != nil { + return xerrors.Errorf("failed to set proc: %w", err) } return nil } diff --git a/go.mod b/go.mod index ccf59c9e2a0c6..fc5b6d394b26a 100644 --- a/go.mod +++ b/go.mod @@ -212,6 +212,7 @@ require ( github.com/google/go-github/v61 v61.0.0 github.com/mocktools/go-smtp-mock/v2 v2.4.0 github.com/natefinch/atomic v1.0.1 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.73 ) require ( @@ -248,6 +249,7 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 // indirect ) require ( diff --git a/go.sum b/go.sum index e8e9d02b1a693..6483c3505565c 100644 --- a/go.sum +++ b/go.sum @@ -1266,6 +1266,10 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg= inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.73 h1:Th2b8jljYqkyZKS3aD3N9VpYsQpHuXLgea+SZUIfODA= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.73/go.mod h1:hbeKwKcboEsxARYmcy/AdPVN11wmT/Wnpgv4k4ftyqY= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 h1:SEAEUiPVylTD4vqqi+vtGkSnXeP2FcRO3FoZB1MklMw= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.73/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= From 3d43a34e043b1ab534a23b8519d09718764b07ff Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 4 Dec 2024 23:29:39 +0000 Subject: [PATCH 06/10] remove debug --- agent/agentexec/cli_linux.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index f35bdef8b33ea..99704b42b308e 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -18,11 +18,6 @@ import ( "kernel.org/pub/linux/libs/security/libcap/cap" ) -func init() { - p := cap.GetProc() - fmt.Println(p.String()) -} - // CLI runs the agent-exec command. It should only be called by the cli package. func CLI() error { // We lock the OS thread here to avoid a race condition where the nice priority @@ -62,6 +57,14 @@ func CLI() error { } } + if *nice == unset { + // If an explicit nice score isn't set, we use the default. + *nice, err = defaultNiceScore() + if err != nil { + return xerrors.Errorf("get default nice score: %w", err) + } + } + // We drop effective caps prior to setting dumpable so that we limit the // impact of someone attempting to hijack the process (i.e. with a debugger) // to take advantage of the capabilities of the agent process. @@ -92,14 +95,6 @@ func CLI() error { printfStdErr("failed to unset dumpable: %v", err) } - if *nice == unset { - // If an explicit nice score isn't set, we use the default. - *nice, err = defaultNiceScore() - if err != nil { - return xerrors.Errorf("get default nice score: %w", err) - } - } - err = unix.Setpriority(unix.PRIO_PROCESS, 0, *nice) if err != nil { // We alert the user instead of failing the command since it can be difficult to debug From 157e4455f4a4efbb89dea01bf00965cd98c3a89b Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 4 Dec 2024 23:34:15 +0000 Subject: [PATCH 07/10] wording --- agent/agentexec/cli_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index 99704b42b308e..3920441205327 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -179,11 +179,11 @@ func dropEffectiveCaps() error { proc := cap.GetProc() err := proc.ClearFlag(cap.Effective) if err != nil { - return xerrors.Errorf("failed to drop effective caps: %w", err) + return xerrors.Errorf("clear effective caps: %w", err) } err = proc.SetProc() if err != nil { - return xerrors.Errorf("failed to set proc: %w", err) + return xerrors.Errorf("set proc: %w", err) } return nil } From d53777d13ea15248ee2df92dc0be8bf9713d3f48 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 5 Dec 2024 12:31:12 +0000 Subject: [PATCH 08/10] add test --- agent/agentexec/cli_linux.go | 5 +++- agent/agentexec/cli_linux_test.go | 47 +++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/agent/agentexec/cli_linux.go b/agent/agentexec/cli_linux.go index 3920441205327..e357ba41acb27 100644 --- a/agent/agentexec/cli_linux.go +++ b/agent/agentexec/cli_linux.go @@ -67,7 +67,10 @@ func CLI() error { // We drop effective caps prior to setting dumpable so that we limit the // impact of someone attempting to hijack the process (i.e. with a debugger) - // to take advantage of the capabilities of the agent process. + // to take advantage of the capabilities of the agent process. We encourage + // users to set cap_net_admin on the agent binary for improved networking + // performance and doing so results in the process having its SET_DUMPABLE + // attribute disabled (meaning we cannot adjust the oom score). err = dropEffectiveCaps() if err != nil { printfStdErr("failed to drop effective caps: %v", err) diff --git a/agent/agentexec/cli_linux_test.go b/agent/agentexec/cli_linux_test.go index aef43a3b6b23b..53b394aa783c8 100644 --- a/agent/agentexec/cli_linux_test.go +++ b/agent/agentexec/cli_linux_test.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sys/unix" + "golang.org/x/xerrors" "github.com/coder/coder/v2/testutil" ) @@ -50,6 +51,32 @@ func TestCLI(t *testing.T) { requireOOMScore(t, cmd.Process.Pid, expectedOOM) requireNiceScore(t, cmd.Process.Pid, expectedNice) }) + + t.Run("Capabilities", func(t *testing.T) { + testdir := filepath.Dir(TestBin) + capDir := filepath.Join(testdir, "caps") + err := os.Mkdir(capDir, 0o755) + require.NoError(t, err) + bin := buildBinary(capDir) + // Try to set capabilities on the binary. This should work fine in CI but + // it's possible some developers may be working in an environment where they don't have the necessary permissions. + err = setCaps(t, bin, "cap_net_admin") + if os.Getenv("CI") != "" { + require.NoError(t, err) + } else if err != nil { + t.Skipf("unable to set capabilities for test: %v", err) + } + ctx := testutil.Context(t, testutil.WaitMedium) + cmd, path := binCmd(ctx, t, bin, 123, 12) + err = cmd.Start() + require.NoError(t, err) + go cmd.Wait() + + waitForSentinel(ctx, t, cmd, path) + // This is what we're really testing, a binary with added capabilities requires setting dumpable. + requireOOMScore(t, cmd.Process.Pid, 123) + requireNiceScore(t, cmd.Process.Pid, 12) + }) } func requireNiceScore(t *testing.T, pid int, score int) { @@ -94,7 +121,7 @@ func waitForSentinel(ctx context.Context, t *testing.T, cmd *exec.Cmd, path stri } } -func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) { +func binCmd(ctx context.Context, t *testing.T, bin string, oom, nice int) (*exec.Cmd, string) { var ( args = execArgs(oom, nice) dir = t.TempDir() @@ -103,7 +130,7 @@ func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) { args = append(args, "sh", "-c", fmt.Sprintf("touch %s && sleep 10m", file)) //nolint:gosec - cmd := exec.CommandContext(ctx, TestBin, args...) + cmd := exec.CommandContext(ctx, bin, args...) // We set this so we can also easily kill the sleep process the shell spawns. cmd.SysProcAttr = &syscall.SysProcAttr{ @@ -125,6 +152,11 @@ func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) { } }) return cmd, file + +} + +func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) { + return binCmd(ctx, t, TestBin, oom, nice) } func expectedOOMScore(t *testing.T) int { @@ -171,3 +203,14 @@ func execArgs(oom int, nice int) []string { execArgs = append(execArgs, "--") return execArgs } + +func setCaps(t *testing.T, bin string, caps ...string) error { + t.Helper() + + setcap := fmt.Sprintf("sudo setcap %s=ep %s", strings.Join(caps, ", "), bin) + out, err := exec.CommandContext(context.Background(), "sh", "-c", setcap).CombinedOutput() + if err != nil { + return xerrors.Errorf("setcap %q (%s): %w", setcap, out, err) + } + return nil +} From 6cd12d74e43c0686940f5e5cb18be3f90972a753 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 5 Dec 2024 12:35:31 +0000 Subject: [PATCH 09/10] fmt --- agent/agentexec/cli_linux_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/agent/agentexec/cli_linux_test.go b/agent/agentexec/cli_linux_test.go index 53b394aa783c8..f164023921e5f 100644 --- a/agent/agentexec/cli_linux_test.go +++ b/agent/agentexec/cli_linux_test.go @@ -152,7 +152,6 @@ func binCmd(ctx context.Context, t *testing.T, bin string, oom, nice int) (*exec } }) return cmd, file - } func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) { From c08ae1f60f0a25e2cef3761a50735e6738608581 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 5 Dec 2024 13:09:41 +0000 Subject: [PATCH 10/10] no prompt for sudo --- agent/agentexec/cli_linux_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agentexec/cli_linux_test.go b/agent/agentexec/cli_linux_test.go index f164023921e5f..088e2af874302 100644 --- a/agent/agentexec/cli_linux_test.go +++ b/agent/agentexec/cli_linux_test.go @@ -206,7 +206,7 @@ func execArgs(oom int, nice int) []string { func setCaps(t *testing.T, bin string, caps ...string) error { t.Helper() - setcap := fmt.Sprintf("sudo setcap %s=ep %s", strings.Join(caps, ", "), bin) + setcap := fmt.Sprintf("sudo -n setcap %s=ep %s", strings.Join(caps, ", "), bin) out, err := exec.CommandContext(context.Background(), "sh", "-c", setcap).CombinedOutput() if err != nil { return xerrors.Errorf("setcap %q (%s): %w", setcap, out, err)