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:
parent
17156ba98f
commit
74bb1ce9e9
13 changed files with 89 additions and 13 deletions
|
@ -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
|
||||
|
|
9
contrib/nnp-test/Dockerfile
Normal file
9
contrib/nnp-test/Dockerfile
Normal 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
|
10
contrib/nnp-test/nnp-test.c
Normal file
10
contrib/nnp-test/nnp-test.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -75,8 +75,13 @@ 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 "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])
|
||||
|
@ -85,7 +90,8 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos
|
|||
case "seccomp":
|
||||
container.SeccompProfile = con[1]
|
||||
default:
|
||||
return fmt.Errorf("Invalid --security-opt: %q", opt)
|
||||
return fmt.Errorf("Invalid --security-opt 2: %q", opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
22
hack/make/.ensure-nnp-test
Normal file
22
hack/make/.ensure-nnp-test
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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])
|
||||
|
|
Loading…
Reference in a new issue