From 87e732a0f3503517d7a66804bb9a7f74977347e5 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Mon, 29 Sep 2014 06:44:32 -0400 Subject: [PATCH 1/4] Add --security-opts options to allow user to customize security configuration security-opts will allow you to customise the security subsystem. For example the labeling system like SELinux will run on a container. --security-opt="label:user:USER" : Set the label user for the container --security-opt="label:role:ROLE" : Set the label role for the container --security-opt="label:type:TYPE" : Set the label type for the container --security-opt="label:level:LEVEL" : Set the label level for the container --security-opt="label:disabled" : Turn off label confinement for the container Since we are passing a list of string options instead of a space separated string of options, I will change function calls to use InitLabels instead of GenLabels. Genlabels interface is Depracated. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- contrib/completion/bash/docker | 5 ++-- daemon/daemon.go | 14 +++++++--- daemon/execdriver/lxc/driver.go | 5 +++- docs/man/docker-run.1.md | 31 +++++++++++++++++++++ docs/sources/reference/run.md | 26 ++++++++++++++++++ integration-cli/docker_cli_run_test.go | 37 ++++++++++++++++++++++++++ runconfig/config.go | 2 ++ runconfig/parse.go | 3 +++ 8 files changed, 117 insertions(+), 6 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index b88f64d59d..3cc85be203 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -620,10 +620,11 @@ _docker_run() case "$cur" in -*) - COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir --cpuset -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir --cpuset -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf --security-opt" -- "$cur" ) ) ;; *) - local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf') + + local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf|--security-opt') if [ $cword -eq $counter ]; then __docker_image_repos_and_tags_and_ids diff --git a/daemon/daemon.go b/daemon/daemon.go index 8f8cff7282..267763fd06 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -529,8 +529,9 @@ func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) { var ( - id string - err error + id string + err error + label_opts []string ) id, name, err = daemon.generateIdAndName(name) if err != nil { @@ -558,7 +559,14 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i } container.root = daemon.containerRoot(container.ID) - if container.ProcessLabel, container.MountLabel, err = label.GenLabels(""); err != nil { + for _, opt := range config.SecurityOpt { + con := strings.SplitN(opt, ":", 2) + if con[0] == "label" { + label_opts = append(label_opts, con[1]) + } + } + + if container.ProcessLabel, container.MountLabel, err = label.InitLabels(label_opts); err != nil { return nil, err } return container, nil diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 0809b05c1e..abb74bf579 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -409,7 +409,10 @@ func rootIsShared() bool { } func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { - root := path.Join(d.root, "containers", c.ID, "config.lxc") + var ( + root = path.Join(d.root, "containers", c.ID, "config.lxc") + label_opts []string + ) fo, err := os.Create(root) if err != nil { diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 1aa75832ca..1a43fd3342 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -23,6 +23,7 @@ docker-run - Run a command in a new container [**--expose**[=*[]*]] [**-h**|**--hostname**[=*HOSTNAME*]] [**-i**|**--interactive**[=*false*]] +[**--security-opt**[=*[]*]] [**--link**[=*[]*]] [**--lxc-conf**[=*[]*]] [**-m**|**--memory**[=*MEMORY*]] @@ -143,6 +144,13 @@ container can be started with the **--link**. **-i**, **--interactive**=*true*|*false* When set to true, keep stdin open even if not attached. The default is false. +**--security-opt**=*secdriver*:*name*:*value* + "label:user:USER" : Set the label user for the container + "label:role:ROLE" : Set the label role for the container + "label:type:TYPE" : Set the label type for the container + "label:level:LEVEL" : Set the label level for the container + "label:disable" : Turn off label confinement for the container + **--link**=*name*:*alias* Add link to another container. The format is name:alias. If the operator uses **--link** when starting the new client container, then the client @@ -383,6 +391,29 @@ to the host directory: Now, writing to the /data1 volume in the container will be allowed and the changes will also be reflected on the host in /var/db. +## Using alternative security labeling + +If you want to use the same label for multiple containers you can override use +the security-opt flag to select an MCS level. This is a common practive for MLS +systems. But it also might help in cases where you want to share the same +content between containers. Run the following command. + + # docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash + +Run the follwing command if you want to disable the labeling controls for just +this container. + + # docker run --security-opt label:disable -i -t fedora bash + +If you decide you would like to work with a tighter policy on your container. +For example if you want to run a container that could only listen on apache +ports, and not connect to the network. You could select an alternate type to +run the container execute the following command. + + # docker run --security-opt label:type:svirt_apache_t -i -t fedora bash + +Note: You would have to write policy defining a svirt_apache_t type. + # HISTORY April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 5bdaa256d6..095319f133 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -225,6 +225,32 @@ the container exits**, you can add the `--rm` flag: --rm=false: Automatically remove the container when it exits (incompatible with -d) +## Security Configuration + --security-opt="label:user:USER" : Set the label user for the container + --security-opt="label:role:ROLE" : Set the label role for the container + --security-opt="label:type:TYPE" : Set the label type for the container + --security-opt="label:level:LEVEL" : Set the label level for the container + --security-opt="label:disable" : Turn off label confinement for the container + +If you want to use the same label for multiple containers you can override use +the security-opt flag to select an MCS level. This is a common practive for MLS +systems. But it also might help in cases where you want to share the same +content between containers. Run the following command. + + # docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash + +Run the follwing command if you want to disable the labeling controls for just +this container. + + # docker run --security-opt label:disable -i -t fedora bash + +If you decide you would like to work with a tighter policy on your container. +For example if you want to run a container that could only listen on apache +ports, and not connect to the network. You could select an alternate type to +run the container execute the following command. + + # docker run --security-opt label:type:svirt_apache_t -i -t fedora bash + ## Runtime Constraints on CPU and Memory The operator can also adjust the performance parameters of the diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 01a3f57638..9b9d8efc5a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -19,6 +19,7 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/networkfs/resolvconf" + "github.com/docker/libcontainer/label" "github.com/kr/pty" ) @@ -1719,6 +1720,42 @@ func TestRunWriteResolvFileAndNotCommit(t *testing.T) { logDone("run - write to /etc/resolv.conf and not commited") } +func TestRunSecurityOptLevel(t *testing.T) { + plabel, _, _ := label.InitLabels(nil) + if plabel != "" { + defer deleteAllContainers() + cmd := exec.Command(dockerBinary, "run", "--security-opt", "label:level:s0:c0,c100", "busybox", "ps", "-eZ") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + id := strings.TrimSpace(out) + if !strings.ContainsAny(id, "s0:c0,c100") { + t.Fatal("security-opt label:level:s0:c0,c100 failed") + } + } + + logDone("run - security-opt label:level") +} + +func TestRunSecurityOptDisable(t *testing.T) { + plabel, _, _ := label.InitLabels(nil) + if plabel != "" { + defer deleteAllContainers() + cmd := exec.Command(dockerBinary, "run", "--security-opt", "label:disable", "busybox", "ps", "-eZ") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + id := strings.TrimSpace(out) + if !strings.ContainsAny(id, "svirt") { + t.Fatal("security-opt label:level:disable failed") + } + } + + logDone("run - security-opt label:disable") +} + func TestRunWithBadDevice(t *testing.T) { name := "baddevice" cmd := exec.Command(dockerBinary, "run", "--name", name, "--device", "/etc", "busybox", "true") diff --git a/runconfig/config.go b/runconfig/config.go index c00110bf71..28e85de647 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -32,6 +32,7 @@ type Config struct { Entrypoint []string NetworkDisabled bool OnBuild []string + SecurityOpt []string } func ContainerConfigFromJob(job *engine.Job) *Config { @@ -55,6 +56,7 @@ func ContainerConfigFromJob(job *engine.Job) *Config { } job.GetenvJson("ExposedPorts", &config.ExposedPorts) job.GetenvJson("Volumes", &config.Volumes) + config.SecurityOpt = job.GetenvList("SecurityOpt") if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { config.PortSpecs = PortSpecs } diff --git a/runconfig/parse.go b/runconfig/parse.go index bc94878b14..42ec68898d 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -43,6 +43,7 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, flEnvFile = opts.NewListOpts(nil) flCapAdd = opts.NewListOpts(nil) flCapDrop = opts.NewListOpts(nil) + flSecurityOpt = opts.NewListOpts(nil) flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container") flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container") @@ -79,6 +80,7 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities") cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities") + cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -254,6 +256,7 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, Volumes: flVolumes.GetMap(), Entrypoint: entrypoint, WorkingDir: *flWorkingDir, + SecurityOpt: flSecurityOpt.GetAll(), } hostConfig := &HostConfig{ From c2c5e57a8ea4b1dc35c58654443a8c4508277904 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Sep 2014 23:34:45 +0000 Subject: [PATCH 2/4] add apparmor: Signed-off-by: Victor Vieux --- daemon/container.go | 2 ++ daemon/daemon.go | 10 +++++++++- daemon/execdriver/driver.go | 1 + daemon/execdriver/lxc/driver.go | 5 +---- daemon/execdriver/native/create.go | 4 ++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 5ea2df2af5..042f4f025b 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -77,6 +77,7 @@ type Container struct { daemon *Daemon MountLabel, ProcessLabel string + AppArmorProfile string RestartCount int // Maps container paths to volume paths. The key in this is the path to which @@ -275,6 +276,7 @@ func populateCommand(c *Container, env []string) error { ProcessLabel: c.GetProcessLabel(), MountLabel: c.GetMountLabel(), LxcConfig: lxcConfig, + AppArmorProfile: c.AppArmorProfile, } return nil diff --git a/daemon/daemon.go b/daemon/daemon.go index 267763fd06..b47498b2e1 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -561,8 +561,16 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i for _, opt := range config.SecurityOpt { con := strings.SplitN(opt, ":", 2) - if con[0] == "label" { + if len(con) == 1 { + return nil, fmt.Errorf("Invalid --security-opt: %q", opt) + } + switch con[0] { + case "label": label_opts = append(label_opts, con[1]) + case "apparmor": + container.AppArmorProfile = con[1] + default: + return nil, fmt.Errorf("Invalid --security-opt: %q", opt) } } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 6a7e79eca6..0be2d50dc4 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -116,4 +116,5 @@ type Command struct { ProcessLabel string `json:"process_label"` MountLabel string `json:"mount_label"` LxcConfig []string `json:"lxc_config"` + AppArmorProfile string `json:"apparmor_profile"` } diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index abb74bf579..0809b05c1e 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -409,10 +409,7 @@ func rootIsShared() bool { } func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { - var ( - root = path.Join(d.root, "containers", c.ID, "config.lxc") - label_opts []string - ) + root := path.Join(d.root, "containers", c.ID, "config.lxc") fo, err := os.Create(root) if err != nil { diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index c3abb9a75b..4f325353e9 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -49,6 +49,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e } } + if c.AppArmorProfile != "" { + container.AppArmorProfile = c.AppArmorProfile + } + if err := d.setupCgroups(container, c); err != nil { return nil, err } From 226bc669aa9a886c5a6855e054b751e72d20971b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Sep 2014 00:59:29 +0000 Subject: [PATCH 3/4] update docs Signed-off-by: Victor Vieux --- docs/man/docker-run.1.md | 4 ++-- docs/sources/reference/run.md | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 1a43fd3342..cd94b15b00 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -393,8 +393,8 @@ changes will also be reflected on the host in /var/db. ## Using alternative security labeling -If you want to use the same label for multiple containers you can override use -the security-opt flag to select an MCS level. This is a common practive for MLS +If you want to use the same label for multiple containers, you can override use +the security-opt flag to select an MCS level. This is a common practice for MLS systems. But it also might help in cases where you want to share the same content between containers. Run the following command. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 095319f133..f5d6cc45e5 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -231,23 +231,23 @@ the container exits**, you can add the `--rm` flag: --security-opt="label:type:TYPE" : Set the label type for the container --security-opt="label:level:LEVEL" : Set the label level for the container --security-opt="label:disable" : Turn off label confinement for the container + --secutity-opt="apparmor:PROFILE" : Set the apparmor profile to be applied + to the container -If you want to use the same label for multiple containers you can override use -the security-opt flag to select an MCS level. This is a common practive for MLS +If you want to use the same label for multiple containers, you can override use +the security-opt flag to select an MCS level. This is a common practice for MLS systems. But it also might help in cases where you want to share the same content between containers. Run the following command. # docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash -Run the follwing command if you want to disable the labeling controls for just +Run the following command if you want to disable the labeling controls for just this container. # docker run --security-opt label:disable -i -t fedora bash -If you decide you would like to work with a tighter policy on your container. -For example if you want to run a container that could only listen on apache -ports, and not connect to the network. You could select an alternate type to -run the container execute the following command. +Run the following command if you want to run a container that could only listen +on apache ports. # docker run --security-opt label:type:svirt_apache_t -i -t fedora bash From 08547dff29490236949317ca604dbda9d8feac4f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Sep 2014 19:10:03 +0000 Subject: [PATCH 4/4] update tests Signed-off-by: Victor Vieux --- daemon/daemon.go | 52 ++++++++++++++------------ daemon/daemon_unit_test.go | 39 +++++++++++++++++++ integration-cli/docker_cli_run_test.go | 37 ------------------ 3 files changed, 68 insertions(+), 60 deletions(-) create mode 100644 daemon/daemon_unit_test.go diff --git a/daemon/daemon.go b/daemon/daemon.go index b47498b2e1..0ee0e3d127 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -527,11 +527,35 @@ func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) return entrypoint, args } +func parseSecurityOpt(container *Container, config *runconfig.Config) error { + var ( + label_opts []string + err error + ) + + for _, opt := range config.SecurityOpt { + con := strings.SplitN(opt, ":", 2) + if len(con) == 1 { + return fmt.Errorf("Invalid --security-opt: %q", opt) + } + switch con[0] { + case "label": + label_opts = append(label_opts, con[1]) + case "apparmor": + container.AppArmorProfile = con[1] + default: + return fmt.Errorf("Invalid --security-opt: %q", opt) + } + } + + container.ProcessLabel, container.MountLabel, err = label.InitLabels(label_opts) + return err +} + func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) { var ( - id string - err error - label_opts []string + id string + err error ) id, name, err = daemon.generateIdAndName(name) if err != nil { @@ -558,26 +582,8 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i execCommands: newExecStore(), } container.root = daemon.containerRoot(container.ID) - - for _, opt := range config.SecurityOpt { - con := strings.SplitN(opt, ":", 2) - if len(con) == 1 { - return nil, fmt.Errorf("Invalid --security-opt: %q", opt) - } - switch con[0] { - case "label": - label_opts = append(label_opts, con[1]) - case "apparmor": - container.AppArmorProfile = con[1] - default: - return nil, fmt.Errorf("Invalid --security-opt: %q", opt) - } - } - - if container.ProcessLabel, container.MountLabel, err = label.InitLabels(label_opts); err != nil { - return nil, err - } - return container, nil + err = parseSecurityOpt(container, config) + return container, err } func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error { diff --git a/daemon/daemon_unit_test.go b/daemon/daemon_unit_test.go new file mode 100644 index 0000000000..f3b899ec8d --- /dev/null +++ b/daemon/daemon_unit_test.go @@ -0,0 +1,39 @@ +package daemon + +import ( + "testing" + + "github.com/docker/docker/runconfig" +) + +func TestParseSecurityOpt(t *testing.T) { + container := &Container{} + config := &runconfig.Config{} + + // test apparmor + config.SecurityOpt = []string{"apparmor:test_profile"} + if err := parseSecurityOpt(container, config); err != nil { + t.Fatalf("Unexpected parseSecurityOpt error: %v", err) + } + if container.AppArmorProfile != "test_profile" { + t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile) + } + + // test valid label + config.SecurityOpt = []string{"label:user:USER"} + if err := parseSecurityOpt(container, config); err != nil { + t.Fatalf("Unexpected parseSecurityOpt error: %v", err) + } + + // test invalid label + config.SecurityOpt = []string{"label"} + if err := parseSecurityOpt(container, config); err == nil { + t.Fatal("Expected parseSecurityOpt error, got nil") + } + + // test invalid opt + config.SecurityOpt = []string{"test"} + if err := parseSecurityOpt(container, config); err == nil { + t.Fatal("Expected parseSecurityOpt error, got nil") + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 9b9d8efc5a..01a3f57638 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -19,7 +19,6 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/networkfs/resolvconf" - "github.com/docker/libcontainer/label" "github.com/kr/pty" ) @@ -1720,42 +1719,6 @@ func TestRunWriteResolvFileAndNotCommit(t *testing.T) { logDone("run - write to /etc/resolv.conf and not commited") } -func TestRunSecurityOptLevel(t *testing.T) { - plabel, _, _ := label.InitLabels(nil) - if plabel != "" { - defer deleteAllContainers() - cmd := exec.Command(dockerBinary, "run", "--security-opt", "label:level:s0:c0,c100", "busybox", "ps", "-eZ") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - id := strings.TrimSpace(out) - if !strings.ContainsAny(id, "s0:c0,c100") { - t.Fatal("security-opt label:level:s0:c0,c100 failed") - } - } - - logDone("run - security-opt label:level") -} - -func TestRunSecurityOptDisable(t *testing.T) { - plabel, _, _ := label.InitLabels(nil) - if plabel != "" { - defer deleteAllContainers() - cmd := exec.Command(dockerBinary, "run", "--security-opt", "label:disable", "busybox", "ps", "-eZ") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - id := strings.TrimSpace(out) - if !strings.ContainsAny(id, "svirt") { - t.Fatal("security-opt label:level:disable failed") - } - } - - logDone("run - security-opt label:disable") -} - func TestRunWithBadDevice(t *testing.T) { name := "baddevice" cmd := exec.Command(dockerBinary, "run", "--name", name, "--device", "/etc", "busybox", "true")