summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2022-09-09 15:06:01 +0200
committerGitHub <noreply@github.com>2022-09-09 15:06:01 +0200
commit8a2ab7c387928782d8a1893c99974638054a0ad0 (patch)
tree92c9b196a37fff53638c5ea54a8a001e7b45e243
parentb0b36430b88da32b63774bc6a9a1f330252b0fd6 (diff)
parent9a286f7126f918677089a92b67cc38a1eb74da11 (diff)
downloadpodman-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.go17
-rw-r--r--pkg/domain/infra/abi/play.go5
-rw-r--r--pkg/k8s.io/api/core/v1/types.go12
-rw-r--r--test/e2e/generate_kube_test.go34
-rw-r--r--test/e2e/play_kube_test.go33
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