Skip to content

Commit

Permalink
Merge pull request #3196 from giuseppe/keep-id
Browse files Browse the repository at this point in the history
userns: add new option --userns=keep-id
  • Loading branch information
openshift-merge-robot authored May 25, 2019
2 parents 3c85122 + 5eb321a commit b1d590b
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 8 deletions.
2 changes: 1 addition & 1 deletion cmd/podman/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
"Username or UID (format: <name|uid>[:<group|gid>])",
)
createFlags.String(
"userns", "",
"userns", os.Getenv("PODMAN_USERNS"),
"User namespace to use",
)
createFlags.String(
Expand Down
4 changes: 3 additions & 1 deletion cmd/podman/libpodruntime/runtime.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
4 changes: 3 additions & 1 deletion docs/podman-create.1.md
Original file line number Diff line number Diff line change
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.
Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value means user namespaces are disabled.

- `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
4 changes: 3 additions & 1 deletion docs/podman-run.1.md
Original file line number Diff line number Diff line change
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.
Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value means user namespaces are disabled.

- `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
Original file line number Diff line number Diff line change
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
16 changes: 16 additions & 0 deletions pkg/rootless/rootless_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivilege
static int open_files_max_fd;
fd_set open_files_set;
static uid_t rootless_uid_init;
static gid_t rootless_gid_init;

static int
syscall_setresuid (uid_t ruid, uid_t euid, uid_t suid)
Expand All @@ -59,6 +60,12 @@ rootless_uid ()
return rootless_uid_init;
}

uid_t
rootless_gid ()
{
return rootless_gid_init;
}

static void
do_pause ()
{
Expand Down Expand Up @@ -224,6 +231,7 @@ static void __attribute__((constructor)) init()
long pid;
char buf[12];
uid_t uid;
gid_t gid;
char path[PATH_MAX];
const char *const suffix = "/libpod/pause.pid";
char *cwd = getcwd (NULL, 0);
Expand Down Expand Up @@ -263,6 +271,7 @@ static void __attribute__((constructor)) init()
}

uid = geteuid ();
gid = getegid ();

sprintf (path, "/proc/%d/ns/user", pid);
fd = open (path, O_RDONLY);
Expand Down Expand Up @@ -310,6 +319,7 @@ static void __attribute__((constructor)) init()

free (cwd);
rootless_uid_init = uid;
rootless_gid_init = gid;
}
}

Expand Down Expand Up @@ -440,6 +450,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
{
pid_t ppid = getpid ();
char uid[16];
char gid[16];
char **argv;
int pid;
char *cwd = getcwd (NULL, 0);
Expand All @@ -451,6 +462,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
}

sprintf (uid, "%d", geteuid ());
sprintf (gid, "%d", getegid ());

argv = get_cmd_line_args (ppid);
if (argv == NULL)
Expand All @@ -477,6 +489,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)

setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1);
setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1);
setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1);

if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0)
{
Expand Down Expand Up @@ -556,6 +569,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
pid_t ppid = getpid ();
char **argv;
char uid[16];
char gid[16];
char *listen_fds = NULL;
char *listen_pid = NULL;
bool do_socket_activation = false;
Expand All @@ -577,6 +591,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
}

sprintf (uid, "%d", geteuid ());
sprintf (gid, "%d", getegid ());

pid = syscall_clone (CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, NULL);
if (pid < 0)
Expand Down Expand Up @@ -621,6 +636,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)

setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1);
setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1);
setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1);

do
ret = read (ready, &b, 1) < 0;
Expand Down
20 changes: 20 additions & 0 deletions pkg/rootless/rootless_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
#cgo remoteclient CFLAGS: -DDISABLE_JOIN_SHORTCUT
#include <stdlib.h>
extern uid_t rootless_uid();
extern uid_t rootless_gid();
extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path);
extern int reexec_in_user_namespace_wait(int pid);
extern int reexec_userns_join(int userns, int mountns, char *pause_pid_file_path);
Expand All @@ -49,10 +50,12 @@ var (
func IsRootless() bool {
isRootlessOnce.Do(func() {
rootlessUIDInit := int(C.rootless_uid())
rootlessGIDInit := int(C.rootless_gid())
if rootlessUIDInit != 0 {
// This happens if we joined the user+mount namespace as part of
os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done")
os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit))
os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit))
}
isRootless = os.Geteuid() != 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != ""
})
Expand All @@ -69,6 +72,23 @@ func GetRootlessUID() int {
return os.Geteuid()
}

// GetRootlessGID returns the GID of the user in the parent userNS
func GetRootlessGID() int {
gidEnv := os.Getenv("_CONTAINERS_ROOTLESS_GID")
if gidEnv != "" {
u, _ := strconv.Atoi(gidEnv)
return u
}

/* If the _CONTAINERS_ROOTLESS_UID is set, assume the gid==uid. */
uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID")
if uidEnv != "" {
u, _ := strconv.Atoi(uidEnv)
return u
}
return os.Getegid()
}

func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error {
path, err := exec.LookPath(tool)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/rootless/rootless_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ func GetRootlessUID() int {
return -1
}

// GetRootlessGID returns the GID of the user in the parent userNS
func GetRootlessGID() int {
return -1
}

// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount
// namespace of the specified PID without looking up its parent. Useful to join directly
// the conmon process. It is a convenience function for JoinUserAndMountNSWithOpts
Expand Down
53 changes: 52 additions & 1 deletion pkg/util/utils.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 b1d590b

Please sign in to comment.