mirror of
https://github.com/containers/podman.git
synced 2025-06-23 10:38:20 +08:00
Merge pull request #6829 from rhatdan/keepid
Add username to /etc/passwd inside of container if --userns keep-id
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 {
|
||||||
|
if c.config.Spec.Process != nil {
|
||||||
return c.config.Spec.Process.Cwd
|
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