From 80c0fceb24b70a85f3f2ca8be29f4a131c0881d4 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 13 Apr 2022 14:06:05 -0400 Subject: Add support for --userns=nomap From a security point of view, it would be nice to be able to map a rootless usernamespace that does not use your own UID within the container. This would add protection against a hostile process escapping the container and reading content in your homedir. Signed-off-by: Daniel J Walsh --- cmd/podman/common/completion.go | 2 +- docs/source/markdown/podman-create.1.md | 11 +++ docs/source/markdown/podman-pod-create.1.md | 22 +++++- docs/source/markdown/podman-run.1.md | 13 +++- pkg/domain/infra/runtime_libpod.go | 67 +++++++++--------- pkg/namespaces/namespaces.go | 7 +- pkg/specgen/generate/namespaces.go | 26 ++++--- pkg/specgen/namespaces.go | 53 +++++++++++--- pkg/util/utils.go | 105 ++++++++++++++++++---------- test/e2e/run_userns_test.go | 13 ++-- test/e2e/toolbox_test.go | 9 ++- test/system/030-run.bats | 11 ++- test/system/065-cp.bats | 8 ++- test/system/075-exec.bats | 1 + test/system/160-volumes.bats | 13 ++-- test/system/170-run-userns.bats | 14 ++++ test/system/500-networking.bats | 1 + 17 files changed, 260 insertions(+), 116 deletions(-) diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3d36162ee..abb943942 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -756,7 +756,7 @@ func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string) // -> same as AutocompleteNamespace with "auto", "keep-id" added func AutocompleteUserNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { results, directive := AutocompleteNamespace(cmd, args, toComplete) - results = append(results, "auto", "keep-id") + results = append(results, "auto", "keep-id", "nomap") return results, directive } diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index f6d028f4d..ea31428fd 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -1225,6 +1225,15 @@ Without this argument the command will be run as root in the container. Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with the **--uidmap** and **--gidmap** options. +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + Valid _mode_ values are: **auto**[:_OPTIONS,..._]: automatically create a unique user namespace. @@ -1247,6 +1256,8 @@ 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 ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. + **ns:**_namespace_: run the container in the given existing user namespace. **private**: create a new namespace for the container. diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index 1bf14efdb..f24458dc6 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -308,14 +308,30 @@ several times to map different ranges. Set the user namespace mode for all the containers in a pod. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled. +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + Valid _mode_ values are: -- *auto[:*_OPTIONS,..._*]*: automatically create a namespace. It is possible to specify these options to `auto`: + - *auto[:*_OPTIONS,..._*]*: automatically create a namespace. It is possible to specify these options to `auto`: + - *gidmapping=*_CONTAINER_GID:HOST_GID:SIZE_ to force a GID mapping to be present in the user namespace. + - *size=*_SIZE_: to specify an explicit size for the automatic user namespace. e.g. `--userns=auto:size=8192`. If `size` is not specified, `auto` will estimate a size for the user namespace. + - *uidmapping=*_CONTAINER_UID:HOST_UID:SIZE_ to force a UID mapping to be present in the user namespace. -- *host*: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). -- *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 ignored for containers created by the root user. + + - *host*: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). + + - *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 ignored for containers created by the root user. + + - *nomap*: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. #### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 8f72d4f49..52143c934 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -1290,6 +1290,15 @@ When a user namespace is not in use, the UID and GID used within the container a Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with the **--uidmap** and **--gidmap** options. +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + Valid _mode_ values are: **auto**[:_OPTIONS,..._]: automatically create a unique user namespace. @@ -1299,6 +1308,7 @@ The `--userns=auto` flag, requires that the user name `containers` and a range o Example: `containers:2147483647:2147483648`. Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinate user ids. The size of the ranges is based on the number of UIDs required in the image. The number of UIDs and GIDs can be overridden with the `size` option. + The rootless option `--userns=keep-id` uses all the subuids and subgids of the user. Using `--userns=auto` when starting new containers will not work as long as any containers exist that were started with `--userns=keep-id`. Valid `auto` options: @@ -1313,10 +1323,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 ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. + **ns:**_namespace_: run the container in the given existing user namespace. **private**: create a new namespace for the container. - This option is incompatible with **--gidmap**, **--uidmap**, **--subuidname** and **--subgidname**. #### **--uts**=*mode* diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index dffd90dbe..5fdc252e2 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -276,46 +276,47 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin if len(subUIDMap) > 0 || len(subGIDMap) > 0 { return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id") } - if rootless.IsRootless() { - min := func(a, b int) int { - if a < b { - return a - } - return b + if !rootless.IsRootless() { + return nil, errors.New("keep-id is only supported in rootless mode") + } + min := func(a, b int) int { + if a < b { + return a } + return b + } - uid := rootless.GetRootlessUID() - gid := rootless.GetRootlessGID() - - uids, gids, err := rootless.GetConfiguredMappings() - if err != nil { - return nil, errors.Wrapf(err, "cannot read mappings") - } - maxUID, maxGID := 0, 0 - for _, u := range uids { - maxUID += u.Size - } - for _, g := range gids { - maxGID += g.Size - } + uid := rootless.GetRootlessUID() + gid := rootless.GetRootlessGID() - options.UIDMap, options.GIDMap = nil, nil + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, errors.Wrapf(err, "cannot read mappings") + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) - if maxUID > uid { - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) - } + options.UIDMap, options.GIDMap = nil, nil - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) - if maxGID > gid { - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) - } + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } - options.HostUIDMapping = false - options.HostGIDMapping = false + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) } + + options.HostUIDMapping = false + options.HostGIDMapping = false // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op return &options, nil } diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index a264a5a0f..bdea7c310 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -96,6 +96,11 @@ func (n UsernsMode) IsKeepID() bool { return n == "keep-id" } +// IsNoMap indicates whether container uses a mapping where the (uid, gid) on the host is not present in the namespace. +func (n UsernsMode) IsNoMap() bool { + return n == "nomap" +} + // IsAuto indicates whether container uses the "auto" userns mode. func (n UsernsMode) IsAuto() bool { parts := strings.Split(string(n), ":") @@ -158,7 +163,7 @@ func (n UsernsMode) IsPrivate() bool { func (n UsernsMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { - case "", privateType, hostType, "keep-id", nsType, "auto": + case "", privateType, hostType, "keep-id", nsType, "auto", "nomap": case containerType: if len(parts) != 2 || parts[1] == "" { return false diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 05c2d1741..d8d1ae652 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -165,21 +165,19 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. // User switch s.UserNS.NSMode { case specgen.KeepID: - if rootless.IsRootless() { - 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() - if err != nil { - return nil, err - } - toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid))) + if !rootless.IsRootless() { + return nil, errors.New("keep-id is only supported in rootless mode") + } + 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() + if err != nil { + return nil, err } - } else { - // keep-id as root doesn't need a user namespace - s.UserNS.NSMode = specgen.Host + toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid))) } case specgen.FromPod: if pod == nil || infraCtr == nil { diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 4412eff29..eaf2daad9 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -55,6 +55,10 @@ const ( // of the namespace itself. // Only used with the user namespace, invalid otherwise. KeepID NamespaceMode = "keep-id" + // NoMap indicates a user namespace to keep the owner uid out + // of the namespace itself. + // Only used with the user namespace, invalid otherwise. + NoMap NamespaceMode = "no-map" // Auto indicates to automatically create a user namespace. // Only used with the user namespace, invalid otherwise. Auto NamespaceMode = "auto" @@ -121,6 +125,11 @@ func (n *Namespace) IsKeepID() bool { return n.NSMode == KeepID } +// IsNoMap indicates the namespace is NoMap +func (n *Namespace) IsNoMap() bool { + return n.NSMode == NoMap +} + func (n *Namespace) String() string { if n.Value != "" { return fmt.Sprintf("%s:%s", n.NSMode, n.Value) @@ -133,7 +142,7 @@ func validateUserNS(n *Namespace) error { return nil } switch n.NSMode { - case Auto, KeepID: + case Auto, KeepID, NoMap: return nil } return n.validate() @@ -299,6 +308,9 @@ func ParseUserNamespace(ns string) (Namespace, error) { case ns == "keep-id": toReturn.NSMode = KeepID return toReturn, nil + case ns == "nomap": + toReturn.NSMode = NoMap + return toReturn, nil case ns == "": toReturn.NSMode = Host return toReturn, nil @@ -548,20 +560,41 @@ func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *gene g.SetProcessUID(uint32(uid)) g.SetProcessGID(uint32(gid)) user = fmt.Sprintf("%d:%d", uid, gid) - fallthrough - case Private: - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + if err := privateUserNamespace(idmappings, g); err != nil { return user, err } - if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) { - return user, errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") + case NoMap: + mappings, uid, gid, err := util.GetNoMapMapping() + if err != nil { + return user, err } - for _, uidmap := range idmappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + idmappings = mappings + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + user = fmt.Sprintf("%d:%d", uid, gid) + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err } - for _, gidmap := range idmappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + case Private: + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err } } return user, nil } + +func privateUserNamespace(idmappings *storage.IDMappingOptions, g *generate.Generator) error { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) { + return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") + } + for _, uidmap := range idmappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range idmappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + return nil +} diff --git a/pkg/util/utils.go b/pkg/util/utils.go index b89978601..aed9aebfc 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -347,55 +347,84 @@ 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) { + if !rootless.IsRootless() { + return nil, -1, -1, errors.New("keep-id is only supported in rootless mode") + } options := stypes.IDMappingOptions{ - HostUIDMapping: true, - HostGIDMapping: true, + HostUIDMapping: false, + HostGIDMapping: false, } - uid, gid := 0, 0 - if rootless.IsRootless() { - min := func(a, b int) int { - if a < b { - return a - } - return b + min := func(a, b int) int { + if a < b { + return a } + return b + } - uid = rootless.GetRootlessUID() - gid = rootless.GetRootlessGID() + uid := rootless.GetRootlessUID() + gid := rootless.GetRootlessGID() - uids, gids, err := rootless.GetConfiguredMappings() - if err != nil { - return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") - } - maxUID, maxGID := 0, 0 - for _, u := range uids { - maxUID += u.Size - } - for _, g := range gids { - maxGID += g.Size - } - - options.UIDMap, options.GIDMap = nil, nil + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") + } + if len(uids) == 0 || len(gids) == 0 { + return nil, -1, -1, errors.Wrapf(err, "keep-id requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly") + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) - if maxUID > uid { - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) - } + options.UIDMap, options.GIDMap = nil, nil - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) - if maxGID > gid { - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) - } + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } - options.HostUIDMapping = false - options.HostGIDMapping = false + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) } - // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op + return &options, uid, gid, nil } +// GetNoMapMapping returns the mappings and the user to use when nomap is used +func GetNoMapMapping() (*stypes.IDMappingOptions, int, int, error) { + if !rootless.IsRootless() { + return nil, -1, -1, errors.New("nomap is only supported in rootless mode") + } + options := stypes.IDMappingOptions{ + HostUIDMapping: false, + HostGIDMapping: false, + } + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") + } + if len(uids) == 0 || len(gids) == 0 { + return nil, -1, -1, errors.Wrapf(err, "nomap requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly") + } + options.UIDMap, options.GIDMap = nil, nil + uid, gid := 0, 0 + for _, u := range uids { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: uid + 1, Size: u.Size}) + uid += u.Size + } + for _, g := range gids { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: gid + 1, Size: g.Size}) + gid += g.Size + } + return &options, 0, 0, nil +} + // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) { options := stypes.IDMappingOptions{ @@ -415,7 +444,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin options.AutoUserNsOpts = *opts return &options, nil } - if mode.IsKeepID() { + if mode.IsKeepID() || mode.IsNoMap() { options.HostUIDMapping = false options.HostGIDMapping = false return &options, nil diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index 5a046b0a4..092621c27 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -78,12 +78,18 @@ var _ = Describe("Podman UserNS support", func() { It("podman --userns=keep-id", func() { session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-u"}) session.WaitWithDefaultTimeout() + if os.Geteuid() == 0 { + Expect(session).Should(Exit(125)) + return + } + Expect(session).Should(Exit(0)) uid := fmt.Sprintf("%d", os.Geteuid()) Expect(session.OutputToString()).To(ContainSubstring(uid)) }) It("podman --userns=keep-id check passwd", func() { + SkipIfNotRootless("keep-id only works in rootless mode") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-un"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -93,6 +99,7 @@ var _ = Describe("Podman UserNS support", func() { }) It("podman --userns=keep-id root owns /usr", func() { + SkipIfNotRootless("keep-id only works in rootless mode") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -100,6 +107,7 @@ var _ = Describe("Podman UserNS support", func() { }) It("podman --userns=keep-id --user root:root", func() { + SkipIfNotRootless("keep-id only works in rootless mode") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "alpine", "id", "-u"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -107,10 +115,7 @@ var _ = Describe("Podman UserNS support", func() { }) It("podman run --userns=keep-id can add users", func() { - if os.Geteuid() == 0 { - Skip("Test only runs without root") - } - + SkipIfNotRootless("keep-id only works in rootless mode") userName := os.Getenv("USER") if userName == "" { Skip("Can't complete test if no username available") diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go index b34fd299c..1fc28a06d 100644 --- a/test/e2e/toolbox_test.go +++ b/test/e2e/toolbox_test.go @@ -160,6 +160,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id --user root:root - entrypoint - entrypoint is executed as root", func() { + SkipIfNotRootless("only meaningful when run rootless") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", ALPINE, "id"}) session.WaitWithDefaultTimeout() @@ -168,6 +169,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id + podman exec - correct names of user and group", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var err error @@ -199,6 +201,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id - entrypoint - adding user with useradd and then removing their password", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var username string = "testuser" @@ -238,6 +241,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var groupName string = "testgroup" @@ -268,6 +272,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id - entrypoint - modifying existing user with usermod - add to new group, change home/shell/uid", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var badHomeDir string = "/home/badtestuser" var badShell string = "/bin/sh" @@ -315,6 +320,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman run --privileged --userns=keep-id --user root:root - entrypoint - (bind)mounting", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration session = podmanTest.Podman([]string{"run", "--privileged", "--userns=keep-id", "--user", "root:root", ALPINE, @@ -329,6 +335,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create + start - with all needed switches for create - sleep as entry-point", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration // These should be most of the switches that Toolbox uses to create a "toolbox" container @@ -365,8 +372,8 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman run --userns=keep-id check $HOME", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration - currentUser, err := user.Current() Expect(err).To(BeNil()) diff --git a/test/system/030-run.bats b/test/system/030-run.bats index aba18badb..aa75a6462 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -273,9 +273,11 @@ echo $rand | 0 | $rand # symptom only manifests on a fedora container image -- we have no # reproducer on alpine. Checking directory ownership is good enough. @test "podman run : user namespace preserved root ownership" { + keep="--userns=keep-id" + is_rootless || keep="" for priv in "" "--privileged"; do for user in "--user=0" "--user=100"; do - for keepid in "" "--userns=keep-id"; do + for keepid in "" ${keep}; do opts="$priv $user $keepid" for dir in /etc /usr;do @@ -290,6 +292,7 @@ echo $rand | 0 | $rand # #6829 : add username to /etc/passwd inside container if --userns=keep-id @test "podman run : add username to /etc/passwd if --userns=keep-id" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" # Default: always run as root run_podman run --rm $IMAGE id -un is "$output" "root" "id -un on regular container" @@ -340,6 +343,7 @@ echo $rand | 0 | $rand # #6991 : /etc/passwd is modifiable @test "podman run : --userns=keep-id: passwd file is modifiable" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" run_podman run -d --userns=keep-id --cap-add=dac_override $IMAGE sh -c 'while ! test -e /tmp/stop; do sleep 0.1; done' cid="$output" @@ -824,6 +828,9 @@ EOF # CVE-2022-1227 : podman top joins container mount NS and uses nsenter from image @test "podman top does not use nsenter from image" { + keepid="--userns=keep-id" + is_rootless || keepid="" + tmpdir=$PODMAN_TMPDIR/build-test mkdir -p $tmpdir tmpbuilddir=$tmpdir/build @@ -838,7 +845,7 @@ EOF test_image="cve_2022_1227_test" run_podman build -t $test_image $tmpbuilddir - run_podman run -d --userns=keep-id $test_image top + run_podman run -d ${keepid} $test_image top ctr="$output" run_podman top $ctr huser,user run_podman rm -f -t0 $ctr diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats index 780fc6737..260ad4800 100644 --- a/test/system/065-cp.bats +++ b/test/system/065-cp.bats @@ -119,7 +119,9 @@ load helpers echo "content" > $srcdir/hostfile userid=$(id -u) - run_podman run --user=$userid --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity + keepid="--userns=keep-id" + is_rootless || keepid="" + run_podman run --user=$userid ${keepid} -d --name cpcontainer $IMAGE sleep infinity run_podman cp $srcdir/hostfile cpcontainer:/tmp/hostfile run_podman exec cpcontainer stat -c "%u" /tmp/hostfile is "$output" "$userid" "copied file is chowned to the container user" @@ -138,7 +140,9 @@ load helpers userid=$(id -u) - run_podman run --user="$userid" --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity + keepid="--userns=keep-id" + is_rootless || keepid="" + run_podman run --user=$userid ${keepid} -d --name cpcontainer $IMAGE sleep infinity run_podman cp -a=false - cpcontainer:/tmp/ < "${tmpdir}/a.tar" run_podman exec cpcontainer stat -c "%u:%g" /tmp/a.txt is "$output" "1042:1043" "copied file retains uid/gid from the tar" diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index 42954e5ec..0a6048b7e 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -87,6 +87,7 @@ load helpers # #6829 : add username to /etc/passwd inside container if --userns=keep-id @test "podman exec - with keep-id" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" # Multiple --userns options confirm command-line override (last one wins) run_podman run -d --userns=private --userns=keep-id $IMAGE sh -c \ "echo READY;while [ ! -f /tmp/stop ]; do sleep 1; done" diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index d0088b994..1a558c599 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -182,13 +182,14 @@ EOF run_podman volume rm $myvol - # Autocreated volumes should also work with keep-id - # All we do here is check status; podman 1.9.1 would fail with EPERM - myvol=myvol$(random_string) - run_podman run --rm -v $myvol:/myvol:z --userns=keep-id $IMAGE \ + if is_rootless; then + # Autocreated volumes should also work with keep-id + # All we do here is check status; podman 1.9.1 would fail with EPERM + myvol=myvol$(random_string) + run_podman run --rm -v $myvol:/myvol:z --userns=keep-id $IMAGE \ touch /myvol/myfile - - run_podman volume rm $myvol + run_podman volume rm $myvol + fi } diff --git a/test/system/170-run-userns.bats b/test/system/170-run-userns.bats index c020a73ab..d754306b2 100644 --- a/test/system/170-run-userns.bats +++ b/test/system/170-run-userns.bats @@ -94,3 +94,17 @@ EOF is ${output} ${secret_content} "Secrets should work with user namespace" run_podman secret rm ${test_name} } + +@test "podman userns=nomap" { + skip_if_not_rootless "--userns=nomap only works in rootless mode" + ns_user=$(id -un) + baseuid=$(egrep "${ns_user}:" /etc/subuid | cut -f2 -d:) + test ! -z ${baseuid} || skip "no IDs allocated for user ${ns_user}" + + test_name="test_$(random_string 12)" + run_podman run -d --userns=nomap $IMAGE sleep 100 + cid=${output} + run_podman top ${cid} huser + is "${output}" "HUSER.*${baseuid}" "Container should start with baseuid from /etc/subuid not user UID" + run_podman rm -t 0 --force ${cid} +} diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 78ad3fe04..1b5e27e95 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -88,6 +88,7 @@ load helpers # Issue #5466 - port-forwarding doesn't work with this option and -d @test "podman networking: port with --userns=keep-id" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" for cidr in "" "$(random_rfc1918_subnet).0/24"; do myport=$(random_free_port 52000-52999) if [[ -z $cidr ]]; then -- cgit v1.2.3-54-g00ecf