From ca489c2f494487af7d6a9bd3e5feb00a22270d5e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 18:41:11 +0000 Subject: [PATCH 1/9] Basic --cap-add and --cap-drop support for native Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 94e6dc978134b61a2b30aa9118f98f6fadd10535 Component: engine --- components/engine/daemon/container.go | 2 ++ components/engine/daemon/execdriver/driver.go | 2 ++ .../engine/daemon/execdriver/native/create.go | 20 +++++++++++++++++++ components/engine/runconfig/hostconfig.go | 2 ++ components/engine/runconfig/parse.go | 7 +++++++ components/engine/utils/utils.go | 9 +++++++++ 6 files changed, 42 insertions(+) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index aacd90a449..1efe511c85 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/daemon/container.go @@ -254,6 +254,8 @@ func populateCommand(c *Container, env []string) error { Resources: resources, AllowedDevices: allowedDevices, AutoCreatedDevices: autoCreatedDevices, + CapAdd: c.hostConfig.CapAdd, + CapDrop: c.hostConfig.CapDrop, } c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} c.command.Env = env diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index a3d3bc260a..0efd4fe71b 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/daemon/execdriver/driver.go @@ -140,6 +140,8 @@ type Command struct { Mounts []Mount `json:"mounts"` AllowedDevices []*devices.Device `json:"allowed_devices"` AutoCreatedDevices []*devices.Device `json:"autocreated_devices"` + CapAdd []string `json:"cap_add"` + CapDrop []string `json:"cap_drop"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index f28507b046..bfcc078834 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -14,6 +14,7 @@ import ( "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/daemon/execdriver/native/configuration" "github.com/dotcloud/docker/daemon/execdriver/native/template" + "github.com/dotcloud/docker/utils" ) // createContainer populates and configures the container type with the @@ -42,6 +43,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e if err := d.setPrivileged(container); err != nil { return nil, err } + } else { + d.setCapabilities(container, c) } if err := d.setupCgroups(container, c); err != nil { @@ -136,6 +139,23 @@ func (d *driver) setPrivileged(container *libcontainer.Config) (err error) { return nil } +func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.Command) { + var caps []string + for _, cap := range container.Capabilities { + if !utils.StringsContains(c.CapDrop, cap) { + caps = append(caps, cap) + } + } + + for _, cap := range c.CapAdd { + if !utils.StringsContains(caps, cap) { + caps = append(caps, cap) + } + } + + container.Capabilities = caps +} + func (d *driver) setupCgroups(container *libcontainer.Config, c *execdriver.Command) error { if c.Resources != nil { container.Cgroups.CpuShares = c.Resources.CpuShares diff --git a/components/engine/runconfig/hostconfig.go b/components/engine/runconfig/hostconfig.go index f4aa69fe97..b10e103450 100644 --- a/components/engine/runconfig/hostconfig.go +++ b/components/engine/runconfig/hostconfig.go @@ -38,6 +38,8 @@ type HostConfig struct { VolumesFrom []string Devices []DeviceMapping NetworkMode NetworkMode + CapAdd []string + CapDrop []string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { diff --git a/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index f7d1d5963f..1574bf8f26 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/runconfig/parse.go @@ -50,6 +50,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts flEnvFile opts.ListOpts + flCapAdd opts.ListOpts + flCapDrop opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run container in the background and print new container ID") @@ -86,6 +88,9 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capability(ies)") + cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capability(ies)") + if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err } @@ -258,6 +263,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf VolumesFrom: flVolumesFrom.GetAll(), NetworkMode: netMode, Devices: deviceMappings, + CapAdd: flCapAdd.GetAll(), + CapDrop: flCapDrop.GetAll(), } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index ef28aceca7..085f493032 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -907,3 +907,12 @@ func ValidateContextDirectory(srcPath string) error { }) return finalError } + +func StringsContains(slice []string, s string) bool { + for _, ss := range slice { + if s == ss { + return true + } + } + return false +} From d4b6724355f6da5c0a78697f1a43775a5a76d7f7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 19:31:01 +0000 Subject: [PATCH 2/9] Basic --cap-add and --cap-drop support for lxc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 21059af3ac0136607dbb57c796f625cfbd045177 Component: engine --- components/engine/daemon/execdriver/driver.go | 2 ++ .../engine/daemon/execdriver/lxc/driver.go | 8 ++++++++ .../daemon/execdriver/lxc/lxc_init_linux.go | 17 ++++++++++++++++- components/engine/sysinit/sysinit.go | 9 +++++++-- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index 0efd4fe71b..d52a2ac96c 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/daemon/execdriver/driver.go @@ -60,6 +60,8 @@ type InitArgs struct { Console string Pipe int Root string + CapAdd string + CapDrop string } // Driver specific information based on diff --git a/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 59daf1afe1..2faada2350 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/daemon/execdriver/lxc/driver.go @@ -122,6 +122,14 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba params = append(params, "-w", c.WorkingDir) } + if len(c.CapAdd) > 0 { + params = append(params, "-cap-add", strings.Join(c.CapAdd, " ")) + } + + if len(c.CapDrop) > 0 { + params = append(params, "-cap-drop", strings.Join(c.CapDrop, " ")) + } + params = append(params, "--", c.Entrypoint) params = append(params, c.Arguments...) diff --git a/components/engine/daemon/execdriver/lxc/lxc_init_linux.go b/components/engine/daemon/execdriver/lxc/lxc_init_linux.go index 1fd497e9aa..6d636efb06 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_init_linux.go +++ b/components/engine/daemon/execdriver/lxc/lxc_init_linux.go @@ -4,6 +4,7 @@ package lxc import ( "fmt" + "strings" "syscall" "github.com/docker/libcontainer/namespaces" @@ -12,6 +13,7 @@ import ( "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/daemon/execdriver/native/template" "github.com/dotcloud/docker/pkg/system" + utils2 "github.com/dotcloud/docker/utils" ) func setHostname(hostname string) error { @@ -48,8 +50,21 @@ func finalizeNamespace(args *execdriver.InitArgs) error { return fmt.Errorf("clear keep caps %s", err) } + var caps []string + for _, cap := range container.Capabilities { + if !utils2.StringsContains(strings.Split(args.CapDrop, " "), cap) { + caps = append(caps, cap) + } + } + + for _, cap := range strings.Split(args.CapAdd, " ") { + if !utils2.StringsContains(caps, cap) { + caps = append(caps, cap) + } + } + // drop all other capabilities - if err := capabilities.DropCapabilities(container.Capabilities); err != nil { + if err := capabilities.DropCapabilities(caps); err != nil { return fmt.Errorf("drop capabilities %s", err) } } diff --git a/components/engine/sysinit/sysinit.go b/components/engine/sysinit/sysinit.go index 62e89ce9e7..1b8746f02a 100644 --- a/components/engine/sysinit/sysinit.go +++ b/components/engine/sysinit/sysinit.go @@ -3,11 +3,12 @@ package sysinit import ( "flag" "fmt" + "log" + "os" + "github.com/dotcloud/docker/daemon/execdriver" _ "github.com/dotcloud/docker/daemon/execdriver/lxc" _ "github.com/dotcloud/docker/daemon/execdriver/native" - "log" - "os" ) func executeProgram(args *execdriver.InitArgs) error { @@ -39,6 +40,8 @@ func SysInit() { pipe = flag.Int("pipe", 0, "sync pipe fd") console = flag.String("console", "", "console (pty slave) path") root = flag.String("root", ".", "root path for configuration files") + capAdd = flag.String("cap-add", "", "capabilities to add") + capDrop = flag.String("cap-drop", "", "capabilities to drop") ) flag.Parse() @@ -54,6 +57,8 @@ func SysInit() { Console: *console, Pipe: *pipe, Root: *root, + CapAdd: *capAdd, + CapDrop: *capDrop, } if err := executeProgram(args); err != nil { From 986cb2347f6c5425e862dd845ee7e596ed3bdb51 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 21:51:15 +0000 Subject: [PATCH 3/9] fix job and add tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 8344b6d7368b90c567f43e0c17d4495e2e7b12f5 Component: engine --- .../integration-cli/docker_cli_run_test.go | 30 +++++++++++++++++++ components/engine/runconfig/hostconfig.go | 6 ++++ 2 files changed, 36 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index cf0f4b7e3d..e813ec6a7d 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -783,6 +783,36 @@ func TestUnPrivilegedCanMknod(t *testing.T) { logDone("run - test un-privileged can mknod") } +func TestCapDropCannotMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-drop=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual == "ok" { + t.Fatalf("expected output not ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test --cap-drop=MKNOD cannot mknod") +} + +func TestCapAddCanDownInterface(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test --cap-add=NET_ADMIN can set eth0 down") +} + func TestPrivilegedCanMount(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") diff --git a/components/engine/runconfig/hostconfig.go b/components/engine/runconfig/hostconfig.go index b10e103450..c68f764588 100644 --- a/components/engine/runconfig/hostconfig.go +++ b/components/engine/runconfig/hostconfig.go @@ -67,5 +67,11 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil { hostConfig.VolumesFrom = VolumesFrom } + if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil { + hostConfig.CapAdd = CapAdd + } + if CapDrop := job.GetenvList("CapDrop"); CapDrop != nil { + hostConfig.CapDrop = CapDrop + } return hostConfig } From 2032a7ad932b4f1a71268d77bbb36852494dd66f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 22:11:35 +0000 Subject: [PATCH 4/9] small refactoring Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: f3ff323fb364495617de3e43f2d09a145a4f2ee3 Component: engine --- .../daemon/execdriver/lxc/lxc_init_linux.go | 14 +------------- .../engine/daemon/execdriver/native/create.go | 16 +--------------- components/engine/daemon/execdriver/utils.go | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 28 deletions(-) create mode 100644 components/engine/daemon/execdriver/utils.go diff --git a/components/engine/daemon/execdriver/lxc/lxc_init_linux.go b/components/engine/daemon/execdriver/lxc/lxc_init_linux.go index 6d636efb06..0117b9dbef 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_init_linux.go +++ b/components/engine/daemon/execdriver/lxc/lxc_init_linux.go @@ -13,7 +13,6 @@ import ( "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/daemon/execdriver/native/template" "github.com/dotcloud/docker/pkg/system" - utils2 "github.com/dotcloud/docker/utils" ) func setHostname(hostname string) error { @@ -50,18 +49,7 @@ func finalizeNamespace(args *execdriver.InitArgs) error { return fmt.Errorf("clear keep caps %s", err) } - var caps []string - for _, cap := range container.Capabilities { - if !utils2.StringsContains(strings.Split(args.CapDrop, " "), cap) { - caps = append(caps, cap) - } - } - - for _, cap := range strings.Split(args.CapAdd, " ") { - if !utils2.StringsContains(caps, cap) { - caps = append(caps, cap) - } - } + caps := execdriver.TweakCapabilities(container.Capabilities, strings.Split(args.CapAdd, " "), strings.Split(args.CapDrop, " ")) // drop all other capabilities if err := capabilities.DropCapabilities(caps); err != nil { diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index bfcc078834..b735151eb5 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -14,7 +14,6 @@ import ( "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/daemon/execdriver/native/configuration" "github.com/dotcloud/docker/daemon/execdriver/native/template" - "github.com/dotcloud/docker/utils" ) // createContainer populates and configures the container type with the @@ -140,20 +139,7 @@ func (d *driver) setPrivileged(container *libcontainer.Config) (err error) { } func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.Command) { - var caps []string - for _, cap := range container.Capabilities { - if !utils.StringsContains(c.CapDrop, cap) { - caps = append(caps, cap) - } - } - - for _, cap := range c.CapAdd { - if !utils.StringsContains(caps, cap) { - caps = append(caps, cap) - } - } - - container.Capabilities = caps + container.Capabilities = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop) } func (d *driver) setupCgroups(container *libcontainer.Config, c *execdriver.Command) error { diff --git a/components/engine/daemon/execdriver/utils.go b/components/engine/daemon/execdriver/utils.go new file mode 100644 index 0000000000..7ca12a596b --- /dev/null +++ b/components/engine/daemon/execdriver/utils.go @@ -0,0 +1,19 @@ +package execdriver + +import "github.com/dotcloud/docker/utils" + +func TweakCapabilities(basics, adds, drops []string) []string { + var caps []string + for _, cap := range basics { + if !utils.StringsContains(drops, cap) { + caps = append(caps, cap) + } + } + + for _, cap := range adds { + if !utils.StringsContains(caps, cap) { + caps = append(caps, cap) + } + } + return caps +} From 669e2fe4795e629dfdac1bef4b555aee6c361391 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 22:31:01 +0000 Subject: [PATCH 5/9] add basic support for 'all' Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 222a6f44016451dcbd2da0003e64521c06e88ba9 Component: engine --- components/engine/daemon/execdriver/utils.go | 21 +++++++++---- .../integration-cli/docker_cli_run_test.go | 30 +++++++++++++++++++ components/engine/utils/utils.go | 4 +-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/components/engine/daemon/execdriver/utils.go b/components/engine/daemon/execdriver/utils.go index 7ca12a596b..4188b9bf07 100644 --- a/components/engine/daemon/execdriver/utils.go +++ b/components/engine/daemon/execdriver/utils.go @@ -1,17 +1,28 @@ package execdriver -import "github.com/dotcloud/docker/utils" +import ( + "strings" + + "github.com/docker/libcontainer/security/capabilities" + "github.com/dotcloud/docker/utils" +) func TweakCapabilities(basics, adds, drops []string) []string { var caps []string - for _, cap := range basics { - if !utils.StringsContains(drops, cap) { - caps = append(caps, cap) + if !utils.StringsContainsNoCase(drops, "all") { + for _, cap := range basics { + if !utils.StringsContainsNoCase(drops, cap) { + caps = append(caps, cap) + } } } for _, cap := range adds { - if !utils.StringsContains(caps, cap) { + if strings.ToLower(cap) == "all" { + caps = capabilities.GetAllCapabilities() + break + } + if !utils.StringsContainsNoCase(caps, cap) { caps = append(caps, cap) } } diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index e813ec6a7d..d4832638b7 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -798,6 +798,21 @@ func TestCapDropCannotMknod(t *testing.T) { logDone("run - test --cap-drop=MKNOD cannot mknod") } +func TestCapDropALLCannotMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual == "ok" { + t.Fatalf("expected output not ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test --cap-drop=ALL cannot mknod") +} + func TestCapAddCanDownInterface(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) @@ -813,6 +828,21 @@ func TestCapAddCanDownInterface(t *testing.T) { logDone("run - test --cap-add=NET_ADMIN can set eth0 down") } +func TestCapAddALLCanDownInterface(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test --cap-add=ALL can set eth0 down") +} + func TestPrivilegedCanMount(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 085f493032..0d44ec0f72 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -908,9 +908,9 @@ func ValidateContextDirectory(srcPath string) error { return finalError } -func StringsContains(slice []string, s string) bool { +func StringsContainsNoCase(slice []string, s string) bool { for _, ss := range slice { - if s == ss { + if strings.ToLower(s) == strings.ToLower(ss) { return true } } From 5201bb5120974a211cebcfd1276a9f8e28497459 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 23:02:39 +0000 Subject: [PATCH 6/9] support add and drop in both order Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 064b5f870db39e33f18d6dd405f2bdab98255ef7 Component: engine --- components/engine/daemon/execdriver/utils.go | 8 +++-- .../integration-cli/docker_cli_run_test.go | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/components/engine/daemon/execdriver/utils.go b/components/engine/daemon/execdriver/utils.go index 4188b9bf07..c3e856e32e 100644 --- a/components/engine/daemon/execdriver/utils.go +++ b/components/engine/daemon/execdriver/utils.go @@ -9,6 +9,11 @@ import ( func TweakCapabilities(basics, adds, drops []string) []string { var caps []string + + if utils.StringsContainsNoCase(adds, "all") { + basics = capabilities.GetAllCapabilities() + } + if !utils.StringsContainsNoCase(drops, "all") { for _, cap := range basics { if !utils.StringsContainsNoCase(drops, cap) { @@ -19,8 +24,7 @@ func TweakCapabilities(basics, adds, drops []string) []string { for _, cap := range adds { if strings.ToLower(cap) == "all" { - caps = capabilities.GetAllCapabilities() - break + continue } if !utils.StringsContainsNoCase(caps, cap) { caps = append(caps, cap) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index d4832638b7..32af41f4e7 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -813,6 +813,21 @@ func TestCapDropALLCannotMknod(t *testing.T) { logDone("run - test --cap-drop=ALL cannot mknod") } +func TestCapDropALLAddMknodCannotMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL --cap-add=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test --cap-drop=ALL --cap-add=MKNOD can mknod") +} + func TestCapAddCanDownInterface(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) @@ -843,6 +858,21 @@ func TestCapAddALLCanDownInterface(t *testing.T) { logDone("run - test --cap-add=ALL can set eth0 down") } +func TestCapAddALLDropNetAdminCanDownInterface(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL --cap-drop=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual == "ok" { + t.Fatalf("expected output not ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test --cap-add=ALL --cap-drop=NET_ADMIN cannot set eth0 down") +} + func TestPrivilegedCanMount(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") From e7f2c9317d03d455ae325c28f567025b65158a43 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 23:38:11 +0000 Subject: [PATCH 7/9] add check for invalid caps Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: c04230c42b7a953ffe50bc37d351f86e80a442e6 Component: engine --- .../daemon/execdriver/lxc/lxc_init_linux.go | 5 ++- .../engine/daemon/execdriver/native/create.go | 9 ++-- components/engine/daemon/execdriver/utils.go | 41 ++++++++++++++++--- .../integration-cli/docker_cli_run_test.go | 24 ++++++++++- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/components/engine/daemon/execdriver/lxc/lxc_init_linux.go b/components/engine/daemon/execdriver/lxc/lxc_init_linux.go index 0117b9dbef..40956e442b 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_init_linux.go +++ b/components/engine/daemon/execdriver/lxc/lxc_init_linux.go @@ -49,7 +49,10 @@ func finalizeNamespace(args *execdriver.InitArgs) error { return fmt.Errorf("clear keep caps %s", err) } - caps := execdriver.TweakCapabilities(container.Capabilities, strings.Split(args.CapAdd, " "), strings.Split(args.CapDrop, " ")) + caps, err := execdriver.TweakCapabilities(container.Capabilities, strings.Split(args.CapAdd, " "), strings.Split(args.CapDrop, " ")) + if err != nil { + return err + } // drop all other capabilities if err := capabilities.DropCapabilities(caps); err != nil { diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index b735151eb5..13f81c7180 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -43,7 +43,9 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e return nil, err } } else { - d.setCapabilities(container, c) + if err := d.setCapabilities(container, c); err != nil { + return nil, err + } } if err := d.setupCgroups(container, c); err != nil { @@ -138,8 +140,9 @@ func (d *driver) setPrivileged(container *libcontainer.Config) (err error) { return nil } -func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.Command) { - container.Capabilities = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop) +func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.Command) (err error) { + container.Capabilities, err = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop) + return err } func (d *driver) setupCgroups(container *libcontainer.Config, c *execdriver.Command) error { diff --git a/components/engine/daemon/execdriver/utils.go b/components/engine/daemon/execdriver/utils.go index c3e856e32e..90c5177421 100644 --- a/components/engine/daemon/execdriver/utils.go +++ b/components/engine/daemon/execdriver/utils.go @@ -1,34 +1,63 @@ package execdriver import ( + "fmt" "strings" "github.com/docker/libcontainer/security/capabilities" "github.com/dotcloud/docker/utils" ) -func TweakCapabilities(basics, adds, drops []string) []string { - var caps []string +func TweakCapabilities(basics, adds, drops []string) ([]string, error) { + var ( + newCaps []string + allCaps = capabilities.GetAllCapabilities() + ) + // look for invalid cap in the drop list + for _, cap := range drops { + if strings.ToLower(cap) == "all" { + continue + } + if !utils.StringsContainsNoCase(allCaps, cap) { + return nil, fmt.Errorf("Unknown capability: %s", cap) + } + } + + // handle --cap-add=all if utils.StringsContainsNoCase(adds, "all") { basics = capabilities.GetAllCapabilities() } if !utils.StringsContainsNoCase(drops, "all") { for _, cap := range basics { + // skip `all` aready handled above + if strings.ToLower(cap) == "all" { + continue + } + + // if we don't drop `all`, add back all the non-dropped caps if !utils.StringsContainsNoCase(drops, cap) { - caps = append(caps, cap) + newCaps = append(newCaps, cap) } } } for _, cap := range adds { + // skip `all` aready handled above if strings.ToLower(cap) == "all" { continue } - if !utils.StringsContainsNoCase(caps, cap) { - caps = append(caps, cap) + + // look for invalid cap in the drop list + if !utils.StringsContainsNoCase(allCaps, cap) { + return nil, fmt.Errorf("Unknown capability: %s", cap) + } + + // add cap if not already in the list + if !utils.StringsContainsNoCase(newCaps, cap) { + newCaps = append(newCaps, cap) } } - return caps + return newCaps, nil } diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 32af41f4e7..dba8e7fe28 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -783,6 +783,16 @@ func TestUnPrivilegedCanMknod(t *testing.T) { logDone("run - test un-privileged can mknod") } +func TestCapDropInvalid(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-drop=CHPASS", "busybox", "ls") + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + logDone("run - test --cap-drop=CHPASS invalid") +} + func TestCapDropCannotMknod(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) @@ -814,7 +824,7 @@ func TestCapDropALLCannotMknod(t *testing.T) { } func TestCapDropALLAddMknodCannotMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL --cap-add=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { t.Fatal(err, out) @@ -828,6 +838,16 @@ func TestCapDropALLAddMknodCannotMknod(t *testing.T) { logDone("run - test --cap-drop=ALL --cap-add=MKNOD can mknod") } +func TestCapAddInvalid(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cap-add=CHPASS", "busybox", "ls") + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + logDone("run - test --cap-add=CHPASS invalid") +} + func TestCapAddCanDownInterface(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) @@ -859,7 +879,7 @@ func TestCapAddALLCanDownInterface(t *testing.T) { } func TestCapAddALLDropNetAdminCanDownInterface(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL --cap-drop=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") + cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "--cap-drop=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { t.Fatal(err, out) From c802db49a3f46d70ccdd2e96cc303ee42fc3ba14 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 10 Jul 2014 23:50:45 +0000 Subject: [PATCH 8/9] add doc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: e7d9854414ed77765db49af136533871ba443f3c Component: engine --- components/engine/docs/sources/reference/run.md | 16 ++++++++++++++-- components/engine/runconfig/parse.go | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index 1bd70e83f0..202d561df3 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/docs/sources/reference/run.md @@ -55,7 +55,7 @@ following options. - [Network Settings](#network-settings) - [Clean Up (--rm)](#clean-up-rm) - [Runtime Constraints on CPU and Memory](#runtime-constraints-on-cpu-and-memory) - - [Runtime Privilege and LXC Configuration](#runtime-privilege-and-lxc-configuration) + - [Runtime Privilege, Linux Capabilities, and LXC Configuration](#runtime-privilege-linux-capabilities-and-lxc-configuration) ## Detached vs Foreground @@ -222,8 +222,10 @@ get the same proportion of CPU cycles, but you can tell the kernel to give more shares of CPU time to one or more containers when you start them via Docker. -## Runtime Privilege and LXC Configuration +## Runtime Privilege, Linux Capabilities, and LXC Configuration + --cap-add: Add Linux capabilities + --cap-drop: Drop Linux capabilities --privileged=false: Give extended privileges to this container --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" @@ -242,6 +244,16 @@ host as processes running outside containers on the host. Additional information about running with `--privileged` is available on the [Docker Blog](http://blog.docker.com/2013/09/docker-can-now-run-within-docker/). +In addition to `--privileged` the operator can have fine grain control over the +capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a default +list of capabilities that are kept. Both flags support the value `all`, so if the +operator wants to have all capabilities but `MKNOD` they could use: + + $ docker run --cap-add=ALL --cap-drop=MKNOD ... + +For interacting with the network stack, instead of using `--privileged` they +should use `--cap-add=NET_ADMIN` to modify the network interfaces. + If the Docker daemon was started using the `lxc` exec-driver (`docker -d --exec-driver=lxc`) then the operator can also specify LXC options using one or more `--lxc-conf` parameters. These can be new parameters or diff --git a/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index 1574bf8f26..3e52007544 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/runconfig/parse.go @@ -88,8 +88,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") - cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capability(ies)") - cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capability(ies)") + cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities") + cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err From 76bdfebd468dfb6b4d442417380f0d59e8bd887c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 11 Jul 2014 23:24:44 +0000 Subject: [PATCH 9/9] update api doc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 6bb27f18d19201d8c0b3c509151935308c001c22 Component: engine --- .../docs/sources/reference/api/docker_remote_api.md | 6 ++++++ .../docs/sources/reference/api/docker_remote_api_v1.14.md | 8 ++++++-- components/engine/docs/sources/reference/run.md | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 04b750f4c5..c2916e5de0 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/docs/sources/reference/api/docker_remote_api.md @@ -43,6 +43,12 @@ You can now use the `stop` parameter to stop running containers before removal **New!** You can now use the `kill` parameter to kill running containers before removal. +`POST /containers/(id)/start` + +**New!** +The `hostConfig` option now accepts the field `CapAdd`, which specifies a list of capabilities +to add, and the field `CapDrop`, which specifies a list of capabilities to drop. + ## v1.13 ### Full Documentation diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.14.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.14.md index c2f897ebef..bdc4fc5a4b 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.14.md +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.14.md @@ -241,7 +241,9 @@ Return low-level information on the container `id` ] }, "Links": ["/name:alias"], - "PublishAllPorts": false + "PublishAllPorts": false, + "CapAdd: ["NET_ADMIN"], + "CapDrop: ["MKNOD"] } } @@ -410,7 +412,9 @@ Start the container `id` "PublishAllPorts":false, "Privileged":false, "Dns": ["8.8.8.8"], - "VolumesFrom": ["parent", "other:ro"] + "VolumesFrom": ["parent", "other:ro"], + "CapAdd: ["NET_ADMIN"], + "CapDrop: ["MKNOD"] } **Example response**: diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index 202d561df3..f8ced8d734 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/docs/sources/reference/run.md @@ -244,7 +244,7 @@ host as processes running outside containers on the host. Additional information about running with `--privileged` is available on the [Docker Blog](http://blog.docker.com/2013/09/docker-can-now-run-within-docker/). -In addition to `--privileged` the operator can have fine grain control over the +In addition to `--privileged`, the operator can have fine grain control over the capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a default list of capabilities that are kept. Both flags support the value `all`, so if the operator wants to have all capabilities but `MKNOD` they could use: