Merge pull request #12627 from rhatdan/passwd

Allow users to add host user accounts to /etc/passwd
This commit is contained in:
OpenShift Merge Robot
2021-12-23 19:28:08 +01:00
committed by GitHub
13 changed files with 128 additions and 9 deletions

View File

@ -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", "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" imageVolumeFlagName := "image-volume"
createFlags.StringVar( createFlags.StringVar(
&cf.ImageVolume, &cf.ImageVolume,

View File

@ -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. 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** #### **--help**
Print usage statement Print usage statement

View File

@ -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 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**. 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** #### **--help**
Print usage statement Print usage statement

View File

@ -198,6 +198,8 @@ type ContainerSecurityConfig struct {
// Groups are additional groups to add the container's user to. These // Groups are additional groups to add the container's user to. These
// are resolved within the container using the container's /etc/passwd. // are resolved within the container using the container's /etc/passwd.
Groups []string `json:"groups,omitempty"` 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 // AddCurrentUserPasswdEntry indicates that Libpod should ensure that
// the container's /etc/passwd contains an entry for the user running // the container's /etc/passwd contains an entry for the user running
// Libpod - mostly used in rootless containers where the user running // Libpod - mostly used in rootless containers where the user running

View File

@ -305,14 +305,41 @@ func (c *Container) getUserOverrides() *lookup.Overrides {
return &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 // Generate spec for a container
// Accepts a map of the container's dependencies // Accepts a map of the container's dependencies
func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
overrides := c.getUserOverrides() overrides := c.getUserOverrides()
execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides) execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides)
if err != nil {
if util.StringInSlice(c.config.User, c.config.HostUsers) {
execUser, err = lookupHostUser(c.config.User)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
g := generate.NewFromSpec(c.config.Spec) 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 // /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 // the user in question already exists in /etc/passwd) or the UID to be added
// is 0). // 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 // Returns password entry (as a string that can be appended to /etc/passwd) and
// any error that occurred. // any error that occurred.
func (c *Container) generatePasswdEntry() (string, error) { func (c *Container) generatePasswdEntry() (string, error) {
passwdString := "" passwdString := ""
addedUID := 0 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 { if c.config.AddCurrentUserPasswdEntry {
entry, uid, _, err := c.generateCurrentUserPasswdEntry() entry, uid, _, err := c.generateCurrentUserPasswdEntry()
if err != nil { if err != nil {
@ -2386,17 +2426,25 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) {
if err != nil { if err != nil {
return "", 0, 0, errors.Wrapf(err, "failed to get current user") return "", 0, 0, errors.Wrapf(err, "failed to get current user")
} }
pwd, err := c.userPasswdEntry(u)
// Lookup the user to see if it exists in the container image. if err != nil {
_, err = lookup.GetUser(c.state.Mountpoint, u.Username)
if err != runcuser.ErrNoPasswdEntries {
return "", 0, 0, err 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)
if err != runcuser.ErrNoPasswdEntries {
return "", err
}
// Lookup the UID to see if it exists in the container image. // Lookup the UID to see if it exists in the container image.
_, err = lookup.GetUser(c.state.Mountpoint, u.Uid) _, err = lookup.GetUser(c.state.Mountpoint, u.Uid)
if err != runcuser.ErrNoPasswdEntries { if err != runcuser.ErrNoPasswdEntries {
return "", 0, 0, err return "", err
} }
// If the user's actual home directory exists, or was mounted in - use // 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)) 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 // 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 // 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 // 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 // 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 // /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 // 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* // 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). // read-only. In this case, the function will return nothing ("", "", nil).
func (c *Container) generatePasswdAndGroup() (string, string, error) { 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 return "", "", nil
} }

View File

@ -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 // WithInitCtrType indicates the container is a initcontainer
func WithInitCtrType(containerType string) CtrCreateOption { func WithInitCtrType(containerType string) CtrCreateOption {
return func(ctr *Container) error { return func(ctr *Container) error {

View File

@ -189,6 +189,7 @@ type ContainerCreateOptions struct {
HealthTimeout string HealthTimeout string
Hostname string `json:"hostname,omitempty"` Hostname string `json:"hostname,omitempty"`
HTTPProxy bool HTTPProxy bool
HostUsers []string
ImageVolume string ImageVolume string
Init bool Init bool
InitContainerType string InitContainerType string

View File

@ -156,6 +156,10 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, nil, nil, err return nil, nil, nil, err
} }
if len(s.HostUsers) > 0 {
options = append(options, libpod.WithHostUsers(s.HostUsers))
}
command, err := makeCommand(ctx, s, imageData, rtc) command, err := makeCommand(ctx, s, imageData, rtc)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err

View File

@ -152,6 +152,9 @@ type ContainerBasicConfig struct {
// Conflicts with UtsNS if UtsNS is not set to private. // Conflicts with UtsNS if UtsNS is not set to private.
// Optional. // Optional.
Hostname string `json:"hostname,omitempty"` 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 sets kernel parameters for the container
Sysctl map[string]string `json:"sysctl,omitempty"` Sysctl map[string]string `json:"sysctl,omitempty"`
// Remove indicates if the container should be removed once it has been started // Remove indicates if the container should be removed once it has been started

View File

@ -437,6 +437,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
s.NetworkOptions = c.Net.NetworkOptions s.NetworkOptions = c.Net.NetworkOptions
s.UseImageHosts = c.Net.NoHosts s.UseImageHosts = c.Net.NoHosts
} }
s.HostUsers = c.HostUsers
s.ImageVolumeMode = c.ImageVolume s.ImageVolumeMode = c.ImageVolume
if s.ImageVolumeMode == "bind" { if s.ImageVolumeMode == "bind" {
s.ImageVolumeMode = "anonymous" s.ImageVolumeMode = "anonymous"

View File

@ -723,3 +723,11 @@ func SocketPath() (string, error) {
// Glue the socket path together // Glue the socket path together
return filepath.Join(xdg, "podman", "podman.sock"), nil 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)
}

View File

@ -711,6 +711,18 @@ EOF
run_podman rmi nomtab 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" { @test "podman run --device-cgroup-rule tests" {
skip_if_rootless "cannot add devices in rootless mode" skip_if_rootless "cannot add devices in rootless mode"

View File

@ -398,6 +398,16 @@ function skip_if_rootless() {
fi 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 # skip_if_remote # ...with an optional message
#################### ####################