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
ResolvConfPath string
SeccompProfile string
NoNewPrivileges bool
}
// 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,
UIDMapping: uidMap,
UTS: uts,
NoNewPrivileges: c.NoNewPrivileges,
}
if 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 {
con := strings.SplitN(opt, ":", 2)
if len(con) == 1 {
return fmt.Errorf("Invalid --security-opt: %q", opt)
}
switch con[0] {
case "label":
labelOpts = append(labelOpts, con[1])
case "apparmor":
container.AppArmorProfile = con[1]
case "seccomp":
container.SeccompProfile = con[1]
default:
return fmt.Errorf("Invalid --security-opt: %q", opt)
switch con[0] {
case "no-new-privileges":
container.NoNewPrivileges = true
default:
return fmt.Errorf("Invalid --security-opt 1: %q", opt)
}
} else {
switch con[0] {
case "label":
labelOpts = append(labelOpts, con[1])
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"`
UIDMapping []idtools.IDMap `json:"uidmapping"`
UTS *UTS `json:"uts"`
NoNewPrivileges bool `json:"no_new_privileges"`
}
// 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.setupRlimits(container, c)
container.NoNewPrivileges = c.NoNewPrivileges
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="apparmor:PROFILE" : Set the apparmor profile to be applied
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
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.
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
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-httpserver
bundle .ensure-syscall-test
bundle .ensure-nnp-test
else
# Note this is Windows to Windows CI, not Windows to Linux CI
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) {
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:level:LEVEL" : Set the label level 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*
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) {
for key, opt := range securityOpts {
con := strings.SplitN(opt, ":", 2)
if len(con) == 1 {
return securityOpts, fmt.Errorf("invalid --security-opt: %q", opt)
if len(con) == 1 && con[0] != "no-new-privileges" {
return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
}
if con[0] == "seccomp" && con[1] != "unconfined" {
f, err := ioutil.ReadFile(con[1])