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
|
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
|
||||||
|
|
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,
|
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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
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-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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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])
|
||||||
|
|
Loading…
Reference in a new issue