Merge pull request #10589 from umohnani8/pod-userns

Add support for pod inside of user namespace.
This commit is contained in:
openshift-ci[bot] 2021-08-10 12:55:52 +00:00 committed by GitHub
commit e136ad485c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 421 additions and 60 deletions

View File

@ -184,6 +184,9 @@ func createInit(c *cobra.Command) error {
if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed {
return errors.Errorf("--cpu-quota and --cpus cannot be set together")
}
if c.Flag("pod").Changed && !strings.HasPrefix(c.Flag("pod").Value.String(), "new:") && c.Flag("userns").Changed {
return errors.Errorf("--userns and --pod cannot be set together")
}
noHosts, err := c.Flags().GetBool("no-hosts")
if err != nil {
@ -309,6 +312,12 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions
if len(podName) < 1 {
return nil, errors.Errorf("new pod name must be at least one character")
}
userns, err := specgen.ParseUserNamespace(cliVals.UserNS)
if err != nil {
return nil, err
}
createOptions := entities.PodCreateOptions{
Name: podName,
Infra: true,
@ -318,6 +327,7 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions
Cpus: cliVals.CPUS,
CpusetCpus: cliVals.CPUSetCPUs,
Pid: cliVals.PID,
Userns: userns,
}
// Unset config values we passed to the pod to prevent them being used twice for the container and pod.
s.ContainerBasicConfig.Hostname = ""

View File

@ -48,6 +48,7 @@ var (
podIDFile string
replace bool
share string
userns string
)
func init() {
@ -72,6 +73,10 @@ func init() {
flags.StringVar(&createOptions.CGroupParent, cgroupParentflagName, "", "Set parent cgroup for the pod")
_ = createCommand.RegisterFlagCompletionFunc(cgroupParentflagName, completion.AutocompleteDefault)
usernsFlagName := "userns"
flags.StringVar(&userns, usernsFlagName, os.Getenv("PODMAN_USERNS"), "User namespace to use")
_ = createCommand.RegisterFlagCompletionFunc(usernsFlagName, common.AutocompleteUserNamespace)
flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with")
infraConmonPidfileFlagName := "infra-conmon-pidfile"
@ -178,6 +183,11 @@ func create(cmd *cobra.Command, args []string) error {
}
}
createOptions.Userns, err = specgen.ParseUserNamespace(userns)
if err != nil {
return err
}
if cmd.Flag("pod-id-file").Changed {
podIDFD, err = util.OpenExclusiveFile(podIDFile)
if err != nil && os.IsExist(err) {

View File

@ -1123,9 +1123,9 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subpordina
Valid `auto`options:
- *gidmapping*=_HOST_GID:CONTAINER_GID:SIZE_: to force a GID mapping to be present in the user namespace.
- *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*=_HOST_UID:CONTAINER_UID:SIZE_: to force a UID mapping to be present in the user namespace.
- *uidmapping*=_CONTAINER_UID:HOST_UID:SIZE_: to force a UID mapping to be present in the user namespace.
**container:**_id_: join the user namespace of the specified container.

View File

@ -164,6 +164,19 @@ podman generates a UUID for each pod, and if a name is not assigned
to the container with **--name** then a random string name will be generated
for it. The name is useful any place you need to identify a pod.
#### **--userns**=*mode*
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.
Valid _mode_ values are:
- *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.
## EXAMPLES
```

View File

@ -1181,9 +1181,9 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subpordina
Valid `auto`options:
- *gidmapping*=_HOST_GID:CONTAINER_GID:SIZE_: to force a GID mapping to be present in the user namespace.
- *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*=_HOST_UID:CONTAINER_UID:SIZE_: to force a UID mapping to be present in the user namespace.
- *uidmapping*=_CONTAINER_UID:HOST_UID:SIZE_: to force a UID mapping to be present in the user namespace.
**container:**_id_: join the user namespace of the specified container.

View File

@ -1020,8 +1020,8 @@ func (c *Container) RWSize() (int64, error) {
}
// IDMappings returns the UID/GID mapping used for the container
func (c *Container) IDMappings() (storage.IDMappingOptions, error) {
return c.config.IDMappings, nil
func (c *Container) IDMappings() storage.IDMappingOptions {
return c.config.IDMappings
}
// RootUID returns the root user mapping from container

View File

@ -367,6 +367,12 @@ func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) {
return
}
*dest = *from
// If we are creating a container inside a pod, we always want to inherit the
// userns settings from the infra container. So clear the auto userns settings
// so that we don't request storage for a new uid/gid map.
if c.PodID() != "" && !c.IsInfra() {
dest.AutoUserNs = false
}
if dest.AutoUserNs {
overrides := c.getUserOverrides()
dest.AutoUserNsOpts.PasswdFile = overrides.ContainerEtcPasswdPath

View File

@ -659,7 +659,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
}
}
if c.config.IDMappings.AutoUserNs {
if c.config.UserNsCtr == "" && c.config.IDMappings.AutoUserNs {
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
return nil, err
}
@ -1884,7 +1884,7 @@ func (c *Container) generateResolvConf() (string, error) {
return "", err
}
return filepath.Join(c.state.RunDir, "resolv.conf"), nil
return destPath, nil
}
// generateHosts creates a containers hosts file

View File

@ -105,6 +105,8 @@ type InspectPodInfraConfig struct {
CPUSetCPUs string `json:"cpuset_cpus,omitempty"`
// Pid is the PID namespace mode of the pod's infra container
PidNS string `json:"pid_ns,omitempty"`
// UserNS is the usernamespace that all the containers in the pod will join.
UserNS string `json:"userns,omitempty"`
}
// InspectPodContainerInfo contains information on a container in a pod.

View File

@ -956,8 +956,9 @@ func WithUserNSFrom(nsCtr *Container) CtrCreateOption {
}
ctr.config.UserNsCtr = nsCtr.ID()
ctr.config.IDMappings = nsCtr.config.IDMappings
if err := JSONDeepCopy(nsCtr.IDMappings(), &ctr.config.IDMappings); err != nil {
return err
}
g := generate.Generator{Config: ctr.config.Spec}
g.ClearLinuxUIDMappings()
@ -968,7 +969,6 @@ func WithUserNSFrom(nsCtr *Container) CtrCreateOption {
for _, gidmap := range nsCtr.config.IDMappings.GIDMap {
g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
}
ctr.config.IDMappings = nsCtr.config.IDMappings
return nil
}
}
@ -2423,6 +2423,24 @@ func WithVolatile() CtrCreateOption {
}
ctr.config.Volatile = true
return nil
}
}
// WithPodUserns sets the userns for the infra container in a pod.
func WithPodUserns(userns specgen.Namespace) PodCreateOption {
return func(pod *Pod) error {
if pod.valid {
return define.ErrPodFinalized
}
if !pod.config.InfraContainer.HasInfraContainer {
return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod userns as no infra container is being created")
}
pod.config.InfraContainer.Userns = userns
return nil
}
}

View File

@ -117,6 +117,7 @@ type InfraContainerConfig struct {
Slirp4netns bool `json:"slirp4netns,omitempty"`
NetworkOptions map[string][]string `json:"network_options,omitempty"`
ResourceLimits *specs.LinuxResources `json:"resource_limits,omitempty"`
Userns specgen.Namespace `json:"userns,omitempty"`
}
// ID retrieves the pod's ID

View File

@ -593,6 +593,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
infraConfig.CPUQuota = p.CPUQuota()
infraConfig.CPUSetCPUs = p.ResourceLim().CPU.Cpus
infraConfig.PidNS = p.PidMode()
infraConfig.UserNS = p.config.InfraContainer.Userns.String()
if len(p.config.InfraContainer.DNSServer) > 0 {
infraConfig.DNSServer = make([]string, 0, len(p.config.InfraContainer.DNSServer))

View File

@ -8,7 +8,9 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/namespaces"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
spec "github.com/opencontainers/runtime-spec/specs-go"
@ -110,9 +112,7 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
options = append(options, WithNetworkOptions(p.config.InfraContainer.NetworkOptions))
}
}
// PostConfigureNetNS should not be set since user namespace sharing is not implemented
// and rootless networking no longer supports post configuration setup
options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, false, netmode, p.config.InfraContainer.Networks))
options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, !p.config.InfraContainer.Userns.IsHost(), netmode, p.config.InfraContainer.Networks))
}
// For each option in InfraContainerConfig - if set, pass into
@ -158,11 +158,39 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
g.Config.Linux.Namespaces = newNS
}
}
for _, ctl := range r.config.Containers.DefaultSysctls {
sysctl := strings.SplitN(ctl, "=", 2)
if len(sysctl) < 2 {
return nil, errors.Errorf("invalid default sysctl %s", ctl)
}
// Ignore net sysctls if --net=host
if p.config.InfraContainer.HostNetwork && strings.HasPrefix(sysctl[0], "net.") {
logrus.Infof("Sysctl %s=%s ignored in containers.conf, since Network Namespace set to host", sysctl[0], sysctl[1])
continue
}
g.AddLinuxSysctl(sysctl[0], sysctl[1])
}
g.SetRootReadonly(true)
g.SetProcessArgs(infraCtrCommand)
logrus.Debugf("Using %q as infra container command", infraCtrCommand)
mapopt, err := util.ParseIDMapping(namespaces.UsernsMode(p.config.InfraContainer.Userns.String()), []string{}, []string{}, "", "")
if err != nil {
return nil, err
}
user, err := specgen.SetupUserNS(mapopt, p.config.InfraContainer.Userns, &g)
if err != nil {
return nil, err
}
if user != "" {
options = append(options, WithUser(user))
}
g.RemoveMount("/dev/shm")
if isRootless {
g.RemoveMount("/dev/pts")
@ -210,14 +238,15 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
options = append(options, WithRootFSFromImage(imgID, imgName, rawImageName))
options = append(options, WithName(containerName))
options = append(options, withIsInfra())
options = append(options, WithIDMappings(*mapopt))
if len(p.config.InfraContainer.ConmonPidFile) > 0 {
options = append(options, WithConmonPidFile(p.config.InfraContainer.ConmonPidFile))
}
newRes := new(spec.LinuxResources)
newRes.CPU = new(spec.LinuxCPU)
newRes.CPU = p.ResourceLim().CPU
g.Config.Linux.Resources.CPU = newRes.CPU
return r.newContainer(ctx, g.Config, options...)
}

View File

@ -30,6 +30,12 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
return
}
// parse userns so we get the valid default value of userns
psg.Userns, err = specgen.ParseUserNamespace(psg.Userns.String())
if err != nil {
utils.Error(w, "failed to parse userns", http.StatusInternalServerError, errors.Wrap(err, "failed to parse userns"))
return
}
pod, err := generate.MakePod(&psg, runtime)
if err != nil {
httpCode := http.StatusInternalServerError

View File

@ -122,6 +122,7 @@ type PodCreateOptions struct {
Pid string
Cpus float64
CpusetCpus string
Userns specgen.Namespace
}
type PodCreateReport struct {
@ -217,6 +218,7 @@ func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error {
s.CPUQuota = *cpuDat.Quota
}
}
s.Userns = p.Userns
return nil
}

View File

@ -303,6 +303,8 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
if opts.NetNSIsHost {
s.NetNS.NSMode = specgen.Host
}
// Always set the userns to host since k8s doesn't have support for userns yet
s.UserNS.NSMode = specgen.Host
// Add labels that come from kube
if len(s.Labels) == 0 {

View File

@ -175,6 +175,11 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if pod == nil || infraCtr == nil {
return nil, errNoInfra
}
// Inherit the user from the infra container if it is set and --user has not
// been set explicitly
if infraCtr.User() != "" && s.User == "" {
toReturn = append(toReturn, libpod.WithUser(infraCtr.User()))
}
toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr))
case specgen.FromContainer:
userCtr, err := rt.LookupContainer(s.UserNS.Value)
@ -184,7 +189,10 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr))
}
if s.IDMappings != nil {
// This wipes the UserNS settings that get set from the infra container
// when we are inheritting from the pod. So only apply this if the container
// is not being created in a pod.
if s.IDMappings != nil && pod == nil {
toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings))
}
if s.User != "" {
@ -379,46 +387,8 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt
}
// User
switch s.UserNS.NSMode {
case specgen.Path:
if _, err := os.Stat(s.UserNS.Value); err != nil {
return errors.Wrap(err, "cannot find specified user namespace path")
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil {
return err
}
// runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
case specgen.Host:
if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil {
return err
}
case specgen.KeepID:
var (
err error
uid, gid int
)
s.IDMappings, uid, gid, err = util.GetKeepIDMapping()
if err != nil {
return err
}
g.SetProcessUID(uint32(uid))
g.SetProcessGID(uint32(gid))
fallthrough
case specgen.Private:
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
return err
}
if s.IDMappings == nil || (len(s.IDMappings.UIDMap) == 0 && len(s.IDMappings.GIDMap) == 0) {
return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace")
}
for _, uidmap := range s.IDMappings.UIDMap {
g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
}
for _, gidmap := range s.IDMappings.GIDMap {
g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
}
if _, err := specgen.SetupUserNS(s.IDMappings, s.UserNS, g); err != nil {
return err
}
// Cgroup
@ -474,7 +444,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt
// GetNamespaceOptions transforms a slice of kernel namespaces
// into a slice of pod create options. Currently, not all
// kernel namespaces are supported, and they will be returned in an error
func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) {
func GetNamespaceOptions(ns []string, netnsIsHost bool) ([]libpod.PodCreateOption, error) {
var options []libpod.PodCreateOption
var erroredOptions []libpod.PodCreateOption
if ns == nil {
@ -486,7 +456,10 @@ func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) {
case "cgroup":
options = append(options, libpod.WithPodCgroups())
case "net":
options = append(options, libpod.WithPodNet())
// share the netns setting with other containers in the pod only when it is not set to host
if !netnsIsHost {
options = append(options, libpod.WithPodNet())
}
case "mnt":
return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level")
case "pid":

View File

@ -27,11 +27,16 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod
)
if !p.NoInfra {
options = append(options, libpod.WithInfraContainer())
nsOptions, err := GetNamespaceOptions(p.SharedNamespaces)
nsOptions, err := GetNamespaceOptions(p.SharedNamespaces, p.NetNS.IsHost())
if err != nil {
return nil, err
}
options = append(options, nsOptions...)
// Use pod user and infra userns only when --userns is not set to host
if !p.Userns.IsHost() {
options = append(options, libpod.WithPodUser())
options = append(options, libpod.WithPodUserns(p.Userns))
}
// Make our exit command
storageConfig := rt.StorageConfig()
@ -154,5 +159,6 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod
if len(p.InfraConmonPidFile) > 0 {
options = append(options, libpod.WithInfraConmonPidFile(p.InfraConmonPidFile))
}
return options, nil
}

View File

@ -1,10 +1,16 @@
package specgen
import (
"fmt"
"os"
"strings"
"github.com/containers/podman/v3/pkg/cgroups"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
"github.com/containers/storage"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
)
@ -103,6 +109,13 @@ func (n *Namespace) IsKeepID() bool {
return n.NSMode == KeepID
}
func (n *Namespace) String() string {
if n.Value != "" {
return fmt.Sprintf("%s:%s", n.NSMode, n.Value)
}
return string(n.NSMode)
}
func validateUserNS(n *Namespace) error {
if n == nil {
return nil
@ -323,3 +336,48 @@ func ParseNetworkString(network string) (Namespace, []string, map[string][]strin
}
return ns, cniNets, networkOptions, nil
}
func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) {
// User
var user string
switch userns.NSMode {
case Path:
if _, err := os.Stat(userns.Value); err != nil {
return user, errors.Wrap(err, "cannot find specified user namespace path")
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), userns.Value); err != nil {
return user, err
}
// runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
case Host:
if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil {
return user, err
}
case KeepID:
mappings, uid, gid, err := util.GetKeepIDMapping()
if err != nil {
return user, err
}
idmappings = mappings
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 {
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")
}
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 user, nil
}

View File

@ -67,6 +67,10 @@ type PodBasicConfig struct {
// Optional (defaults to private if unset). This sets the PID namespace of the infra container
// This configuration will then be shared with the entire pod if PID namespace sharing is enabled via --share
Pid Namespace `json:"pid,omitempty:"`
// Userns is used to indicate which kind of Usernamespace to enter.
// Any containers created within the pod will inherit the pod's userns settings.
// Optional
Userns Namespace `json:"userns,omitempty"`
}
// PodNetworkConfig contains networking configuration for a pod.

View File

@ -1114,7 +1114,7 @@ var _ = Describe("Podman play kube", func() {
})
It("podman play kube should share ipc,net,uts when shareProcessNamespace is set", func() {
SkipIfRootless("Requires root priviledges for sharing few namespaces")
SkipIfRootless("Requires root privileges for sharing few namespaces")
err := writeYaml(sharedNamespacePodYaml, kubeYaml)
Expect(err).To(BeNil())

View File

@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
@ -621,4 +622,223 @@ ENTRYPOINT ["sleep","99999"]
Expect(podCreate).Should(ExitWithError())
})
It("podman pod create with --userns=keep-id", func() {
if os.Geteuid() == 0 {
Skip("Test only runs without root")
}
podName := "testPod"
podCreate := podmanTest.Podman([]string{"pod", "create", "--userns", "keep-id", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session := podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "id", "-u"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
uid := fmt.Sprintf("%d", os.Geteuid())
ok, _ := session.GrepString(uid)
Expect(ok).To(BeTrue())
// Check passwd
session = podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "id", "-un"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
u, err := user.Current()
Expect(err).To(BeNil())
ok, _ = session.GrepString(u.Name)
Expect(ok).To(BeTrue())
// root owns /usr
session = podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "stat", "-c%u", "/usr"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(Equal("0"))
// fail if --pod and --userns set together
session = podmanTest.Podman([]string{"run", "--pod", podName, "--userns", "keep-id", ALPINE, "id", "-u"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
})
It("podman pod create with --userns=keep-id can add users", func() {
if os.Geteuid() == 0 {
Skip("Test only runs without root")
}
podName := "testPod"
podCreate := podmanTest.Podman([]string{"pod", "create", "--userns", "keep-id", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
ctrName := "ctr-name"
session := podmanTest.Podman([]string{"run", "--pod", podName, "-d", "--stop-signal", "9", "--name", ctrName, fedoraMinimal, "sleep", "600"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// container inside pod inherits user form infra container if --user is not set
// etc/passwd entry will look like 1000:*:1000:1000:container user:/:/bin/sh
exec1 := podmanTest.Podman([]string{"exec", ctrName, "cat", "/etc/passwd"})
exec1.WaitWithDefaultTimeout()
Expect(exec1).Should(Exit(0))
Expect(exec1.OutputToString()).To(ContainSubstring("container"))
exec2 := podmanTest.Podman([]string{"exec", ctrName, "useradd", "testuser"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(Exit(0))
exec3 := podmanTest.Podman([]string{"exec", ctrName, "cat", "/etc/passwd"})
exec3.WaitWithDefaultTimeout()
Expect(exec3).Should(Exit(0))
Expect(exec3.OutputToString()).To(ContainSubstring("testuser"))
})
It("podman pod create with --userns=auto", func() {
u, err := user.Current()
Expect(err).To(BeNil())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := ioutil.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
m := make(map[string]string)
for i := 0; i < 5; i++ {
podName := "testPod" + strconv.Itoa(i)
podCreate := podmanTest.Podman([]string{"pod", "create", "--userns=auto", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session := podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
l := session.OutputToString()
Expect(strings.Contains(l, "1024")).To(BeTrue())
m[l] = l
}
// check for no duplicates
Expect(len(m)).To(Equal(5))
})
It("podman pod create --userns=auto:size=%d", func() {
u, err := user.Current()
Expect(err).To(BeNil())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := ioutil.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
podName := "testPod"
podCreate := podmanTest.Podman([]string{"pod", "create", "--userns=auto:size=500", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session := podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
ok, _ := session.GrepString("500")
podName = "testPod-1"
podCreate = podmanTest.Podman([]string{"pod", "create", "--userns=auto:size=3000", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session = podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
ok, _ = session.GrepString("3000")
Expect(ok).To(BeTrue())
})
It("podman pod create --userns=auto:uidmapping=", func() {
u, err := user.Current()
Expect(err).To(BeNil())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := ioutil.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
podName := "testPod"
podCreate := podmanTest.Podman([]string{"pod", "create", "--userns=auto:uidmapping=0:0:1", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session := podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
output := session.OutputToString()
Expect(output).To(MatchRegexp("\\s0\\s0\\s1"))
podName = "testPod-1"
podCreate = podmanTest.Podman([]string{"pod", "create", "--userns=auto:size=8192,uidmapping=0:0:1", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session = podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
ok, _ := session.GrepString("8191")
Expect(ok).To(BeTrue())
})
It("podman pod create --userns=auto:gidmapping=", func() {
u, err := user.Current()
Expect(err).To(BeNil())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := ioutil.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
podName := "testPod"
podCreate := podmanTest.Podman([]string{"pod", "create", "--userns=auto:gidmapping=0:0:1", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session := podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/gid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
output := session.OutputToString()
Expect(output).To(MatchRegexp("\\s0\\s0\\s1"))
podName = "testPod-1"
podCreate = podmanTest.Podman([]string{"pod", "create", "--userns=auto:size=8192,gidmapping=0:0:1", "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
session = podmanTest.Podman([]string{"run", "--pod", podName, ALPINE, "cat", "/proc/self/gid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
ok, _ := session.GrepString("8191")
Expect(ok).To(BeTrue())
})
})