Browse Source

Merge pull request #8299 from vieux/pr_7425

Add --security-opts options to allow user to customize container labels and apparmor profile
Andrea Luzzardi 10 years ago
parent
commit
d40ab6f123

+ 3 - 2
contrib/completion/bash/docker

@@ -620,10 +620,11 @@ _docker_run()
 
 
 	case "$cur" in
 	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
 			if [ $cword -eq $counter ]; then
 				__docker_image_repos_and_tags_and_ids
 				__docker_image_repos_and_tags_and_ids

+ 2 - 0
daemon/container.go

@@ -77,6 +77,7 @@ type Container struct {
 
 
 	daemon                   *Daemon
 	daemon                   *Daemon
 	MountLabel, ProcessLabel string
 	MountLabel, ProcessLabel string
+	AppArmorProfile          string
 	RestartCount             int
 	RestartCount             int
 
 
 	// Maps container paths to volume paths.  The key in this is the path to which
 	// 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(),
 		ProcessLabel:       c.GetProcessLabel(),
 		MountLabel:         c.GetMountLabel(),
 		MountLabel:         c.GetMountLabel(),
 		LxcConfig:          lxcConfig,
 		LxcConfig:          lxcConfig,
+		AppArmorProfile:    c.AppArmorProfile,
 	}
 	}
 
 
 	return nil
 	return nil

+ 27 - 5
daemon/daemon.go

@@ -527,6 +527,31 @@ func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string)
 	return entrypoint, args
 	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) {
 func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) {
 	var (
 	var (
 		id  string
 		id  string
@@ -557,11 +582,8 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
 		execCommands:    newExecStore(),
 		execCommands:    newExecStore(),
 	}
 	}
 	container.root = daemon.containerRoot(container.ID)
 	container.root = daemon.containerRoot(container.ID)
-
-	if container.ProcessLabel, container.MountLabel, err = label.GenLabels(""); 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 {
 func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error {

+ 39 - 0
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")
+	}
+}

+ 1 - 0
daemon/execdriver/driver.go

@@ -116,4 +116,5 @@ type Command struct {
 	ProcessLabel       string            `json:"process_label"`
 	ProcessLabel       string            `json:"process_label"`
 	MountLabel         string            `json:"mount_label"`
 	MountLabel         string            `json:"mount_label"`
 	LxcConfig          []string          `json:"lxc_config"`
 	LxcConfig          []string          `json:"lxc_config"`
+	AppArmorProfile    string            `json:"apparmor_profile"`
 }
 }

+ 4 - 0
daemon/execdriver/native/create.go

@@ -50,6 +50,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 {
 	if err := d.setupCgroups(container, c); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 31 - 0
docs/man/docker-run.1.md

@@ -23,6 +23,7 @@ docker-run - Run a command in a new container
 [**--expose**[=*[]*]]
 [**--expose**[=*[]*]]
 [**-h**|**--hostname**[=*HOSTNAME*]]
 [**-h**|**--hostname**[=*HOSTNAME*]]
 [**-i**|**--interactive**[=*false*]]
 [**-i**|**--interactive**[=*false*]]
+[**--security-opt**[=*[]*]]
 [**--link**[=*[]*]]
 [**--link**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**-m**|**--memory**[=*MEMORY*]]
@@ -143,6 +144,13 @@ container can be started with the **--link**.
 **-i**, **--interactive**=*true*|*false*
 **-i**, **--interactive**=*true*|*false*
    When set to true, keep stdin open even if not attached. The default is 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*
 **--link**=*name*:*alias*
    Add link to another container. The format is name:alias. If the operator
    Add link to another container. The format is name:alias. If the operator
 uses **--link** when starting the new client container, then the client
 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
 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.
 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 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 
+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
 # HISTORY
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
 based on docker.com source material and internal work.
 based on docker.com source material and internal work.

+ 26 - 0
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)
     --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
+    --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 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 following command if you want to disable the labeling controls for just 
+this container.
+
+    # docker run --security-opt label:disable -i -t fedora bash
+
+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
+
 ## Runtime Constraints on CPU and Memory
 ## Runtime Constraints on CPU and Memory
 
 
 The operator can also adjust the performance parameters of the
 The operator can also adjust the performance parameters of the

+ 2 - 0
runconfig/config.go

@@ -32,6 +32,7 @@ type Config struct {
 	Entrypoint      []string
 	Entrypoint      []string
 	NetworkDisabled bool
 	NetworkDisabled bool
 	OnBuild         []string
 	OnBuild         []string
+	SecurityOpt     []string
 }
 }
 
 
 func ContainerConfigFromJob(job *engine.Job) *Config {
 func ContainerConfigFromJob(job *engine.Job) *Config {
@@ -55,6 +56,7 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
 	}
 	}
 	job.GetenvJson("ExposedPorts", &config.ExposedPorts)
 	job.GetenvJson("ExposedPorts", &config.ExposedPorts)
 	job.GetenvJson("Volumes", &config.Volumes)
 	job.GetenvJson("Volumes", &config.Volumes)
+	config.SecurityOpt = job.GetenvList("SecurityOpt")
 	if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
 	if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
 		config.PortSpecs = PortSpecs
 		config.PortSpecs = PortSpecs
 	}
 	}

+ 3 - 0
runconfig/parse.go

@@ -43,6 +43,7 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config,
 		flEnvFile     = opts.NewListOpts(nil)
 		flEnvFile     = opts.NewListOpts(nil)
 		flCapAdd      = opts.NewListOpts(nil)
 		flCapAdd      = opts.NewListOpts(nil)
 		flCapDrop     = opts.NewListOpts(nil)
 		flCapDrop     = opts.NewListOpts(nil)
+		flSecurityOpt = opts.NewListOpts(nil)
 
 
 		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
 		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")
 		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(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
 	cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop 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 {
 	if err := cmd.Parse(args); err != nil {
 		return nil, nil, cmd, err
 		return nil, nil, cmd, err
@@ -254,6 +256,7 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config,
 		Volumes:         flVolumes.GetMap(),
 		Volumes:         flVolumes.GetMap(),
 		Entrypoint:      entrypoint,
 		Entrypoint:      entrypoint,
 		WorkingDir:      *flWorkingDir,
 		WorkingDir:      *flWorkingDir,
+		SecurityOpt:     flSecurityOpt.GetAll(),
 	}
 	}
 
 
 	hostConfig := &HostConfig{
 	hostConfig := &HostConfig{