From d7fda019bb7e24f42f8ae1ddecb3fd52df3c48bf Mon Sep 17 00:00:00 2001 From: Daniel Zhang Date: Mon, 9 Jan 2017 09:22:05 +0800 Subject: [PATCH] Add daemon flag to set no_new_priv as default for unprivileged containers. Signed-off-by: Daniel Zhang --- daemon/config/config_unix.go | 1 + daemon/container.go | 2 +- daemon/daemon_solaris.go | 4 +++ daemon/daemon_unix.go | 11 ++++++++ daemon/daemon_unix_test.go | 29 +++++++++++++++++++++ daemon/daemon_windows.go | 4 +++ docs/reference/commandline/dockerd.md | 2 ++ docs/reference/run.md | 2 +- integration-cli/docker_cli_run_unix_test.go | 15 ++++++++++- 9 files changed, 67 insertions(+), 3 deletions(-) diff --git a/daemon/config/config_unix.go b/daemon/config/config_unix.go index f0f1fe8e57..8f1da59198 100644 --- a/daemon/config/config_unix.go +++ b/daemon/config/config_unix.go @@ -30,6 +30,7 @@ type Config struct { InitPath string `json:"init-path,omitempty"` SeccompProfile string `json:"seccomp-profile,omitempty"` ShmSize opts.MemBytes `json:"default-shm-size,omitempty"` + NoNewPrivileges bool `json:"no-new-privileges,omitempty"` } // BridgeConfig stores all the bridge driver specific diff --git a/daemon/container.go b/daemon/container.go index 065e4d29bd..904ef110f3 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -181,7 +181,7 @@ func (daemon *Daemon) generateHostname(id string, config *containertypes.Config) func (daemon *Daemon) setSecurityOptions(container *container.Container, hostConfig *containertypes.HostConfig) error { container.Lock() defer container.Unlock() - return parseSecurityOpt(container, hostConfig) + return daemon.parseSecurityOpt(container, hostConfig) } func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *containertypes.HostConfig) error { diff --git a/daemon/daemon_solaris.go b/daemon/daemon_solaris.go index 69b4c459d7..4a6fac9283 100644 --- a/daemon/daemon_solaris.go +++ b/daemon/daemon_solaris.go @@ -66,6 +66,10 @@ func (daemon *Daemon) cleanupMountsByID(id string) error { return nil } +func (daemon *Daemon) parseSecurityOpt(container *container.Container, hostConfig *containertypes.HostConfig) error { + return parseSecurityOpt(container, hostConfig) +} + func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error { //Since config.SecurityOpt is specifically defined as a "List of string values to //customize labels for MLs systems, such as SELinux" diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 06f6f38415..14c527d609 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -162,6 +162,11 @@ func getBlkioWeightDevices(config containertypes.Resources) ([]specs.WeightDevic return blkioWeightDevices, nil } +func (daemon *Daemon) parseSecurityOpt(container *container.Container, hostConfig *containertypes.HostConfig) error { + container.NoNewPrivileges = daemon.configStore.NoNewPrivileges + return parseSecurityOpt(container, hostConfig) +} + func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error { var ( labelOpts []string @@ -193,6 +198,12 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos container.AppArmorProfile = con[1] case "seccomp": container.SeccompProfile = con[1] + case "no-new-privileges": + noNewPrivileges, err := strconv.ParseBool(con[1]) + if err != nil { + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + container.NoNewPrivileges = noNewPrivileges default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } diff --git a/daemon/daemon_unix_test.go b/daemon/daemon_unix_test.go index 9889bd19fe..ebb0a0186a 100644 --- a/daemon/daemon_unix_test.go +++ b/daemon/daemon_unix_test.go @@ -180,6 +180,35 @@ func TestParseSecurityOpt(t *testing.T) { } } +func TestParseNNPSecurityOptions(t *testing.T) { + daemon := &Daemon{ + configStore: &config.Config{NoNewPrivileges: true}, + } + container := &container.Container{} + config := &containertypes.HostConfig{} + + // test NNP when "daemon:true" and "no-new-privileges=false"" + config.SecurityOpt = []string{"no-new-privileges=false"} + + if err := daemon.parseSecurityOpt(container, config); err != nil { + t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err) + } + if container.NoNewPrivileges { + t.Fatalf("container.NoNewPrivileges should be FALSE: %v", container.NoNewPrivileges) + } + + // test NNP when "daemon:false" and "no-new-privileges=true"" + daemon.configStore.NoNewPrivileges = false + config.SecurityOpt = []string{"no-new-privileges=true"} + + if err := daemon.parseSecurityOpt(container, config); err != nil { + t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err) + } + if !container.NoNewPrivileges { + t.Fatalf("container.NoNewPrivileges should be TRUE: %v", container.NoNewPrivileges) + } +} + func TestNetworkOptions(t *testing.T) { daemon := &Daemon{} dconfigCorrect := &config.Config{ diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 93154af1b2..6507e283c2 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -48,6 +48,10 @@ func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.Weight return nil, nil } +func (daemon *Daemon) parseSecurityOpt(container *container.Container, hostConfig *containertypes.HostConfig) error { + return parseSecurityOpt(container, hostConfig) +} + func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error { return nil } diff --git a/docs/reference/commandline/dockerd.md b/docs/reference/commandline/dockerd.md index 1ebdc3b666..84f4d998ed 100644 --- a/docs/reference/commandline/dockerd.md +++ b/docs/reference/commandline/dockerd.md @@ -70,6 +70,7 @@ Options: --max-concurrent-uploads int Set the max concurrent uploads for each push (default 5) --metrics-addr string Set address and port to serve the metrics api (default "") --mtu int Set the containers network MTU + --no-new-privileges Disable container processes from gaining new privileges --oom-score-adjust int Set the oom_score_adj for the daemon (default -500) -p, --pidfile string Path to use for daemon PID file (default "/var/run/docker.pid") --raw-logs Full timestamps without ANSI coloring @@ -1190,6 +1191,7 @@ This is a full example of the allowed configuration options on Linux: "seccomp-profile": "", "insecure-registries": [], "disable-legacy-registry": false, + "no-new-privileges": false, "default-runtime": "runc", "oom-score-adjust": -500, "runtimes": { diff --git a/docs/reference/run.md b/docs/reference/run.md index d26c625004..62275e02ac 100644 --- a/docs/reference/run.md +++ b/docs/reference/run.md @@ -630,7 +630,7 @@ with the same logic -- if the original volume was specified with a name it will --security-opt="label=level:LEVEL" : Set the label level for the container --security-opt="label=disable" : Turn off label confinement for the container --security-opt="apparmor=PROFILE" : Set the apparmor profile to be applied to the container - --security-opt="no-new-privileges" : Disable container processes from gaining new privileges + --security-opt="no-new-privileges:true|false" : Disable/enable container processes from gaining new privileges --security-opt="seccomp=unconfined" : Turn off seccomp confinement for the container --security-opt="seccomp=profile.json": White listed syscalls seccomp Json file to be used as a seccomp filter diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 0318b1de1c..7a17766976 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -1140,12 +1140,25 @@ func (s *DockerSuite) TestRunSeccompDefaultProfileNS(c *check.C) { } } -// TestRunNoNewPrivSetuid checks that --security-opt=no-new-privileges prevents +// TestRunNoNewPrivSetuid checks that --security-opt='no-new-privileges=true' prevents // effective uid transtions on executing setuid binaries. func (s *DockerSuite) TestRunNoNewPrivSetuid(c *check.C) { testRequires(c, DaemonIsLinux, NotUserNamespace, SameHostDaemon) ensureNNPTest(c) + // test that running a setuid binary results in no effective uid transition + icmd.RunCommand(dockerBinary, "run", "--security-opt", "no-new-privileges=true", "--user", "1000", + "nnp-test", "/usr/bin/nnp-test").Assert(c, icmd.Expected{ + Out: "EUID=1000", + }) +} + +// TestLegacyRunNoNewPrivSetuid checks that --security-opt=no-new-privileges prevents +// effective uid transtions on executing setuid binaries. +func (s *DockerSuite) TestLegacyRunNoNewPrivSetuid(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace, SameHostDaemon) + ensureNNPTest(c) + // test that running a setuid binary results in no effective uid transition icmd.RunCommand(dockerBinary, "run", "--security-opt", "no-new-privileges", "--user", "1000", "nnp-test", "/usr/bin/nnp-test").Assert(c, icmd.Expected{