github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/integration/container/mounts_linux_test.go (about) 1 package container // import "github.com/demonoid81/moby/integration/container" 2 3 import ( 4 "context" 5 "fmt" 6 "path/filepath" 7 "testing" 8 "time" 9 10 "github.com/demonoid81/moby/api/types" 11 containertypes "github.com/demonoid81/moby/api/types/container" 12 mounttypes "github.com/demonoid81/moby/api/types/mount" 13 "github.com/demonoid81/moby/api/types/network" 14 "github.com/demonoid81/moby/api/types/versions" 15 "github.com/demonoid81/moby/client" 16 "github.com/demonoid81/moby/integration/internal/container" 17 "github.com/demonoid81/moby/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{}, "") 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, "") 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 }