Skip to content

Commit

Permalink
Allow users to add host user accounts to /etc/passwd
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
rhatdan committed Dec 23, 2021
1 parent a7f1c05 commit e8c06fa
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 7 deletions.
8 changes: 8 additions & 0 deletions cmd/podman/common/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions docs/source/markdown/podman-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions docs/source/markdown/podman-run.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions libpod/container_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 56 additions & 7 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down
11 changes: 11 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ type ContainerCreateOptions struct {
HealthTimeout string
Hostname string `json:"hostname,omitempty"`
HTTPProxy bool
HostUsers []string
ImageVolume string
Init bool
InitContainerType string
Expand Down
4 changes: 4 additions & 0 deletions pkg/specgen/generate/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions pkg/specgen/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/specgenutil/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions pkg/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
12 changes: 12 additions & 0 deletions test/system/030-run.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
10 changes: 10 additions & 0 deletions test/system/helpers.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
####################
Expand Down

0 comments on commit e8c06fa

Please sign in to comment.