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) _ = 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.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.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") 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, 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. 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 ## EXAMPLES
Recreate the pod and containers as described in a file called `demo.yml` Recreate the pod and containers as described in a file called `demo.yml`

View File

@ -43,4 +43,6 @@ type KubeOptions struct {
LogOptions *[]string LogOptions *[]string
// Start - don't start the pod if false // Start - don't start the pod if false
Start *bool 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 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 LogOptions []string
// Start - don't start the pod if false // Start - don't start the pod if false
Start types.OptionalBool Start types.OptionalBool
// Userns - define the user namespace to use.
Userns string
} }
// PlayKubePod represents a single pod and associated containers created by play kube // 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 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 // FIXME This is very hard to support properly with a good ux
if len(options.StaticIPs) > *ipIndex { if len(options.StaticIPs) > *ipIndex {
if !podOpt.Net.Network.IsBridge() { if !podOpt.Net.Network.IsBridge() {
@ -352,6 +362,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
infraImage := util.DefaultContainerConfig().Engine.InfraImage infraImage := util.DefaultContainerConfig().Engine.InfraImage
infraOptions := entities.NewInfraContainerCreateOptions() infraOptions := entities.NewInfraContainerCreateOptions()
infraOptions.Hostname = podSpec.PodSpecGen.PodBasicConfig.Hostname infraOptions.Hostname = podSpec.PodSpecGen.PodBasicConfig.Hostname
infraOptions.UserNS = options.Userns
podSpec.PodSpecGen.InfraImage = infraImage podSpec.PodSpecGen.InfraImage = infraImage
podSpec.PodSpecGen.NoInfra = false podSpec.PodSpecGen.NoInfra = false
podSpec.PodSpecGen.InfraContainerSpec = specgen.NewSpecGenerator(infraImage, false) podSpec.PodSpecGen.InfraContainerSpec = specgen.NewSpecGenerator(infraImage, false)
@ -428,6 +439,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
RestartPolicy: ctrRestartPolicy, RestartPolicy: ctrRestartPolicy,
SeccompPaths: seccompPaths, SeccompPaths: seccompPaths,
SecretsManager: secretsManager, SecretsManager: secretsManager,
UserNSIsHost: p.Userns.IsHost(),
Volumes: volumes, Volumes: volumes,
} }
specGen, err := kube.ToSpecGen(ctx, &specgenOpts) specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
@ -476,6 +488,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
RestartPolicy: ctrRestartPolicy, RestartPolicy: ctrRestartPolicy,
SeccompPaths: seccompPaths, SeccompPaths: seccompPaths,
SecretsManager: secretsManager, SecretsManager: secretsManager,
UserNSIsHost: p.Userns.IsHost(),
Volumes: volumes, Volumes: volumes,
} }
specGen, err := kube.ToSpecGen(ctx, &specgenOpts) 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 { if opts.Annotations != nil {
options.WithAnnotations(opts.Annotations) options.WithAnnotations(opts.Annotations)
} }
options.WithNoHosts(opts.NoHosts) options.WithNoHosts(opts.NoHosts).WithUserns(opts.Userns)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
options.WithSkipTLSVerify(s == types.OptionalBoolTrue) options.WithSkipTLSVerify(s == types.OptionalBoolTrue)
} }

View File

@ -120,6 +120,8 @@ type CtrSpecGenOptions struct {
RestartPolicy string RestartPolicy string
// NetNSIsHost tells the container to use the host netns // NetNSIsHost tells the container to use the host netns
NetNSIsHost bool NetNSIsHost bool
// UserNSIsHost tells the container to use the host userns
UserNSIsHost bool
// SecretManager to access the secrets // SecretManager to access the secrets
SecretsManager *secrets.SecretsManager SecretsManager *secrets.SecretsManager
// LogDriver which should be used for the container // LogDriver which should be used for the container
@ -389,8 +391,9 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
if opts.NetNSIsHost { if opts.NetNSIsHost {
s.NetNS.NSMode = specgen.Host s.NetNS.NSMode = specgen.Host
} }
// Always set the userns to host since k8s doesn't have support for userns yet if opts.UserNSIsHost {
s.UserNS.NSMode = specgen.Host s.UserNS.NSMode = specgen.Host
}
// Add labels that come from kube // Add labels that come from kube
if len(s.Labels) == 0 { if len(s.Labels) == 0 {

View File

@ -8,6 +8,7 @@ import (
"net" "net"
"net/url" "net/url"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -3633,6 +3634,55 @@ ENV OPENJ9_JAVA_OPTIONS=%q
inspect.WaitWithDefaultTimeout() inspect.WaitWithDefaultTimeout()
Expect(start).Should(Exit(0)) Expect(start).Should(Exit(0))
Expect((inspect.InspectContainerToJSON()[0]).HostConfig.LogConfig.Tag).To(Equal("{{.ImageName}}")) 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))))
}) })
}) })