瀏覽代碼

Merge pull request #36644 from jessfraz/rawaccess

api: add MaskedPaths and ReadonlyPaths options
Sebastiaan van Stijn 7 年之前
父節點
當前提交
1fe0e49d20
共有 5 個文件被更改,包括 199 次插入0 次删除
  1. 10 0
      api/swagger.yaml
  2. 6 0
      api/types/container/host_config.go
  3. 11 0
      daemon/create_unix.go
  4. 8 0
      daemon/oci_linux.go
  5. 164 0
      integration/container/create_test.go

+ 10 - 0
api/swagger.yaml

@@ -772,6 +772,16 @@ definitions:
               - "default"
               - "process"
               - "hyperv"
+          MaskedPaths:
+            type: "array"
+            description: "The list of paths to be masked inside the container (this overrides the default set of paths)"
+            items:
+              type: "string"
+          ReadonlyPaths:
+            type: "array"
+            description: "The list of paths to be set as read-only inside the container (this overrides the default set of paths)"
+            items:
+              type: "string"
 
   ContainerConfig:
     description: "Configuration for a container that is portable between hosts"

+ 6 - 0
api/types/container/host_config.go

@@ -401,6 +401,12 @@ type HostConfig struct {
 	// Mounts specs used by the container
 	Mounts []mount.Mount `json:",omitempty"`
 
+	// MaskedPaths is the list of paths to be masked inside the container (this overrides the default set of paths)
+	MaskedPaths []string
+
+	// ReadonlyPaths is the list of paths to be set as read-only inside the container (this overrides the default set of paths)
+	ReadonlyPaths []string
+
 	// Run a custom init inside the container, if null, use the daemon's configured settings
 	Init *bool `json:",omitempty"`
 }

+ 11 - 0
daemon/create_unix.go

@@ -11,6 +11,7 @@ import (
 	containertypes "github.com/docker/docker/api/types/container"
 	mounttypes "github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/oci"
 	"github.com/docker/docker/pkg/stringid"
 	volumeopts "github.com/docker/docker/volume/service/opts"
 	"github.com/opencontainers/selinux/go-selinux/label"
@@ -29,6 +30,16 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
 		return err
 	}
 
+	// Set the default masked and readonly paths with regard to the host config options if they are not set.
+	if hostConfig.MaskedPaths == nil && !hostConfig.Privileged {
+		hostConfig.MaskedPaths = oci.DefaultSpec().Linux.MaskedPaths // Set it to the default if nil
+		container.HostConfig.MaskedPaths = hostConfig.MaskedPaths
+	}
+	if hostConfig.ReadonlyPaths == nil && !hostConfig.Privileged {
+		hostConfig.ReadonlyPaths = oci.DefaultSpec().Linux.ReadonlyPaths // Set it to the default if nil
+		container.HostConfig.ReadonlyPaths = hostConfig.ReadonlyPaths
+	}
+
 	for spec := range config.Volumes {
 		name := stringid.GenerateNonCryptoID()
 		destination := filepath.Clean(spec)

+ 8 - 0
daemon/oci_linux.go

@@ -903,6 +903,14 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
 	s.Process.OOMScoreAdj = &c.HostConfig.OomScoreAdj
 	s.Linux.MountLabel = c.MountLabel
 
+	// Set the masked and readonly paths with regard to the host config options if they are set.
+	if c.HostConfig.MaskedPaths != nil {
+		s.Linux.MaskedPaths = c.HostConfig.MaskedPaths
+	}
+	if c.HostConfig.ReadonlyPaths != nil {
+		s.Linux.ReadonlyPaths = c.HostConfig.ReadonlyPaths
+	}
+
 	return &s, nil
 }
 

+ 164 - 0
integration/container/create_test.go

@@ -2,14 +2,21 @@ package container // import "github.com/docker/docker/integration/container"
 
 import (
 	"context"
+	"encoding/json"
+	"fmt"
 	"strconv"
 	"testing"
+	"time"
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
+	ctr "github.com/docker/docker/integration/internal/container"
 	"github.com/docker/docker/internal/test/request"
+	"github.com/docker/docker/oci"
 	"github.com/gotestyourself/gotestyourself/assert"
 	is "github.com/gotestyourself/gotestyourself/assert/cmp"
+	"github.com/gotestyourself/gotestyourself/poll"
 	"github.com/gotestyourself/gotestyourself/skip"
 )
 
@@ -137,3 +144,160 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
 		assert.Check(t, is.ErrorContains(err, tc.expectedError))
 	}
 }
+func TestCreateWithCustomMaskedPaths(t *testing.T) {
+	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
+
+	defer setupTest(t)()
+	client := request.NewAPIClient(t)
+	ctx := context.Background()
+
+	testCases := []struct {
+		maskedPaths []string
+		expected    []string
+	}{
+		{
+			maskedPaths: []string{},
+			expected:    []string{},
+		},
+		{
+			maskedPaths: nil,
+			expected:    oci.DefaultSpec().Linux.MaskedPaths,
+		},
+		{
+			maskedPaths: []string{"/proc/kcore", "/proc/keys"},
+			expected:    []string{"/proc/kcore", "/proc/keys"},
+		},
+	}
+
+	checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
+		_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
+		assert.NilError(t, err)
+
+		var inspectJSON map[string]interface{}
+		err = json.Unmarshal(b, &inspectJSON)
+		assert.NilError(t, err)
+
+		cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
+		assert.Check(t, is.Equal(true, ok), name)
+
+		maskedPaths, ok := cfg["MaskedPaths"].([]interface{})
+		assert.Check(t, is.Equal(true, ok), name)
+
+		mps := []string{}
+		for _, mp := range maskedPaths {
+			mps = append(mps, mp.(string))
+		}
+
+		assert.DeepEqual(t, expected, mps)
+	}
+
+	for i, tc := range testCases {
+		name := fmt.Sprintf("create-masked-paths-%d", i)
+		config := container.Config{
+			Image: "busybox",
+			Cmd:   []string{"true"},
+		}
+		hc := container.HostConfig{}
+		if tc.maskedPaths != nil {
+			hc.MaskedPaths = tc.maskedPaths
+		}
+
+		// Create the container.
+		c, err := client.ContainerCreate(context.Background(),
+			&config,
+			&hc,
+			&network.NetworkingConfig{},
+			name,
+		)
+		assert.NilError(t, err)
+
+		checkInspect(t, ctx, name, tc.expected)
+
+		// Start the container.
+		err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
+		assert.NilError(t, err)
+
+		poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
+
+		checkInspect(t, ctx, name, tc.expected)
+	}
+}
+
+func TestCreateWithCustomReadonlyPaths(t *testing.T) {
+	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
+
+	defer setupTest(t)()
+	client := request.NewAPIClient(t)
+	ctx := context.Background()
+
+	testCases := []struct {
+		doc           string
+		readonlyPaths []string
+		expected      []string
+	}{
+		{
+			readonlyPaths: []string{},
+			expected:      []string{},
+		},
+		{
+			readonlyPaths: nil,
+			expected:      oci.DefaultSpec().Linux.ReadonlyPaths,
+		},
+		{
+			readonlyPaths: []string{"/proc/asound", "/proc/bus"},
+			expected:      []string{"/proc/asound", "/proc/bus"},
+		},
+	}
+
+	checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
+		_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
+		assert.NilError(t, err)
+
+		var inspectJSON map[string]interface{}
+		err = json.Unmarshal(b, &inspectJSON)
+		assert.NilError(t, err)
+
+		cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
+		assert.Check(t, is.Equal(true, ok), name)
+
+		readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
+		assert.Check(t, is.Equal(true, ok), name)
+
+		rops := []string{}
+		for _, rop := range readonlyPaths {
+			rops = append(rops, rop.(string))
+		}
+		assert.DeepEqual(t, expected, rops)
+	}
+
+	for i, tc := range testCases {
+		name := fmt.Sprintf("create-readonly-paths-%d", i)
+		config := container.Config{
+			Image: "busybox",
+			Cmd:   []string{"true"},
+		}
+		hc := container.HostConfig{}
+		if tc.readonlyPaths != nil {
+			hc.ReadonlyPaths = tc.readonlyPaths
+		}
+
+		// Create the container.
+		c, err := client.ContainerCreate(context.Background(),
+			&config,
+			&hc,
+			&network.NetworkingConfig{},
+			name,
+		)
+		assert.NilError(t, err)
+
+		checkInspect(t, ctx, name, tc.expected)
+
+		// Start the container.
+		err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
+		assert.NilError(t, err)
+
+		poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
+
+		checkInspect(t, ctx, name, tc.expected)
+	}
+}