diff options
28 files changed, 453 insertions, 169 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 @@ -147,7 +147,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func RestartPod(name: string) string](#RestartPod) -[func SearchImages(query: string, limit: , filter: ImageSearchFilter) ImageSearchResult](#SearchImages) +[func SearchImages(query: string, limit: ?int, filter: ImageSearchFilter) ImageSearchResult](#SearchImages) [func SendFile(type: string, length: int) string](#SendFile) @@ -1012,7 +1012,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.RestartPod '{"name": "135 ### <a name="SearchImages"></a>func SearchImages <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method SearchImages(query: [string](https://godoc.org/builtin#string), limit: [](#), filter: [ImageSearchFilter](#ImageSearchFilter)) [ImageSearchResult](#ImageSearchResult)</div> +method SearchImages(query: [string](https://godoc.org/builtin#string), limit: [?int](#?int), filter: [ImageSearchFilter](#ImageSearchFilter)) [ImageSearchResult](#ImageSearchResult)</div> SearchImages searches available registries for images that contain the contents of "query" in their name. If "limit" is given, limits the amount of search results per registry. @@ -1352,197 +1352,197 @@ Create is an input structure for creating containers. args [[]string](#[]string) -addHost [](#) +addHost [?[]string](#?[]string) -annotation [](#) +annotation [?[]string](#?[]string) -attach [](#) +attach [?[]string](#?[]string) -blkioWeight [](#) +blkioWeight [?string](#?string) -blkioWeightDevice [](#) +blkioWeightDevice [?[]string](#?[]string) -capAdd [](#) +capAdd [?[]string](#?[]string) -capDrop [](#) +capDrop [?[]string](#?[]string) -cgroupParent [](#) +cgroupParent [?string](#?string) -cidFile [](#) +cidFile [?string](#?string) -conmonPidfile [](#) +conmonPidfile [?string](#?string) -command [](#) +command [?[]string](#?[]string) -cpuPeriod [](#) +cpuPeriod [?int](#?int) -cpuQuota [](#) +cpuQuota [?int](#?int) -cpuRtPeriod [](#) +cpuRtPeriod [?int](#?int) -cpuRtRuntime [](#) +cpuRtRuntime [?int](#?int) -cpuShares [](#) +cpuShares [?int](#?int) -cpus [](#) +cpus [?float](#?float) -cpuSetCpus [](#) +cpuSetCpus [?string](#?string) -cpuSetMems [](#) +cpuSetMems [?string](#?string) -detach [](#) +detach [?bool](#?bool) -detachKeys [](#) +detachKeys [?string](#?string) -device [](#) +device [?[]string](#?[]string) -deviceReadBps [](#) +deviceReadBps [?[]string](#?[]string) -deviceReadIops [](#) +deviceReadIops [?[]string](#?[]string) -deviceWriteBps [](#) +deviceWriteBps [?[]string](#?[]string) -deviceWriteIops [](#) +deviceWriteIops [?[]string](#?[]string) -dns [](#) +dns [?[]string](#?[]string) -dnsOpt [](#) +dnsOpt [?[]string](#?[]string) -dnsSearch [](#) +dnsSearch [?[]string](#?[]string) -dnsServers [](#) +dnsServers [?[]string](#?[]string) -entrypoint [](#) +entrypoint [?string](#?string) -env [](#) +env [?[]string](#?[]string) -envFile [](#) +envFile [?[]string](#?[]string) -expose [](#) +expose [?[]string](#?[]string) -gidmap [](#) +gidmap [?[]string](#?[]string) -groupadd [](#) +groupadd [?[]string](#?[]string) -healthcheckCommand [](#) +healthcheckCommand [?string](#?string) -healthcheckInterval [](#) +healthcheckInterval [?string](#?string) -healthcheckRetries [](#) +healthcheckRetries [?int](#?int) -healthcheckStartPeriod [](#) +healthcheckStartPeriod [?string](#?string) -healthcheckTimeout [](#) +healthcheckTimeout [?string](#?string) -hostname [](#) +hostname [?string](#?string) -imageVolume [](#) +imageVolume [?string](#?string) -init [](#) +init [?bool](#?bool) -initPath [](#) +initPath [?string](#?string) -interactive [](#) +interactive [?bool](#?bool) -ip [](#) +ip [?string](#?string) -ipc [](#) +ipc [?string](#?string) -kernelMemory [](#) +kernelMemory [?string](#?string) -label [](#) +label [?[]string](#?[]string) -labelFile [](#) +labelFile [?[]string](#?[]string) -logDriver [](#) +logDriver [?string](#?string) -logOpt [](#) +logOpt [?[]string](#?[]string) -macAddress [](#) +macAddress [?string](#?string) -memory [](#) +memory [?string](#?string) -memoryReservation [](#) +memoryReservation [?string](#?string) -memorySwap [](#) +memorySwap [?string](#?string) -memorySwappiness [](#) +memorySwappiness [?int](#?int) -name [](#) +name [?string](#?string) -net [](#) +net [?string](#?string) -network [](#) +network [?string](#?string) -noHosts [](#) +noHosts [?bool](#?bool) -oomKillDisable [](#) +oomKillDisable [?bool](#?bool) -oomScoreAdj [](#) +oomScoreAdj [?int](#?int) -pid [](#) +pid [?string](#?string) -pidsLimit [](#) +pidsLimit [?int](#?int) -pod [](#) +pod [?string](#?string) -privileged [](#) +privileged [?bool](#?bool) -publish [](#) +publish [?[]string](#?[]string) -publishAll [](#) +publishAll [?bool](#?bool) -quiet [](#) +quiet [?bool](#?bool) -readonly [](#) +readonly [?bool](#?bool) -readonlytmpfs [](#) +readonlytmpfs [?bool](#?bool) -restart [](#) +restart [?string](#?string) -rm [](#) +rm [?bool](#?bool) -rootfs [](#) +rootfs [?bool](#?bool) -securityOpt [](#) +securityOpt [?[]string](#?[]string) -shmSize [](#) +shmSize [?string](#?string) -stopSignal [](#) +stopSignal [?string](#?string) -stopTimeout [](#) +stopTimeout [?int](#?int) -storageOpt [](#) +storageOpt [?[]string](#?[]string) -subuidname [](#) +subuidname [?string](#?string) -subgidname [](#) +subgidname [?string](#?string) -sysctl [](#) +sysctl [?[]string](#?[]string) -systemd [](#) +systemd [?bool](#?bool) -tmpfs [](#) +tmpfs [?[]string](#?[]string) -tty [](#) +tty [?bool](#?bool) -uidmap [](#) +uidmap [?[]string](#?[]string) -ulimit [](#) +ulimit [?[]string](#?[]string) -user [](#) +user [?string](#?string) -userns [](#) +userns [?string](#?string) -uts [](#) +uts [?string](#?string) -mount [](#) +mount [?[]string](#?[]string) -volume [](#) +volume [?[]string](#?[]string) -volumesFrom [](#) +volumesFrom [?[]string](#?[]string) -workDir [](#) +workDir [?string](#?string) ### <a name="DiffInfo"></a>type DiffInfo @@ -1628,9 +1628,9 @@ compress [bool](https://godoc.org/builtin#bool) -is_official [](#) +is_official [?bool](#?bool) -is_automated [](#) +is_automated [?bool](#?bool) star_count [int](https://godoc.org/builtin#int) ### <a name="ImageSearchResult"></a>type ImageSearchResult @@ -1882,21 +1882,21 @@ mounts [string](https://godoc.org/builtin#string) all [bool](https://godoc.org/builtin#bool) -filters [](#) +filters [?[]string](#?[]string) -last [](#) +last [?int](#?int) -latest [](#) +latest [?bool](#?bool) -noTrunc [](#) +noTrunc [?bool](#?bool) -pod [](#) +pod [?bool](#?bool) -quiet [](#) +quiet [?bool](#?bool) -sort [](#) +sort [?string](#?string) -sync [](#) +sync [?bool](#?bool) ### <a name="Runlabel"></a>type Runlabel Runlabel describes the required input for container runlabel 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/create.go b/cmd/podman/create.go index cb3ba14c5..2351f5860 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/pkg/adapter" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -72,6 +73,10 @@ func createInit(c *cliconfig.PodmanCommand) error { defer span.Finish() } + if c.IsSet("privileged") && c.IsSet("security-opt") { + logrus.Warn("setting security options with --privileged has no effect") + } + // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). 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/docs/varlink/apidoc.go b/docs/varlink/apidoc.go index 7f1d60bc8..884ce54fe 100644 --- a/docs/varlink/apidoc.go +++ b/docs/varlink/apidoc.go @@ -44,6 +44,8 @@ func typeToString(input *idl.Type) string { return "map[string]" case idl.TypeInt: return "int" + case idl.TypeMaybe: + return fmt.Sprintf("?%s", typeToString(input.ElementType)) } return "" } diff --git a/install.md b/install.md index ae74acdf8..94de2ea74 100644 --- a/install.md +++ b/install.md @@ -159,7 +159,7 @@ git submodule update --init # for Fedora, CentOS, RHEL sudo yum install -y automake bison e2fsprogs-devel fuse-devel libtool xz-devel zlib-devel # for Debian, Ubuntu etc. -sudo apt-get install -y automake bison e2fsprogs fuse liblzma-dev libtool zlib1g +sudo apt-get install -y automake bison e2fsprogs e2fslibs-dev fuse libfuse-dev libgpgme-dev liblzma-dev libtool zlib1g ./autogen.sh --prefix=/usr --libdir=/usr/lib64 --sysconfdir=/etc # remove --nonet option due to https:/github.com/ostreedev/ostree/issues/1374 diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index c5e404155..f25f76092 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -25,7 +25,7 @@ import ( "github.com/containers/libpod/pkg/lookup" "github.com/containers/libpod/pkg/resolvconf" "github.com/containers/libpod/pkg/rootless" - "github.com/cyphar/filepath-securejoin" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -188,11 +188,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } // Apply AppArmor checks and load the default profile if needed. - updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile) - if err != nil { - return nil, err + if !c.config.Privileged { + updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile) + if err != nil { + return nil, err + } + g.SetProcessApparmorProfile(updatedProfile) } - g.SetProcessApparmorProfile(updatedProfile) if err := c.makeBindMounts(); err != nil { return nil, err diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 392a7029e..2e0e83c05 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/psgo" "github.com/pkg/errors" ) @@ -47,7 +48,9 @@ func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, // filters on the data. We need to change the API here and the // varlink API to return a [][]string if we want to make use of // filtering. - psgoOutput, err := psgo.JoinNamespaceAndProcessInfo(pid, descriptors) + opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()} + + psgoOutput, err := psgo.JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &opts) if err != nil { return nil, err } diff --git a/libpod/pod_top_linux.go b/libpod/pod_top_linux.go index f49e28c9d..e08e5e83a 100644 --- a/libpod/pod_top_linux.go +++ b/libpod/pod_top_linux.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/psgo" ) @@ -43,7 +44,8 @@ func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) { // filters on the data. We need to change the API here and the // varlink API to return a [][]string if we want to make use of // filtering. - output, err := psgo.JoinNamespaceAndProcessInfoByPids(pids, descriptors) + opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()} + output, err := psgo.JoinNamespaceAndProcessInfoByPidsWithOptions(pids, descriptors, &opts) if err != nil { return nil, err } 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 16e03472e..f8df5d3d0 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,9 +130,6 @@ 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) }) It("podman cp tar", func() { 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()) + }) }) diff --git a/test/test_podman_baseline.sh b/test/test_podman_baseline.sh index 5c24229bb..92bc8e20c 100755 --- a/test/test_podman_baseline.sh +++ b/test/test_podman_baseline.sh @@ -504,6 +504,16 @@ EOF echo "failed" fi + #Expected to pass (as root with --privileged). + #Note that the profile should not be loaded letting the mount succeed. + podman run --privileged docker.io/library/alpine:latest sh -c "mkdir tmp2; mount --bind tmp tmp2" + rc=$? + echo -n "root with specified AppArmor profile but --privileged: " + if [ $rc == 0 ]; then + echo "passed" + else + echo "failed" + fi #Expected to fail (as rootless) sudo -u "#1000" podman run --security-opt apparmor=$aaProfile docker.io/library/alpine:latest echo hello rc=$? diff --git a/vendor.conf b/vendor.conf index f9b7b128d..5c41d6908 100644 --- a/vendor.conf +++ b/vendor.conf @@ -20,7 +20,7 @@ github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 github.com/containers/storage v1.12.7 -github.com/containers/psgo v1.2.1 +github.com/containers/psgo v1.3.0 github.com/coreos/go-systemd v14 github.com/coreos/pkg v4 github.com/cri-o/ocicni 0c180f981b27ef6036fa5be29bcb4dd666e406eb diff --git a/vendor/github.com/containers/psgo/internal/proc/ns.go b/vendor/github.com/containers/psgo/internal/proc/ns.go index 5d5ef2814..53e5ebda0 100644 --- a/vendor/github.com/containers/psgo/internal/proc/ns.go +++ b/vendor/github.com/containers/psgo/internal/proc/ns.go @@ -15,10 +15,20 @@ package proc import ( + "bufio" "fmt" + "io" "os" + + "github.com/pkg/errors" ) +type IDMap struct { + ContainerID int + HostID int + Size int +} + // ParsePIDNamespace returns the content of /proc/$pid/ns/pid. func ParsePIDNamespace(pid string) (string, error) { pidNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/pid", pid)) @@ -36,3 +46,34 @@ func ParseUserNamespace(pid string) (string, error) { } return userNS, nil } + +// ReadMappings reads the user namespace mappings at the specified path +func ReadMappings(path string) ([]IDMap, error) { + file, err := os.Open(path) + if err != nil { + return nil, errors.Wrapf(err, "cannot open %s", path) + } + defer file.Close() + + mappings := []IDMap{} + + buf := bufio.NewReader(file) + for { + line, _, err := buf.ReadLine() + if err != nil { + if err == io.EOF { + return mappings, nil + } + return nil, errors.Wrapf(err, "cannot read line from %s", path) + } + if line == nil { + return mappings, nil + } + + containerID, hostID, size := 0, 0, 0 + if _, err := fmt.Sscanf(string(line), "%d %d %d", &containerID, &hostID, &size); err != nil { + return nil, errors.Wrapf(err, "cannot parse %s", string(line)) + } + mappings = append(mappings, IDMap{ContainerID: containerID, HostID: hostID, Size: size}) + } +} diff --git a/vendor/github.com/containers/psgo/internal/process/process.go b/vendor/github.com/containers/psgo/internal/process/process.go index 2aebfe9cc..68241264e 100644 --- a/vendor/github.com/containers/psgo/internal/process/process.go +++ b/vendor/github.com/containers/psgo/internal/process/process.go @@ -45,7 +45,7 @@ type Process struct { Hgroup string } -// LookupGID returns the textual group ID, if it can be optained, or the +// LookupGID returns the textual group ID, if it can be obtained, or the // decimal representation otherwise. func LookupGID(gid string) (string, error) { gidNum, err := strconv.Atoi(gid) @@ -59,7 +59,7 @@ func LookupGID(gid string) (string, error) { return g.Name, nil } -// LookupUID return the textual user ID, if it can be optained, or the decimal +// LookupUID return the textual user ID, if it can be obtained, or the decimal // representation otherwise. func LookupUID(uid string) (string, error) { uidNum, err := strconv.Atoi(uid) diff --git a/vendor/github.com/containers/psgo/psgo.go b/vendor/github.com/containers/psgo/psgo.go index f1936f917..4986c9c71 100644 --- a/vendor/github.com/containers/psgo/psgo.go +++ b/vendor/github.com/containers/psgo/psgo.go @@ -28,6 +28,7 @@ package psgo import ( "fmt" + "io/ioutil" "os" "runtime" "sort" @@ -43,6 +44,31 @@ import ( "golang.org/x/sys/unix" ) +// IDMap specifies a mapping range from the host to the container IDs. +type IDMap struct { + // ContainerID is the first ID in the container. + ContainerID int + // HostID is the first ID in the host. + HostID int + // Size specifies how long is the range. e.g. 1 means a single user + // is mapped. + Size int +} + +// JoinNamespaceOpts specifies different options for joining the specified namespaces. +type JoinNamespaceOpts struct { + // UIDMap specifies a mapping for UIDs in the container. If specified + // huser will perform the reverse mapping. + UIDMap []IDMap + // GIDMap specifies a mapping for GIDs in the container. If specified + // hgroup will perform the reverse mapping. + GIDMap []IDMap + + // FillMappings specified whether UIDMap and GIDMap must be initialized + // with the current user namespace. + FillMappings bool +} + type psContext struct { // Processes in the container. containersProcesses []*process.Process @@ -50,6 +76,8 @@ type psContext struct { hostProcesses []*process.Process // tty and pty devices. ttys *[]dev.TTY + // Various options + opts *JoinNamespaceOpts } // processFunc is used to map a given aixFormatDescriptor to a corresponding @@ -69,10 +97,36 @@ type aixFormatDescriptor struct { // onHost controls if data of the corresponding host processes will be // extracted as well. onHost bool - // procFN points to the corresponding method to etract the desired data. + // procFN points to the corresponding method to extract the desired data. procFn processFunc } +// findID converts the specified id to the host mapping +func findID(idStr string, mapping []IDMap, lookupFunc func(uid string) (string, error), overflowFile string) (string, error) { + if len(mapping) == 0 { + return idStr, nil + } + + id, err := strconv.ParseInt(idStr, 10, 0) + if err != nil { + return "", errors.Wrapf(err, "cannot parse %s", idStr) + } + for _, m := range mapping { + if int(id) >= m.ContainerID && int(id) < m.ContainerID+m.Size { + user := fmt.Sprintf("%d", m.HostID+(int(id)-m.ContainerID)) + + return lookupFunc(user) + } + } + + // User not found, read the overflow + overflow, err := ioutil.ReadFile(overflowFile) + if err != nil { + return "", errors.Wrapf(err, "cannot read %s", overflowFile) + } + return string(overflow), nil +} + // translateDescriptors parses the descriptors and returns a correspodning slice of // aixFormatDescriptors. Descriptors can be specified in the normal and in the // code form (if supported). If the descriptors slice is empty, the @@ -272,6 +326,46 @@ func ListDescriptors() (list []string) { // JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins // the mount namespace of the specified pid before extracting data from `/proc`. func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error) { + return JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &JoinNamespaceOpts{}) +} + +func readMappings(path string) ([]IDMap, error) { + mappings, err := proc.ReadMappings(path) + if err != nil { + return nil, err + } + var res []IDMap + for _, i := range mappings { + m := IDMap{ContainerID: i.ContainerID, HostID: i.HostID, Size: i.Size} + res = append(res, m) + } + return res, nil +} + +func contextFromOptions(options *JoinNamespaceOpts) (*psContext, error) { + ctx := new(psContext) + ctx.opts = options + if ctx.opts != nil && ctx.opts.FillMappings { + uidMappings, err := readMappings("/proc/self/uid_map") + if err != nil { + return nil, err + } + + gidMappings, err := readMappings("/proc/self/gid_map") + if err != nil { + return nil, err + } + ctx.opts.UIDMap = uidMappings + ctx.opts.GIDMap = gidMappings + + ctx.opts.FillMappings = false + } + return ctx, nil +} + +// JoinNamespaceAndProcessInfoWithOptions has the same semantics as ProcessInfo but joins +// the mount namespace of the specified pid before extracting data from `/proc`. +func JoinNamespaceAndProcessInfoWithOptions(pid string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) { var ( data [][]string dataErr error @@ -283,7 +377,10 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, return nil, err } - ctx := new(psContext) + ctx, err := contextFromOptions(options) + if err != nil { + return nil, err + } // extract data from host processes only on-demand / when at least one // of the specified descriptors requires host data @@ -356,10 +453,10 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, return data, dataErr } -// JoinNamespaceAndProcessInfoByPids has similar semantics to +// JoinNamespaceAndProcessInfoByPidsWithOptions has similar semantics to // JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving -// PID namepsace only once. -func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) { +// PID namespace only once. +func JoinNamespaceAndProcessInfoByPidsWithOptions(pids []string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) { // Extracting data from processes that share the same PID namespace // would yield duplicate results. Avoid that by extracting data only // from the first process in `pids` from a given PID namespace. @@ -385,7 +482,7 @@ func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][ data := [][]string{} for i, pid := range pidList { - pidData, err := JoinNamespaceAndProcessInfo(pid, descriptors) + pidData, err := JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, options) if os.IsNotExist(errors.Cause(err)) { // catch race conditions continue @@ -402,6 +499,13 @@ func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][ return data, nil } +// JoinNamespaceAndProcessInfoByPids has similar semantics to +// JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving +// PID namespace only once. +func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) { + return JoinNamespaceAndProcessInfoByPidsWithOptions(pids, descriptors, &JoinNamespaceOpts{}) +} + // ProcessInfo returns the process information of all processes in the current // mount namespace. The input format must be a comma-separated list of // supported AIX format descriptors. If the input string is empty, the @@ -425,7 +529,10 @@ func ProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) return nil, err } - ctx := new(psContext) + ctx, err := contextFromOptions(nil) + if err != nil { + return nil, err + } ctx.containersProcesses, err = process.FromPIDs(pids, false) if err != nil { return nil, err @@ -725,6 +832,9 @@ func processHPID(p *process.Process, ctx *psContext) (string, error) { // of the (container) or "?" if no corresponding process could be found. func processHUSER(p *process.Process, ctx *psContext) (string, error) { if hp := findHostProcess(p, ctx); hp != nil { + if ctx.opts != nil && len(ctx.opts.UIDMap) > 0 { + return findID(p.Status.Uids[1], ctx.opts.UIDMap, process.LookupUID, "/proc/sys/fs/overflowuid") + } return hp.Huser, nil } return "?", nil @@ -735,6 +845,9 @@ func processHUSER(p *process.Process, ctx *psContext) (string, error) { // found. func processHGROUP(p *process.Process, ctx *psContext) (string, error) { if hp := findHostProcess(p, ctx); hp != nil { + if ctx.opts != nil && len(ctx.opts.GIDMap) > 0 { + return findID(p.Status.Gids[1], ctx.opts.GIDMap, process.LookupGID, "/proc/sys/fs/overflowgid") + } return hp.Hgroup, nil } return "?", nil |