mirror of
https://github.com/containers/podman.git
synced 2025-05-17 23:26:08 +08:00
Merge pull request #12627 from rhatdan/passwd
Allow users to add host user accounts to /etc/passwd
This commit is contained in:
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
// Lookup the user to see if it exists in the container image.
|
||||
_, err = lookup.GetUser(c.state.Mountpoint, u.Username)
|
||||
if err != runcuser.ErrNoPasswdEntries {
|
||||
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)
|
||||
if err != runcuser.ErrNoPasswdEntries {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -189,6 +189,7 @@ type ContainerCreateOptions struct {
|
||||
HealthTimeout string
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
HTTPProxy bool
|
||||
HostUsers []string
|
||||
ImageVolume string
|
||||
Init bool
|
||||
InitContainerType string
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
####################
|
||||
|
Reference in New Issue
Block a user