Merge pull request #14140 from giuseppe/play-kube-userns
kube: add support for --userns=[auto|host]
This commit is contained in:
		
						commit
						76dc9ef32d
					
				| 
						 | 
				
			
			@ -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")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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))))
 | 
			
		||||
	})
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue