mirror of
https://github.com/containers/podman.git
synced 2025-05-23 01:57:56 +08:00
Make changes to /etc/passwd on disk for non-read only
Bind-mounting /etc/passwd into the container is problematic becuase of how system utilities like `useradd` work. They want to make a copy and then rename to try to prevent breakage; this is, unfortunately, impossible when the file they want to rename is a bind mount. The current behavior is fine for read-only containers, though, because we expect useradd to fail in those cases. Instead of bind-mounting, we can edit /etc/passwd in the container's rootfs. This is kind of gross, because the change will show up in `podman diff` and similar tools, and will be included in images made by `podman commit`. However, it's a lot better than breaking important system tools. Fixes #6953 Signed-off-by: Matthew Heon <matthew.heon@pm.me>
This commit is contained in:

committed by
Matthew Heon

parent
9051546c4d
commit
bae6853906
@ -1458,11 +1458,26 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, error) {
|
|||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user.LookupId(strconv.Itoa(rootless.GetRootlessUID()))
|
u, err := user.LookupId(strconv.Itoa(rootless.GetRootlessUID()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrapf(err, "failed to get current user")
|
return "", errors.Wrapf(err, "failed to get current user")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s:x:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Username, c.WorkingDir()), nil
|
|
||||||
|
// Lookup the user to see if it exists in the container image.
|
||||||
|
_, err = lookup.GetUser(c.state.Mountpoint, u.Username)
|
||||||
|
if err != User.ErrNoPasswdEntries {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user's actual home directory exists, or was mounted in - use
|
||||||
|
// that.
|
||||||
|
homeDir := c.WorkingDir()
|
||||||
|
if MountExists(c.config.Spec.Mounts, u.HomeDir) {
|
||||||
|
homeDir = u.HomeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s:x:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Username, homeDir), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateUserPasswdEntry generates an /etc/passwd entry for the container user
|
// generateUserPasswdEntry generates an /etc/passwd entry for the container user
|
||||||
@ -1488,12 +1503,9 @@ func (c *Container) generateUserPasswdEntry() (string, error) {
|
|||||||
|
|
||||||
// Lookup the user to see if it exists in the container image
|
// Lookup the user to see if it exists in the container image
|
||||||
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
|
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
|
||||||
if err != nil && err != User.ErrNoPasswdEntries {
|
if err != User.ErrNoPasswdEntries {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupspec != "" {
|
if groupspec != "" {
|
||||||
ugid, err := strconv.ParseUint(groupspec, 10, 32)
|
ugid, err := strconv.ParseUint(groupspec, 10, 32)
|
||||||
@ -1542,6 +1554,32 @@ func (c *Container) generatePasswd() (string, error) {
|
|||||||
if pwd == "" {
|
if pwd == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are *not* read-only - edit /etc/passwd in the container.
|
||||||
|
// This is *gross* (shows up in changes to the container, will be
|
||||||
|
// committed to images based on the container) but it actually allows us
|
||||||
|
// to add users to the container (a bind mount breaks useradd).
|
||||||
|
// We should never get here twice, because generateUserPasswdEntry will
|
||||||
|
// not return anything if the user already exists in /etc/passwd.
|
||||||
|
if !c.IsReadOnly() {
|
||||||
|
containerPasswd, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd")
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "error looking up location of container %s /etc/passwd", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(containerPasswd, os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "error opening container %s /etc/passwd", c.ID())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.WriteString(pwd); err != nil {
|
||||||
|
return "", errors.Wrapf(err, "unable to append to container %s /etc/passwd", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
|
originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
|
||||||
orig, err := ioutil.ReadFile(originPasswdFile)
|
orig, err := ioutil.ReadFile(originPasswdFile)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
@ -35,27 +35,27 @@ var _ = Describe("Podman run passwd", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("podman run no user specified ", func() {
|
It("podman run no user specified ", func() {
|
||||||
session := podmanTest.Podman([]string{"run", BB, "mount"})
|
session := podmanTest.Podman([]string{"run", "--read-only", BB, "mount"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
Expect(session.LineInOutputContains("passwd")).To(BeFalse())
|
Expect(session.LineInOutputContains("passwd")).To(BeFalse())
|
||||||
})
|
})
|
||||||
It("podman run user specified in container", func() {
|
It("podman run user specified in container", func() {
|
||||||
session := podmanTest.Podman([]string{"run", "-u", "bin", BB, "mount"})
|
session := podmanTest.Podman([]string{"run", "--read-only", "-u", "bin", BB, "mount"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
Expect(session.LineInOutputContains("passwd")).To(BeFalse())
|
Expect(session.LineInOutputContains("passwd")).To(BeFalse())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman run UID specified in container", func() {
|
It("podman run UID specified in container", func() {
|
||||||
session := podmanTest.Podman([]string{"run", "-u", "2:1", BB, "mount"})
|
session := podmanTest.Podman([]string{"run", "--read-only", "-u", "2:1", BB, "mount"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
Expect(session.LineInOutputContains("passwd")).To(BeFalse())
|
Expect(session.LineInOutputContains("passwd")).To(BeFalse())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman run UID not specified in container", func() {
|
It("podman run UID not specified in container", func() {
|
||||||
session := podmanTest.Podman([]string{"run", "-u", "20001:1", BB, "mount"})
|
session := podmanTest.Podman([]string{"run", "--read-only", "-u", "20001:1", BB, "mount"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
Expect(session.LineInOutputContains("passwd")).To(BeTrue())
|
Expect(session.LineInOutputContains("passwd")).To(BeTrue())
|
||||||
|
@ -113,6 +113,31 @@ var _ = Describe("Podman UserNS support", func() {
|
|||||||
Expect(session.OutputToString()).To(Equal("0"))
|
Expect(session.OutputToString()).To(Equal("0"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman run --userns=keep-id can add users", func() {
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
Skip("Test only runs without root")
|
||||||
|
}
|
||||||
|
|
||||||
|
userName := os.Getenv("USER")
|
||||||
|
if userName == "" {
|
||||||
|
Skip("Can't complete test if no username available")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrName := "ctr-name"
|
||||||
|
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "-d", "--stop-signal", "9", "--name", ctrName, fedoraMinimal, "sleep", "600"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
exec1 := podmanTest.Podman([]string{"exec", "-t", "-i", ctrName, "cat", "/etc/passwd"})
|
||||||
|
exec1.WaitWithDefaultTimeout()
|
||||||
|
Expect(exec1.ExitCode()).To(Equal(0))
|
||||||
|
Expect(exec1.OutputToString()).To(ContainSubstring(userName))
|
||||||
|
|
||||||
|
exec2 := podmanTest.Podman([]string{"exec", "-t", "-i", ctrName, "useradd", "testuser"})
|
||||||
|
exec2.WaitWithDefaultTimeout()
|
||||||
|
Expect(exec2.ExitCode()).To(Equal(0))
|
||||||
|
})
|
||||||
|
|
||||||
It("podman --userns=auto", func() {
|
It("podman --userns=auto", func() {
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
Reference in New Issue
Block a user