Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add username to /etc/passwd inside of container if --userns keep-id #6829

Merged
merged 1 commit into from
Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/podman/common/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
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 @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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")
}
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 @@ -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
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