aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/runlabel.go5
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/container_internal_linux.go417
-rw-r--r--libpod/container_internal_linux_test.go34
-rw-r--r--libpod/options.go12
-rw-r--r--pkg/domain/infra/abi/containers_runlabel.go32
-rw-r--r--pkg/domain/infra/abi/images.go10
-rw-r--r--test/e2e/run_passwd_test.go54
-rw-r--r--test/e2e/run_userns_test.go7
-rw-r--r--test/e2e/runlabel_test.go7
-rw-r--r--test/system/030-run.bats18
-rw-r--r--test/system/120-load.bats34
12 files changed, 508 insertions, 124 deletions
diff --git a/cmd/podman/containers/runlabel.go b/cmd/podman/containers/runlabel.go
index 81eec2a42..5ee8c9d6c 100644
--- a/cmd/podman/containers/runlabel.go
+++ b/cmd/podman/containers/runlabel.go
@@ -30,7 +30,7 @@ var (
RunE: runlabel,
Args: cobra.MinimumNArgs(2),
Example: `podman container runlabel run imageID
- podman container runlabel --pull install imageID arg1 arg2
+ podman container runlabel install imageID arg1 arg2
podman container runlabel --display run myImage`,
}
)
@@ -51,7 +51,7 @@ func init() {
flags.StringVar(&runlabelOptions.Optional1, "opt1", "", "Optional parameter to pass for install")
flags.StringVar(&runlabelOptions.Optional2, "opt2", "", "Optional parameter to pass for install")
flags.StringVar(&runlabelOptions.Optional3, "opt3", "", "Optional parameter to pass for install")
- flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents")
+ flags.BoolVarP(&runlabelOptions.Pull, "pull", "p", true, "Pull the image if it does not exist locally prior to executing the label contents")
flags.BoolVarP(&runlabelOptions.Quiet, "quiet", "q", false, "Suppress output information when installing images")
flags.BoolVar(&runlabelOptions.Replace, "replace", false, "Replace existing container with a new one from the image")
flags.StringVar(&runlabelOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
@@ -61,6 +61,7 @@ func init() {
_ = flags.MarkHidden("opt1")
_ = flags.MarkHidden("opt2")
_ = flags.MarkHidden("opt3")
+ _ = flags.MarkHidden("pull")
_ = flags.MarkHidden("signature-policy")
if err := flags.MarkDeprecated("pull", "podman will pull if not found in local storage"); err != nil {
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index c3f07a48b..5a0a0edfa 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -380,6 +380,8 @@ func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) {
}
dest.GIDMap = append(dest.GIDMap, g)
}
+ dest.HostUIDMapping = false
+ dest.HostGIDMapping = false
}
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 605b526a4..86a28c176 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -36,7 +36,7 @@ import (
"github.com/containers/podman/v2/utils"
"github.com/containers/storage/pkg/archive"
securejoin "github.com/cyphar/filepath-securejoin"
- User "github.com/opencontainers/runc/libcontainer/user"
+ runcuser "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"
@@ -1276,7 +1276,7 @@ func (c *Container) makeBindMounts() error {
// SHM is always added when we mount the container
c.state.BindMounts["/dev/shm"] = c.config.ShmDir
- newPasswd, err := c.generatePasswd()
+ newPasswd, newGroup, err := c.generatePasswdAndGroup()
if err != nil {
return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID())
}
@@ -1286,9 +1286,16 @@ func (c *Container) makeBindMounts() error {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/passwd")
}
- logrus.Debugf("adding entry to /etc/passwd for non existent default user")
c.state.BindMounts["/etc/passwd"] = newPasswd
}
+ if newGroup != "" {
+ // Make /etc/group
+ if _, ok := c.state.BindMounts["/etc/group"]; ok {
+ // If it already exists, delete so we can recreate
+ delete(c.state.BindMounts, "/etc/group")
+ }
+ c.state.BindMounts["/etc/group"] = newGroup
+ }
// Make /etc/hostname
// This should never change, so no need to recreate if it exists
@@ -1507,23 +1514,171 @@ func (c *Container) getHosts() string {
return hosts
}
+// generateGroupEntry generates an entry or entries into /etc/group as
+// required by container configuration.
+// Generatlly speaking, we will make an entry under two circumstances:
+// 1. The container is started as a specific user:group, and that group is both
+// numeric, and does not already exist in /etc/group.
+// 2. It is requested that Libpod add the group that launched Podman to
+// /etc/group via AddCurrentUserPasswdEntry (though this does not trigger if
+// the group in question already exists in /etc/passwd).
+// Returns group entry (as a string that can be appended to /etc/group) and any
+// error that occurred.
+func (c *Container) generateGroupEntry() (string, error) {
+ groupString := ""
+
+ // Things we *can't* handle: adding the user we added in
+ // generatePasswdEntry to any *existing* groups.
+ addedGID := 0
+ if c.config.AddCurrentUserPasswdEntry {
+ entry, gid, err := c.generateCurrentUserGroupEntry()
+ if err != nil {
+ return "", err
+ }
+ groupString += entry
+ addedGID = gid
+ }
+ if c.config.User != "" {
+ entry, _, err := c.generateUserGroupEntry(addedGID)
+ if err != nil {
+ return "", err
+ }
+ groupString += entry
+ }
+
+ return groupString, nil
+}
+
+// Make an entry in /etc/group for the group of the user running podman iff we
+// are rootless.
+func (c *Container) generateCurrentUserGroupEntry() (string, int, error) {
+ gid := rootless.GetRootlessGID()
+ if gid == 0 {
+ return "", 0, nil
+ }
+
+ g, err := user.LookupGroupId(strconv.Itoa(gid))
+ if err != nil {
+ return "", 0, errors.Wrapf(err, "failed to get current group")
+ }
+
+ // Lookup group name to see if it exists in the image.
+ _, err = lookup.GetGroup(c.state.Mountpoint, g.Name)
+ if err != runcuser.ErrNoGroupEntries {
+ return "", 0, err
+ }
+
+ // Lookup GID to see if it exists in the image.
+ _, err = lookup.GetGroup(c.state.Mountpoint, g.Gid)
+ if err != runcuser.ErrNoGroupEntries {
+ return "", 0, err
+ }
+
+ // We need to get the username of the rootless user so we can add it to
+ // the group.
+ username := ""
+ uid := rootless.GetRootlessUID()
+ if uid != 0 {
+ u, err := user.LookupId(strconv.Itoa(uid))
+ if err != nil {
+ return "", 0, errors.Wrapf(err, "failed to get current user to make group entry")
+ }
+ username = u.Username
+ }
+
+ // Make the entry.
+ return fmt.Sprintf("%s:x:%s:%s\n", g.Name, g.Gid, username), gid, nil
+}
+
+// Make an entry in /etc/group for the group the container was specified to run
+// as.
+func (c *Container) generateUserGroupEntry(addedGID int) (string, int, error) {
+ if c.config.User == "" {
+ return "", 0, nil
+ }
+
+ splitUser := strings.SplitN(c.config.User, ":", 2)
+ group := splitUser[0]
+ if len(splitUser) > 1 {
+ group = splitUser[1]
+ }
+
+ gid, err := strconv.ParseUint(group, 10, 32)
+ if err != nil {
+ return "", 0, nil
+ }
+
+ if addedGID != 0 && addedGID == int(gid) {
+ return "", 0, nil
+ }
+
+ // Check if the group already exists
+ _, err = lookup.GetGroup(c.state.Mountpoint, group)
+ if err != runcuser.ErrNoGroupEntries {
+ return "", 0, err
+ }
+
+ return fmt.Sprintf("%d:x:%d:%s\n", gid, gid, splitUser[0]), int(gid), nil
+}
+
+// generatePasswdEntry generates an entry or entries into /etc/passwd as
+// required by container configuration.
+// Generally speaking, we will make an entry under two circumstances:
+// 1. The container is started as a specific user who is not in /etc/passwd.
+// This only triggers if the user is given as a *numeric* ID.
+// 2. It is requested that Libpod add the user that launched Podman to
+// /etc/passwd via AddCurrentUserPasswdEntry (though this does not trigger if
+// the user in question already exists in /etc/passwd) or the UID to be added
+// is 0).
+// Returns password entry (as a string that can be appended to /etc/passwd) and
+// any error that occurred.
+func (c *Container) generatePasswdEntry() (string, error) {
+ passwdString := ""
+
+ addedUID := 0
+ if c.config.AddCurrentUserPasswdEntry {
+ entry, uid, _, err := c.generateCurrentUserPasswdEntry()
+ if err != nil {
+ return "", err
+ }
+ passwdString += entry
+ addedUID = uid
+ }
+ if c.config.User != "" {
+ entry, _, _, err := c.generateUserPasswdEntry(addedUID)
+ if err != nil {
+ return "", err
+ }
+ passwdString += entry
+ }
+
+ return passwdString, nil
+}
+
// generateCurrentUserPasswdEntry generates an /etc/passwd entry for the user
-// running the container engine
-func (c *Container) generateCurrentUserPasswdEntry() (string, error) {
+// running the container engine.
+// Returns a passwd entry for the user, and the UID and GID of the added entry.
+func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) {
uid := rootless.GetRootlessUID()
if uid == 0 {
- return "", nil
+ return "", 0, 0, nil
}
- u, err := user.LookupId(strconv.Itoa(rootless.GetRootlessUID()))
+ u, err := user.LookupId(strconv.Itoa(uid))
if err != nil {
- return "", errors.Wrapf(err, "failed to get current user")
+ return "", 0, 0, errors.Wrapf(err, "failed to get current user")
}
// Lookup the user to see if it exists in the container image.
_, err = lookup.GetUser(c.state.Mountpoint, u.Username)
- if err != User.ErrNoPasswdEntries {
- return "", err
+ if err != runcuser.ErrNoPasswdEntries {
+ return "", 0, 0, err
+ }
+
+ // Lookup the UID to see if it exists in the container image.
+ _, err = lookup.GetUser(c.state.Mountpoint, u.Uid)
+ if err != runcuser.ErrNoPasswdEntries {
+ return "", 0, 0, err
}
// If the user's actual home directory exists, or was mounted in - use
@@ -1533,18 +1688,22 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, error) {
homeDir = u.HomeDir
}
- return fmt.Sprintf("%s:x:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Username, homeDir), nil
+ return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Username, homeDir), uid, rootless.GetRootlessGID(), nil
}
// generateUserPasswdEntry generates an /etc/passwd entry for the container user
// to run in the container.
-func (c *Container) generateUserPasswdEntry() (string, error) {
+// The UID and GID of the added entry will also be returned.
+// Accepts one argument, that being any UID that has already been added to the
+// passwd file by other functions; if it matches the UID we were given, we don't
+// need to do anything.
+func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, error) {
var (
groupspec string
gid int
)
if c.config.User == "" {
- return "", nil
+ return "", 0, 0, nil
}
splitSpec := strings.SplitN(c.config.User, ":", 2)
userspec := splitSpec[0]
@@ -1554,13 +1713,17 @@ func (c *Container) generateUserPasswdEntry() (string, error) {
// If a non numeric User, then don't generate passwd
uid, err := strconv.ParseUint(userspec, 10, 32)
if err != nil {
- return "", nil
+ return "", 0, 0, nil
+ }
+
+ if addedUID != 0 && int(uid) == addedUID {
+ return "", 0, 0, nil
}
// Lookup the user to see if it exists in the container image
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
- if err != User.ErrNoPasswdEntries {
- return "", err
+ if err != runcuser.ErrNoPasswdEntries {
+ return "", 0, 0, err
}
if groupspec != "" {
@@ -1570,96 +1733,180 @@ func (c *Container) generateUserPasswdEntry() (string, error) {
} else {
group, err := lookup.GetGroup(c.state.Mountpoint, groupspec)
if err != nil {
- return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
+ return "", 0, 0, errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
}
gid = group.Gid
}
}
- return fmt.Sprintf("%d:x:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), nil
+ return fmt.Sprintf("%d:*:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), int(uid), gid, nil
}
-// generatePasswd generates a container specific passwd file,
-// iff g.config.User is a number
-func (c *Container) generatePasswd() (string, error) {
+// generatePasswdAndGroup generates container-specific passwd and group files
+// iff g.config.User is a number or we are configured to make a passwd entry for
+// the current user.
+// Returns path to file to mount at /etc/passwd, path to file to mount at
+// /etc/group, and any error that occurred. If no passwd/group file were
+// required, the empty string will be returned for those path (this may occur
+// even if no error happened).
+// This may modify the mounted container's /etc/passwd and /etc/group instead of
+// making copies to bind-mount in, so we don't break useradd (it wants to make a
+// copy of /etc/passwd and rename the copy to /etc/passwd, which is impossible
+// with a bind mount). This is done in cases where the container is *not*
+// read-only. In this case, the function will return nothing ("", "", nil).
+func (c *Container) generatePasswdAndGroup() (string, string, error) {
if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" {
- return "", nil
+ return "", "", nil
}
+
+ needPasswd := true
+ needGroup := true
+
+ // First, check if there's a mount at /etc/passwd or group, we don't
+ // want to interfere with user mounts.
if MountExists(c.config.Spec.Mounts, "/etc/passwd") {
- return "", nil
+ needPasswd = false
}
- // Re-use passwd if possible
- passwdPath := filepath.Join(c.config.StaticDir, "passwd")
- if _, err := os.Stat(passwdPath); err == nil {
- return passwdPath, nil
+ if MountExists(c.config.Spec.Mounts, "/etc/group") {
+ needGroup = false
}
- // Check if container has a /etc/passwd - if it doesn't do nothing.
- passwdPath, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd")
- if err != nil {
- return "", errors.Wrapf(err, "error creating path to container %s /etc/passwd", c.ID())
+
+ // Next, check if we already made the files. If we didn, don't need to
+ // do anything more.
+ if needPasswd {
+ passwdPath := filepath.Join(c.config.StaticDir, "passwd")
+ if _, err := os.Stat(passwdPath); err == nil {
+ needPasswd = false
+ }
}
- if _, err := os.Stat(passwdPath); err != nil {
- if os.IsNotExist(err) {
- return "", nil
+ if needGroup {
+ groupPath := filepath.Join(c.config.StaticDir, "group")
+ if _, err := os.Stat(groupPath); err == nil {
+ needGroup = false
}
- return "", errors.Wrapf(err, "unable to access container %s /etc/passwd", c.ID())
}
- pwd := ""
- if c.config.User != "" {
- entry, err := c.generateUserPasswdEntry()
+
+ // Next, check if the container even has a /etc/passwd or /etc/group.
+ // If it doesn't we don't want to create them ourselves.
+ if needPasswd {
+ exists, err := c.checkFileExistsInRootfs("/etc/passwd")
if err != nil {
- return "", err
+ return "", "", err
}
- pwd += entry
+ needPasswd = exists
}
- if c.config.AddCurrentUserPasswdEntry {
- entry, err := c.generateCurrentUserPasswdEntry()
+ if needGroup {
+ exists, err := c.checkFileExistsInRootfs("/etc/group")
if err != nil {
- return "", err
+ return "", "", err
}
- pwd += entry
+ needGroup = exists
}
- if pwd == "" {
- return "", nil
+
+ // If we don't need a /etc/passwd or /etc/group at this point we can
+ // just return.
+ if !needPasswd && !needGroup {
+ return "", "", nil
}
- // If we are *not* read-only - edit /etc/passwd in the container.
- // This is *gross* (shows up in changes to the container, will be
- // committed to images based on the container) but it actually allows us
- // to add users to the container (a bind mount breaks useradd).
- // We should never get here twice, because generateUserPasswdEntry will
- // not return anything if the user already exists in /etc/passwd.
- if !c.IsReadOnly() {
- containerPasswd, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd")
+ passwdPath := ""
+ groupPath := ""
+
+ ro := c.IsReadOnly()
+
+ if needPasswd {
+ passwdEntry, err := c.generatePasswdEntry()
if err != nil {
- return "", errors.Wrapf(err, "error looking up location of container %s /etc/passwd", c.ID())
+ return "", "", err
}
- f, err := os.OpenFile(containerPasswd, os.O_APPEND|os.O_WRONLY, 0600)
+ needsWrite := passwdEntry != ""
+ switch {
+ case ro && needsWrite:
+ logrus.Debugf("Making /etc/passwd for container %s", c.ID())
+ originPasswdFile, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd")
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error creating path to container %s /etc/passwd", c.ID())
+ }
+ orig, err := ioutil.ReadFile(originPasswdFile)
+ if err != nil && !os.IsNotExist(err) {
+ return "", "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
+ }
+ passwdFile, err := c.writeStringToStaticDir("passwd", string(orig)+passwdEntry)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "failed to create temporary passwd file")
+ }
+ if err := os.Chmod(passwdFile, 0644); err != nil {
+ return "", "", err
+ }
+ passwdPath = passwdFile
+ case !ro && needsWrite:
+ logrus.Debugf("Modifying container %s /etc/passwd", c.ID())
+ containerPasswd, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd")
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error looking up location of container %s /etc/passwd", c.ID())
+ }
+
+ f, err := os.OpenFile(containerPasswd, os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error opening container %s /etc/passwd", c.ID())
+ }
+ defer f.Close()
+
+ if _, err := f.WriteString(passwdEntry); err != nil {
+ return "", "", errors.Wrapf(err, "unable to append to container %s /etc/passwd", c.ID())
+ }
+ default:
+ logrus.Debugf("Not modifying container %s /etc/passwd", c.ID())
+ }
+ }
+ if needGroup {
+ groupEntry, err := c.generateGroupEntry()
if err != nil {
- return "", errors.Wrapf(err, "error opening container %s /etc/passwd", c.ID())
+ return "", "", err
}
- defer f.Close()
- if _, err := f.WriteString(pwd); err != nil {
- return "", errors.Wrapf(err, "unable to append to container %s /etc/passwd", c.ID())
- }
+ needsWrite := groupEntry != ""
+ switch {
+ case ro && needsWrite:
+ logrus.Debugf("Making /etc/group for container %s", c.ID())
+ originGroupFile, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/group")
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error creating path to container %s /etc/group", c.ID())
+ }
+ orig, err := ioutil.ReadFile(originGroupFile)
+ if err != nil && !os.IsNotExist(err) {
+ return "", "", errors.Wrapf(err, "unable to read group file %s", originGroupFile)
+ }
+ groupFile, err := c.writeStringToStaticDir("group", string(orig)+groupEntry)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "failed to create temporary group file")
+ }
+ if err := os.Chmod(groupFile, 0644); err != nil {
+ return "", "", err
+ }
+ groupPath = groupFile
+ case !ro && needsWrite:
+ logrus.Debugf("Modifying container %s /etc/group", c.ID())
+ containerGroup, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/group")
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error looking up location of container %s /etc/group", c.ID())
+ }
- return "", nil
- }
+ f, err := os.OpenFile(containerGroup, os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error opening container %s /etc/group", c.ID())
+ }
+ defer f.Close()
- 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)
- }
- passwdFile, err := c.writeStringToStaticDir("passwd", string(orig)+pwd)
- if err != nil {
- return "", errors.Wrapf(err, "failed to create temporary passwd file")
- }
- if err := os.Chmod(passwdFile, 0644); err != nil {
- return "", err
+ if _, err := f.WriteString(groupEntry); err != nil {
+ return "", "", errors.Wrapf(err, "unable to append to container %s /etc/group", c.ID())
+ }
+ default:
+ logrus.Debugf("Not modifying container %s /etc/group", c.ID())
+ }
}
- return passwdFile, nil
+
+ return passwdPath, groupPath, nil
}
func (c *Container) copyOwnerAndPerms(source, dest string) error {
@@ -1751,3 +1998,23 @@ func (c *Container) copyTimezoneFile(zonePath string) (string, error) {
func (c *Container) cleanupOverlayMounts() error {
return overlay.CleanupContent(c.config.StaticDir)
}
+
+// Check if a file exists at the given path in the container's root filesystem.
+// Container must already be mounted for this to be used.
+func (c *Container) checkFileExistsInRootfs(file string) (bool, error) {
+ checkPath, err := securejoin.SecureJoin(c.state.Mountpoint, file)
+ if err != nil {
+ return false, errors.Wrapf(err, "cannot create path to container %s file %q", c.ID(), file)
+ }
+ stat, err := os.Stat(checkPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, errors.Wrapf(err, "error accessing container %s file %q", c.ID(), file)
+ }
+ if stat.IsDir() {
+ return false, nil
+ }
+ return true, nil
+}
diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go
index 41c22fb45..1465ffbea 100644
--- a/libpod/container_internal_linux_test.go
+++ b/libpod/container_internal_linux_test.go
@@ -29,16 +29,42 @@ func TestGenerateUserPasswdEntry(t *testing.T) {
Mountpoint: "/does/not/exist/tmp/",
},
}
- user, err := c.generateUserPasswdEntry()
+ user, _, _, err := c.generateUserPasswdEntry(0)
if err != nil {
t.Fatal(err)
}
- assert.Equal(t, user, "123:x:123:456:container user:/:/bin/sh\n")
+ assert.Equal(t, user, "123:*:123:456:container user:/:/bin/sh\n")
c.config.User = "567"
- user, err = c.generateUserPasswdEntry()
+ user, _, _, err = c.generateUserPasswdEntry(0)
if err != nil {
t.Fatal(err)
}
- assert.Equal(t, user, "567:x:567:0:container user:/:/bin/sh\n")
+ assert.Equal(t, user, "567:*:567:0:container user:/:/bin/sh\n")
+}
+
+func TestGenerateUserGroupEntry(t *testing.T) {
+ c := Container{
+ config: &ContainerConfig{
+ Spec: &spec.Spec{},
+ ContainerSecurityConfig: ContainerSecurityConfig{
+ User: "123:456",
+ },
+ },
+ state: &ContainerState{
+ Mountpoint: "/does/not/exist/tmp/",
+ },
+ }
+ group, _, err := c.generateUserGroupEntry(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, group, "456:x:456:123\n")
+
+ c.config.User = "567"
+ group, _, err = c.generateUserGroupEntry(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, group, "567:x:567:567\n")
}
diff --git a/libpod/options.go b/libpod/options.go
index dccbb8741..7eec530ea 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -18,6 +18,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -897,6 +898,17 @@ func WithUserNSFrom(nsCtr *Container) CtrCreateOption {
ctr.config.UserNsCtr = nsCtr.ID()
ctr.config.IDMappings = nsCtr.config.IDMappings
+ g := generate.NewFromSpec(ctr.config.Spec)
+
+ g.ClearLinuxUIDMappings()
+ for _, uidmap := range nsCtr.config.IDMappings.UIDMap {
+ g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
+ }
+ g.ClearLinuxGIDMappings()
+ for _, gidmap := range nsCtr.config.IDMappings.GIDMap {
+ g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
+ }
+ ctr.config.IDMappings = nsCtr.config.IDMappings
return nil
}
}
diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go
index 3983ba3a8..30a5a55b8 100644
--- a/pkg/domain/infra/abi/containers_runlabel.go
+++ b/pkg/domain/infra/abi/containers_runlabel.go
@@ -7,12 +7,10 @@ import (
"path/filepath"
"strings"
- "github.com/containers/image/v5/types"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/image"
"github.com/containers/podman/v2/pkg/domain/entities"
envLib "github.com/containers/podman/v2/pkg/env"
- "github.com/containers/podman/v2/pkg/util"
"github.com/containers/podman/v2/utils"
"github.com/google/shlex"
"github.com/pkg/errors"
@@ -89,29 +87,17 @@ func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imag
// Fallthrough and pull!
}
- // Parse credentials if specified.
- var credentials *types.DockerAuthConfig
- if options.Credentials != "" {
- credentials, err = util.ParseRegistryCreds(options.Credentials)
- if err != nil {
- return nil, err
- }
- }
-
- // Suppress pull progress bars if requested.
- pullOutput := os.Stdout
- if options.Quiet {
- pullOutput = nil // c/image/copy takes care of the rest
+ pullOptions := entities.ImagePullOptions{
+ Quiet: options.Quiet,
+ CertDir: options.CertDir,
+ SkipTLSVerify: options.SkipTLSVerify,
+ SignaturePolicy: options.SignaturePolicy,
+ Authfile: options.Authfile,
}
-
- // Pull the image.
- dockerRegistryOptions := image.DockerRegistryOptions{
- DockerCertPath: options.CertDir,
- DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
- DockerRegistryCreds: credentials,
+ if _, err := pull(ctx, ic.Libpod.ImageRuntime(), imageRef, pullOptions, &label); err != nil {
+ return nil, err
}
-
- return ic.Libpod.ImageRuntime().New(ctx, imageRef, options.SignaturePolicy, options.Authfile, pullOutput, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing)
+ return ic.Libpod.ImageRuntime().NewFromLocal(imageRef)
}
// generateRunlabelCommand generates the to-be-executed command as a string
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 33060b34b..23aef9573 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -214,7 +214,7 @@ func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer
return l
}
-func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) {
+func pull(ctx context.Context, runtime *image.Runtime, rawImage string, options entities.ImagePullOptions, label *string) (*entities.ImagePullReport, error) {
var writer io.Writer
if !options.Quiet {
writer = os.Stderr
@@ -246,7 +246,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
}
if !options.AllTags {
- newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
+ newImage, err := runtime.New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, label, util.PullImageAlways)
if err != nil {
return nil, err
}
@@ -280,7 +280,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
foundIDs := []string{}
for _, tag := range tags {
name := rawImage + ":" + tag
- newImage, err := ir.Libpod.ImageRuntime().New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
+ newImage, err := runtime.New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
if err != nil {
logrus.Errorf("error pulling image %q", name)
continue
@@ -294,6 +294,10 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
return &entities.ImagePullReport{Images: foundIDs}, nil
}
+func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) {
+ return pull(ctx, ir.Libpod.ImageRuntime(), rawImage, options, nil)
+}
+
func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) {
reports := []*entities.ImageInspectReport{}
errs := []error{}
diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go
index c48876dee..dfb8c72a1 100644
--- a/test/e2e/run_passwd_test.go
+++ b/test/e2e/run_passwd_test.go
@@ -71,4 +71,58 @@ USER 1000`
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Not(ContainSubstring("passwd")))
})
+
+ It("podman run with no user specified does not change --group specified", func() {
+ session := podmanTest.Podman([]string{"run", "--read-only", BB, "mount"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("/etc/group")).To(BeFalse())
+ })
+
+ It("podman run group specified in container", func() {
+ session := podmanTest.Podman([]string{"run", "--read-only", "-u", "root:bin", BB, "mount"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("/etc/group")).To(BeFalse())
+ })
+
+ It("podman run non-numeric group not specified in container", func() {
+ session := podmanTest.Podman([]string{"run", "--read-only", "-u", "root:doesnotexist", BB, "mount"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman run numeric group specified in container", func() {
+ session := podmanTest.Podman([]string{"run", "--read-only", "-u", "root:11", BB, "mount"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("/etc/group")).To(BeFalse())
+ })
+
+ It("podman run numeric group not specified in container", func() {
+ session := podmanTest.Podman([]string{"run", "--read-only", "-u", "20001:20001", BB, "mount"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("/etc/group")).To(BeTrue())
+ })
+
+ It("podman run numeric user not specified in container modifies group", func() {
+ session := podmanTest.Podman([]string{"run", "--read-only", "-u", "20001", BB, "mount"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("/etc/group")).To(BeTrue())
+ })
+
+ It("podman run numeric group from image and no group file", func() {
+ SkipIfRemote()
+ dockerfile := `FROM alpine
+RUN rm -f /etc/passwd /etc/shadow /etc/group
+USER 1000`
+ imgName := "testimg"
+ podmanTest.BuildImage(dockerfile, imgName, "false")
+ session := podmanTest.Podman([]string{"run", "--rm", imgName, "ls", "/etc/"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(Not(ContainSubstring("/etc/group")))
+ })
})
diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go
index 25f8d0d15..8d860cfc3 100644
--- a/test/e2e/run_userns_test.go
+++ b/test/e2e/run_userns_test.go
@@ -277,6 +277,13 @@ var _ = Describe("Podman UserNS support", func() {
ok, _ := session.GrepString("4998")
Expect(ok).To(BeTrue())
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--userns=container:" + ctrName, "--net=container:" + ctrName, "alpine", "cat", "/proc/self/uid_map"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ ok, _ = session.GrepString("4998")
+ Expect(ok).To(BeTrue())
})
It("podman --user with volume", func() {
diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go
index f17b4d560..0eb679fbf 100644
--- a/test/e2e/runlabel_test.go
+++ b/test/e2e/runlabel_test.go
@@ -29,6 +29,8 @@ var _ = Describe("podman container runlabel", func() {
)
BeforeEach(func() {
+ // runlabel is not supported for remote connections
+ SkipIfRemote()
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
@@ -46,7 +48,6 @@ var _ = Describe("podman container runlabel", func() {
})
It("podman container runlabel (podman --version)", func() {
- SkipIfRemote()
image := "podman-runlabel-test:podman"
podmanTest.BuildImage(PodmanDockerfile, image, "false")
@@ -60,7 +61,6 @@ var _ = Describe("podman container runlabel", func() {
})
It("podman container runlabel (ls -la)", func() {
- SkipIfRemote()
image := "podman-runlabel-test:ls"
podmanTest.BuildImage(LsDockerfile, image, "false")
@@ -72,9 +72,7 @@ var _ = Describe("podman container runlabel", func() {
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
})
-
It("podman container runlabel --display", func() {
- SkipIfRemote()
image := "podman-runlabel-test:ls"
podmanTest.BuildImage(LsDockerfile, image, "false")
@@ -115,7 +113,6 @@ var _ = Describe("podman container runlabel", func() {
})
It("runlabel should fail with nonexist authfile", func() {
- SkipIfRemote()
image := "podman-runlabel-test:podman"
podmanTest.BuildImage(PodmanDockerfile, image, "false")
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index 0b92554b8..4e518c571 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -189,9 +189,19 @@ echo $rand | 0 | $rand
is "$(< $cidfile)" "$cid" "contents of cidfile == container ID"
- conmon_pid=$(< $pidfile)
- is "$(readlink /proc/$conmon_pid/exe)" ".*/conmon" \
- "conmon pidfile (= PID $conmon_pid) points to conmon process"
+ # Cross-check --conmon-pidfile against 'podman inspect'
+ local conmon_pid_from_file=$(< $pidfile)
+ run_podman inspect --format '{{.State.ConmonPid}}' $cid
+ local conmon_pid_from_inspect="$output"
+ is "$conmon_pid_from_file" "$conmon_pid_from_inspect" \
+ "Conmon pid in pidfile matches what 'podman inspect' claims"
+
+ # /proc/PID/exe should be a symlink to a conmon executable
+ # FIXME: 'echo' and 'ls' are to help debug #7580, a CI flake
+ echo "conmon pid = $conmon_pid_from_file"
+ ls -l /proc/$conmon_pid_from_file
+ is "$(readlink /proc/$conmon_pid_from_file/exe)" ".*/conmon" \
+ "conmon pidfile (= PID $conmon_pid_from_file) points to conmon process"
# All OK. Kill container.
run_podman rm -f $cid
@@ -204,7 +214,7 @@ echo $rand | 0 | $rand
}
@test "podman run docker-archive" {
- skip_if_remote "FIXME: pending #7116"
+ skip_if_remote "podman-remote does not support docker-archive (#7116)"
# Create an image that, when run, outputs a random magic string
expect=$(random_string 20)
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index 86b396c4a..d7aa16d95 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -27,25 +27,43 @@ verify_iid_and_name() {
}
@test "podman save to pipe and load" {
- get_iid_and_name
+ # Generate a random name and tag (must be lower-case)
+ local random_name=x$(random_string 12 | tr A-Z a-z)
+ local random_tag=t$(random_string 7 | tr A-Z a-z)
+ local fqin=localhost/$random_name:$random_tag
+ run_podman tag $IMAGE $fqin
+
+ archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar
# We can't use run_podman because that uses the BATS 'run' function
# which redirects stdout and stderr. Here we need to guarantee
# that podman's stdout is a pipe, not any other form of redirection
- $PODMAN save --format oci-archive $IMAGE | cat >$archive
+ $PODMAN save --format oci-archive $fqin | cat >$archive
if [ "$status" -ne 0 ]; then
die "Command failed: podman save ... | cat"
fi
# Make sure we can reload it
- # FIXME: when/if 7337 gets fixed, add a random tag instead of rmi'ing
- # FIXME: when/if 7371 gets fixed, use verify_iid_and_name()
- run_podman rmi $iid
+ run_podman rmi $fqin
run_podman load -i $archive
- # FIXME: cannot compare IID, see #7371
- run_podman images -a --format '{{.Repository}}:{{.Tag}}'
- is "$output" "$IMAGE" "image preserves name across save/load"
+ # FIXME: cannot compare IID, see #7371, so we check only the tag
+ run_podman images $fqin --format '{{.Repository}}:{{.Tag}}'
+ is "$output" "$fqin" "image preserves name across save/load"
+
+ # FIXME: when/if 7337 gets fixed, load with a new tag
+ if false; then
+ local new_name=x$(random_string 14 | tr A-Z a-z)
+ local new_tag=t$(random_string 6 | tr A-Z a-z)
+ run_podman rmi $fqin
+ fqin=localhost/$new_name:$new_tag
+ run_podman load -i $archive $fqin
+ run_podman images $fqin --format '{{.Repository}}:{{.Tag}}'
+ is "$output" "$fqin" "image can be loaded with new name:tag"
+ fi
+
+ # Clean up
+ run_podman rmi $fqin
}