Skip to content

Commit

Permalink
Add username to /etc/passwd inside of container if --userns keep-id
Browse files Browse the repository at this point in the history
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 <dwalsh@redhat.com>
  • Loading branch information
rhatdan authored and vrothberg committed Aug 11, 2020
1 parent 92d01d7 commit 3dfd863
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 10 deletions.
2 changes: 2 additions & 0 deletions cmd/podman/common/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,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
Expand Down
8 changes: 7 additions & 1 deletion libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,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
Expand Down Expand Up @@ -774,7 +777,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
Expand Down
58 changes: 50 additions & 8 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/ioutil"
"net"
"os"
"os/user"
"path"
"path/filepath"
"strconv"
Expand All @@ -33,7 +34,7 @@ import (
"github.com/containers/libpod/v2/pkg/util"
"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"
Expand Down Expand Up @@ -1420,9 +1421,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
Expand All @@ -1440,14 +1455,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 {
Expand All @@ -1460,14 +1477,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")
}
Expand Down
42 changes: 42 additions & 0 deletions libpod/container_internal_linux_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
14 changes: 14 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,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
Expand Down
4 changes: 3 additions & 1 deletion pkg/specgen/generate/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/run_userns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 3dfd863

Please sign in to comment.