diff --git a/integration/capabilities/capabilities_linux_test.go b/integration/capabilities/capabilities_linux_test.go new file mode 100644 index 0000000000..272f3dcdb7 --- /dev/null +++ b/integration/capabilities/capabilities_linux_test.go @@ -0,0 +1,108 @@ +package capabilities + +import ( + "bytes" + "context" + "io" + "strings" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/integration/internal/container" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/docker/testutil/fakecontext" + + "gotest.tools/v3/assert" + "gotest.tools/v3/poll" +) + +func TestNoNewPrivileges(t *testing.T) { + defer setupTest(t)() + + withFileCapability := ` + FROM debian:bullseye-slim + RUN apt-get update && apt-get install -y libcap2-bin --no-install-recommends + RUN setcap CAP_DAC_OVERRIDE=+eip /bin/cat + RUN echo "hello" > /txt && chown 0:0 /txt && chmod 700 /txt + RUN useradd -u 1500 test + ` + imageTag := "captest" + + source := fakecontext.New(t, "", fakecontext.WithDockerfile(withFileCapability)) + defer source.Close() + + client := testEnv.APIClient() + + // Build image + ctx := context.TODO() + resp, err := client.ImageBuild(ctx, + source.AsTarReader(t), + types.ImageBuildOptions{ + Tags: []string{imageTag}, + }) + assert.NilError(t, err) + _, err = io.Copy(io.Discard, resp.Body) + assert.NilError(t, err) + resp.Body.Close() + + testCases := []struct { + doc string + opts []func(*container.TestContainerConfig) + stdOut, stdErr string + }{ + { + doc: "CapabilityRequested=true", + opts: []func(*container.TestContainerConfig){ + container.WithUser("test"), + container.WithCapability("CAP_DAC_OVERRIDE"), + }, + stdOut: "hello", + }, + { + doc: "CapabilityRequested=false", + opts: []func(*container.TestContainerConfig){ + container.WithUser("test"), + container.WithDropCapability("CAP_DAC_OVERRIDE"), + }, + stdErr: "exec /bin/cat: operation not permitted", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.doc, func(t *testing.T) { + // Run the container with the image + opts := append(tc.opts, + container.WithImage(imageTag), + container.WithCmd("/bin/cat", "/txt"), + container.WithSecurityOpt("no-new-privileges=true"), + ) + cid := container.Run(ctx, t, client, opts...) + poll.WaitOn(t, container.IsInState(ctx, client, cid, "exited"), poll.WithDelay(100*time.Millisecond)) + + // Assert on outputs + logReader, err := client.ContainerLogs(ctx, cid, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + }) + assert.NilError(t, err) + defer logReader.Close() + + var actualStdout, actualStderr bytes.Buffer + _, err = stdcopy.StdCopy(&actualStdout, &actualStderr, logReader) + assert.NilError(t, err) + + stdOut := strings.TrimSpace(actualStdout.String()) + stdErr := strings.TrimSpace(actualStderr.String()) + if stdOut != tc.stdOut { + t.Fatalf("test produced invalid output: %q, expected %q. Stderr:%q", stdOut, tc.stdOut, stdErr) + } + if stdErr != tc.stdErr { + t.Fatalf("test produced invalid error: %q, expected %q. Stdout:%q", stdErr, tc.stdErr, stdOut) + + } + }) + } + +} diff --git a/integration/capabilities/main_linux_test.go b/integration/capabilities/main_linux_test.go new file mode 100644 index 0000000000..0f074dd156 --- /dev/null +++ b/integration/capabilities/main_linux_test.go @@ -0,0 +1,33 @@ +package capabilities + +import ( + "fmt" + "os" + "testing" + + "github.com/docker/docker/testutil/environment" +) + +var testEnv *environment.Execution + +func TestMain(m *testing.M) { + var err error + testEnv, err = environment.New() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = environment.EnsureFrozenImagesLinux(testEnv) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + testEnv.Print() + os.Exit(m.Run()) +} + +func setupTest(t *testing.T) func() { + environment.ProtectAll(t, testEnv) + return func() { testEnv.Clean(t) } +} diff --git a/integration/internal/container/ops.go b/integration/internal/container/ops.go index fbc3b6b33b..7f173ae0b9 100644 --- a/integration/internal/container/ops.go +++ b/integration/internal/container/ops.go @@ -255,3 +255,21 @@ func WithCDIDevices(cdiDeviceNames ...string) func(*TestContainerConfig) { c.HostConfig.DeviceRequests = append(c.HostConfig.DeviceRequests, request) } } + +func WithCapability(capabilities ...string) func(*TestContainerConfig) { + return func(c *TestContainerConfig) { + c.HostConfig.CapAdd = append(c.HostConfig.CapAdd, capabilities...) + } +} + +func WithDropCapability(capabilities ...string) func(*TestContainerConfig) { + return func(c *TestContainerConfig) { + c.HostConfig.CapDrop = append(c.HostConfig.CapDrop, capabilities...) + } +} + +func WithSecurityOpt(opt string) func(*TestContainerConfig) { + return func(c *TestContainerConfig) { + c.HostConfig.SecurityOpt = append(c.HostConfig.SecurityOpt, opt) + } +}