summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2021-12-16 09:24:24 -0500
committerDaniel J Walsh <dwalsh@redhat.com>2021-12-23 07:51:27 -0500
commite8c06fac97f56ccc710584731d8b52ed58fbd2dd (patch)
treea9ec6546c64a0ae393cda0290256977a63da8eed
parenta7f1c05366c86a05e247049f8837b4aadc54fc50 (diff)
downloadpodman-e8c06fac97f56ccc710584731d8b52ed58fbd2dd.tar.gz
podman-e8c06fac97f56ccc710584731d8b52ed58fbd2dd.tar.bz2
podman-e8c06fac97f56ccc710584731d8b52ed58fbd2dd.zip
Allow users to add host user accounts to /etc/passwd
Some containers require certain user account(s) to exist within the container when they are run. This option will allow callers to add a bunch of passwd entries from the host to the container even if the entries are not in the local /etc/passwd file on the host. Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1935831 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
-rw-r--r--cmd/podman/common/create.go8
-rw-r--r--docs/source/markdown/podman-create.1.md5
-rw-r--r--docs/source/markdown/podman-run.1.md5
-rw-r--r--libpod/container_config.go2
-rw-r--r--libpod/container_internal_linux.go63
-rw-r--r--libpod/options.go11
-rw-r--r--pkg/domain/entities/pods.go1
-rw-r--r--pkg/specgen/generate/container_create.go4
-rw-r--r--pkg/specgen/specgen.go3
-rw-r--r--pkg/specgenutil/specgen.go1
-rw-r--r--pkg/util/utils.go8
-rw-r--r--test/system/030-run.bats12
-rw-r--r--test/system/helpers.bash10
13 files changed, 126 insertions, 7 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 1b01cd001..f02c5713b 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -292,6 +292,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
"Set proxy environment variables in the container based on the host proxy vars",
)
+ hostUserFlagName := "hostuser"
+ createFlags.StringSliceVar(
+ &cf.HostUsers,
+ hostUserFlagName, []string{},
+ "Host user account to add to /etc/passwd within container",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(hostUserFlagName, completion.AutocompleteNone)
+
imageVolumeFlagName := "image-volume"
createFlags.StringVar(
&cf.ImageVolume,
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index eeed49185..99e31bb84 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -410,6 +410,11 @@ Container host name
Sets the container host name that is available inside the container. Can only be used with a private UTS namespace `--uts=private` (default). If `--pod` is specified and the pod shares the UTS namespace (default) the pod's hostname will be used.
+#### **--hostuser**=*name*
+
+Add a user account to /etc/passwd from the host to the container. The Username
+or UID must exist on the host system.
+
#### **--help**
Print usage statement
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 48f7295fd..81c0e3fd4 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -446,6 +446,11 @@ The initialization time needed for a container to bootstrap. The value can be ex
The maximum time allowed to complete the healthcheck before an interval is considered failed. Like start-period, the
value can be expressed in a time format such as **1m22s**. The default value is **30s**.
+#### **--hostuser**=*name*
+
+Add a user account to /etc/passwd from the host to the container. The Username
+or UID must exist on the host system.
+
#### **--help**
Print usage statement
diff --git a/libpod/container_config.go b/libpod/container_config.go
index db65063b5..a43fd632b 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -198,6 +198,8 @@ type ContainerSecurityConfig struct {
// Groups are additional groups to add the container's user to. These
// are resolved within the container using the container's /etc/passwd.
Groups []string `json:"groups,omitempty"`
+ // HostUsers are a list of host user accounts to add to /etc/passwd
+ HostUsers []string `json:"HostUsers,omitempty"`
// AddCurrentUserPasswdEntry indicates that Libpod should ensure that
// the container's /etc/passwd contains an entry for the user running
// Libpod - mostly used in rootless containers where the user running
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 62e198d7c..7745646b6 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -305,13 +305,40 @@ func (c *Container) getUserOverrides() *lookup.Overrides {
return &overrides
}
+func lookupHostUser(name string) (*runcuser.ExecUser, error) {
+ var execUser runcuser.ExecUser
+ // Lookup User on host
+ u, err := util.LookupUser(name)
+ if err != nil {
+ return &execUser, err
+ }
+ uid, err := strconv.ParseUint(u.Uid, 8, 32)
+ if err != nil {
+ return &execUser, err
+ }
+
+ gid, err := strconv.ParseUint(u.Gid, 8, 32)
+ if err != nil {
+ return &execUser, err
+ }
+ execUser.Uid = int(uid)
+ execUser.Gid = int(gid)
+ execUser.Home = u.HomeDir
+ return &execUser, nil
+}
+
// Generate spec for a container
// Accepts a map of the container's dependencies
func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
overrides := c.getUserOverrides()
execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides)
if err != nil {
- return nil, err
+ if util.StringInSlice(c.config.User, c.config.HostUsers) {
+ execUser, err = lookupHostUser(c.config.User)
+ }
+ if err != nil {
+ return nil, err
+ }
}
g := generate.NewFromSpec(c.config.Spec)
@@ -2348,12 +2375,25 @@ func (c *Container) generateUserGroupEntry(addedGID int) (string, int, error) {
// /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).
+// 3. The user specified additional host user accounts to add the the /etc/passwd file
// 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
+ for _, userid := range c.config.HostUsers {
+ // Lookup User on host
+ u, err := util.LookupUser(userid)
+ if err != nil {
+ return "", err
+ }
+ entry, err := c.userPasswdEntry(u)
+ if err != nil {
+ return "", err
+ }
+ passwdString += entry
+ }
if c.config.AddCurrentUserPasswdEntry {
entry, uid, _, err := c.generateCurrentUserPasswdEntry()
if err != nil {
@@ -2386,17 +2426,25 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) {
if err != nil {
return "", 0, 0, errors.Wrapf(err, "failed to get current user")
}
+ pwd, err := c.userPasswdEntry(u)
+ if err != nil {
+ return "", 0, 0, err
+ }
+
+ return pwd, uid, rootless.GetRootlessGID(), nil
+}
+func (c *Container) userPasswdEntry(u *user.User) (string, error) {
// Lookup the user to see if it exists in the container image.
- _, err = lookup.GetUser(c.state.Mountpoint, u.Username)
+ _, err := lookup.GetUser(c.state.Mountpoint, u.Username)
if err != runcuser.ErrNoPasswdEntries {
- return "", 0, 0, err
+ return "", 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
+ return "", err
}
// If the user's actual home directory exists, or was mounted in - use
@@ -2430,7 +2478,7 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) {
c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", homeDir))
}
- return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), uid, rootless.GetRootlessGID(), nil
+ return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), nil
}
// generateUserPasswdEntry generates an /etc/passwd entry for the container user
@@ -2485,7 +2533,7 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err
// 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.
+// the current user or the user specified HostsUsers
// 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
@@ -2496,7 +2544,8 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err
// 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 == "" {
+ if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" &&
+ len(c.config.HostUsers) == 0 {
return "", "", nil
}
diff --git a/libpod/options.go b/libpod/options.go
index 85d7b4689..204f2a457 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1768,6 +1768,17 @@ func WithPidFile(pidFile string) CtrCreateOption {
}
}
+// WithHostUsers indicates host users to add to /etc/passwd
+func WithHostUsers(hostUsers []string) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+ ctr.config.HostUsers = hostUsers
+ return nil
+ }
+}
+
// WithInitCtrType indicates the container is a initcontainer
func WithInitCtrType(containerType string) CtrCreateOption {
return func(ctr *Container) error {
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index 5c5fa0cb3..f9850e5a8 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -189,6 +189,7 @@ type ContainerCreateOptions struct {
HealthTimeout string
Hostname string `json:"hostname,omitempty"`
HTTPProxy bool
+ HostUsers []string
ImageVolume string
Init bool
InitContainerType string
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 1debf6c0e..7ab9d1b29 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -156,6 +156,10 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, nil, nil, err
}
+ if len(s.HostUsers) > 0 {
+ options = append(options, libpod.WithHostUsers(s.HostUsers))
+ }
+
command, err := makeCommand(ctx, s, imageData, rtc)
if err != nil {
return nil, nil, nil, err
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 5a1cc1144..5989456c9 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -152,6 +152,9 @@ type ContainerBasicConfig struct {
// Conflicts with UtsNS if UtsNS is not set to private.
// Optional.
Hostname string `json:"hostname,omitempty"`
+ // HostUses is a list of host usernames or UIDs to add to the container
+ // /etc/passwd file
+ HostUsers []string `json:"hostusers,omitempty"`
// Sysctl sets kernel parameters for the container
Sysctl map[string]string `json:"sysctl,omitempty"`
// Remove indicates if the container should be removed once it has been started
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
index be8f277cc..8e43cc50e 100644
--- a/pkg/specgenutil/specgen.go
+++ b/pkg/specgenutil/specgen.go
@@ -437,6 +437,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
s.NetworkOptions = c.Net.NetworkOptions
s.UseImageHosts = c.Net.NoHosts
}
+ s.HostUsers = c.HostUsers
s.ImageVolumeMode = c.ImageVolume
if s.ImageVolumeMode == "bind" {
s.ImageVolumeMode = "anonymous"
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 208d815d9..390057c32 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -723,3 +723,11 @@ func SocketPath() (string, error) {
// Glue the socket path together
return filepath.Join(xdg, "podman", "podman.sock"), nil
}
+
+func LookupUser(name string) (*user.User, error) {
+ // Assume UID look up first, if it fails lookup by username
+ if u, err := user.LookupId(name); err == nil {
+ return u, err
+ }
+ return user.Lookup(name)
+}
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index c0b61b613..130cf5492 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -711,6 +711,18 @@ EOF
run_podman rmi nomtab
}
+@test "podman run --hostuser tests" {
+ skip_if_not_rootless "test whether hostuser is successfully added"
+ user=$(id -un)
+ run_podman 1 run --rm $IMAGE grep $user /etc/passwd
+ run_podman run --hostuser=$user --rm $IMAGE grep $user /etc/passwd
+ user=$(id -u)
+ run_podman run --hostuser=$user --rm $IMAGE grep $user /etc/passwd
+ run_podman run --hostuser=$user --user $user --rm $IMAGE grep $user /etc/passwd
+ user=bogus
+ run_podman 126 run --hostuser=$user --rm $IMAGE grep $user /etc/passwd
+}
+
@test "podman run --device-cgroup-rule tests" {
skip_if_rootless "cannot add devices in rootless mode"
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 97b6db05c..415c9010e 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -398,6 +398,16 @@ function skip_if_rootless() {
fi
}
+######################
+# skip_if_not_rootless # ...with an optional message
+######################
+function skip_if_not_rootless() {
+ if ! is_rootless; then
+ local msg=$(_add_label_if_missing "$1" "rootfull")
+ skip "${msg:-not applicable under rootlfull podman}"
+ fi
+}
+
####################
# skip_if_remote # ...with an optional message
####################