Skip to content

Commit

Permalink
Merge pull request #13881 from rhatdan/userns
Browse files Browse the repository at this point in the history
Add support for --userns=nomap
  • Loading branch information
openshift-merge-robot authored Apr 22, 2022
2 parents 22500d7 + 80c0fce commit 1bafde2
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 116 deletions.
2 changes: 1 addition & 1 deletion cmd/podman/common/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
11 changes: 11 additions & 0 deletions docs/source/markdown/podman-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
22 changes: 19 additions & 3 deletions docs/source/markdown/podman-pod-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]]*]

Expand Down
13 changes: 12 additions & 1 deletion docs/source/markdown/podman-run.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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*
Expand Down
67 changes: 34 additions & 33 deletions pkg/domain/infra/runtime_libpod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/namespaces/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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), ":")
Expand Down Expand Up @@ -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
Expand Down
26 changes: 12 additions & 14 deletions pkg/specgen/generate/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
53 changes: 43 additions & 10 deletions pkg/specgen/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 1bafde2

Please sign in to comment.