diff options
-rw-r--r-- | .cirrus.yml | 6 | ||||
-rw-r--r-- | cmd/podman/common.go | 2 | ||||
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 4 | ||||
-rw-r--r-- | cmd/podman/shared/create.go | 7 | ||||
-rw-r--r-- | contrib/cirrus/packer/fedora_setup.sh | 3 | ||||
-rw-r--r-- | contrib/cirrus/packer/ubuntu_setup.sh | 3 | ||||
-rw-r--r-- | contrib/gate/Dockerfile | 5 | ||||
-rw-r--r-- | docs/podman-create.1.md | 4 | ||||
-rw-r--r-- | docs/podman-run.1.md | 4 | ||||
-rw-r--r-- | pkg/namespaces/namespaces.go | 7 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 16 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 20 | ||||
-rw-r--r-- | pkg/rootless/rootless_unsupported.go | 5 | ||||
-rw-r--r-- | pkg/util/utils.go | 53 | ||||
-rw-r--r-- | test/e2e/cp_test.go | 44 | ||||
-rw-r--r-- | test/e2e/run_userns_test.go | 9 |
16 files changed, 149 insertions, 43 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index f3a0776db..e36b4f484 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -31,9 +31,9 @@ env: #### #### Cache-image names to test with ### - FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-5170730531028992" - PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-5170730531028992" - UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-5170730531028992" + FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-5171433328607232" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-5171433328607232" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-5171433328607232" #### #### Variables for composing new cache-images (used in PR testing) from diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 5e26d9bfd..054b01247 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -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( diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index b8d77602d..898c81515 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -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" @@ -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 } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index d1f704374..3c9b17804 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -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" @@ -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 } @@ -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 diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index e031129d7..33e240895 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -66,7 +66,8 @@ ooe.sh sudo dnf install -y \ unzip \ vim \ which \ - xz + xz \ + zip install_varlink diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index 56d7f962e..17e274d97 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -86,7 +86,8 @@ ooe.sh sudo -E apt-get -qq install \ socat \ unzip \ vim \ - xz-utils + xz-utils \ + zip echo "Fixing Ubuntu kernel not enabling swap accounting by default" SEDCMD='s/^GRUB_CMDLINE_LINUX="(.*)"/GRUB_CMDLINE_LINUX="\1 cgroup_enable=memory swapaccount=1"/g' diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile index e44c2fd4f..9a6f5dc8d 100644 --- a/contrib/gate/Dockerfile +++ b/contrib/gate/Dockerfile @@ -31,11 +31,12 @@ RUN dnf -y install \ python3-pytoml \ python3-pyyaml \ python3-varlink \ - containers-common \ - slirp4netns \ rsync \ + slirp4netns \ + unzip \ which \ xz \ + zip \ && dnf clean all ENV GOPATH="/go" \ diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 58e579605..cbd6d9a99 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -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 diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index a9484a517..78e8a5d6e 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -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 diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index fde6118af..ec9276344 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -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()) @@ -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 } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index a08cfd36a..098ca7830 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -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) @@ -59,6 +60,12 @@ rootless_uid () return rootless_uid_init; } +uid_t +rootless_gid () +{ + return rootless_gid_init; +} + static void do_pause () { @@ -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); @@ -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); @@ -310,6 +319,7 @@ static void __attribute__((constructor)) init() free (cwd); rootless_uid_init = uid; + rootless_gid_init = gid; } } @@ -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); @@ -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) @@ -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) { @@ -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; @@ -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) @@ -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; diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index ddf881368..9132c0fe5 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -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); @@ -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") != "" }) @@ -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 { diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index 42f8f3aec..221baff97 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -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 diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 2a52e5129..a074f276c 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -3,6 +3,7 @@ package util import ( "fmt" "os" + ouser "os/user" "path/filepath" "strings" "sync" @@ -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" @@ -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 } diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index 1dfa8f50d..71fc064a5 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -39,11 +39,10 @@ var _ = Describe("Podman cp", func() { }) It("podman cp file", func() { - path, err := os.Getwd() - Expect(err).To(BeNil()) - filePath := filepath.Join(path, "cp_test.txt") + srcPath := filepath.Join(podmanTest.RunRoot, "cp_test.txt") + dstPath := filepath.Join(podmanTest.RunRoot, "cp_from_container") fromHostToContainer := []byte("copy from host to container") - err = ioutil.WriteFile(filePath, fromHostToContainer, 0644) + err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644) Expect(err).To(BeNil()) session := podmanTest.Podman([]string{"create", ALPINE, "cat", "foo"}) @@ -51,24 +50,22 @@ var _ = Describe("Podman cp", func() { Expect(session.ExitCode()).To(Equal(0)) name := session.OutputToString() - session = podmanTest.Podman([]string{"cp", filepath.Join(path, "cp_test.txt"), name + ":foo"}) + session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"cp", name + ":foo", filepath.Join(path, "cp_from_container")}) + session = podmanTest.Podman([]string{"cp", name + ":foo", dstPath}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - - os.Remove("cp_from_container") - os.Remove("cp_test.txt") }) It("podman cp file to dir", func() { - path, err := os.Getwd() - Expect(err).To(BeNil()) - filePath := filepath.Join(path, "cp_test.txt") + srcPath := filepath.Join(podmanTest.RunRoot, "cp_test.txt") + dstDir := filepath.Join(podmanTest.RunRoot, "receive") fromHostToContainer := []byte("copy from host to container directory") - err = ioutil.WriteFile(filePath, fromHostToContainer, 0644) + err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644) + Expect(err).To(BeNil()) + err = os.Mkdir(dstDir, 0755) Expect(err).To(BeNil()) session := podmanTest.Podman([]string{"create", ALPINE, "ls", "foodir/"}) @@ -76,11 +73,11 @@ var _ = Describe("Podman cp", func() { Expect(session.ExitCode()).To(Equal(0)) name := session.OutputToString() - session = podmanTest.Podman([]string{"cp", filepath.Join(path, "cp_test.txt"), name + ":foodir/"}) + session = podmanTest.Podman([]string{"cp", srcPath, name + ":foodir/"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"cp", name + ":foodir/cp_test.txt", path + "/receive/"}) + session = podmanTest.Podman([]string{"cp", name + ":foodir/cp_test.txt", dstDir}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -89,10 +86,8 @@ var _ = Describe("Podman cp", func() { }) It("podman cp dir to dir", func() { - path, err := os.Getwd() - Expect(err).To(BeNil()) - testDirPath := filepath.Join(path, "TestDir") - err = os.Mkdir(testDirPath, 0777) + testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir") + err := os.Mkdir(testDirPath, 0755) Expect(err).To(BeNil()) session := podmanTest.Podman([]string{"create", ALPINE, "ls", "/foodir"}) @@ -107,15 +102,11 @@ var _ = Describe("Podman cp", func() { session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - - os.RemoveAll(testDirPath) }) It("podman cp stdin/stdout", func() { - path, err := os.Getwd() - Expect(err).To(BeNil()) - testDirPath := filepath.Join(path, "TestDir") - err = os.Mkdir(testDirPath, 0777) + testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir") + err := os.Mkdir(testDirPath, 0755) Expect(err).To(BeNil()) cmd := exec.Command("tar", "-zcvf", "file.tar.gz", testDirPath) _, err = cmd.Output() @@ -139,8 +130,5 @@ var _ = Describe("Podman cp", func() { session = podmanTest.Podman([]string{"cp", name + ":/foo.tar.gz", "-"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - - os.Remove("file.tar.gz") - os.RemoveAll(testDirPath) }) }) diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index f7f0e1c9a..ce6971cd1 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -3,6 +3,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -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()) + }) }) |