github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/integration/container/mounts_linux_test.go (about)

     1  package container // import "github.com/docker/docker/integration/container"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path/filepath"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	containertypes "github.com/docker/docker/api/types/container"
    12  	mounttypes "github.com/docker/docker/api/types/mount"
    13  	"github.com/docker/docker/api/types/network"
    14  	"github.com/docker/docker/api/types/versions"
    15  	"github.com/docker/docker/client"
    16  	"github.com/docker/docker/integration/internal/container"
    17  	"github.com/docker/docker/pkg/system"
    18  	"github.com/moby/sys/mount"
    19  	"gotest.tools/v3/assert"
    20  	is "gotest.tools/v3/assert/cmp"
    21  	"gotest.tools/v3/fs"
    22  	"gotest.tools/v3/poll"
    23  	"gotest.tools/v3/skip"
    24  )
    25  
    26  func TestContainerNetworkMountsNoChown(t *testing.T) {
    27  	// chown only applies to Linux bind mounted volumes; must be same host to verify
    28  	skip.If(t, testEnv.IsRemoteDaemon)
    29  
    30  	defer setupTest(t)()
    31  
    32  	ctx := context.Background()
    33  
    34  	tmpDir := fs.NewDir(t, "network-file-mounts", fs.WithMode(0755), fs.WithFile("nwfile", "network file bind mount", fs.WithMode(0644)))
    35  	defer tmpDir.Remove()
    36  
    37  	tmpNWFileMount := tmpDir.Join("nwfile")
    38  
    39  	config := containertypes.Config{
    40  		Image: "busybox",
    41  	}
    42  	hostConfig := containertypes.HostConfig{
    43  		Mounts: []mounttypes.Mount{
    44  			{
    45  				Type:   "bind",
    46  				Source: tmpNWFileMount,
    47  				Target: "/etc/resolv.conf",
    48  			},
    49  			{
    50  				Type:   "bind",
    51  				Source: tmpNWFileMount,
    52  				Target: "/etc/hostname",
    53  			},
    54  			{
    55  				Type:   "bind",
    56  				Source: tmpNWFileMount,
    57  				Target: "/etc/hosts",
    58  			},
    59  		},
    60  	}
    61  
    62  	cli, err := client.NewClientWithOpts(client.FromEnv)
    63  	assert.NilError(t, err)
    64  	defer cli.Close()
    65  
    66  	ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
    67  	assert.NilError(t, err)
    68  	// container will exit immediately because of no tty, but we only need the start sequence to test the condition
    69  	err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
    70  	assert.NilError(t, err)
    71  
    72  	// Check that host-located bind mount network file did not change ownership when the container was started
    73  	// Note: If the user specifies a mountpath from the host, we should not be
    74  	// attempting to chown files outside the daemon's metadata directory
    75  	// (represented by `daemon.repository` at init time).
    76  	// This forces users who want to use user namespaces to handle the
    77  	// ownership needs of any external files mounted as network files
    78  	// (/etc/resolv.conf, /etc/hosts, /etc/hostname) separately from the
    79  	// daemon. In all other volume/bind mount situations we have taken this
    80  	// same line--we don't chown host file content.
    81  	// See GitHub PR 34224 for details.
    82  	statT, err := system.Stat(tmpNWFileMount)
    83  	assert.NilError(t, err)
    84  	assert.Check(t, is.Equal(uint32(0), statT.UID()), "bind mounted network file should not change ownership from root")
    85  }
    86  
    87  func TestMountDaemonRoot(t *testing.T) {
    88  	skip.If(t, testEnv.IsRemoteDaemon)
    89  
    90  	defer setupTest(t)()
    91  	client := testEnv.APIClient()
    92  	ctx := context.Background()
    93  	info, err := client.Info(ctx)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  
    98  	for _, test := range []struct {
    99  		desc        string
   100  		propagation mounttypes.Propagation
   101  		expected    mounttypes.Propagation
   102  	}{
   103  		{
   104  			desc:        "default",
   105  			propagation: "",
   106  			expected:    mounttypes.PropagationRSlave,
   107  		},
   108  		{
   109  			desc:        "private",
   110  			propagation: mounttypes.PropagationPrivate,
   111  		},
   112  		{
   113  			desc:        "rprivate",
   114  			propagation: mounttypes.PropagationRPrivate,
   115  		},
   116  		{
   117  			desc:        "slave",
   118  			propagation: mounttypes.PropagationSlave,
   119  		},
   120  		{
   121  			desc:        "rslave",
   122  			propagation: mounttypes.PropagationRSlave,
   123  			expected:    mounttypes.PropagationRSlave,
   124  		},
   125  		{
   126  			desc:        "shared",
   127  			propagation: mounttypes.PropagationShared,
   128  		},
   129  		{
   130  			desc:        "rshared",
   131  			propagation: mounttypes.PropagationRShared,
   132  			expected:    mounttypes.PropagationRShared,
   133  		},
   134  	} {
   135  		t.Run(test.desc, func(t *testing.T) {
   136  			test := test
   137  			t.Parallel()
   138  
   139  			propagationSpec := fmt.Sprintf(":%s", test.propagation)
   140  			if test.propagation == "" {
   141  				propagationSpec = ""
   142  			}
   143  			bindSpecRoot := info.DockerRootDir + ":" + "/foo" + propagationSpec
   144  			bindSpecSub := filepath.Join(info.DockerRootDir, "containers") + ":/foo" + propagationSpec
   145  
   146  			for name, hc := range map[string]*containertypes.HostConfig{
   147  				"bind root":    {Binds: []string{bindSpecRoot}},
   148  				"bind subpath": {Binds: []string{bindSpecSub}},
   149  				"mount root": {
   150  					Mounts: []mounttypes.Mount{
   151  						{
   152  							Type:        mounttypes.TypeBind,
   153  							Source:      info.DockerRootDir,
   154  							Target:      "/foo",
   155  							BindOptions: &mounttypes.BindOptions{Propagation: test.propagation},
   156  						},
   157  					},
   158  				},
   159  				"mount subpath": {
   160  					Mounts: []mounttypes.Mount{
   161  						{
   162  							Type:        mounttypes.TypeBind,
   163  							Source:      filepath.Join(info.DockerRootDir, "containers"),
   164  							Target:      "/foo",
   165  							BindOptions: &mounttypes.BindOptions{Propagation: test.propagation},
   166  						},
   167  					},
   168  				},
   169  			} {
   170  				t.Run(name, func(t *testing.T) {
   171  					hc := hc
   172  					t.Parallel()
   173  
   174  					c, err := client.ContainerCreate(ctx, &containertypes.Config{
   175  						Image: "busybox",
   176  						Cmd:   []string{"true"},
   177  					}, hc, nil, nil, "")
   178  
   179  					if err != nil {
   180  						if test.expected != "" {
   181  							t.Fatal(err)
   182  						}
   183  						// expected an error, so this is ok and should not continue
   184  						return
   185  					}
   186  					if test.expected == "" {
   187  						t.Fatal("expected create to fail")
   188  					}
   189  
   190  					defer func() {
   191  						if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil {
   192  							panic(err)
   193  						}
   194  					}()
   195  
   196  					inspect, err := client.ContainerInspect(ctx, c.ID)
   197  					if err != nil {
   198  						t.Fatal(err)
   199  					}
   200  					if len(inspect.Mounts) != 1 {
   201  						t.Fatalf("unexpected number of mounts: %+v", inspect.Mounts)
   202  					}
   203  
   204  					m := inspect.Mounts[0]
   205  					if m.Propagation != test.expected {
   206  						t.Fatalf("got unexpected propagation mode, expected %q, got: %v", test.expected, m.Propagation)
   207  					}
   208  				})
   209  			}
   210  		})
   211  	}
   212  }
   213  
   214  func TestContainerBindMountNonRecursive(t *testing.T) {
   215  	skip.If(t, testEnv.IsRemoteDaemon)
   216  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "BindOptions.NonRecursive requires API v1.40")
   217  	skip.If(t, testEnv.IsRootless, "cannot be tested because RootlessKit executes the daemon in private mount namespace (https://github.com/rootless-containers/rootlesskit/issues/97)")
   218  
   219  	defer setupTest(t)()
   220  
   221  	tmpDir1 := fs.NewDir(t, "tmpdir1", fs.WithMode(0755),
   222  		fs.WithDir("mnt", fs.WithMode(0755)))
   223  	defer tmpDir1.Remove()
   224  	tmpDir1Mnt := filepath.Join(tmpDir1.Path(), "mnt")
   225  	tmpDir2 := fs.NewDir(t, "tmpdir2", fs.WithMode(0755),
   226  		fs.WithFile("file", "should not be visible when NonRecursive", fs.WithMode(0644)))
   227  	defer tmpDir2.Remove()
   228  
   229  	err := mount.Mount(tmpDir2.Path(), tmpDir1Mnt, "none", "bind,ro")
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	defer func() {
   234  		if err := mount.Unmount(tmpDir1Mnt); err != nil {
   235  			t.Fatal(err)
   236  		}
   237  	}()
   238  
   239  	// implicit is recursive (NonRecursive: false)
   240  	implicit := mounttypes.Mount{
   241  		Type:     "bind",
   242  		Source:   tmpDir1.Path(),
   243  		Target:   "/foo",
   244  		ReadOnly: true,
   245  	}
   246  	recursive := implicit
   247  	recursive.BindOptions = &mounttypes.BindOptions{
   248  		NonRecursive: false,
   249  	}
   250  	recursiveVerifier := []string{"test", "-f", "/foo/mnt/file"}
   251  	nonRecursive := implicit
   252  	nonRecursive.BindOptions = &mounttypes.BindOptions{
   253  		NonRecursive: true,
   254  	}
   255  	nonRecursiveVerifier := []string{"test", "!", "-f", "/foo/mnt/file"}
   256  
   257  	ctx := context.Background()
   258  	client := testEnv.APIClient()
   259  	containers := []string{
   260  		container.Run(ctx, t, client, container.WithMount(implicit), container.WithCmd(recursiveVerifier...)),
   261  		container.Run(ctx, t, client, container.WithMount(recursive), container.WithCmd(recursiveVerifier...)),
   262  		container.Run(ctx, t, client, container.WithMount(nonRecursive), container.WithCmd(nonRecursiveVerifier...)),
   263  	}
   264  
   265  	for _, c := range containers {
   266  		poll.WaitOn(t, container.IsSuccessful(ctx, client, c), poll.WithDelay(100*time.Millisecond))
   267  	}
   268  }