Skip to content

Commit

Permalink
Merge pull request #5728 from hashicorp/restore-08-caps
Browse files Browse the repository at this point in the history
drivers/exec: Restore 0.8 capabilities
  • Loading branch information
Mahmood Ali committed May 29, 2019
2 parents d017b5f + 6217d50 commit 86a6569
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 26 deletions.
1 change: 1 addition & 0 deletions drivers/exec/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
57 changes: 33 additions & 24 deletions drivers/shared/executor/executor_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
//
Expand Down
85 changes: 84 additions & 1 deletion drivers/shared/executor/executor_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion drivers/shared/executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 86a6569

Please sign in to comment.