github.com/rish1988/moby@v25.0.2+incompatible/integration/volume/mount_test.go (about) 1 package volume 2 3 import ( 4 "context" 5 "path/filepath" 6 "strings" 7 "testing" 8 9 containertypes "github.com/docker/docker/api/types/container" 10 "github.com/docker/docker/api/types/mount" 11 "github.com/docker/docker/api/types/network" 12 "github.com/docker/docker/api/types/versions" 13 "github.com/docker/docker/api/types/volume" 14 "github.com/docker/docker/client" 15 "github.com/docker/docker/integration/internal/container" 16 "github.com/docker/docker/internal/safepath" 17 "gotest.tools/v3/assert" 18 is "gotest.tools/v3/assert/cmp" 19 "gotest.tools/v3/skip" 20 ) 21 22 func TestRunMountVolumeSubdir(t *testing.T) { 23 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.45"), "skip test from new feature") 24 25 ctx := setupTest(t) 26 apiClient := testEnv.APIClient() 27 28 testVolumeName := setupTestVolume(t, apiClient) 29 30 for _, tc := range []struct { 31 name string 32 opts mount.VolumeOptions 33 cmd []string 34 volumeTarget string 35 createErr string 36 startErr string 37 expected string 38 skipPlatform string 39 }{ 40 {name: "subdir", opts: mount.VolumeOptions{Subpath: "subdir"}, cmd: []string{"ls", "/volume"}, expected: "hello.txt"}, 41 {name: "subdir link", opts: mount.VolumeOptions{Subpath: "hack/good"}, cmd: []string{"ls", "/volume"}, expected: "hello.txt"}, 42 {name: "subdir with copy data", opts: mount.VolumeOptions{Subpath: "bin"}, volumeTarget: "/bin", cmd: []string{"ls", "/bin/busybox"}, expected: "/bin/busybox", skipPlatform: "windows:copy not supported on Windows"}, 43 {name: "file", opts: mount.VolumeOptions{Subpath: "bar.txt"}, cmd: []string{"cat", "/volume"}, expected: "foo", skipPlatform: "windows:file bind mounts not supported on Windows"}, 44 {name: "relative with backtracks", opts: mount.VolumeOptions{Subpath: "../../../../../../etc/passwd"}, cmd: []string{"cat", "/volume"}, createErr: "subpath must be a relative path within the volume"}, 45 {name: "not existing", opts: mount.VolumeOptions{Subpath: "not-existing-path"}, cmd: []string{"cat", "/volume"}, startErr: (&safepath.ErrNotAccessible{}).Error()}, 46 47 {name: "mount link", opts: mount.VolumeOptions{Subpath: filepath.Join("hack", "root")}, cmd: []string{"ls", "/volume"}, startErr: (&safepath.ErrEscapesBase{}).Error()}, 48 {name: "mount link link", opts: mount.VolumeOptions{Subpath: filepath.Join("hack", "bad")}, cmd: []string{"ls", "/volume"}, startErr: (&safepath.ErrEscapesBase{}).Error()}, 49 } { 50 t.Run(tc.name, func(t *testing.T) { 51 if tc.skipPlatform != "" { 52 platform, reason, _ := strings.Cut(tc.skipPlatform, ":") 53 if testEnv.DaemonInfo.OSType == platform { 54 t.Skip(reason) 55 } 56 } 57 58 cfg := containertypes.Config{ 59 Image: "busybox", 60 Cmd: tc.cmd, 61 } 62 hostCfg := containertypes.HostConfig{ 63 Mounts: []mount.Mount{ 64 { 65 Type: mount.TypeVolume, 66 Source: testVolumeName, 67 Target: "/volume", 68 VolumeOptions: &tc.opts, 69 }, 70 }, 71 } 72 if testEnv.DaemonInfo.OSType == "windows" { 73 hostCfg.Mounts[0].Target = `C:\volume` 74 } 75 if tc.volumeTarget != "" { 76 hostCfg.Mounts[0].Target = tc.volumeTarget 77 } 78 79 ctrName := strings.ReplaceAll(t.Name(), "/", "_") 80 create, creatErr := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, &network.NetworkingConfig{}, nil, ctrName) 81 id := create.ID 82 if id != "" { 83 defer apiClient.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) 84 } 85 86 if tc.createErr != "" { 87 assert.ErrorContains(t, creatErr, tc.createErr) 88 return 89 } 90 assert.NilError(t, creatErr, "container creation failed") 91 92 startErr := apiClient.ContainerStart(ctx, id, containertypes.StartOptions{}) 93 if tc.startErr != "" { 94 assert.ErrorContains(t, startErr, tc.startErr) 95 return 96 } 97 assert.NilError(t, startErr) 98 99 output, err := container.Output(ctx, apiClient, id) 100 assert.Check(t, err) 101 t.Logf("stdout:\n%s", output.Stdout) 102 t.Logf("stderr:\n%s", output.Stderr) 103 104 inspect, err := apiClient.ContainerInspect(ctx, id) 105 if assert.Check(t, err) { 106 assert.Check(t, is.Equal(inspect.State.ExitCode, 0)) 107 } 108 109 assert.Check(t, is.Equal(strings.TrimSpace(output.Stderr), "")) 110 assert.Check(t, is.Equal(strings.TrimSpace(output.Stdout), tc.expected)) 111 }) 112 } 113 } 114 115 // setupTestVolume sets up a volume with: 116 // . 117 // |-- bar.txt (file with "foo") 118 // |-- bin (directory) 119 // |-- subdir (directory) 120 // | |-- hello.txt (file with "world") 121 // |-- hack (directory) 122 // | |-- root (symlink to /) 123 // | |-- good (symlink to ../subdir) 124 // | |-- bad (symlink to root) 125 func setupTestVolume(t *testing.T, client client.APIClient) string { 126 t.Helper() 127 ctx := context.Background() 128 129 volumeName := t.Name() + "-volume" 130 131 err := client.VolumeRemove(ctx, volumeName, true) 132 assert.NilError(t, err, "failed to clean volume") 133 134 _, err = client.VolumeCreate(ctx, volume.CreateOptions{ 135 Name: volumeName, 136 }) 137 assert.NilError(t, err, "failed to setup volume") 138 139 mount := mount.Mount{ 140 Type: mount.TypeVolume, 141 Source: volumeName, 142 Target: "/volume", 143 } 144 145 rootFs := "/" 146 if testEnv.DaemonInfo.OSType == "windows" { 147 mount.Target = `C:\volume` 148 rootFs = `C:` 149 } 150 151 initCmd := "echo foo > /volume/bar.txt && " + 152 "mkdir /volume/bin && " + 153 "mkdir /volume/subdir && " + 154 "echo world > /volume/subdir/hello.txt && " + 155 "mkdir /volume/hack && " + 156 "ln -s " + rootFs + " /volume/hack/root && " + 157 "ln -s ../subdir /volume/hack/good && " + 158 "ln -s root /volume/hack/bad &&" + 159 "mkdir /volume/hack/iwanttobehackedwithtoctou" 160 161 opts := []func(*container.TestContainerConfig){ 162 container.WithMount(mount), 163 container.WithCmd("sh", "-c", initCmd+"; ls -lah /volume /volume/hack/"), 164 } 165 if testEnv.DaemonInfo.OSType == "windows" { 166 // Can't create symlinks under HyperV isolation 167 opts = append(opts, container.WithIsolation(containertypes.IsolationProcess)) 168 } 169 170 cid := container.Run(ctx, t, client, opts...) 171 defer client.ContainerRemove(ctx, cid, containertypes.RemoveOptions{Force: true}) 172 output, err := container.Output(ctx, client, cid) 173 174 t.Logf("Setup stderr:\n%s", output.Stderr) 175 t.Logf("Setup stdout:\n%s", output.Stdout) 176 177 assert.NilError(t, err) 178 assert.Assert(t, is.Equal(output.Stderr, "")) 179 180 inspect, err := client.ContainerInspect(ctx, cid) 181 assert.NilError(t, err) 182 assert.Assert(t, is.Equal(inspect.State.ExitCode, 0)) 183 184 return volumeName 185 }