mirror of
https://github.com/containers/podman.git
synced 2025-07-03 17:27:18 +08:00
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 <dwalsh@redhat.com>
This commit is contained in:
@ -241,6 +241,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
|
|||||||
// If some mappings are specified, assume a private user namespace
|
// If some mappings are specified, assume a private user namespace
|
||||||
if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
|
if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
|
||||||
s.UserNS.NSMode = specgen.Private
|
s.UserNS.NSMode = specgen.Private
|
||||||
|
} else {
|
||||||
|
s.UserNS.NSMode = specgen.NamespaceMode(userNS)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Terminal = c.TTY
|
s.Terminal = c.TTY
|
||||||
|
@ -278,6 +278,9 @@ type ContainerConfig struct {
|
|||||||
User string `json:"user,omitempty"`
|
User string `json:"user,omitempty"`
|
||||||
// Additional groups to add
|
// Additional groups to add
|
||||||
Groups []string `json:"groups,omitempty"`
|
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
|
// Namespace Config
|
||||||
// IDs of container to share namespaces with
|
// IDs of container to share namespaces with
|
||||||
@ -786,7 +789,10 @@ func (c *Container) Hostname() string {
|
|||||||
|
|
||||||
// WorkingDir returns the containers working dir
|
// WorkingDir returns the containers working dir
|
||||||
func (c *Container) WorkingDir() string {
|
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
|
// State Accessors
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -34,7 +35,7 @@ import (
|
|||||||
"github.com/containers/libpod/v2/utils"
|
"github.com/containers/libpod/v2/utils"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
securejoin "github.com/cyphar/filepath-securejoin"
|
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"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/opencontainers/runtime-tools/generate"
|
"github.com/opencontainers/runtime-tools/generate"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
@ -1448,9 +1449,23 @@ func (c *Container) getHosts() string {
|
|||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
// generatePasswd generates a container specific passwd file,
|
// generateCurrentUserPasswdEntry generates an /etc/passwd entry for the user
|
||||||
// iff g.config.User is a number
|
// running the container engine
|
||||||
func (c *Container) generatePasswd() (string, error) {
|
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 (
|
var (
|
||||||
groupspec string
|
groupspec string
|
||||||
gid int
|
gid int
|
||||||
@ -1468,14 +1483,16 @@ func (c *Container) generatePasswd() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 != nil && err != User.ErrNoPasswdEntries {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupspec != "" {
|
if groupspec != "" {
|
||||||
ugid, err := strconv.ParseUint(groupspec, 10, 32)
|
ugid, err := strconv.ParseUint(groupspec, 10, 32)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -1488,14 +1505,39 @@ func (c *Container) generatePasswd() (string, error) {
|
|||||||
gid = group.Gid
|
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")
|
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) {
|
||||||
return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
|
return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
|
||||||
}
|
}
|
||||||
|
passwdFile, err := c.writeStringToRundir("passwd", string(orig)+pwd)
|
||||||
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 {
|
if err != nil {
|
||||||
return "", errors.Wrapf(err, "failed to create temporary passwd file")
|
return "", errors.Wrapf(err, "failed to create temporary passwd file")
|
||||||
}
|
}
|
||||||
|
42
libpod/container_internal_linux_test.go
Normal file
42
libpod/container_internal_linux_test.go
Normal file
@ -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")
|
||||||
|
}
|
@ -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
|
// WithUserNSFrom indicates the the container should join the user namespace of
|
||||||
// the given container.
|
// the given container.
|
||||||
// If the container has joined a pod, it can only join the namespaces of
|
// If the container has joined a pod, it can only join the namespaces of
|
||||||
|
@ -153,7 +153,9 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
|
|||||||
// User
|
// User
|
||||||
switch s.UserNS.NSMode {
|
switch s.UserNS.NSMode {
|
||||||
case specgen.KeepID:
|
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
|
// keep-id as root doesn't need a user namespace
|
||||||
s.UserNS.NSMode = specgen.Host
|
s.UserNS.NSMode = specgen.Host
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,16 @@ var _ = Describe("Podman UserNS support", func() {
|
|||||||
Expect(ok).To(BeTrue())
|
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() {
|
It("podman --userns=keep-id root owns /usr", func() {
|
||||||
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"})
|
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
|
Reference in New Issue
Block a user