Merge pull request #14140 from giuseppe/play-kube-userns

kube: add support for --userns=[auto|host]
This commit is contained in:
OpenShift Merge Robot
2022-05-10 13:28:16 -04:00
committed by GitHub
9 changed files with 133 additions and 3 deletions

View File

@ -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")

View File

@ -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`

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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 {

View File

@ -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))))
})
})