diff --git a/gqt/runner/runner.go b/gqt/runner/runner.go index 323b78f1a..c363c41f0 100644 --- a/gqt/runner/runner.go +++ b/gqt/runner/runner.go @@ -96,6 +96,7 @@ type GdnRunnerConfig struct { CleanupProcessDirsOnWait *bool `flag:"cleanup-process-dirs-on-wait"` DisablePrivilegedContainers *bool `flag:"disable-privileged-containers"` AppArmor string `flag:"apparmor"` + NoNewPrivileges *bool `flag:"no-new-privileges"` Tag string `flag:"tag"` NetworkPool string `flag:"network-pool"` ContainerdSocket string `flag:"containerd-socket"` diff --git a/gqt/security_test.go b/gqt/security_test.go index 11d96790f..b17ebbdd2 100644 --- a/gqt/security_test.go +++ b/gqt/security_test.go @@ -160,6 +160,111 @@ var _ = Describe("Security", func() { }) }) + Describe("NoNewPrivileges", func() { + Context("when the --no-new-privileges flag is set", func() { + BeforeEach(func() { + config.NoNewPrivileges = boolptr(true) + }) + + Context("when running processes in unprivileged containers", func() { + var ( + container garden.Container + err error + ) + + JustBeforeEach(func() { + container, err = client.Create(garden.ContainerSpec{}) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should set NoNewPrivileges on the process", func() { + buffer := gbytes.NewBuffer() + + _, err = container.Run(garden.ProcessSpec{ + Path: "grep", + Args: []string{"NoNewPrivs", "/proc/self/status"}, + }, garden.ProcessIO{ + Stdout: io.MultiWriter(GinkgoWriter, buffer), + Stderr: GinkgoWriter, + }) + Expect(err).NotTo(HaveOccurred()) + + Eventually(buffer).Should(gbytes.Say(`NoNewPrivs:\s+1`)) + }) + + Context("when running a pea", func() { + var peaRootfs string + + BeforeEach(func() { + peaRootfs = createPeaRootfsTar() + }) + + AfterEach(func() { + Expect(os.RemoveAll(filepath.Dir(peaRootfs))).To(Succeed()) + }) + + It("should set NoNewPrivileges on the process", func() { + buffer := gbytes.NewBuffer() + + _, err = container.Run(garden.ProcessSpec{ + Path: "grep", + Args: []string{"NoNewPrivs", "/proc/self/status"}, + Image: garden.ImageRef{URI: peaRootfs}, + }, garden.ProcessIO{ + Stdout: io.MultiWriter(GinkgoWriter, buffer), + Stderr: GinkgoWriter, + }) + Expect(err).NotTo(HaveOccurred()) + + Eventually(buffer).Should(gbytes.Say(`NoNewPrivs:\s+1`)) + }) + }) + }) + + Context("when running processes in privileged containers", func() { + It("should not set NoNewPrivileges", func() { + container, err := client.Create(garden.ContainerSpec{ + Privileged: true, + }) + Expect(err).NotTo(HaveOccurred()) + + buffer := gbytes.NewBuffer() + + _, err = container.Run(garden.ProcessSpec{ + Path: "grep", + Args: []string{"NoNewPrivs", "/proc/self/status"}, + }, garden.ProcessIO{ + Stdout: io.MultiWriter(GinkgoWriter, buffer), + Stderr: GinkgoWriter, + }) + Expect(err).NotTo(HaveOccurred()) + + Eventually(buffer).Should(gbytes.Say(`NoNewPrivs:\s+0`)) + }) + }) + }) + + Context("when the --no-new-privileges flag is not set", func() { + It("should not set NoNewPrivileges on processes in unprivileged containers", func() { + container, err := client.Create(garden.ContainerSpec{}) + Expect(err).NotTo(HaveOccurred()) + + buffer := gbytes.NewBuffer() + + _, err = container.Run(garden.ProcessSpec{ + Path: "grep", + Args: []string{"NoNewPrivs", "/proc/self/status"}, + }, garden.ProcessIO{ + Stdout: io.MultiWriter(GinkgoWriter, buffer), + Stderr: GinkgoWriter, + }) + Expect(err).NotTo(HaveOccurred()) + + Eventually(buffer).Should(gbytes.Say(`NoNewPrivs:\s+0`)) + }) + }) + }) + Describe("ptrace in seccomp allow rules", func() { It("should allow the ptrace syscall without CAP_SYS_PTRACE", func() { container, err := client.Create(garden.ContainerSpec{ diff --git a/guardiancmd/command.go b/guardiancmd/command.go index 78e930ca6..ea7df75f8 100644 --- a/guardiancmd/command.go +++ b/guardiancmd/command.go @@ -117,6 +117,7 @@ type CommonCommand struct { DefaultGraceTime time.Duration `long:"default-grace-time" description:"Default time after which idle containers should expire."` DestroyContainersOnStartup bool `long:"destroy-containers-on-startup" description:"Clean up all the existing containers on startup."` ApparmorProfile string `long:"apparmor" description:"Apparmor profile to use for unprivileged container processes"` + NoNewPrivileges bool `long:"no-new-privileges" description:"Set NoNewPrivileges on unprivileged container processes"` } `group:"Container Lifecycle"` Bin struct { @@ -527,6 +528,10 @@ func (cmd *CommonCommand) wireContainerizer( } unprivilegedBundle.Spec.Linux.Seccomp = seccomp + if cmd.Containers.NoNewPrivileges { + unprivilegedBundle.Spec.Process.NoNewPrivileges = true + } + if cmd.Containers.ApparmorProfile != "" { unprivilegedBundle = unprivilegedBundle.WithApparmorProfile(cmd.Containers.ApparmorProfile) } diff --git a/rundmc/processes/builder.go b/rundmc/processes/builder.go index 25f2f66f2..39069143f 100644 --- a/rundmc/processes/builder.go +++ b/rundmc/processes/builder.go @@ -53,6 +53,7 @@ func (p *ProcBuilder) BuildProcess(bndl goci.Bndl, spec garden.ProcessSpec, user Rlimits: toRlimits(spec.Limits), Terminal: spec.TTY != nil, ApparmorProfile: bndl.Process().ApparmorProfile, + NoNewPrivileges: bndl.Process().NoNewPrivileges, } } diff --git a/rundmc/processes/builder_test.go b/rundmc/processes/builder_test.go index 29def9dbe..b7e1dd746 100644 --- a/rundmc/processes/builder_test.go +++ b/rundmc/processes/builder_test.go @@ -196,6 +196,20 @@ var _ = Describe("ProcBuilder", func() { Expect(preparedProc.ApparmorProfile).To(Equal("default-profile")) }) + It("passes NoNewPrivileges from the bundle", func() { + Expect(preparedProc.NoNewPrivileges).To(BeFalse()) + }) + + Context("when the bundle has NoNewPrivileges set", func() { + BeforeEach(func() { + bndl.Spec.Process.NoNewPrivileges = true + }) + + It("propagates NoNewPrivileges to the process", func() { + Expect(preparedProc.NoNewPrivileges).To(BeTrue()) + }) + }) + It("passes the UID, GID and SGIDs", func() { Expect(preparedProc.User.UID).To(Equal(uint32(1))) Expect(preparedProc.User.GID).To(Equal(uint32(2)))