From 1756af6fafabd9197feb56c0324e49dd7d30b11f Mon Sep 17 00:00:00 2001 From: Kenfe-Mickael Laventure Date: Fri, 6 May 2016 15:09:46 -0700 Subject: [PATCH] Allow adding rules to cgroup devices.allow on container create/run This introduce a new `--device-cgroup-rule` flag that allow a user to add one or more entry to the container cgroup device `devices.allow` Signed-off-by: Kenfe-Mickael Laventure --- api/types/container/host_config.go | 1 + cli/command/container/opts.go | 20 +++++++++++++ contrib/completion/bash/docker | 1 + contrib/completion/fish/docker.fish | 2 ++ contrib/completion/zsh/_docker | 1 + daemon/oci_linux.go | 40 ++++++++++++++++++++++++++ docs/reference/commandline/create.md | 1 + docs/reference/commandline/run.md | 1 + integration-cli/docker_cli_run_test.go | 14 +++++++++ man/docker-run.1.md | 11 +++++++ 10 files changed, 92 insertions(+) diff --git a/api/types/container/host_config.go b/api/types/container/host_config.go index d34fa1405c..d4a0c575f7 100644 --- a/api/types/container/host_config.go +++ b/api/types/container/host_config.go @@ -251,6 +251,7 @@ type Resources struct { CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 Devices []DeviceMapping // List of devices to map inside the container + DeviceCgroupRules []string // List of rule to be added to the device cgroup DiskQuota int64 // Disk limit (in bytes) KernelMemory int64 // Kernel memory limit (in bytes) MemoryReservation int64 // Memory soft limit (in bytes) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 55cc3c3b29..eabf9faca9 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "path" + "regexp" "strconv" "strings" "time" @@ -21,6 +22,10 @@ import ( "github.com/spf13/pflag" ) +var ( + deviceCgroupRuleRegexp = regexp.MustCompile("^[acb] ([0-9]+|\\*):([0-9]+|\\*) [rwm]{1,3}$") +) + // containerOptions is a data object with all the options for creating a container type containerOptions struct { attach opts.ListOpts @@ -36,6 +41,7 @@ type containerOptions struct { deviceWriteIOps opts.ThrottledeviceOpt env opts.ListOpts labels opts.ListOpts + deviceCgroupRules opts.ListOpts devices opts.ListOpts ulimits *opts.UlimitOpt sysctls *opts.MapOpts @@ -127,6 +133,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { dns: opts.NewListOpts(opts.ValidateIPAddress), dnsOptions: opts.NewListOpts(nil), dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), + deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule), deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), @@ -154,6 +161,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { // General purpose flags flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") + flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list") flags.Var(&copts.devices, "device", "Add a host device to the container") flags.VarP(&copts.env, "env", "e", "Set environment variables") flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") @@ -548,6 +556,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *c IOMaximumIOps: copts.ioMaxIOps, IOMaximumBandwidth: uint64(maxIOBandwidth), Ulimits: copts.ulimits.GetList(), + DeviceCgroupRules: copts.deviceCgroupRules.GetAll(), Devices: deviceMappings, } @@ -762,6 +771,17 @@ func parseDevice(device string) (container.DeviceMapping, error) { return deviceMapping, nil } +// validateDeviceCgroupRule validates a device cgroup rule string format +// It will make sure 'val' is in the form: +// 'type major:minor mode' +func validateDeviceCgroupRule(val string) (string, error) { + if deviceCgroupRuleRegexp.MatchString(val) { + return val, nil + } + + return val, fmt.Errorf("invalid device cgroup format '%s'", val) +} + // validDeviceMode checks if the mode for device is valid or not. // Valid mode is a composition of r (read), w (write), and m (mknod). func validDeviceMode(mode string) bool { diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 79a82d8b3f..76fb035f77 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1346,6 +1346,7 @@ _docker_container_run() { --cpuset-mems --cpu-shares -c --device + --device-cgroup-rule --device-read-bps --device-read-iops --device-write-bps diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 8833e8caa8..d3ebcf1837 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -121,6 +121,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cap-drop -d complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cidfile -d 'Write the container ID to the file' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cpuset -d 'CPUs in which to allow execution (0-3, 0,1)' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l device -d 'Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)' +complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l device-cgroup-rule -d 'Add a rule to the cgroup allowed devices list (e.g. --device-cgroup-rule="c 13:37 rwm")' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns -d 'Set custom DNS servers' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns-opt -d "Set custom DNS options (Use --dns-opt='' if you don't wish to set options)" complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns-search -d "Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)" @@ -312,6 +313,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Wri complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cpuset -d 'CPUs in which to allow execution (0-3, 0,1)' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s d -l detach -d 'Detached mode: run the container in the background and print the new container ID' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l device -d 'Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)' +complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l device-cgroup-rule -d 'Add a rule to the cgroup allowed devices list (e.g. --device-cgroup-rule="c 13:37 rwm")' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom DNS servers' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns-opt -d "Set custom DNS options (Use --dns-opt='' if you don't wish to set options)" complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns-search -d "Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)" diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 813f7a7181..1ca96e487b 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -546,6 +546,7 @@ __docker_container_subcommand() { "($help)--cidfile=[Write the container ID to the file]:CID file:_files" "($help)--cpus=[Number of CPUs (default 0.000)]:cpus: " "($help)*--device=[Add a host device to the container]:device:_files" + "($help)*--device-cgroup-rule=[Add a rule to the cgroup allowed devices list]:device:cgroup: " "($help)*--device-read-bps=[Limit the read rate (bytes per second) from a device]:device:IO rate: " "($help)*--device-read-iops=[Limit the read rate (IO per second) from a device]:device:IO rate: " "($help)*--device-write-bps=[Limit the write rate (bytes per second) to a device]:device:IO rate: " diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index a72b0b873d..a0671ed188 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "sort" "strconv" "strings" @@ -27,6 +28,10 @@ import ( specs "github.com/opencontainers/runtime-spec/specs-go" ) +var ( + deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\*) ([rwm]{1,3})$") +) + func setResources(s *specs.Spec, r containertypes.Resources) error { weightDevices, err := getBlkioWeightDevices(r) if err != nil { @@ -106,6 +111,41 @@ func setDevices(s *specs.Spec, c *container.Container) error { devs = append(devs, d...) devPermissions = append(devPermissions, dPermissions...) } + + for _, deviceCgroupRule := range c.HostConfig.DeviceCgroupRules { + ss := deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1) + if len(ss[0]) != 5 { + return fmt.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule) + } + matches := ss[0] + + dPermissions := specs.DeviceCgroup{ + Allow: true, + Type: &matches[1], + Access: &matches[4], + } + if matches[2] == "*" { + major := int64(-1) + dPermissions.Major = &major + } else { + major, err := strconv.ParseInt(matches[2], 10, 64) + if err != nil { + return fmt.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule) + } + dPermissions.Major = &major + } + if matches[3] == "*" { + minor := int64(-1) + dPermissions.Minor = &minor + } else { + minor, err := strconv.ParseInt(matches[3], 10, 64) + if err != nil { + return fmt.Errorf("invalid minor value in device cgroup rule format: '%s'", deviceCgroupRule) + } + dPermissions.Minor = &minor + } + devPermissions = append(devPermissions, dPermissions) + } } s.Linux.Devices = append(s.Linux.Devices, devs...) diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 253c72da70..3587e03fd0 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -44,6 +44,7 @@ Options: --cpuset-cpus string CPUs in which to allow execution (0-3, 0,1) --cpuset-mems string MEMs in which to allow execution (0-3, 0,1) --device value Add a host device to the container (default []) + --device-cgroup-rule value Add a rule to the cgroup allowed devices list --device-read-bps value Limit read rate (bytes per second) from a device (default []) --device-read-iops value Limit read rate (IO per second) from a device (default []) --device-write-bps value Limit write rate (bytes per second) to a device (default []) diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 9bacea4b1f..3cddb9d374 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -48,6 +48,7 @@ Options: -d, --detach Run container in background and print container ID --detach-keys string Override the key sequence for detaching a container --device value Add a host device to the container (default []) + --device-cgroup-rule value Add a rule to the cgroup allowed devices list --device-read-bps value Limit read rate (bytes per second) from a device (default []) --device-read-iops value Limit read rate (IO per second) from a device (default []) --device-write-bps value Limit write rate (bytes per second) to a device (default []) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 9749af4cda..4df8044536 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -4444,3 +4444,17 @@ func (s *DockerSuite) TestRunHostnameInHostMode(c *check.C) { out, _ := dockerCmd(c, "run", "--net=host", "--hostname=foobar", "busybox", "sh", "-c", `echo $HOSTNAME && hostname`) c.Assert(strings.TrimSpace(out), checker.Equals, expectedOutput) } + +func (s *DockerSuite) TestRunAddDeviceCgroupRule(c *check.C) { + testRequires(c, DaemonIsLinux) + + deviceRule := "c 7:128 rwm" + + out, _ := dockerCmd(c, "run", "--rm", "busybox", "cat", "/sys/fs/cgroup/devices/devices.list") + if strings.Contains(out, deviceRule) { + c.Fatalf("%s shouldn't been in the device.list", deviceRule) + } + + out, _ = dockerCmd(c, "run", "--rm", fmt.Sprintf("--device-cgroup-rule=%s", deviceRule), "busybox", "grep", deviceRule, "/sys/fs/cgroup/devices/devices.list") + c.Assert(strings.TrimSpace(out), checker.Equals, deviceRule) +} diff --git a/man/docker-run.1.md b/man/docker-run.1.md index 4e03331521..804aa1309f 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -27,6 +27,7 @@ docker-run - Run a command in a new container [**-d**|**--detach**] [**--detach-keys**[=*[]*]] [**--device**[=*[]*]] +[**--device-cgroup-rule**[=*[]*]] [**--device-read-bps**[=*[]*]] [**--device-read-iops**[=*[]*]] [**--device-write-bps**[=*[]*]] @@ -246,6 +247,16 @@ See **config-json(5)** for documentation on using a configuration file. **--device**=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) +**--device-cgroup-rule**=[] + Add a rule to the cgroup allowed devices list. + + The rule is expected to be in the format specified in the Linux kernel documentation (Documentation/cgroup-v1/devices.txt): + - type: `a` (all), `c` (char) or `b` (block) + - major and minor: either a number or `*` for all + - permission: a composition of `r` (read), `w` (write) and `m` (mknod) + + Example: `c 1:3 mr`: allow for character device with major `1` and minor `3` to be created (`m`) and read (`r`) + **--device-read-bps**=[] Limit read rate from a device (e.g. --device-read-bps=/dev/sda:1mb)