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>
This commit is contained in:
Daniel J Walsh
2018-10-09 07:54:37 -04:00
parent da5c89497f
commit 04a537756d
7 changed files with 169 additions and 1 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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))
}

View File

@ -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())
})
})

View File

@ -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() {