diff options
author | Daniel J Walsh <dwalsh@redhat.com> | 2018-10-09 07:54:37 -0400 |
---|---|---|
committer | Daniel J Walsh <dwalsh@redhat.com> | 2018-10-12 07:08:13 -0400 |
commit | 04a537756d9b7b526759c02b5b5d68c135b210ea (patch) | |
tree | 49e8a0e7610af0b6b752d28262ef78e98e0849b3 | |
parent | da5c89497f9d6ee5cb6e826d7db7cca5686ab4f7 (diff) | |
download | podman-04a537756d9b7b526759c02b5b5d68c135b210ea.tar.gz podman-04a537756d9b7b526759c02b5b5d68c135b210ea.tar.bz2 podman-04a537756d9b7b526759c02b5b5d68c135b210ea.zip |
Generate a passwd file for users not in container
If someone runs podman as a user (uid) that is not defined in the container
we want generate a passwd file so that getpwuid() will work inside of container.
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
-rw-r--r-- | libpod/container.go | 5 | ||||
-rw-r--r-- | libpod/container_internal.go | 66 | ||||
-rw-r--r-- | pkg/chrootuser/user.go | 7 | ||||
-rw-r--r-- | pkg/chrootuser/user_basic.go | 4 | ||||
-rw-r--r-- | pkg/chrootuser/user_linux.go | 26 | ||||
-rw-r--r-- | test/e2e/run_passwd_test.go | 60 | ||||
-rw-r--r-- | test/e2e/run_test.go | 2 |
7 files changed, 169 insertions, 1 deletions
diff --git a/libpod/container.go b/libpod/container.go index 5997c0b66..4e17b1102 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -642,6 +642,11 @@ func (c *Container) Hostname() string { return c.ID()[:12] } +// WorkingDir returns the containers working dir +func (c *Container) WorkingDir() string { + return c.config.Spec.Process.Cwd +} + // State Accessors // Require locking diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 77bba9e85..ab79aa790 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "strings" "syscall" @@ -946,6 +947,19 @@ func (c *Container) makeBindMounts() error { } c.state.BindMounts["/etc/resolv.conf"] = newResolv + newPasswd, err := c.generatePasswd() + if err != nil { + return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) + } + if newPasswd != "" { + // Make /etc/passwd + if _, ok := c.state.BindMounts["/etc/passwd"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/passwd") + } + logrus.Debugf("adding entry to /etc/passwd for non existent default user") + c.state.BindMounts["/etc/passwd"] = newPasswd + } // Make /etc/hosts if _, ok := c.state.BindMounts["/etc/hosts"]; ok { // If it already exists, delete so we can recreate @@ -1017,6 +1031,58 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) return filepath.Join(c.state.DestinationRunDir, destFile), nil } +// generatePasswd generates a container specific passwd file, +// iff g.config.User is a number +func (c *Container) generatePasswd() (string, error) { + var ( + groupspec string + gid uint32 + ) + if c.config.User == "" { + return "", nil + } + spec := strings.SplitN(c.config.User, ":", 2) + userspec := spec[0] + if len(spec) > 1 { + groupspec = spec[1] + } + // If a non numeric User, then don't generate passwd + uid, err := strconv.ParseUint(userspec, 10, 32) + if err != nil { + return "", nil + } + // if UID exists inside of container rootfs /etc/passwd then + // don't generate passwd + if _, _, err := chrootuser.LookupUIDInContainer(c.state.Mountpoint, uid); err == nil { + return "", nil + } + if err == nil && groupspec != "" { + if !c.state.Mounted { + return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID()) + } + gid, err = chrootuser.GetGroup(c.state.Mountpoint, groupspec) + if err != nil { + return "", errors.Wrapf(err, "unable to get gid from %s formporary passwd file") + } + } + + originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") + orig, err := ioutil.ReadFile(originPasswdFile) + if err != nil { + return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) + } + + pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir()) + passwdFile, err := c.writeStringToRundir("passwd", pwd) + if err != nil { + return "", errors.Wrapf(err, "failed to create temporary passwd fileo") + } + if os.Chmod(passwdFile, 0644); err != nil { + return "", err + } + return passwdFile, nil +} + // generateResolvConf generates a containers resolv.conf func (c *Container) generateResolvConf() (string, error) { // Determine the endpoint for resolv.conf in case it is a symlink diff --git a/pkg/chrootuser/user.go b/pkg/chrootuser/user.go index 3de138b86..c83dcc230 100644 --- a/pkg/chrootuser/user.go +++ b/pkg/chrootuser/user.go @@ -99,3 +99,10 @@ func GetAdditionalGroupsForUser(rootdir string, userid uint64) ([]uint32, error) } return gids, nil } + +// LookupUIDInContainer returns username and gid associated with a UID in a container +// it will use the /etc/passwd files inside of the rootdir +// to return this information. +func LookupUIDInContainer(rootdir string, uid uint64) (user string, gid uint64, err error) { + return lookupUIDInContainer(rootdir, uid) +} diff --git a/pkg/chrootuser/user_basic.go b/pkg/chrootuser/user_basic.go index 4ed7918e9..79b0b24b5 100644 --- a/pkg/chrootuser/user_basic.go +++ b/pkg/chrootuser/user_basic.go @@ -21,3 +21,7 @@ func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64 func lookupAdditionalGroupsForUIDInContainer(rootdir string, userid uint64) (gid []uint32, err error) { return nil, errors.New("supplemental groups list lookup by uid not supported") } + +func lookupUIDInContainer(rootdir string, uid uint64) (string, uint64, error) { + return "", 0, errors.New("UID lookup not supported") +} diff --git a/pkg/chrootuser/user_linux.go b/pkg/chrootuser/user_linux.go index acd0af822..583eca569 100644 --- a/pkg/chrootuser/user_linux.go +++ b/pkg/chrootuser/user_linux.go @@ -265,3 +265,29 @@ func lookupGroupInContainer(rootdir, groupname string) (gid uint64, err error) { return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname)) } + +func lookupUIDInContainer(rootdir string, uid uint64) (string, uint64, error) { + cmd, f, err := openChrootedFile(rootdir, "/etc/passwd") + if err != nil { + return "", 0, err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewReader(f) + defer f.Close() + + lookupUser.Lock() + defer lookupUser.Unlock() + + pwd := parseNextPasswd(rc) + for pwd != nil { + if pwd.uid != uid { + pwd = parseNextPasswd(rc) + continue + } + return pwd.name, pwd.gid, nil + } + + return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up uid %q", uid)) +} diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go new file mode 100644 index 000000000..cea457ae4 --- /dev/null +++ b/test/e2e/run_passwd_test.go @@ -0,0 +1,60 @@ +package integration + +import ( + "os" + + "fmt" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman run passwd", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("podman run no user specified ", func() { + session := podmanTest.Podman([]string{"run", ALPINE, "mount"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOutputContains("passwd")).To(BeFalse()) + }) + It("podman run user specified in container", func() { + session := podmanTest.Podman([]string{"run", "-u", "bin", ALPINE, "mount"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOutputContains("passwd")).To(BeFalse()) + }) + + It("podman run UID specified in container", func() { + session := podmanTest.Podman([]string{"run", "-u", "2:1", ALPINE, "mount"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOutputContains("passwd")).To(BeFalse()) + }) + + It("podman run UID not specified in container", func() { + session := podmanTest.Podman([]string{"run", "-u", "20001:1", ALPINE, "mount"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOutputContains("passwd")).To(BeTrue()) + }) +}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index a443d4ca5..271651056 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -401,7 +401,7 @@ var _ = Describe("Podman run", func() { session := podmanTest.Podman([]string{"run", "--rm", "--user=1234", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(Equal("uid=1234 gid=0(root)")) + Expect(session.OutputToString()).To(Equal("uid=1234(1234) gid=0(root)")) }) It("podman run with user (integer, in /etc/passwd)", func() { |