podman: add uid and gid options to keep-id

add two new options to the keep-id user namespace option:

- uid: allow to override the UID used inside the container.
- gid: allow to override the GID used inside the container.

For example, the following command will map the rootless user (that
has UID=0 inside the rootless user namespace) to the UID=11 inside the
container user namespace:

$ podman run --userns=keep-id:uid=11 --rm -ti  fedora cat /proc/self/uid_map
         0          1         11
        11          0          1
        12         12      65525

Closes: https://github.com/containers/podman/issues/15294

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano
2022-08-19 15:15:47 +02:00
parent cd62606046
commit e015c9e3f7
8 changed files with 103 additions and 5 deletions

View File

@ -687,6 +687,11 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat
**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user.
Valid `keep-id` options:
- *uid*=UID: override the UID inside the container that will be used to map the current rootless user to.
- *gid*=GID: override the GID inside the container that will be used to map the current rootless user to.
**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user.
**ns:**_namespace_: run the container in the given existing user namespace.

View File

@ -260,6 +260,11 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat
**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user.
Valid `keep-id` options:
- *uid*=UID: override the UID inside the container that will be used to map the current rootless user to.
- *gid*=GID: override the GID inside the container that will be used to map the current rootless user to.
**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user.
**ns:**_namespace_: run the pod in the given existing user namespace.

View File

@ -745,6 +745,11 @@ The rootless option `--userns=keep-id` uses all the subuids and subgids of the u
**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user.
Valid `keep-id` options:
- *uid*=UID: override the UID inside the container that will be used to map the current rootless user to.
- *gid*=GID: override the GID inside the container that will be used to map the current rootless user to.
**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user.
**ns:**_namespace_: run the container in the given existing user namespace.

View File

@ -21,6 +21,14 @@ const (
slirpType = "slirp4netns"
)
// KeepIDUserNsOptions defines how to keepIDmatically create a user namespace.
type KeepIDUserNsOptions struct {
// UID is the target uid in the user namespace.
UID *uint32
// GID is the target uid in the user namespace.
GID *uint32
}
// CgroupMode represents cgroup mode in the container.
type CgroupMode string
@ -93,7 +101,8 @@ func (n UsernsMode) IsHost() bool {
// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is kept inside of the namespace.
func (n UsernsMode) IsKeepID() bool {
return n == "keep-id"
parts := strings.Split(string(n), ":")
return parts[0] == "keep-id"
}
// IsNoMap indicates whether container uses a mapping where the (uid, gid) on the host is not present in the namespace.
@ -154,6 +163,44 @@ func (n UsernsMode) GetAutoOptions() (*types.AutoUserNsOptions, error) {
return &options, nil
}
// GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up
// a user namespace.
func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) {
parts := strings.SplitN(string(n), ":", 2)
if parts[0] != "keep-id" {
return nil, fmt.Errorf("wrong user namespace mode")
}
options := KeepIDUserNsOptions{}
if len(parts) == 1 {
return &options, nil
}
for _, o := range strings.Split(parts[1], ",") {
v := strings.SplitN(o, "=", 2)
if len(v) != 2 {
return nil, fmt.Errorf("invalid option specified: %q", o)
}
switch v[0] {
case "uid":
s, err := strconv.ParseUint(v[1], 10, 32)
if err != nil {
return nil, err
}
v := uint32(s)
options.UID = &v
case "gid":
s, err := strconv.ParseUint(v[1], 10, 32)
if err != nil {
return nil, err
}
v := uint32(s)
options.GID = &v
default:
return nil, fmt.Errorf("unknown option specified: %q", v[0])
}
}
return &options, nil
}
// IsPrivate indicates whether the container uses the a private userns.
func (n UsernsMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer())

View File

@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/namespaces"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/specgen"
"github.com/containers/podman/v4/pkg/util"
@ -198,12 +199,18 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
if !rootless.IsRootless() {
return nil, errors.New("keep-id is only supported in rootless mode")
}
toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry())
opts, err := namespaces.UsernsMode(s.UserNS.String()).GetKeepIDOptions()
if err != nil {
return nil, err
}
if opts.UID == nil && opts.GID == nil {
toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry())
}
// If user is not overridden, set user in the container
// to user running Podman.
if s.User == "" {
_, uid, gid, err := util.GetKeepIDMapping()
_, uid, gid, err := util.GetKeepIDMapping(opts)
if err != nil {
return nil, err
}

View File

@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/cgroups"
cutil "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/namespaces"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage"
spec "github.com/opencontainers/runtime-spec/specs-go"
@ -308,6 +309,14 @@ func ParseUserNamespace(ns string) (Namespace, error) {
case ns == "keep-id":
toReturn.NSMode = KeepID
return toReturn, nil
case strings.HasPrefix(ns, "keep-id:"):
split := strings.SplitN(ns, ":", 2)
if len(split) != 2 {
return toReturn, errors.New("invalid setting for keep-id: mode")
}
toReturn.NSMode = KeepID
toReturn.Value = split[1]
return toReturn, nil
case ns == "nomap":
toReturn.NSMode = NoMap
return toReturn, nil
@ -490,7 +499,11 @@ func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *gene
return user, err
}
case KeepID:
mappings, uid, gid, err := util.GetKeepIDMapping()
opts, err := namespaces.UsernsMode(userns.String()).GetKeepIDOptions()
if err != nil {
return user, err
}
mappings, uid, gid, err := util.GetKeepIDMapping(opts)
if err != nil {
return user, err
}

View File

@ -342,7 +342,7 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) {
}
// GetKeepIDMapping returns the mappings and the user to use when keep-id is used
func GetKeepIDMapping() (*stypes.IDMappingOptions, int, int, error) {
func GetKeepIDMapping(opts *namespaces.KeepIDUserNsOptions) (*stypes.IDMappingOptions, int, int, error) {
if !rootless.IsRootless() {
return nil, -1, -1, errors.New("keep-id is only supported in rootless mode")
}
@ -359,6 +359,12 @@ func GetKeepIDMapping() (*stypes.IDMappingOptions, int, int, error) {
uid := rootless.GetRootlessUID()
gid := rootless.GetRootlessGID()
if opts.UID != nil {
uid = int(*opts.UID)
}
if opts.GID != nil {
gid = int(*opts.GID)
}
uids, gids, err := rootless.GetConfiguredMappings()
if err != nil {

View File

@ -113,6 +113,16 @@ var _ = Describe("Podman UserNS support", func() {
Expect(session).Should(Exit(0))
uid := fmt.Sprintf("%d", os.Geteuid())
Expect(session.OutputToString()).To(ContainSubstring(uid))
session = podmanTest.Podman([]string{"run", "--userns=keep-id:uid=10,gid=12", "alpine", "sh", "-c", "echo $(id -u):$(id -g)"})
session.WaitWithDefaultTimeout()
if os.Geteuid() == 0 {
Expect(session).Should(Exit(125))
return
}
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("10:12"))
})
It("podman --userns=keep-id check passwd", func() {