Merge pull request #6968 from vieux/cap_add_drop

Add support for --cap-add and --cap-drop
This commit is contained in:
Michael Crosby 2014-07-14 10:42:29 -07:00
commit 7ebd49c49a
14 changed files with 260 additions and 7 deletions

View File

@ -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

View File

@ -60,6 +60,8 @@ type InitArgs struct {
Console string
Pipe int
Root string
CapAdd string
CapDrop string
}
// Driver specific information based on
@ -140,6 +142,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

View File

@ -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...)

View File

@ -4,6 +4,7 @@ package lxc
import (
"fmt"
"strings"
"syscall"
"github.com/docker/libcontainer/namespaces"
@ -48,8 +49,13 @@ func finalizeNamespace(args *execdriver.InitArgs) error {
return fmt.Errorf("clear keep caps %s", err)
}
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(container.Capabilities); err != nil {
if err := capabilities.DropCapabilities(caps); err != nil {
return fmt.Errorf("drop capabilities %s", err)
}
}

View File

@ -42,6 +42,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
if err := d.setPrivileged(container); err != nil {
return nil, err
}
} else {
if err := d.setCapabilities(container, c); err != nil {
return nil, err
}
}
if err := d.setupCgroups(container, c); err != nil {
@ -136,6 +140,11 @@ func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
return nil
}
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 {
if c.Resources != nil {
container.Cgroups.CpuShares = c.Resources.CpuShares

View File

@ -0,0 +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, 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) {
newCaps = append(newCaps, cap)
}
}
}
for _, cap := range adds {
// skip `all` aready handled above
if strings.ToLower(cap) == "all" {
continue
}
// 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 newCaps, nil
}

View File

@ -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

View File

@ -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**:

View File

@ -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

View File

@ -783,6 +783,116 @@ 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)
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 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 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 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)
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 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 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")

View File

@ -38,6 +38,8 @@ type HostConfig struct {
VolumesFrom []string
Devices []DeviceMapping
NetworkMode NetworkMode
CapAdd []string
CapDrop []string
}
func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
@ -65,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
}

View File

@ -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 capabilities")
cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
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 {

View File

@ -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 {

View File

@ -907,3 +907,12 @@ func ValidateContextDirectory(srcPath string) error {
})
return finalError
}
func StringsContainsNoCase(slice []string, s string) bool {
for _, ss := range slice {
if strings.ToLower(s) == strings.ToLower(ss) {
return true
}
}
return false
}