Support selinux options with bind mounts play/gen

When using play kube and generate kube, we need to support if bind
mounts have selinux options.  As kubernetes does not support selinux in
this way, we tuck the selinux values into a pod annotation for
generation of the kube yaml.  Then on play, we check annotations to see
if a value for the mount exists and apply it.

Fixes BZ #1984081

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude 2021-09-29 14:57:33 -05:00
parent 966b6030fa
commit 1ff6a5082a
5 changed files with 58 additions and 20 deletions

View File

@ -90,3 +90,6 @@ const (
// DefaultRlimitValue is the value set by default for nofile and nproc // DefaultRlimitValue is the value set by default for nofile and nproc
const RLimitDefaultValue = uint64(1048576) const RLimitDefaultValue = uint64(1048576)
// BindMountPrefix distinguishes its annotations from others
const BindMountPrefix = "bind-mount-options:"

View File

@ -241,11 +241,13 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po
isInit := ctr.IsInitCtr() isInit := ctr.IsInitCtr()
ctr, volumes, _, err := containerToV1Container(ctx, ctr) ctr, volumes, _, annotations, err := containerToV1Container(ctx, ctr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for k, v := range annotations {
podAnnotations[define.BindMountPrefix+k] = v
}
// Since port bindings for the pod are handled by the // Since port bindings for the pod are handled by the
// infra container, wipe them here. // infra container, wipe them here.
ctr.Ports = nil ctr.Ports = nil
@ -271,7 +273,7 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po
deDupPodVolumes[vol.Name] = &vol deDupPodVolumes[vol.Name] = &vol
} }
} else { } else {
_, _, infraDNS, err := containerToV1Container(ctx, ctr) _, _, infraDNS, _, err := containerToV1Container(ctx, ctr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -359,17 +361,19 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod,
if !ctr.HostNetwork() { if !ctr.HostNetwork() {
hostNetwork = false hostNetwork = false
} }
kubeCtr, kubeVols, ctrDNS, err := containerToV1Container(ctx, ctr) kubeCtr, kubeVols, ctrDNS, annotations, err := containerToV1Container(ctx, ctr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for k, v := range annotations {
kubeAnnotations[define.BindMountPrefix+k] = v
}
if isInit { if isInit {
kubeInitCtrs = append(kubeInitCtrs, kubeCtr) kubeInitCtrs = append(kubeInitCtrs, kubeCtr)
} else { } else {
kubeCtrs = append(kubeCtrs, kubeCtr) kubeCtrs = append(kubeCtrs, kubeCtr)
} }
kubeVolumes = append(kubeVolumes, kubeVols...) kubeVolumes = append(kubeVolumes, kubeVols...)
// Combine DNS information in sum'd structure // Combine DNS information in sum'd structure
if ctrDNS != nil { if ctrDNS != nil {
// nameservers // nameservers
@ -415,42 +419,44 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod,
// containerToV1Container converts information we know about a libpod container // containerToV1Container converts information we know about a libpod container
// to a V1.Container specification. // to a V1.Container specification.
func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []v1.Volume, *v1.PodDNSConfig, error) { func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []v1.Volume, *v1.PodDNSConfig, map[string]string, error) {
kubeContainer := v1.Container{} kubeContainer := v1.Container{}
kubeVolumes := []v1.Volume{} kubeVolumes := []v1.Volume{}
annotations := make(map[string]string)
kubeSec, err := generateKubeSecurityContext(c) kubeSec, err := generateKubeSecurityContext(c)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, annotations, err
} }
// NOTE: a privileged container mounts all of /dev/*. // NOTE: a privileged container mounts all of /dev/*.
if !c.Privileged() && len(c.config.Spec.Linux.Devices) > 0 { if !c.Privileged() && len(c.config.Spec.Linux.Devices) > 0 {
// TODO Enable when we can support devices and their names // TODO Enable when we can support devices and their names
kubeContainer.VolumeDevices = generateKubeVolumeDeviceFromLinuxDevice(c.config.Spec.Linux.Devices) kubeContainer.VolumeDevices = generateKubeVolumeDeviceFromLinuxDevice(c.config.Spec.Linux.Devices)
return kubeContainer, kubeVolumes, nil, errors.Wrapf(define.ErrNotImplemented, "linux devices") return kubeContainer, kubeVolumes, nil, annotations, errors.Wrapf(define.ErrNotImplemented, "linux devices")
} }
if len(c.config.UserVolumes) > 0 { if len(c.config.UserVolumes) > 0 {
volumeMounts, volumes, err := libpodMountsToKubeVolumeMounts(c) volumeMounts, volumes, localAnnotations, err := libpodMountsToKubeVolumeMounts(c)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, nil, err
} }
annotations = localAnnotations
kubeContainer.VolumeMounts = volumeMounts kubeContainer.VolumeMounts = volumeMounts
kubeVolumes = append(kubeVolumes, volumes...) kubeVolumes = append(kubeVolumes, volumes...)
} }
envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env) envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, annotations, err
} }
portmappings, err := c.PortMappings() portmappings, err := c.PortMappings()
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, annotations, err
} }
ports, err := ocicniPortMappingToContainerPort(portmappings) ports, err := ocicniPortMappingToContainerPort(portmappings)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, annotations, err
} }
// Handle command and arguments. // Handle command and arguments.
@ -469,11 +475,11 @@ func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []
kubeContainer.Stdin = c.Stdin() kubeContainer.Stdin = c.Stdin()
img, _, err := c.runtime.libimageRuntime.LookupImage(image, nil) img, _, err := c.runtime.libimageRuntime.LookupImage(image, nil)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, annotations, err
} }
imgData, err := img.Inspect(ctx, false) imgData, err := img.Inspect(ctx, false)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, nil, err return kubeContainer, kubeVolumes, nil, annotations, err
} }
if reflect.DeepEqual(imgData.Config.Cmd, kubeContainer.Command) { if reflect.DeepEqual(imgData.Config.Cmd, kubeContainer.Command) {
kubeContainer.Command = nil kubeContainer.Command = nil
@ -555,7 +561,7 @@ func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []
} }
dns.Options = dnsOptions dns.Options = dnsOptions
} }
return kubeContainer, kubeVolumes, &dns, nil return kubeContainer, kubeVolumes, &dns, annotations, nil
} }
// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts // ocicniPortMappingToContainerPort takes an ocicni portmapping and converts
@ -606,16 +612,23 @@ func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) {
} }
// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands // libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands
func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, error) { func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, map[string]string, error) {
namedVolumes, mounts := c.sortUserVolumes(c.config.Spec) namedVolumes, mounts := c.sortUserVolumes(c.config.Spec)
vms := make([]v1.VolumeMount, 0, len(mounts)) vms := make([]v1.VolumeMount, 0, len(mounts))
vos := make([]v1.Volume, 0, len(mounts)) vos := make([]v1.Volume, 0, len(mounts))
annotations := make(map[string]string)
var suffix string var suffix string
for index, m := range mounts { for index, m := range mounts {
for _, opt := range m.Options {
if opt == "Z" || opt == "z" {
annotations[m.Source] = opt
break
}
}
vm, vo, err := generateKubeVolumeMount(m) vm, vo, err := generateKubeVolumeMount(m)
if err != nil { if err != nil {
return vms, vos, err return vms, vos, annotations, err
} }
// Name will be the same, so use the index as suffix // Name will be the same, so use the index as suffix
suffix = fmt.Sprintf("-%d", index) suffix = fmt.Sprintf("-%d", index)
@ -629,7 +642,7 @@ func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume
vms = append(vms, vm) vms = append(vms, vm)
vos = append(vos, vo) vos = append(vos, vo)
} }
return vms, vos, nil return vms, vos, annotations, nil
} }
// generateKubePersistentVolumeClaim converts a ContainerNamedVolume to a Kubernetes PersistentVolumeClaim // generateKubePersistentVolumeClaim converts a ContainerNamedVolume to a Kubernetes PersistentVolumeClaim

View File

@ -319,8 +319,8 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil { if err != nil {
return nil, err return nil, err
} }
specgenOpts := kube.CtrSpecGenOptions{ specgenOpts := kube.CtrSpecGenOptions{
Annotations: annotations,
Container: initCtr, Container: initCtr,
Image: pulledImage, Image: pulledImage,
Volumes: volumes, Volumes: volumes,

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/parse"
"github.com/containers/common/pkg/secrets" "github.com/containers/common/pkg/secrets"
"github.com/containers/image/v5/manifest" "github.com/containers/image/v5/manifest"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/libpod/network/types"
ann "github.com/containers/podman/v3/pkg/annotations" ann "github.com/containers/podman/v3/pkg/annotations"
"github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities"
@ -86,6 +87,8 @@ func ToPodOpt(ctx context.Context, podName string, p entities.PodCreateOptions,
} }
type CtrSpecGenOptions struct { type CtrSpecGenOptions struct {
// Annotations from the Pod
Annotations map[string]string
// Container as read from the pod yaml // Container as read from the pod yaml
Container v1.Container Container v1.Container
// Image available to use (pulled or found local) // Image available to use (pulled or found local)
@ -289,6 +292,14 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
volume.MountPath = dest volume.MountPath = dest
switch volumeSource.Type { switch volumeSource.Type {
case KubeVolumeTypeBindMount: case KubeVolumeTypeBindMount:
// If the container has bind mounts, we need to check if
// a selinux mount option exists for it
for k, v := range opts.Annotations {
// Make sure the z/Z option is not already there (from editing the YAML)
if strings.Replace(k, define.BindMountPrefix, "", 1) == volumeSource.Source && !util.StringInSlice("z", options) && !util.StringInSlice("Z", options) {
options = append(options, v)
}
}
mount := spec.Mount{ mount := spec.Mount{
Destination: volume.MountPath, Destination: volume.MountPath,
Source: volumeSource.Source, Source: volumeSource.Source,

View File

@ -6,6 +6,8 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/util" "github.com/containers/podman/v3/pkg/util"
. "github.com/containers/podman/v3/test/utils" . "github.com/containers/podman/v3/test/utils"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
@ -555,6 +557,15 @@ var _ = Describe("Podman generate kube", func() {
kube.WaitWithDefaultTimeout() kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0)) Expect(kube).Should(Exit(0))
b, err := ioutil.ReadFile(outputFile)
Expect(err).ShouldNot(HaveOccurred())
pod := new(v1.Pod)
err = yaml.Unmarshal(b, pod)
Expect(err).To(BeNil())
val, found := pod.Annotations[define.BindMountPrefix+vol1]
Expect(found).To(BeTrue())
Expect(val).To(HaveSuffix("z"))
rm := podmanTest.Podman([]string{"pod", "rm", "-f", "test1"}) rm := podmanTest.Podman([]string{"pod", "rm", "-f", "test1"})
rm.WaitWithDefaultTimeout() rm.WaitWithDefaultTimeout()
Expect(rm).Should(Exit(0)) Expect(rm).Should(Exit(0))