Add support for NoNewPrivileges in docker

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>

Add tests for no-new-privileges

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>

Update documentation for no-new-privileges

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2016-02-21 21:31:21 -08:00
parent 17156ba98f
commit 74bb1ce9e9
13 changed files with 89 additions and 13 deletions

View file

@ -50,6 +50,7 @@ type Container struct {
ShmPath string ShmPath string
ResolvConfPath string ResolvConfPath string
SeccompProfile string SeccompProfile string
NoNewPrivileges bool
} }
// CreateDaemonEnvironment returns the list of all environment variables given the list of // CreateDaemonEnvironment returns the list of all environment variables given the list of

View file

@ -0,0 +1,9 @@
FROM buildpack-deps:jessie
COPY . /usr/src/
WORKDIR /usr/src/
RUN gcc -g -Wall -static nnp-test.c -o /usr/bin/nnp-test
RUN chmod +s /usr/bin/nnp-test

View file

@ -0,0 +1,10 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
printf("EUID=%d\n", geteuid());
return 0;
}

View file

@ -270,6 +270,7 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro
SeccompProfile: c.SeccompProfile, SeccompProfile: c.SeccompProfile,
UIDMapping: uidMap, UIDMapping: uidMap,
UTS: uts, UTS: uts,
NoNewPrivileges: c.NoNewPrivileges,
} }
if c.HostConfig.CgroupParent != "" { if c.HostConfig.CgroupParent != "" {
c.Command.CgroupParent = c.HostConfig.CgroupParent c.Command.CgroupParent = c.HostConfig.CgroupParent

View file

@ -75,17 +75,23 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos
for _, opt := range config.SecurityOpt { for _, opt := range config.SecurityOpt {
con := strings.SplitN(opt, ":", 2) con := strings.SplitN(opt, ":", 2)
if len(con) == 1 { if len(con) == 1 {
return fmt.Errorf("Invalid --security-opt: %q", opt) switch con[0] {
} case "no-new-privileges":
switch con[0] { container.NoNewPrivileges = true
case "label": default:
labelOpts = append(labelOpts, con[1]) return fmt.Errorf("Invalid --security-opt 1: %q", opt)
case "apparmor": }
container.AppArmorProfile = con[1] } else {
case "seccomp": switch con[0] {
container.SeccompProfile = con[1] case "label":
default: labelOpts = append(labelOpts, con[1])
return fmt.Errorf("Invalid --security-opt: %q", opt) case "apparmor":
container.AppArmorProfile = con[1]
case "seccomp":
container.SeccompProfile = con[1]
default:
return fmt.Errorf("Invalid --security-opt 2: %q", opt)
}
} }
} }

View file

@ -124,6 +124,7 @@ type Command struct {
SeccompProfile string `json:"seccomp_profile"` SeccompProfile string `json:"seccomp_profile"`
UIDMapping []idtools.IDMap `json:"uidmapping"` UIDMapping []idtools.IDMap `json:"uidmapping"`
UTS *UTS `json:"uts"` UTS *UTS `json:"uts"`
NoNewPrivileges bool `json:"no_new_privileges"`
} }
// SetRootPropagation sets the root mount propagation mode. // SetRootPropagation sets the root mount propagation mode.

View file

@ -122,6 +122,8 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
d.setupLabels(container, c) d.setupLabels(container, c)
d.setupRlimits(container, c) d.setupRlimits(container, c)
container.NoNewPrivileges = c.NoNewPrivileges
return container, nil return container, nil
} }

View file

@ -605,6 +605,8 @@ with the same logic -- if the original volume was specified with a name it will
--security-opt="label:disable" : Turn off label confinement 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 --security-opt="apparmor:PROFILE" : Set the apparmor profile to be applied
to the container to the container
--security-opt="no-new-privileges" : Disable container processes from gaining
new privileges
You can override the default labeling scheme for each container by specifying You can override the default labeling scheme for each container by specifying
the `--security-opt` flag. For example, you can specify the MCS/MLS level, a the `--security-opt` flag. For example, you can specify the MCS/MLS level, a
@ -631,6 +633,13 @@ command:
> **Note**: You would have to write policy defining a `svirt_apache_t` type. > **Note**: You would have to write policy defining a `svirt_apache_t` type.
If you want to prevent your container processes from gaining additional
privileges, you can execute the following command:
$ docker run --security-opt no-new-privileges -it centos bash
For more details, see [kernel documentation](https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt).
## Specifying custom cgroups ## Specifying custom cgroups
Using the `--cgroup-parent` flag, you can pass a specific cgroup to run a Using the `--cgroup-parent` flag, you can pass a specific cgroup to run a

View file

@ -0,0 +1,22 @@
#!/bin/bash
set -e
# Build a C binary for testing no-new-privileges
# and compile it for target daemon
if [ "$DOCKER_ENGINE_GOOS" = "linux" ]; then
if [ "$DOCKER_ENGINE_OSARCH" = "$DOCKER_CLIENT_OSARCH" ]; then
tmpdir=$(mktemp -d)
gcc -g -Wall -static contrib/nnp-test/nnp-test.c -o "${tmpdir}/nnp-test"
dockerfile="${tmpdir}/Dockerfile"
cat <<-EOF > "$dockerfile"
FROM debian:jessie
COPY . /usr/bin/
RUN chmod +s /usr/bin/nnp-test
EOF
docker build --force-rm ${DOCKER_BUILD_ARGS} -qt nnp-test "${tmpdir}" > /dev/null
rm -rf "${tmpdir}"
else
docker build ${DOCKER_BUILD_ARGS} -qt nnp-test contrib/nnp-test > /dev/null
fi
fi

View file

@ -7,6 +7,7 @@ if [ $DOCKER_ENGINE_GOOS != "windows" ]; then
bundle .ensure-frozen-images bundle .ensure-frozen-images
bundle .ensure-httpserver bundle .ensure-httpserver
bundle .ensure-syscall-test bundle .ensure-syscall-test
bundle .ensure-nnp-test
else else
# Note this is Windows to Windows CI, not Windows to Linux CI # Note this is Windows to Windows CI, not Windows to Linux CI
bundle .ensure-frozen-images-windows bundle .ensure-frozen-images-windows

View file

@ -895,6 +895,18 @@ func (s *DockerSuite) TestRunSeccompDefaultProfile(c *check.C) {
} }
} }
// TestRunNoNewPrivSetuid checks that --security-opt=no-new-privileges prevents
// effective uid transtions on executing setuid binaries.
func (s *DockerSuite) TestRunNoNewPrivSetuid(c *check.C) {
testRequires(c, DaemonIsLinux, NotUserNamespace, SameHostDaemon)
// test that running a setuid binary results in no effective uid transition
runCmd := exec.Command(dockerBinary, "run", "--security-opt", "no-new-privileges", "--user", "1000", "nnp-test", "/usr/bin/nnp-test")
if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "EUID=1000") {
c.Fatalf("expected output to contain EUID=1000, got %s: %v", out, err)
}
}
func (s *DockerSuite) TestRunApparmorProcDirectory(c *check.C) { func (s *DockerSuite) TestRunApparmorProcDirectory(c *check.C) {
testRequires(c, SameHostDaemon, Apparmor) testRequires(c, SameHostDaemon, Apparmor)

View file

@ -459,6 +459,8 @@ its root filesystem mounted as read only prohibiting any writes.
"label:type:TYPE" : Set the label type for the container "label:type:TYPE" : Set the label type for the container
"label:level:LEVEL" : Set the label level for the container "label:level:LEVEL" : Set the label level for the container
"label:disable" : Turn off label confinement for the container "label:disable" : Turn off label confinement for the container
"no-new-privileges" : Disable container processes from gaining additional privileges
**--stop-signal**=*SIGTERM* **--stop-signal**=*SIGTERM*
Signal to stop a container. Default is SIGTERM. Signal to stop a container. Default is SIGTERM.

View file

@ -500,8 +500,8 @@ func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]st
func parseSecurityOpts(securityOpts []string) ([]string, error) { func parseSecurityOpts(securityOpts []string) ([]string, error) {
for key, opt := range securityOpts { for key, opt := range securityOpts {
con := strings.SplitN(opt, ":", 2) con := strings.SplitN(opt, ":", 2)
if len(con) == 1 { if len(con) == 1 && con[0] != "no-new-privileges" {
return securityOpts, fmt.Errorf("invalid --security-opt: %q", opt) return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
} }
if con[0] == "seccomp" && con[1] != "unconfined" { if con[0] == "seccomp" && con[1] != "unconfined" {
f, err := ioutil.ReadFile(con[1]) f, err := ioutil.ReadFile(con[1])