diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2022-09-09 15:06:01 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-09 15:06:01 +0200 |
commit | 8a2ab7c387928782d8a1893c99974638054a0ad0 (patch) | |
tree | 92c9b196a37fff53638c5ea54a8a001e7b45e243 | |
parent | b0b36430b88da32b63774bc6a9a1f330252b0fd6 (diff) | |
parent | 9a286f7126f918677089a92b67cc38a1eb74da11 (diff) | |
download | podman-8a2ab7c387928782d8a1893c99974638054a0ad0.tar.gz podman-8a2ab7c387928782d8a1893c99974638054a0ad0.tar.bz2 podman-8a2ab7c387928782d8a1893c99974638054a0ad0.zip |
Merge pull request #15692 from giuseppe/pod-spec-userns
kube: plug HostUsers in the pod spec
-rw-r--r-- | libpod/kube.go | 17 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 5 | ||||
-rw-r--r-- | pkg/k8s.io/api/core/v1/types.go | 12 | ||||
-rw-r--r-- | test/e2e/generate_kube_test.go | 34 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 33 |
5 files changed, 96 insertions, 5 deletions
diff --git a/libpod/kube.go b/libpod/kube.go index c7aa4b57d..1f4831006 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -62,6 +62,7 @@ func (p *Pod) GenerateForKube(ctx context.Context) (*v1.Pod, []v1.ServicePort, e extraHost := make([]v1.HostAlias, 0) hostNetwork := false + hostUsers := true if p.HasInfraContainer() { infraContainer, err := p.getInfraContainer() if err != nil { @@ -87,8 +88,9 @@ func (p *Pod) GenerateForKube(ctx context.Context) (*v1.Pod, []v1.ServicePort, e return nil, servicePorts, err } hostNetwork = infraContainer.NetworkMode() == string(namespaces.NetworkMode(specgen.Host)) + hostUsers = infraContainer.IDMappings().HostUIDMapping && infraContainer.IDMappings().HostGIDMapping } - pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork) + pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork, hostUsers) if err != nil { return nil, servicePorts, err } @@ -348,7 +350,7 @@ func containersToServicePorts(containers []v1.Container) ([]v1.ServicePort, erro return sps, nil } -func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, ports []v1.ContainerPort, hostNetwork bool) (*v1.Pod, error) { +func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, ports []v1.ContainerPort, hostNetwork, hostUsers bool) (*v1.Pod, error) { deDupPodVolumes := make(map[string]*v1.Volume) first := true podContainers := make([]v1.Container, 0, len(containers)) @@ -446,10 +448,11 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po podVolumes, &dnsInfo, hostNetwork, + hostUsers, hostname), nil } -func newPodObject(podName string, annotations map[string]string, initCtrs, containers []v1.Container, volumes []v1.Volume, dnsOptions *v1.PodDNSConfig, hostNetwork bool, hostname string) *v1.Pod { +func newPodObject(podName string, annotations map[string]string, initCtrs, containers []v1.Container, volumes []v1.Volume, dnsOptions *v1.PodDNSConfig, hostNetwork, hostUsers bool, hostname string) *v1.Pod { tm := v12.TypeMeta{ Kind: "Pod", APIVersion: "v1", @@ -481,6 +484,9 @@ func newPodObject(podName string, annotations map[string]string, initCtrs, conta EnableServiceLinks: &enableServiceLinks, AutomountServiceAccountToken: &automountServiceAccountToken, } + if !hostUsers { + ps.HostUsers = &hostUsers + } if dnsOptions != nil && (len(dnsOptions.Nameservers)+len(dnsOptions.Searches)+len(dnsOptions.Options) > 0) { ps.DNSConfig = dnsOptions } @@ -498,6 +504,7 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod, kubeCtrs := make([]v1.Container, 0, len(ctrs)) kubeInitCtrs := []v1.Container{} kubeVolumes := make([]v1.Volume, 0) + hostUsers := true hostNetwork := true podDNS := v1.PodDNSConfig{} kubeAnnotations := make(map[string]string) @@ -527,6 +534,9 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod, if !ctr.HostNetwork() { hostNetwork = false } + if !(ctr.IDMappings().HostUIDMapping && ctr.IDMappings().HostGIDMapping) { + hostUsers = false + } kubeCtr, kubeVols, ctrDNS, annotations, err := containerToV1Container(ctx, ctr) if err != nil { return nil, err @@ -588,6 +598,7 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod, kubeVolumes, &podDNS, hostNetwork, + hostUsers, hostname), nil } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 12786afcd..57d795682 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -355,6 +355,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY if options.Userns == "" { options.Userns = "host" + if podYAML.Spec.HostUsers != nil && !*podYAML.Spec.HostUsers { + options.Userns = "auto" + } + } else if podYAML.Spec.HostUsers != nil { + logrus.Info("overriding the user namespace mode in the pod spec") } // Validate the userns modes supported. diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go index d47178878..6f20cd351 100644 --- a/pkg/k8s.io/api/core/v1/types.go +++ b/pkg/k8s.io/api/core/v1/types.go @@ -1984,6 +1984,18 @@ type PodSpec struct { // Default to false. // +optional SetHostnameAsFQDN *bool `json:"setHostnameAsFQDN,omitempty"` + // Use the host's user namespace. + // Optional: Default to true. + // If set to true or not present, the pod will be run in the host user namespace, useful + // for when the pod needs a feature only available to the host user namespace, such as + // loading a kernel module with CAP_SYS_MODULE. + // When set to false, a new userns is created for the pod. Setting false is useful for + // mitigating container breakout vulnerabilities even allowing users to run their + // containers as root without actually having root privileges on the host. + // This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature. + // +k8s:conversion-gen=false + // +optional + HostUsers *bool `json:"hostUsers,omitempty"` } type UnsatisfiableConstraintAction string diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 39acff6dc..d8308aeea 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -3,6 +3,7 @@ package integration import ( "io/ioutil" "os" + "os/user" "path/filepath" "strconv" "strings" @@ -270,6 +271,39 @@ var _ = Describe("Podman generate kube", func() { Expect(numContainers).To(Equal(1)) }) + It("podman generate kube on pod with user namespace", 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") + } + podSession := podmanTest.Podman([]string{"pod", "create", "--name", "testPod", "--userns=auto"}) + podSession.WaitWithDefaultTimeout() + Expect(podSession).Should(Exit(0)) + + session := podmanTest.Podman([]string{"create", "--name", "topcontainer", "--pod", "testPod", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + kube := podmanTest.Podman([]string{"generate", "kube", "testPod"}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + pod := new(v1.Pod) + err = yaml.Unmarshal(kube.Out.Contents(), pod) + Expect(err).To(BeNil()) + expected := false + Expect(pod.Spec).To(HaveField("HostUsers", &expected)) + }) + It("podman generate kube on pod with host network", func() { podSession := podmanTest.Podman([]string{"pod", "create", "--name", "testHostNetwork", "--network", "host"}) podSession.WaitWithDefaultTimeout() diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index d1eb960cd..baa74cb51 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -380,6 +380,9 @@ spec: restartPolicy: {{ .RestartPolicy }} hostname: {{ .Hostname }} hostNetwork: {{ .HostNetwork }} +{{ if .HostUsers }} + hostUsers: {{ .HostUsers }} +{{ end }} hostAliases: {{ range .HostAliases }} - hostnames: @@ -844,6 +847,7 @@ type Pod struct { RestartPolicy string Hostname string HostNetwork bool + HostUsers *bool HostAliases []HostAlias Ctrs []*Ctr InitCtrs []*Ctr @@ -968,6 +972,12 @@ func withHostNetwork() podOption { } } +func withHostUsers(val bool) podOption { + return func(pod *Pod) { + pod.HostUsers = &val + } +} + // Deployment describes the options a kube yaml can be configured at deployment level type Deployment struct { Name string @@ -3783,8 +3793,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q 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() { + It("podman play kube using a user namespace", func() { u, err := user.Current() Expect(err).To(BeNil()) name := u.Name @@ -3831,6 +3840,26 @@ ENV OPENJ9_JAVA_OPTIONS=%q usernsInCtr.WaitWithDefaultTimeout() Expect(usernsInCtr).Should(Exit(0)) Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig)))) + + // Now try with hostUsers in the pod spec + for _, hostUsers := range []bool{true, false} { + pod = getPod(withHostUsers(hostUsers)) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube = podmanTest.PodmanNoCache([]string{"play", "kube", "--replace", 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)) + if hostUsers { + Expect(string(usernsInCtr.Out.Contents())).To(Equal(string(initialUsernsConfig))) + } else { + Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig)))) + } + } }) // Check the block devices are exposed inside container |