mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
api: add configurable MaskedPaths and ReadOnlyPaths to the API
This adds MaskedPaths and ReadOnlyPaths options to HostConfig for containers so that a user can override the default values. When the value sent through the API is nil the default is used. Otherwise the default is overridden. Adds integration tests for MaskedPaths and ReadonlyPaths. Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
ca25df3b54
commit
3694c1e34e
5 changed files with 199 additions and 0 deletions
|
@ -772,6 +772,16 @@ definitions:
|
||||||
- "default"
|
- "default"
|
||||||
- "process"
|
- "process"
|
||||||
- "hyperv"
|
- "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:
|
ContainerConfig:
|
||||||
description: "Configuration for a container that is portable between hosts"
|
description: "Configuration for a container that is portable between hosts"
|
||||||
|
|
|
@ -401,6 +401,12 @@ type HostConfig struct {
|
||||||
// Mounts specs used by the container
|
// Mounts specs used by the container
|
||||||
Mounts []mount.Mount `json:",omitempty"`
|
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
|
// Run a custom init inside the container, if null, use the daemon's configured settings
|
||||||
Init *bool `json:",omitempty"`
|
Init *bool `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
containertypes "github.com/docker/docker/api/types/container"
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
mounttypes "github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/container"
|
"github.com/docker/docker/container"
|
||||||
|
"github.com/docker/docker/oci"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
volumeopts "github.com/docker/docker/volume/service/opts"
|
volumeopts "github.com/docker/docker/volume/service/opts"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
|
@ -29,6 +30,16 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
|
||||||
return err
|
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 {
|
for spec := range config.Volumes {
|
||||||
name := stringid.GenerateNonCryptoID()
|
name := stringid.GenerateNonCryptoID()
|
||||||
destination := filepath.Clean(spec)
|
destination := filepath.Clean(spec)
|
||||||
|
|
|
@ -903,6 +903,14 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
|
||||||
s.Process.OOMScoreAdj = &c.HostConfig.OomScoreAdj
|
s.Process.OOMScoreAdj = &c.HostConfig.OomScoreAdj
|
||||||
s.Linux.MountLabel = c.MountLabel
|
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
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,21 @@ package container // import "github.com/docker/docker/integration/container"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"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/internal/test/request"
|
||||||
|
"github.com/docker/docker/oci"
|
||||||
"github.com/gotestyourself/gotestyourself/assert"
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||||
|
"github.com/gotestyourself/gotestyourself/poll"
|
||||||
"github.com/gotestyourself/gotestyourself/skip"
|
"github.com/gotestyourself/gotestyourself/skip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -137,3 +144,160 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
|
||||||
assert.Check(t, is.ErrorContains(err, tc.expectedError))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue