summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/source/markdown/podman-login.1.md12
-rw-r--r--docs/source/markdown/podman-logout.1.md4
-rw-r--r--libpod/container_internal_linux.go417
-rw-r--r--libpod/container_internal_linux_test.go34
-rw-r--r--libpod/kube.go2
-rw-r--r--pkg/domain/infra/abi/play.go6
-rw-r--r--test/e2e/play_kube_test.go44
-rw-r--r--test/e2e/run_networking_test.go10
-rw-r--r--test/e2e/run_passwd_test.go54
-rw-r--r--test/system/030-run.bats18
-rw-r--r--test/system/120-load.bats34
11 files changed, 527 insertions, 108 deletions
diff --git a/docs/source/markdown/podman-login.1.md b/docs/source/markdown/podman-login.1.md
index 79c7ff640..efc7f05e2 100644
--- a/docs/source/markdown/podman-login.1.md
+++ b/docs/source/markdown/podman-login.1.md
@@ -12,9 +12,13 @@ and password. If the registry is not specified, the first registry under [regist
from registries.conf will be used. **podman login** reads in the username and password from STDIN.
The username and password can also be set using the **username** and **password** flags.
The path of the authentication file can be specified by the user by setting the **authfile**
-flag. The default path used is **${XDG\_RUNTIME\_DIR}/containers/auth.json**. If there is a valid
-username and password in the **authfile** , Podman will use those existing credentials if the user does not pass in a username.
-If those credentials are not present, Podman will then use any existing credentials found in **$HOME/.docker/config.json**.
+flag. The default path for reading and writing credentials is **${XDG\_RUNTIME\_DIR}/containers/auth.json**.
+Podman will use existing credentials if the user does not pass in a username.
+Podman will first search for the username and password in the **${XDG\_RUNTIME\_DIR}/containers/auth.json**, if they are not valid,
+Podman will then use any existing credentials found in **$HOME/.docker/config.json**.
+If those credentials are not present, Podman will create **${XDG\_RUNTIME\_DIR}/containers/auth.json** (if the file does not exist) and
+will then store the username and password from STDIN as a base64 encoded string in it.
+For more details about format and configurations of the auth,json file, please refer to containers-auth.json(5)
**podman [GLOBAL OPTIONS]**
@@ -104,7 +108,7 @@ Login Succeeded!
```
## SEE ALSO
-podman(1), podman-logout(1)
+podman(1), podman-logout(1), containers-auth.json(5)
## HISTORY
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
diff --git a/docs/source/markdown/podman-logout.1.md b/docs/source/markdown/podman-logout.1.md
index 8b9f75760..0ff954d43 100644
--- a/docs/source/markdown/podman-logout.1.md
+++ b/docs/source/markdown/podman-logout.1.md
@@ -10,7 +10,7 @@ podman\-logout - Logout of a container registry
**podman logout** logs out of a specified registry server by deleting the cached credentials
stored in the **auth.json** file. If the registry is not specified, the first registry under [registries.search]
from registries.conf will be used. The path of the authentication file can be overridden by the user by setting the **authfile** flag.
-The default path used is **${XDG\_RUNTIME\_DIR}/containers/auth.json**.
+The default path used is **${XDG\_RUNTIME\_DIR}/containers/auth.json**. For more details about format and configurations of the auth,json file, please refer to containers-auth.json(5)
All the cached credentials can be removed by setting the **all** flag.
**podman [GLOBAL OPTIONS]**
@@ -54,7 +54,7 @@ Remove login credentials for all registries
```
## SEE ALSO
-podman(1), podman-login(1)
+podman(1), podman-login(1), containers-auth.json(5)
## HISTORY
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
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/kube.go b/libpod/kube.go
index c4367db2f..9d5cbe68b 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -199,7 +199,7 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
labels["app"] = removeUnderscores(podName)
om := v12.ObjectMeta{
// The name of the pod is container_name-libpod
- Name: removeUnderscores(podName),
+ Name: podName,
Labels: labels,
// CreationTimestamp seems to be required, so adding it; in doing so, the timestamp
// will reflect time this is run (not container create time) because the conversion
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 8b583fd3d..6dfb52c63 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -560,6 +560,7 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
containerConfig.Env = envs
for _, volume := range containerYAML.VolumeMounts {
+ var readonly string
hostPath, exists := volumes[volume.Name]
if !exists {
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
@@ -567,7 +568,10 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
return nil, errors.Wrapf(err, "error in parsing MountPath")
}
- containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath))
+ if volume.ReadOnly {
+ readonly = ":ro"
+ }
+ containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s%s", hostPath, volume.MountPath, readonly))
}
return &containerConfig, nil
}
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 121cea017..5e01971cb 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -99,6 +99,12 @@ spec:
hostPort: {{ .Port }}
protocol: TCP
workingDir: /
+ volumeMounts:
+ {{ if .VolumeMount }}
+ - name: {{.VolumeName}}
+ mountPath: {{ .VolumeMountPath }}
+ readonly: {{.VolumeReadOnly}}
+ {{ end }}
{{ end }}
{{ end }}
{{ end }}
@@ -383,12 +389,16 @@ type Ctr struct {
PullPolicy string
HostIP string
Port string
+ VolumeMount bool
+ VolumeMountPath string
+ VolumeName string
+ VolumeReadOnly bool
}
// getCtr takes a list of ctrOptions and returns a Ctr with sane defaults
// and the configured options
func getCtr(options ...ctrOption) *Ctr {
- c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, defaultCtrArg, true, false, nil, nil, "", "", ""}
+ c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, defaultCtrArg, true, false, nil, nil, "", "", "", false, "", "", false}
for _, option := range options {
option(&c)
}
@@ -448,6 +458,15 @@ func withHostIP(ip string, port string) ctrOption {
}
}
+func withVolumeMount(mountPath string, readonly bool) ctrOption {
+ return func(c *Ctr) {
+ c.VolumeMountPath = mountPath
+ c.VolumeName = defaultVolName
+ c.VolumeReadOnly = readonly
+ c.VolumeMount = true
+ }
+}
+
func getCtrNameInPod(pod *Pod) string {
return fmt.Sprintf("%s-%s", pod.Name, defaultCtrName)
}
@@ -1035,4 +1054,27 @@ spec:
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).NotTo(Equal(0))
})
+
+ It("podman play kube test with read only volume", func() {
+ hostPathLocation := filepath.Join(tempdir, "file")
+ f, err := os.Create(hostPathLocation)
+ Expect(err).To(BeNil())
+ f.Close()
+
+ ctr := getCtr(withVolumeMount(hostPathLocation, true), withImage(BB))
+ pod := getPod(withVolume(getVolume("File", hostPathLocation)), withCtr(ctr))
+ err = generatePodKubeYaml(pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{.HostConfig.Binds}}'"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(Equal(0))
+
+ correct := fmt.Sprintf("%s:%s:%s", hostPathLocation, hostPathLocation, "ro")
+ Expect(inspect.OutputToString()).To(ContainSubstring(correct))
+ })
})
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index a48f7c83e..c20bfe631 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -535,15 +535,12 @@ var _ = Describe("Podman run networking", func() {
create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.30.0/24", netName})
create.WaitWithDefaultTimeout()
Expect(create.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
run := podmanTest.Podman([]string{"run", "-t", "-i", "--rm", "--net", netName, "--ip", ipAddr, ALPINE, "ip", "addr"})
run.WaitWithDefaultTimeout()
Expect(run.ExitCode()).To(BeZero())
Expect(run.OutputToString()).To(ContainSubstring(ipAddr))
-
- netrm := podmanTest.Podman([]string{"network", "rm", netName})
- netrm.WaitWithDefaultTimeout()
- Expect(netrm.ExitCode()).To(BeZero())
})
It("podman run with new:pod and static-ip", func() {
@@ -555,6 +552,7 @@ var _ = Describe("Podman run networking", func() {
create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.40.0/24", netName})
create.WaitWithDefaultTimeout()
Expect(create.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
run := podmanTest.Podman([]string{"run", "-t", "-i", "--rm", "--pod", "new:" + podname, "--net", netName, "--ip", ipAddr, ALPINE, "ip", "addr"})
run.WaitWithDefaultTimeout()
@@ -564,9 +562,5 @@ var _ = Describe("Podman run networking", func() {
podrm := podmanTest.Podman([]string{"pod", "rm", "-f", podname})
podrm.WaitWithDefaultTimeout()
Expect(podrm.ExitCode()).To(BeZero())
-
- netrm := podmanTest.Podman([]string{"network", "rm", netName})
- netrm.WaitWithDefaultTimeout()
- Expect(netrm.ExitCode()).To(BeZero())
})
})
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/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
}