summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2022-04-13 14:06:05 -0400
committerDaniel J Walsh <dwalsh@redhat.com>2022-04-21 15:29:04 -0400
commit80c0fceb24b70a85f3f2ca8be29f4a131c0881d4 (patch)
treef7ceffaaf30f4b8057638db446b5512fbbe27318 /pkg
parent121dde6234ddfcaf11abea03449bfd2a11da90a5 (diff)
downloadpodman-80c0fceb24b70a85f3f2ca8be29f4a131c0881d4.tar.gz
podman-80c0fceb24b70a85f3f2ca8be29f4a131c0881d4.tar.bz2
podman-80c0fceb24b70a85f3f2ca8be29f4a131c0881d4.zip
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 <dwalsh@redhat.com>
Diffstat (limited to 'pkg')
-rw-r--r--pkg/domain/infra/runtime_libpod.go67
-rw-r--r--pkg/namespaces/namespaces.go7
-rw-r--r--pkg/specgen/generate/namespaces.go26
-rw-r--r--pkg/specgen/namespaces.go53
-rw-r--r--pkg/util/utils.go105
5 files changed, 162 insertions, 96 deletions
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