Skip to content

Commit

Permalink
userns: add new option --userns=keep-id
Browse files Browse the repository at this point in the history
it creates a namespace where the current UID:GID on the host is mapped
to the same UID:GID in the container.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
  • Loading branch information
giuseppe committed May 24, 2019
1 parent 6df320c commit f09370c
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 5 deletions.
4 changes: 3 additions & 1 deletion cmd/podman/libpodruntime/runtime.go
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
Expand Down Expand Up @@ -37,11 +38,12 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber bool,
subgidname := c.Flags().Lookup("subgidname")
if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) &&
(uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) {
userns, _ := c.Flags().GetString("userns")
uidmapVal, _ := c.Flags().GetStringSlice("uidmap")
gidmapVal, _ := c.Flags().GetStringSlice("gidmap")
subuidVal, _ := c.Flags().GetString("subuidname")
subgidVal, _ := c.Flags().GetString("subgidname")
mappings, err := util.ParseIDMapping(uidmapVal, gidmapVal, subuidVal, subgidVal)
mappings, err := util.ParseIDMapping(namespaces.UsernsMode(userns), uidmapVal, gidmapVal, subuidVal, subgidVal)
if err != nil {
return nil, err
}
Expand Down
7 changes: 5 additions & 2 deletions cmd/podman/shared/create.go
Expand Up @@ -19,6 +19,7 @@ import (
ann "github.com/containers/libpod/pkg/annotations"
"github.com/containers/libpod/pkg/inspect"
ns "github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/rootless"
cc "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/pkg/signal"
Expand Down Expand Up @@ -283,7 +284,7 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
namespaces map[string]string
)

idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname"))
idmappings, err := util.ParseIDMapping(ns.UsernsMode(c.String("userns")), c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname"))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -451,7 +452,9 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
// USER
user := c.String("user")
if user == "" {
if data == nil {
if usernsMode.IsKeepID() {
user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
} else if data == nil {
user = "0"
} else {
user = data.Config.User
Expand Down
2 changes: 2 additions & 0 deletions docs/podman-create.1.md
Expand Up @@ -727,11 +727,13 @@ The followings examples are all valid:
Without this argument the command will be run as root in the container.

**--userns**=host
**--userns**=keep-id
**--userns**=ns:my_namespace

Set the user namespace mode for the container. The use of userns is disabled by default.

- `host`: run in the user namespace of the caller. This is the default if no user namespace options are set. The processes running in the container will have the same privileges on the host as any other process launched by the calling user.
- `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.
- `ns`: run the container in the given existing user namespace.

This option is incompatible with --gidmap, --uidmap, --subuid and --subgid
Expand Down
2 changes: 2 additions & 0 deletions docs/podman-run.1.md
Expand Up @@ -763,11 +763,13 @@ The followings examples are all valid:
Without this argument the command will be run as root in the container.

**--userns**=host
**--userns**=keep-id
**--userns**=ns:my_namespace

Set the user namespace mode for the container. The use of userns is disabled by default.

- `host`: run in the user namespace of the caller. This is the default if no user namespace options are set. The processes running in the container will have the same privileges on the host as any other process launched by the calling user.
- `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.
- `ns`: run the container in the given existing user namespace.

This option is incompatible with --gidmap, --uidmap, --subuid and --subgid
Expand Down
7 changes: 6 additions & 1 deletion pkg/namespaces/namespaces.go
Expand Up @@ -12,6 +12,11 @@ func (n UsernsMode) IsHost() bool {
return n == "host"
}

// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is lept inside of the namespace.
func (n UsernsMode) IsKeepID() bool {
return n == "keep-id"
}

// IsPrivate indicates whether the container uses the a private userns.
func (n UsernsMode) IsPrivate() bool {
return !(n.IsHost())
Expand All @@ -21,7 +26,7 @@ func (n UsernsMode) IsPrivate() bool {
func (n UsernsMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
case "", "host", "keep-id":
default:
return false
}
Expand Down
53 changes: 52 additions & 1 deletion pkg/util/utils.go
Expand Up @@ -3,6 +3,7 @@ package util
import (
"fmt"
"os"
ouser "os/user"
"path/filepath"
"strings"
"sync"
Expand All @@ -11,6 +12,8 @@ import (
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -131,11 +134,59 @@ func GetImageConfig(changes []string) (v1.ImageConfig, error) {
}

// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
func ParseIDMapping(UIDMapSlice, GIDMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) {
func ParseIDMapping(mode namespaces.UsernsMode, UIDMapSlice, GIDMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) {
options := storage.IDMappingOptions{
HostUIDMapping: true,
HostGIDMapping: true,
}

if mode.IsKeepID() {
if len(UIDMapSlice) > 0 || len(GIDMapSlice) > 0 {
return nil, errors.New("cannot specify custom mappings with --userns=keep-id")
}
if len(subUIDMap) > 0 || len(subGIDMap) > 0 {
return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id")
}
if rootless.IsRootless() {
uid := rootless.GetRootlessUID()
gid := rootless.GetRootlessGID()

username := os.Getenv("USER")
if username == "" {
user, err := ouser.LookupId(fmt.Sprintf("%d", uid))
if err == nil {
username = user.Username
}
}
mappings, err := idtools.NewIDMappings(username, username)
if err != nil {
return nil, errors.Wrapf(err, "cannot find mappings for user %s", username)
}
maxUID, maxGID := 0, 0
for _, u := range mappings.UIDs() {
maxUID += u.Size
}
for _, g := range mappings.GIDs() {
maxGID += g.Size
}

options.UIDMap, options.GIDMap = nil, nil

options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: uid})
options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})

options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: gid})
options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
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
}

if subGIDMap == "" && subUIDMap != "" {
subGIDMap = subUIDMap
}
Expand Down
9 changes: 9 additions & 0 deletions test/e2e/run_userns_test.go
Expand Up @@ -3,6 +3,7 @@
package integration

import (
"fmt"
"os"

. "github.com/containers/libpod/test/utils"
Expand Down Expand Up @@ -76,4 +77,12 @@ var _ = Describe("Podman UserNS support", func() {
Expect(ok).To(BeTrue())
})

It("podman --userns=keep-id", func() {
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-u"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
uid := fmt.Sprintf("%d", os.Geteuid())
ok, _ := session.GrepString(uid)
Expect(ok).To(BeTrue())
})
})

0 comments on commit f09370c

Please sign in to comment.