diff --git a/drivers/exec/driver_test.go b/drivers/exec/driver_test.go index 2c0a565bcd38..6319ed69aa1c 100644 --- a/drivers/exec/driver_test.go +++ b/drivers/exec/driver_test.go @@ -475,6 +475,7 @@ func TestExecDriver_DevicesAndMounts(t *testing.T) { task := &drivers.TaskConfig{ ID: uuid.Generate(), Name: "test", + User: "root", // need permission to read mounts paths Resources: testResources, StdoutPath: filepath.Join(tmpDir, "task-stdout"), StderrPath: filepath.Join(tmpDir, "task-stderr"), diff --git a/drivers/shared/executor/executor_linux.go b/drivers/shared/executor/executor_linux.go index cdaea5dd8f23..14a406aae45f 100644 --- a/drivers/shared/executor/executor_linux.go +++ b/drivers/shared/executor/executor_linux.go @@ -45,26 +45,8 @@ var ( // ExecutorCgroupMeasuredCpuStats is the list of CPU stats captures by the executor ExecutorCgroupMeasuredCpuStats = []string{"System Mode", "User Mode", "Throttled Periods", "Throttled Time", "Percent"} - - // allCaps is all linux capabilities which is used to configure libcontainer - allCaps []string ) -// initialize the allCaps var with all capabilities available on the system -func init() { - last := capability.CAP_LAST_CAP - // workaround for RHEL6 which has no /proc/sys/kernel/cap_last_cap - if last == capability.Cap(63) { - last = capability.CAP_BLOCK_SUSPEND - } - for _, cap := range capability.List() { - if cap > last { - continue - } - allCaps = append(allCaps, fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))) - } -} - // LibcontainerExecutor implements an Executor with the runc/libcontainer api type LibcontainerExecutor struct { id string @@ -569,17 +551,44 @@ func (l *LibcontainerExecutor) handleExecWait(ch chan *waitResult, process *libc func configureCapabilities(cfg *lconfigs.Config, command *ExecCommand) error { // TODO: allow better control of these - cfg.Capabilities = &lconfigs.Capabilities{ - Bounding: allCaps, - Permitted: allCaps, - Inheritable: allCaps, - Ambient: allCaps, - Effective: allCaps, + // use capabilities list as prior to adopting libcontainer in 0.9 + allCaps := supportedCaps() + + // match capabilities used in Nomad 0.8 + if command.User == "root" { + cfg.Capabilities = &lconfigs.Capabilities{ + Bounding: allCaps, + Permitted: allCaps, + Effective: allCaps, + Ambient: nil, + Inheritable: nil, + } + } else { + cfg.Capabilities = &lconfigs.Capabilities{ + Bounding: allCaps, + } } return nil } +// supportedCaps returns a list of all supported capabilities in kernel +func supportedCaps() []string { + allCaps := []string{} + last := capability.CAP_LAST_CAP + // workaround for RHEL6 which has no /proc/sys/kernel/cap_last_cap + if last == capability.Cap(63) { + last = capability.CAP_BLOCK_SUSPEND + } + for _, cap := range capability.List() { + if cap > last { + continue + } + allCaps = append(allCaps, fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))) + } + return allCaps +} + // configureIsolation prepares the isolation primitives of the container. // The process runs in a container configured with the following: // diff --git a/drivers/shared/executor/executor_linux_test.go b/drivers/shared/executor/executor_linux_test.go index 9d2a1e41e1db..ed87a1dc7910 100644 --- a/drivers/shared/executor/executor_linux_test.go +++ b/drivers/shared/executor/executor_linux_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strconv" "strings" "testing" @@ -44,6 +45,7 @@ func testExecutorCommandWithChroot(t *testing.T) *testExecCmd { "/etc/ld.so.cache": "/etc/ld.so.cache", "/etc/ld.so.conf": "/etc/ld.so.conf", "/etc/ld.so.conf.d": "/etc/ld.so.conf.d", + "/etc/passwd": "/etc/passwd", "/lib": "/lib", "/lib64": "/lib64", "/usr/lib": "/usr/lib", @@ -150,7 +152,8 @@ usr/ /etc/: ld.so.cache ld.so.conf -ld.so.conf.d/` +ld.so.conf.d/ +passwd` tu.WaitForResult(func() (bool, error) { output := testExecCmd.stdout.String() act := strings.TrimSpace(string(output)) @@ -239,6 +242,86 @@ func TestExecutor_EscapeContainer(t *testing.T) { require.NoError(err) } +func TestExecutor_Capabilities(t *testing.T) { + t.Parallel() + testutil.ExecCompatible(t) + + cases := []struct { + user string + caps string + }{ + { + user: "nobody", + caps: ` +CapInh: 0000000000000000 +CapPrm: 0000000000000000 +CapEff: 0000000000000000 +CapBnd: 0000003fffffffff +CapAmb: 0000000000000000`, + }, + { + user: "root", + caps: ` +CapInh: 0000000000000000 +CapPrm: 0000003fffffffff +CapEff: 0000003fffffffff +CapBnd: 0000003fffffffff +CapAmb: 0000000000000000`, + }, + } + + for _, c := range cases { + t.Run(c.user, func(t *testing.T) { + require := require.New(t) + + testExecCmd := testExecutorCommandWithChroot(t) + execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir + defer allocDir.Destroy() + + execCmd.User = c.user + execCmd.ResourceLimits = true + execCmd.Cmd = "/bin/bash" + execCmd.Args = []string{"-c", "cat /proc/$$/status"} + + executor := NewExecutorWithIsolation(testlog.HCLogger(t)) + defer executor.Shutdown("SIGKILL", 0) + + _, err := executor.Launch(execCmd) + require.NoError(err) + + ch := make(chan interface{}) + go func() { + executor.Wait(context.Background()) + close(ch) + }() + + select { + case <-ch: + // all good + case <-time.After(5 * time.Second): + require.Fail("timeout waiting for exec to shutdown") + } + + canonical := func(s string) string { + s = strings.TrimSpace(s) + s = regexp.MustCompile("[ \t]+").ReplaceAllString(s, " ") + s = regexp.MustCompile("[\n\r]+").ReplaceAllString(s, "\n") + return s + } + + expected := canonical(c.caps) + tu.WaitForResult(func() (bool, error) { + output := canonical(testExecCmd.stdout.String()) + if !strings.Contains(output, expected) { + return false, fmt.Errorf("capabilities didn't match: want\n%v\n; got:\n%v\n", expected, output) + } + return true, nil + }, func(err error) { require.NoError(err) }) + }) + } + +} + func TestExecutor_ClientCleanup(t *testing.T) { t.Parallel() testutil.ExecCompatible(t) diff --git a/drivers/shared/executor/executor_test.go b/drivers/shared/executor/executor_test.go index 65af774a5333..ac8b9d03442a 100644 --- a/drivers/shared/executor/executor_test.go +++ b/drivers/shared/executor/executor_test.go @@ -473,7 +473,7 @@ func setupRootfsBinary(t *testing.T, rootfs, path string) { t.Helper() dst := filepath.Join(rootfs, path) - err := os.MkdirAll(filepath.Dir(dst), 666) + err := os.MkdirAll(filepath.Dir(dst), 0755) require.NoError(t, err) src := filepath.Join(