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.
Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com>
... | ... |
@@ -86,8 +86,13 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er |
86 | 86 |
// remapped root (user namespaces) |
87 | 87 |
rootIDs := daemon.idMappings.RootPair() |
88 | 88 |
for _, mount := range netMounts { |
89 |
- if err := os.Chown(mount.Source, rootIDs.UID, rootIDs.GID); err != nil { |
|
90 |
- return nil, err |
|
89 |
+ // we should only modify ownership of network files within our own container |
|
90 |
+ // metadata repository. If the user specifies a mount path external, it is |
|
91 |
+ // up to the user to make sure the file has proper ownership for userns |
|
92 |
+ if strings.Index(mount.Source, daemon.repository) == 0 { |
|
93 |
+ if err := os.Chown(mount.Source, rootIDs.UID, rootIDs.GID); err != nil { |
|
94 |
+ return nil, err |
|
95 |
+ } |
|
91 | 96 |
} |
92 | 97 |
} |
93 | 98 |
return append(mounts, netMounts...), nil |
94 | 99 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,77 @@ |
0 |
+// +build !windows |
|
1 |
+ |
|
2 |
+package main |
|
3 |
+ |
|
4 |
+import ( |
|
5 |
+ "io/ioutil" |
|
6 |
+ "os" |
|
7 |
+ "path/filepath" |
|
8 |
+ |
|
9 |
+ "github.com/docker/docker/api/types" |
|
10 |
+ containertypes "github.com/docker/docker/api/types/container" |
|
11 |
+ mounttypes "github.com/docker/docker/api/types/mount" |
|
12 |
+ networktypes "github.com/docker/docker/api/types/network" |
|
13 |
+ "github.com/docker/docker/client" |
|
14 |
+ "github.com/docker/docker/integration-cli/checker" |
|
15 |
+ "github.com/docker/docker/pkg/ioutils" |
|
16 |
+ "github.com/docker/docker/pkg/system" |
|
17 |
+ "github.com/go-check/check" |
|
18 |
+ "github.com/stretchr/testify/assert" |
|
19 |
+ "golang.org/x/net/context" |
|
20 |
+) |
|
21 |
+ |
|
22 |
+func (s *DockerSuite) TestContainersAPINetworkMountsNoChown(c *check.C) { |
|
23 |
+ // chown only applies to Linux bind mounted volumes; must be same host to verify |
|
24 |
+ testRequires(c, DaemonIsLinux, SameHostDaemon) |
|
25 |
+ |
|
26 |
+ tmpDir, err := ioutils.TempDir("", "test-network-mounts") |
|
27 |
+ c.Assert(err, checker.IsNil) |
|
28 |
+ defer os.RemoveAll(tmpDir) |
|
29 |
+ |
|
30 |
+ // make tmp dir readable by anyone to allow userns process to mount from |
|
31 |
+ err = os.Chmod(tmpDir, 0755) |
|
32 |
+ c.Assert(err, checker.IsNil) |
|
33 |
+ // create temp files to use as network mounts |
|
34 |
+ tmpNWFileMount := filepath.Join(tmpDir, "nwfile") |
|
35 |
+ |
|
36 |
+ err = ioutil.WriteFile(tmpNWFileMount, []byte("network file bind mount"), 0644) |
|
37 |
+ c.Assert(err, checker.IsNil) |
|
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.NewEnvClient() |
|
63 |
+ c.Assert(err, checker.IsNil) |
|
64 |
+ defer cli.Close() |
|
65 |
+ |
|
66 |
+ ctrCreate, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") |
|
67 |
+ c.Assert(err, checker.IsNil) |
|
68 |
+ // container will exit immediately because of no tty, but we only need the start sequence to test the condition |
|
69 |
+ err = cli.ContainerStart(context.Background(), ctrCreate.ID, types.ContainerStartOptions{}) |
|
70 |
+ c.Assert(err, checker.IsNil) |
|
71 |
+ |
|
72 |
+ // check that host-located bind mount network file did not change ownership when the container was started |
|
73 |
+ statT, err := system.Stat(tmpNWFileMount) |
|
74 |
+ c.Assert(err, checker.IsNil) |
|
75 |
+ assert.Equal(c, uint32(0), statT.UID(), "bind mounted network file should not change ownership from root") |
|
76 |
+} |
... | ... |
@@ -3115,6 +3115,11 @@ func (s *DockerSuite) TestRunNetworkFilesBindMount(c *check.C) { |
3115 | 3115 |
filename := createTmpFile(c, expected) |
3116 | 3116 |
defer os.Remove(filename) |
3117 | 3117 |
|
3118 |
+ // for user namespaced test runs, the temp file must be accessible to unprivileged root |
|
3119 |
+ if err := os.Chmod(filename, 0646); err != nil { |
|
3120 |
+ c.Fatalf("error modifying permissions of %s: %v", filename, err) |
|
3121 |
+ } |
|
3122 |
+ |
|
3118 | 3123 |
nwfiles := []string{"/etc/resolv.conf", "/etc/hosts", "/etc/hostname"} |
3119 | 3124 |
|
3120 | 3125 |
for i := range nwfiles { |
... | ... |
@@ -3132,6 +3137,11 @@ func (s *DockerSuite) TestRunNetworkFilesBindMountRO(c *check.C) { |
3132 | 3132 |
filename := createTmpFile(c, "test123") |
3133 | 3133 |
defer os.Remove(filename) |
3134 | 3134 |
|
3135 |
+ // for user namespaced test runs, the temp file must be accessible to unprivileged root |
|
3136 |
+ if err := os.Chmod(filename, 0646); err != nil { |
|
3137 |
+ c.Fatalf("error modifying permissions of %s: %v", filename, err) |
|
3138 |
+ } |
|
3139 |
+ |
|
3135 | 3140 |
nwfiles := []string{"/etc/resolv.conf", "/etc/hosts", "/etc/hostname"} |
3136 | 3141 |
|
3137 | 3142 |
for i := range nwfiles { |
... | ... |
@@ -3149,6 +3159,11 @@ func (s *DockerSuite) TestRunNetworkFilesBindMountROFilesystem(c *check.C) { |
3149 | 3149 |
filename := createTmpFile(c, "test123") |
3150 | 3150 |
defer os.Remove(filename) |
3151 | 3151 |
|
3152 |
+ // for user namespaced test runs, the temp file must be accessible to unprivileged root |
|
3153 |
+ if err := os.Chmod(filename, 0646); err != nil { |
|
3154 |
+ c.Fatalf("error modifying permissions of %s: %v", filename, err) |
|
3155 |
+ } |
|
3156 |
+ |
|
3152 | 3157 |
nwfiles := []string{"/etc/resolv.conf", "/etc/hosts", "/etc/hostname"} |
3153 | 3158 |
|
3154 | 3159 |
for i := range nwfiles { |