From 6c6670f12a3e6b91c1ebb09e7d9e4f49f89dccc0 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 30 Jun 2020 15:44:14 -0400 Subject: Add username to /etc/passwd inside of container if --userns keep-id If I enter a continer with --userns keep-id, my UID will be present inside of the container, but most likely my user will not be defined. This patch will take information about the user and stick it into the container. Signed-off-by: Daniel J Walsh --- cmd/podman/common/specgen.go | 2 ++ libpod/container.go | 8 ++++- libpod/container_internal_linux.go | 58 ++++++++++++++++++++++++++++----- libpod/container_internal_linux_test.go | 42 ++++++++++++++++++++++++ libpod/options.go | 14 ++++++++ pkg/specgen/generate/namespaces.go | 4 ++- test/e2e/run_userns_test.go | 10 ++++++ 7 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 libpod/container_internal_linux_test.go diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 0948e78f1..eca0da32b 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -241,6 +241,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // If some mappings are specified, assume a private user namespace if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) { s.UserNS.NSMode = specgen.Private + } else { + s.UserNS.NSMode = specgen.NamespaceMode(userNS) } s.Terminal = c.TTY diff --git a/libpod/container.go b/libpod/container.go index fa90fef7a..f7abfb005 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -278,6 +278,9 @@ type ContainerConfig struct { User string `json:"user,omitempty"` // Additional groups to add Groups []string `json:"groups,omitempty"` + // AddCurrentUserPasswdEntry indicates that the current user passwd entry + // should be added to the /etc/passwd within the container + AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` // Namespace Config // IDs of container to share namespaces with @@ -786,7 +789,10 @@ func (c *Container) Hostname() string { // WorkingDir returns the containers working dir func (c *Container) WorkingDir() string { - return c.config.Spec.Process.Cwd + if c.config.Spec.Process != nil { + return c.config.Spec.Process.Cwd + } + return "/" } // State Accessors diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 8bf6092c3..a3a57ae0f 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "os/user" "path" "path/filepath" "strconv" @@ -34,7 +35,7 @@ import ( "github.com/containers/libpod/v2/utils" "github.com/containers/storage/pkg/archive" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/opencontainers/runc/libcontainer/user" + User "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -1448,9 +1449,23 @@ func (c *Container) getHosts() string { return hosts } -// generatePasswd generates a container specific passwd file, -// iff g.config.User is a number -func (c *Container) generatePasswd() (string, error) { +// generateCurrentUserPasswdEntry generates an /etc/passwd entry for the user +// running the container engine +func (c *Container) generateCurrentUserPasswdEntry() (string, error) { + uid := rootless.GetRootlessUID() + if uid == 0 { + return "", nil + } + u, err := user.LookupId(strconv.Itoa(rootless.GetRootlessUID())) + if err != nil { + 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 +} + +// generateUserPasswdEntry generates an /etc/passwd entry for the container user +// to run in the container. +func (c *Container) generateUserPasswdEntry() (string, error) { var ( groupspec string gid int @@ -1468,14 +1483,16 @@ func (c *Container) generatePasswd() (string, error) { if err != nil { return "", nil } + // Lookup the user to see if it exists in the container image _, err = lookup.GetUser(c.state.Mountpoint, userspec) - if err != nil && err != user.ErrNoPasswdEntries { + if err != nil && err != User.ErrNoPasswdEntries { return "", err } if err == nil { return "", nil } + if groupspec != "" { ugid, err := strconv.ParseUint(groupspec, 10, 32) if err == nil { @@ -1488,14 +1505,39 @@ func (c *Container) generatePasswd() (string, error) { gid = group.Gid } } + return fmt.Sprintf("%d:x:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), nil +} + +// generatePasswd generates a container specific passwd file, +// iff g.config.User is a number +func (c *Container) generatePasswd() (string, error) { + if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" { + return "", nil + } + pwd := "" + if c.config.User != "" { + entry, err := c.generateUserPasswdEntry() + if err != nil { + return "", err + } + pwd += entry + } + if c.config.AddCurrentUserPasswdEntry { + entry, err := c.generateCurrentUserPasswdEntry() + if err != nil { + return "", err + } + pwd += entry + } + if pwd == "" { + return "", nil + } originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") orig, err := ioutil.ReadFile(originPasswdFile) if err != nil && !os.IsNotExist(err) { 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) + passwdFile, err := c.writeStringToRundir("passwd", string(orig)+pwd) if err != nil { return "", errors.Wrapf(err, "failed to create temporary passwd file") } diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go new file mode 100644 index 000000000..078cc53a7 --- /dev/null +++ b/libpod/container_internal_linux_test.go @@ -0,0 +1,42 @@ +// +build linux + +package libpod + +import ( + "io/ioutil" + "os" + "testing" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/assert" +) + +func TestGenerateUserPasswdEntry(t *testing.T) { + dir, err := ioutil.TempDir("", "libpod_test_") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + c := Container{ + config: &ContainerConfig{ + User: "123:456", + Spec: &spec.Spec{}, + }, + state: &ContainerState{ + Mountpoint: "/does/not/exist/tmp/", + }, + } + user, err := c.generateUserPasswdEntry() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, user, "123:x:123:456:container user:/:/bin/sh\n") + + c.config.User = "567" + user, err = c.generateUserPasswdEntry() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, user, "567:x:567:0:container user:/:/bin/sh\n") +} diff --git a/libpod/options.go b/libpod/options.go index 61d1676f1..104d7c9db 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -866,6 +866,20 @@ func WithPIDNSFrom(nsCtr *Container) CtrCreateOption { } } +// WithAddCurrentUserPasswdEntry indicates that container should add current +// user entry to /etc/passwd, since the UID will be mapped into the container, +// via user namespace +func WithAddCurrentUserPasswdEntry() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.AddCurrentUserPasswdEntry = true + return nil + } +} + // WithUserNSFrom indicates the the container should join the user namespace of // the given container. // If the container has joined a pod, it can only join the namespaces of diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 566830cd8..09d6ba445 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -153,7 +153,9 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. // User switch s.UserNS.NSMode { case specgen.KeepID: - if !rootless.IsRootless() { + if rootless.IsRootless() { + toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry()) + } else { // keep-id as root doesn't need a user namespace s.UserNS.NSMode = specgen.Host } diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index 9f0466468..42f13537d 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -89,6 +89,16 @@ var _ = Describe("Podman UserNS support", func() { Expect(ok).To(BeTrue()) }) + It("podman --userns=keep-id check passwd", func() { + session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-un"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + u, err := user.Current() + Expect(err).To(BeNil()) + ok, _ := session.GrepString(u.Name) + Expect(ok).To(BeTrue()) + }) + It("podman --userns=keep-id root owns /usr", func() { session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"}) session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf