This corrects `docker cp` behavior when user namespaces are enabled.
Instead of chown'ing copied-in files to real root (0,0), the code
queries for the remapped root uid & gid and sets the chown option
properly.
Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
| ... | ... |
@@ -250,13 +250,13 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path |
| 250 | 250 |
return ErrRootFSReadOnly |
| 251 | 251 |
} |
| 252 | 252 |
|
| 253 |
+ uid, gid := daemon.GetRemappedUIDGID() |
|
| 253 | 254 |
options := &archive.TarOptions{
|
| 255 |
+ NoOverwriteDirNonDir: noOverwriteDirNonDir, |
|
| 254 | 256 |
ChownOpts: &archive.TarChownOptions{
|
| 255 |
- UID: 0, GID: 0, // TODO: use config.User? Remap to userns root? |
|
| 257 |
+ UID: uid, GID: gid, // TODO: should all ownership be set to root (either real or remapped)? |
|
| 256 | 258 |
}, |
| 257 |
- NoOverwriteDirNonDir: noOverwriteDirNonDir, |
|
| 258 | 259 |
} |
| 259 |
- |
|
| 260 | 260 |
if err := chrootarchive.Untar(content, resolvedPath, options); err != nil {
|
| 261 | 261 |
return err |
| 262 | 262 |
} |
| 263 | 263 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package main |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/pkg/integration/checker" |
|
| 10 |
+ "github.com/docker/docker/pkg/system" |
|
| 11 |
+ "github.com/go-check/check" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+// Check ownership is root, both in non-userns and userns enabled modes |
|
| 15 |
+func (s *DockerSuite) TestCpCheckDestOwnership(c *check.C) {
|
|
| 16 |
+ testRequires(c, DaemonIsLinux, SameHostDaemon) |
|
| 17 |
+ tmpVolDir := getTestDir(c, "test-cp-tmpvol") |
|
| 18 |
+ containerID := makeTestContainer(c, |
|
| 19 |
+ testContainerOptions{volumes: []string{fmt.Sprintf("%s:/tmpvol", tmpVolDir)}})
|
|
| 20 |
+ |
|
| 21 |
+ tmpDir := getTestDir(c, "test-cp-to-check-ownership") |
|
| 22 |
+ defer os.RemoveAll(tmpDir) |
|
| 23 |
+ |
|
| 24 |
+ makeTestContentInDir(c, tmpDir) |
|
| 25 |
+ |
|
| 26 |
+ srcPath := cpPath(tmpDir, "file1") |
|
| 27 |
+ dstPath := containerCpPath(containerID, "/tmpvol", "file1") |
|
| 28 |
+ |
|
| 29 |
+ err := runDockerCp(c, srcPath, dstPath) |
|
| 30 |
+ c.Assert(err, checker.IsNil) |
|
| 31 |
+ |
|
| 32 |
+ stat, err := system.Stat(filepath.Join(tmpVolDir, "file1")) |
|
| 33 |
+ c.Assert(err, checker.IsNil) |
|
| 34 |
+ uid, gid, err := getRootUIDGID() |
|
| 35 |
+ c.Assert(err, checker.IsNil) |
|
| 36 |
+ c.Assert(stat.UID(), checker.Equals, uint32(uid), check.Commentf("Copied file not owned by container root UID"))
|
|
| 37 |
+ c.Assert(stat.GID(), checker.Equals, uint32(gid), check.Commentf("Copied file not owned by container root GID"))
|
|
| 38 |
+} |
| ... | ... |
@@ -1781,6 +1781,23 @@ func runSleepingContainerInImage(c *check.C, image string, extraArgs ...string) |
| 1781 | 1781 |
return dockerCmd(c, args...) |
| 1782 | 1782 |
} |
| 1783 | 1783 |
|
| 1784 |
+func getRootUIDGID() (int, int, error) {
|
|
| 1785 |
+ uidgid := strings.Split(filepath.Base(dockerBasePath), ".") |
|
| 1786 |
+ if len(uidgid) == 1 {
|
|
| 1787 |
+ //user namespace remapping is not turned on; return 0 |
|
| 1788 |
+ return 0, 0, nil |
|
| 1789 |
+ } |
|
| 1790 |
+ uid, err := strconv.Atoi(uidgid[0]) |
|
| 1791 |
+ if err != nil {
|
|
| 1792 |
+ return 0, 0, err |
|
| 1793 |
+ } |
|
| 1794 |
+ gid, err := strconv.Atoi(uidgid[1]) |
|
| 1795 |
+ if err != nil {
|
|
| 1796 |
+ return 0, 0, err |
|
| 1797 |
+ } |
|
| 1798 |
+ return uid, gid, nil |
|
| 1799 |
+} |
|
| 1800 |
+ |
|
| 1784 | 1801 |
// minimalBaseImage returns the name of the minimal base image for the current |
| 1785 | 1802 |
// daemon platform. |
| 1786 | 1803 |
func minimalBaseImage() string {
|