2018-02-05 16:05:59 -05:00
|
|
|
package container // import "github.com/docker/docker/integration/container"
|
2017-12-18 16:02:23 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2018-01-18 16:55:27 -05:00
|
|
|
"path/filepath"
|
2017-12-18 16:02:23 -05:00
|
|
|
"testing"
|
2018-10-10 06:20:13 -04:00
|
|
|
"time"
|
2017-12-18 16:02:23 -05:00
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
2018-10-10 06:20:13 -04:00
|
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
|
|
mounttypes "github.com/docker/docker/api/types/mount"
|
2018-02-04 12:38:04 -05:00
|
|
|
"github.com/docker/docker/api/types/network"
|
2018-10-10 06:20:13 -04:00
|
|
|
"github.com/docker/docker/api/types/versions"
|
2018-02-04 12:38:04 -05:00
|
|
|
"github.com/docker/docker/client"
|
2018-10-10 06:20:13 -04:00
|
|
|
"github.com/docker/docker/integration/internal/container"
|
|
|
|
"github.com/docker/docker/pkg/mount"
|
2018-02-04 12:38:04 -05:00
|
|
|
"github.com/docker/docker/pkg/system"
|
2018-06-11 09:32:11 -04:00
|
|
|
"gotest.tools/assert"
|
|
|
|
is "gotest.tools/assert/cmp"
|
|
|
|
"gotest.tools/fs"
|
2018-10-10 06:20:13 -04:00
|
|
|
"gotest.tools/poll"
|
2018-06-11 09:32:11 -04:00
|
|
|
"gotest.tools/skip"
|
2017-12-18 16:02:23 -05:00
|
|
|
)
|
|
|
|
|
2018-02-04 12:38:04 -05:00
|
|
|
func TestContainerNetworkMountsNoChown(t *testing.T) {
|
|
|
|
// chown only applies to Linux bind mounted volumes; must be same host to verify
|
2019-01-03 06:32:05 -05:00
|
|
|
skip.If(t, testEnv.IsRemoteDaemon)
|
2018-02-04 12:38:04 -05:00
|
|
|
|
|
|
|
defer setupTest(t)()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
tmpDir := fs.NewDir(t, "network-file-mounts", fs.WithMode(0755), fs.WithFile("nwfile", "network file bind mount", fs.WithMode(0644)))
|
|
|
|
defer tmpDir.Remove()
|
|
|
|
|
|
|
|
tmpNWFileMount := tmpDir.Join("nwfile")
|
|
|
|
|
2018-10-10 06:20:13 -04:00
|
|
|
config := containertypes.Config{
|
2018-02-04 12:38:04 -05:00
|
|
|
Image: "busybox",
|
|
|
|
}
|
2018-10-10 06:20:13 -04:00
|
|
|
hostConfig := containertypes.HostConfig{
|
|
|
|
Mounts: []mounttypes.Mount{
|
2018-02-04 12:38:04 -05:00
|
|
|
{
|
|
|
|
Type: "bind",
|
|
|
|
Source: tmpNWFileMount,
|
|
|
|
Target: "/etc/resolv.conf",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: "bind",
|
|
|
|
Source: tmpNWFileMount,
|
|
|
|
Target: "/etc/hostname",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: "bind",
|
|
|
|
Source: tmpNWFileMount,
|
|
|
|
Target: "/etc/hosts",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-01-03 16:49:00 -05:00
|
|
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
2018-03-13 15:28:34 -04:00
|
|
|
assert.NilError(t, err)
|
2018-02-04 12:38:04 -05:00
|
|
|
defer cli.Close()
|
|
|
|
|
|
|
|
ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, "")
|
2018-03-13 15:28:34 -04:00
|
|
|
assert.NilError(t, err)
|
2018-02-04 12:38:04 -05:00
|
|
|
// container will exit immediately because of no tty, but we only need the start sequence to test the condition
|
|
|
|
err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
|
2018-03-13 15:28:34 -04:00
|
|
|
assert.NilError(t, err)
|
2018-02-04 12:38:04 -05:00
|
|
|
|
2018-02-06 19:19:38 -05:00
|
|
|
// Check that host-located bind mount network file did not change ownership when the container was started
|
|
|
|
// Note: If the user specifies a mountpath from the host, we should not be
|
|
|
|
// attempting to chown files outside the daemon's metadata directory
|
|
|
|
// (represented by `daemon.repository` at init time).
|
|
|
|
// This forces users who want to use user namespaces to handle the
|
|
|
|
// ownership needs of any external files mounted as network files
|
|
|
|
// (/etc/resolv.conf, /etc/hosts, /etc/hostname) separately from the
|
|
|
|
// daemon. In all other volume/bind mount situations we have taken this
|
|
|
|
// same line--we don't chown host file content.
|
|
|
|
// See GitHub PR 34224 for details.
|
2018-02-04 12:38:04 -05:00
|
|
|
statT, err := system.Stat(tmpNWFileMount)
|
2018-03-13 15:28:34 -04:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(uint32(0), statT.UID()), "bind mounted network file should not change ownership from root")
|
2018-02-04 12:38:04 -05:00
|
|
|
}
|
2018-01-18 16:55:27 -05:00
|
|
|
|
|
|
|
func TestMountDaemonRoot(t *testing.T) {
|
2019-01-03 06:32:05 -05:00
|
|
|
skip.If(t, testEnv.IsRemoteDaemon)
|
2018-01-18 16:55:27 -05:00
|
|
|
|
2019-01-02 08:16:25 -05:00
|
|
|
defer setupTest(t)()
|
|
|
|
client := testEnv.APIClient()
|
2018-01-18 16:55:27 -05:00
|
|
|
ctx := context.Background()
|
|
|
|
info, err := client.Info(ctx)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range []struct {
|
|
|
|
desc string
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation mounttypes.Propagation
|
|
|
|
expected mounttypes.Propagation
|
2018-01-18 16:55:27 -05:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
desc: "default",
|
|
|
|
propagation: "",
|
2018-10-10 06:20:13 -04:00
|
|
|
expected: mounttypes.PropagationRSlave,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "private",
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation: mounttypes.PropagationPrivate,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "rprivate",
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation: mounttypes.PropagationRPrivate,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "slave",
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation: mounttypes.PropagationSlave,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "rslave",
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation: mounttypes.PropagationRSlave,
|
|
|
|
expected: mounttypes.PropagationRSlave,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "shared",
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation: mounttypes.PropagationShared,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "rshared",
|
2018-10-10 06:20:13 -04:00
|
|
|
propagation: mounttypes.PropagationRShared,
|
|
|
|
expected: mounttypes.PropagationRShared,
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
|
|
test := test
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
propagationSpec := fmt.Sprintf(":%s", test.propagation)
|
|
|
|
if test.propagation == "" {
|
|
|
|
propagationSpec = ""
|
|
|
|
}
|
|
|
|
bindSpecRoot := info.DockerRootDir + ":" + "/foo" + propagationSpec
|
|
|
|
bindSpecSub := filepath.Join(info.DockerRootDir, "containers") + ":/foo" + propagationSpec
|
|
|
|
|
2018-10-10 06:20:13 -04:00
|
|
|
for name, hc := range map[string]*containertypes.HostConfig{
|
2018-01-18 16:55:27 -05:00
|
|
|
"bind root": {Binds: []string{bindSpecRoot}},
|
|
|
|
"bind subpath": {Binds: []string{bindSpecSub}},
|
|
|
|
"mount root": {
|
2018-10-10 06:20:13 -04:00
|
|
|
Mounts: []mounttypes.Mount{
|
2018-01-18 16:55:27 -05:00
|
|
|
{
|
2018-10-10 06:20:13 -04:00
|
|
|
Type: mounttypes.TypeBind,
|
2018-01-18 16:55:27 -05:00
|
|
|
Source: info.DockerRootDir,
|
|
|
|
Target: "/foo",
|
2018-10-10 06:20:13 -04:00
|
|
|
BindOptions: &mounttypes.BindOptions{Propagation: test.propagation},
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"mount subpath": {
|
2018-10-10 06:20:13 -04:00
|
|
|
Mounts: []mounttypes.Mount{
|
2018-01-18 16:55:27 -05:00
|
|
|
{
|
2018-10-10 06:20:13 -04:00
|
|
|
Type: mounttypes.TypeBind,
|
2018-01-18 16:55:27 -05:00
|
|
|
Source: filepath.Join(info.DockerRootDir, "containers"),
|
|
|
|
Target: "/foo",
|
2018-10-10 06:20:13 -04:00
|
|
|
BindOptions: &mounttypes.BindOptions{Propagation: test.propagation},
|
2018-01-18 16:55:27 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
hc := hc
|
|
|
|
t.Parallel()
|
|
|
|
|
2018-10-10 06:20:13 -04:00
|
|
|
c, err := client.ContainerCreate(ctx, &containertypes.Config{
|
2018-01-18 16:55:27 -05:00
|
|
|
Image: "busybox",
|
|
|
|
Cmd: []string{"true"},
|
|
|
|
}, hc, nil, "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if test.expected != "" {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
// expected an error, so this is ok and should not continue
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if test.expected == "" {
|
|
|
|
t.Fatal("expected create to fail")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
inspect, err := client.ContainerInspect(ctx, c.ID)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(inspect.Mounts) != 1 {
|
|
|
|
t.Fatalf("unexpected number of mounts: %+v", inspect.Mounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
m := inspect.Mounts[0]
|
|
|
|
if m.Propagation != test.expected {
|
|
|
|
t.Fatalf("got unexpected propagation mode, expected %q, got: %v", test.expected, m.Propagation)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2018-10-10 06:20:13 -04:00
|
|
|
|
|
|
|
func TestContainerBindMountNonRecursive(t *testing.T) {
|
2019-01-03 06:32:05 -05:00
|
|
|
skip.If(t, testEnv.IsRemoteDaemon)
|
2018-10-10 06:20:13 -04:00
|
|
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "BindOptions.NonRecursive requires API v1.40")
|
|
|
|
|
|
|
|
defer setupTest(t)()
|
|
|
|
|
|
|
|
tmpDir1 := fs.NewDir(t, "tmpdir1", fs.WithMode(0755),
|
|
|
|
fs.WithDir("mnt", fs.WithMode(0755)))
|
|
|
|
defer tmpDir1.Remove()
|
|
|
|
tmpDir1Mnt := filepath.Join(tmpDir1.Path(), "mnt")
|
|
|
|
tmpDir2 := fs.NewDir(t, "tmpdir2", fs.WithMode(0755),
|
|
|
|
fs.WithFile("file", "should not be visible when NonRecursive", fs.WithMode(0644)))
|
|
|
|
defer tmpDir2.Remove()
|
|
|
|
|
|
|
|
err := mount.Mount(tmpDir2.Path(), tmpDir1Mnt, "none", "bind,ro")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := mount.Unmount(tmpDir1Mnt); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// implicit is recursive (NonRecursive: false)
|
|
|
|
implicit := mounttypes.Mount{
|
|
|
|
Type: "bind",
|
|
|
|
Source: tmpDir1.Path(),
|
|
|
|
Target: "/foo",
|
|
|
|
ReadOnly: true,
|
|
|
|
}
|
|
|
|
recursive := implicit
|
|
|
|
recursive.BindOptions = &mounttypes.BindOptions{
|
|
|
|
NonRecursive: false,
|
|
|
|
}
|
|
|
|
recursiveVerifier := []string{"test", "-f", "/foo/mnt/file"}
|
|
|
|
nonRecursive := implicit
|
|
|
|
nonRecursive.BindOptions = &mounttypes.BindOptions{
|
|
|
|
NonRecursive: true,
|
|
|
|
}
|
|
|
|
nonRecursiveVerifier := []string{"test", "!", "-f", "/foo/mnt/file"}
|
|
|
|
|
|
|
|
ctx := context.Background()
|
2019-01-02 08:16:25 -05:00
|
|
|
client := testEnv.APIClient()
|
2018-10-10 06:20:13 -04:00
|
|
|
containers := []string{
|
|
|
|
container.Run(t, ctx, client, container.WithMount(implicit), container.WithCmd(recursiveVerifier...)),
|
|
|
|
container.Run(t, ctx, client, container.WithMount(recursive), container.WithCmd(recursiveVerifier...)),
|
|
|
|
container.Run(t, ctx, client, container.WithMount(nonRecursive), container.WithCmd(nonRecursiveVerifier...)),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range containers {
|
|
|
|
poll.WaitOn(t, container.IsSuccessful(ctx, client, c), poll.WithDelay(100*time.Millisecond))
|
|
|
|
}
|
|
|
|
}
|