diff options
-rw-r--r-- | cmd/podman/play/kube.go | 6 | ||||
-rw-r--r-- | docs/source/markdown/podman-play-kube.1.md | 39 | ||||
-rw-r--r-- | pkg/bindings/play/types.go | 2 | ||||
-rw-r--r-- | pkg/bindings/play/types_kube_options.go | 15 | ||||
-rw-r--r-- | pkg/domain/entities/play.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 13 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/play.go | 2 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/kube.go | 7 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 50 |
9 files changed, 133 insertions, 3 deletions
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 3be7396ce..5fe059139 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -98,6 +98,12 @@ func init() { ) _ = kubeCmd.RegisterFlagCompletionFunc(logOptFlagName, common.AutocompleteLogOpt) + usernsFlagName := "userns" + flags.StringVar(&kubeOptions.Userns, usernsFlagName, os.Getenv("PODMAN_USERNS"), + "User namespace to use", + ) + _ = kubeCmd.RegisterFlagCompletionFunc(usernsFlagName, common.AutocompleteUserNamespace) + flags.BoolVar(&kubeOptions.NoHosts, "no-hosts", false, "Do not create /etc/hosts within the pod's containers, instead use the version from the image") flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 8ed71b734..5c4bdc8c4 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -243,6 +243,45 @@ Require HTTPS and verify certificates when contacting registries (default: true) then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. +#### **--userns**=*mode* + +Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with the **--uidmap** and **--gidmap** options. + +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + +Valid _mode_ values are: + +**auto**[:_OPTIONS,..._]: automatically create a unique user namespace. + +The `--userns=auto` flag, requires that the user name `containers` and a range of subordinate user ids that the Podman container is allowed to use be specified in the /etc/subuid and /etc/subgid files. + +Example: `containers:2147483647:2147483648`. + +Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinate user ids. The size of the ranges is based on the number of UIDs required in the image. The number of UIDs and GIDs can be overridden with the `size` option. The `auto` options currently does not work in rootless mode + + Valid `auto` options: + + - *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. + +**container:**_id_: join the user namespace of the specified container. + +**host**: create a new namespace for the container. + +**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. + +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. + +**ns:**_namespace_: run the pod in the given existing user namespace. + ## EXAMPLES Recreate the pod and containers as described in a file called `demo.yml` diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go index dbff4304b..5aaa87b8c 100644 --- a/pkg/bindings/play/types.go +++ b/pkg/bindings/play/types.go @@ -43,4 +43,6 @@ type KubeOptions struct { LogOptions *[]string // Start - don't start the pod if false Start *bool + // Userns - define the user namespace to use. + Userns *string } diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go index d7a452ea2..54c9a8e74 100644 --- a/pkg/bindings/play/types_kube_options.go +++ b/pkg/bindings/play/types_kube_options.go @@ -272,3 +272,18 @@ func (o *KubeOptions) GetStart() bool { } return *o.Start } + +// WithUserns set field Userns to given value +func (o *KubeOptions) WithUserns(value string) *KubeOptions { + o.Userns = &value + return o +} + +// GetUserns returns value of field Userns +func (o *KubeOptions) GetUserns() string { + if o.Userns == nil { + var z string + return z + } + return *o.Userns +} diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index c9dc3f08c..bf7c33f2b 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -54,6 +54,8 @@ type PlayKubeOptions struct { LogOptions []string // Start - don't start the pod if false Start types.OptionalBool + // Userns - define the user namespace to use. + Userns string } // PlayKubePod represents a single pod and associated containers created by play kube diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index f44b46a6d..019361694 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -222,6 +222,16 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY podOpt.Net.NetworkOptions = netOpts } + if options.Userns == "" { + options.Userns = "host" + } + + // Validate the userns modes supported. + podOpt.Userns, err = specgen.ParseUserNamespace(options.Userns) + if err != nil { + return nil, err + } + // FIXME This is very hard to support properly with a good ux if len(options.StaticIPs) > *ipIndex { if !podOpt.Net.Network.IsBridge() { @@ -352,6 +362,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY infraImage := util.DefaultContainerConfig().Engine.InfraImage infraOptions := entities.NewInfraContainerCreateOptions() infraOptions.Hostname = podSpec.PodSpecGen.PodBasicConfig.Hostname + infraOptions.UserNS = options.Userns podSpec.PodSpecGen.InfraImage = infraImage podSpec.PodSpecGen.NoInfra = false podSpec.PodSpecGen.InfraContainerSpec = specgen.NewSpecGenerator(infraImage, false) @@ -428,6 +439,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY RestartPolicy: ctrRestartPolicy, SeccompPaths: seccompPaths, SecretsManager: secretsManager, + UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) @@ -476,6 +488,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY RestartPolicy: ctrRestartPolicy, SeccompPaths: seccompPaths, SecretsManager: secretsManager, + UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go index d9637254a..d731a1d6c 100644 --- a/pkg/domain/infra/tunnel/play.go +++ b/pkg/domain/infra/tunnel/play.go @@ -20,7 +20,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, opts en if opts.Annotations != nil { options.WithAnnotations(opts.Annotations) } - options.WithNoHosts(opts.NoHosts) + options.WithNoHosts(opts.NoHosts).WithUserns(opts.Userns) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { options.WithSkipTLSVerify(s == types.OptionalBoolTrue) } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 04195d15a..e4c149abf 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -120,6 +120,8 @@ type CtrSpecGenOptions struct { RestartPolicy string // NetNSIsHost tells the container to use the host netns NetNSIsHost bool + // UserNSIsHost tells the container to use the host userns + UserNSIsHost bool // SecretManager to access the secrets SecretsManager *secrets.SecretsManager // LogDriver which should be used for the container @@ -389,8 +391,9 @@ 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 + if opts.UserNSIsHost { + s.UserNS.NSMode = specgen.Host + } // Add labels that come from kube if len(s.Labels) == 0 { diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index c627ada53..216c3357c 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -8,6 +8,7 @@ import ( "net" "net/url" "os" + "os/user" "path/filepath" "strconv" "strings" @@ -3633,6 +3634,55 @@ ENV OPENJ9_JAVA_OPTIONS=%q inspect.WaitWithDefaultTimeout() Expect(start).Should(Exit(0)) Expect((inspect.InspectContainerToJSON()[0]).HostConfig.LogConfig.Tag).To(Equal("{{.ImageName}}")) + }) + + // Check that --userns=auto creates a user namespace + It("podman play kube --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") + } + + initialUsernsConfig, err := ioutil.ReadFile("/proc/self/uid_map") + Expect(err).To(BeNil()) + if os.Geteuid() != 0 { + unshare := podmanTest.Podman([]string{"unshare", "cat", "/proc/self/uid_map"}) + unshare.WaitWithDefaultTimeout() + Expect(unshare).Should(Exit(0)) + initialUsernsConfig = unshare.Out.Contents() + } + + pod := getPod() + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + usernsInCtr := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/proc/self/uid_map"}) + usernsInCtr.WaitWithDefaultTimeout() + Expect(usernsInCtr).Should(Exit(0)) + // the conversion to string is needed for better error messages + Expect(string(usernsInCtr.Out.Contents())).To(Equal(string(initialUsernsConfig))) + + // PodmanNoCache is a workaround for https://github.com/containers/storage/issues/1232 + kube = podmanTest.PodmanNoCache([]string{"play", "kube", "--replace", "--userns=auto", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + usernsInCtr = podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/proc/self/uid_map"}) + usernsInCtr.WaitWithDefaultTimeout() + Expect(usernsInCtr).Should(Exit(0)) + Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig)))) }) }) |