From b5a99e08168ac36c9be74623c24ccdca3ce35234 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 21 Feb 2023 16:18:15 -0500 Subject: [PATCH] Must use mountlabel when creating builtin volumes Signed-off-by: Daniel J Walsh --- libpod/options.go | 12 ++++++++ libpod/pod.go | 12 ++++++++ libpod/runtime_ctr.go | 5 ++- libpod/runtime_volume_common.go | 10 +++++- libpod/util_freebsd.go | 2 +- libpod/util_linux.go | 17 ++++++----- libpod/util_linux_test.go | 2 +- libpod/util_unsupported.go | 2 +- libpod/volume.go | 2 ++ pkg/domain/infra/abi/play.go | 47 ++++++++++++++++++++++++++--- pkg/specgen/generate/kube/volume.go | 15 ++++----- test/system/160-volumes.bats | 10 ++++++ test/system/700-play.bats | 8 ++++- 13 files changed, 120 insertions(+), 24 deletions(-) diff --git a/libpod/options.go b/libpod/options.go index d6b170e408..13ee549478 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1665,6 +1665,18 @@ func WithVolumeLabels(labels map[string]string) VolumeCreateOption { } } +// WithVolumeMountLabel sets the MountLabel of the volume. +func WithVolumeMountLabel(mountLabel string) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.MountLabel = mountLabel + return nil + } +} + // WithVolumeOptions sets the options of the volume. func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { diff --git a/libpod/pod.go b/libpod/pod.go index 89f6d6cfe3..e9ce876373 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -107,6 +107,18 @@ func (p *Pod) Name() string { return p.config.Name } +// MountLabel returns the SELinux label associated with the pod +func (p *Pod) MountLabel() (string, error) { + if !p.HasInfraContainer() { + return "", nil + } + ctr, err := p.infraContainer() + if err != nil { + return "", err + } + return ctr.MountLabel(), nil +} + // Namespace returns the pod's libpod namespace. // Namespaces are used to logically separate containers and pods in the state. func (p *Pod) Namespace() string { diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index c19a2133ac..6a90785161 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -495,7 +495,10 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai logrus.Debugf("Creating new volume %s for container", vol.Name) // The volume does not exist, so we need to create it. - volOptions := []VolumeCreateOption{WithVolumeName(vol.Name)} + volOptions := []VolumeCreateOption{ + WithVolumeName(vol.Name), + WithVolumeMountLabel(ctr.MountLabel()), + } if isAnonymous { volOptions = append(volOptions, withSetAnon()) } diff --git a/libpod/runtime_volume_common.go b/libpod/runtime_volume_common.go index 5c22580320..54fc158be0 100644 --- a/libpod/runtime_volume_common.go +++ b/libpod/runtime_volume_common.go @@ -20,6 +20,7 @@ import ( "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/stringid" pluginapi "github.com/docker/go-plugins-helpers/volume" + "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" ) @@ -122,6 +123,13 @@ func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, opti storageConfig := storage.ContainerOptions{ LabelOpts: []string{"filetype:container_file_t:s0"}, } + if len(volume.config.MountLabel) > 0 { + context, err := selinux.NewContext(volume.config.MountLabel) + if err != nil { + return nil, fmt.Errorf("failed to get SELinux context from %s: %w", volume.config.MountLabel, err) + } + storageConfig.LabelOpts = []string{fmt.Sprintf("filetype:%s:s0", context["type"])} + } if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil { return nil, fmt.Errorf("creating backing storage for image driver: %w", err) } @@ -161,7 +169,7 @@ func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, opti if err := idtools.SafeChown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", fullVolPath, volume.config.UID, volume.config.GID, err) } - if err := LabelVolumePath(fullVolPath); err != nil { + if err := LabelVolumePath(fullVolPath, volume.config.MountLabel); err != nil { return nil, err } if volume.config.DisableQuota { diff --git a/libpod/util_freebsd.go b/libpod/util_freebsd.go index 894fa502b4..a6942fc9f2 100644 --- a/libpod/util_freebsd.go +++ b/libpod/util_freebsd.go @@ -25,7 +25,7 @@ func deleteSystemdCgroup(path string, resources *spec.LinuxResources) error { } // No equivalent on FreeBSD? -func LabelVolumePath(path string) error { +func LabelVolumePath(path, mountLabel string) error { return nil } diff --git a/libpod/util_linux.go b/libpod/util_linux.go index efc11710f4..155bd91857 100644 --- a/libpod/util_linux.go +++ b/libpod/util_linux.go @@ -109,13 +109,16 @@ var lvpReleaseLabel = label.ReleaseLabel // LabelVolumePath takes a mount path for a volume and gives it an // selinux label of either shared or not -func LabelVolumePath(path string) error { - _, mountLabel, err := lvpInitLabels([]string{}) - if err != nil { - return fmt.Errorf("getting default mountlabels: %w", err) - } - if err := lvpReleaseLabel(mountLabel); err != nil { - return fmt.Errorf("releasing label %q: %w", mountLabel, err) +func LabelVolumePath(path, mountLabel string) error { + if mountLabel == "" { + var err error + _, mountLabel, err = lvpInitLabels([]string{}) + if err != nil { + return fmt.Errorf("getting default mountlabels: %w", err) + } + if err := lvpReleaseLabel(mountLabel); err != nil { + return fmt.Errorf("releasing label %q: %w", mountLabel, err) + } } if err := lvpRelabel(path, mountLabel, true); err != nil { diff --git a/libpod/util_linux_test.go b/libpod/util_linux_test.go index 5fcb04bebc..455fb84315 100644 --- a/libpod/util_linux_test.go +++ b/libpod/util_linux_test.go @@ -34,6 +34,6 @@ func TestLabelVolumePath(t *testing.T) { } // LabelVolumePath should not return an error if the operation is unsupported. - err := LabelVolumePath("/foo/bar") + err := LabelVolumePath("/foo/bar", "") assert.NoError(t, err) } diff --git a/libpod/util_unsupported.go b/libpod/util_unsupported.go index fc3d00274f..f38213a100 100644 --- a/libpod/util_unsupported.go +++ b/libpod/util_unsupported.go @@ -22,6 +22,6 @@ func Unmount(mount string) { // LabelVolumePath takes a mount path for a volume and gives it an // selinux label of either shared or not -func LabelVolumePath(path string) error { +func LabelVolumePath(path, mountLabel string) error { return errors.New("not implemented LabelVolumePath") } diff --git a/libpod/volume.go b/libpod/volume.go index e76062011c..537d53bf38 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -67,6 +67,8 @@ type VolumeConfig struct { // StorageImageID is the ID of the image the volume was based off of. // Only used for image volumes. StorageImageID string `json:"storageImageID,omitempty"` + // MountLabel is the SELinux label to assign to mount points + MountLabel string `json:"mountlabel,omitempty"` } // VolumeState holds the volume's mutable state. diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index a9a821c076..4a146a4c6a 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -34,6 +34,7 @@ import ( "github.com/coreos/go-systemd/v22/daemon" "github.com/ghodss/yaml" "github.com/opencontainers/go-digest" + "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" yamlv3 "gopkg.in/yaml.v3" ) @@ -279,7 +280,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options } } - r, err := ic.playKubePVC(ctx, &pvcYAML) + r, err := ic.playKubePVC(ctx, "", &pvcYAML) if err != nil { return nil, err } @@ -535,7 +536,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY configMaps = append(configMaps, cm) } - volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps, secretsManager) + mountLabel, err := getMountLabel(podYAML.Spec.SecurityContext) + if err != nil { + return nil, nil, err + } + + volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps, secretsManager, mountLabel) if err != nil { return nil, nil, err } @@ -544,7 +550,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // defined by a configmap or secret for _, v := range volumes { if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional { - vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source)) + volumeOptions := []libpod.VolumeCreateOption{ + libpod.WithVolumeName(v.Source), + libpod.WithVolumeMountLabel(mountLabel), + } + vol, err := ic.Libpod.NewVolume(ctx, volumeOptions...) if err != nil { if errors.Is(err, define.ErrVolumeExists) { // Volume for this configmap already exists do not @@ -971,7 +981,7 @@ func (ic *ContainerEngine) getImageAndLabelInfo(ctx context.Context, cwd string, } // playKubePVC creates a podman volume from a kube persistent volume claim. -func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.PersistentVolumeClaim) (*entities.PlayKubeReport, error) { +func (ic *ContainerEngine) playKubePVC(ctx context.Context, mountLabel string, pvcYAML *v1.PersistentVolumeClaim) (*entities.PlayKubeReport, error) { var report entities.PlayKubeReport opts := make(map[string]string) @@ -987,6 +997,7 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste libpod.WithVolumeName(name), libpod.WithVolumeLabels(pvcYAML.Labels), libpod.WithVolumeIgnoreIfExist(), + libpod.WithVolumeMountLabel(mountLabel), } // Get pvc annotations and create remaining podman volume options if available. @@ -1415,3 +1426,31 @@ func (ic *ContainerEngine) playKubeSecret(secret *v1.Secret) (*entities.SecretCr return r, nil } + +func getMountLabel(securityContext *v1.PodSecurityContext) (string, error) { + var mountLabel string + if securityContext == nil { + return "", nil + } + seopt := securityContext.SELinuxOptions + if seopt == nil { + return mountLabel, nil + } + if seopt.Level == "" && seopt.FileType == "" { + return mountLabel, nil + } + privLabel := selinux.PrivContainerMountLabel() + con, err := selinux.NewContext(privLabel) + if err != nil { + return mountLabel, err + } + + con["level"] = "s0" + if seopt.Level != "" { + con["level"] = seopt.Level + } + if seopt.FileType != "" { + con["type"] = seopt.FileType + } + return con.Get(), nil +} diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index c792372b1f..b87557280d 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -51,7 +51,7 @@ type KubeVolume struct { } // Create a KubeVolume from an HostPathVolumeSource -func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) { +func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource, mountLabel string) (*KubeVolume, error) { if hostPath.Type != nil { switch *hostPath.Type { case v1.HostPathDirectoryOrCreate: @@ -59,7 +59,7 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) return nil, err } // Label a newly created volume - if err := libpod.LabelVolumePath(hostPath.Path); err != nil { + if err := libpod.LabelVolumePath(hostPath.Path, mountLabel); err != nil { return nil, fmt.Errorf("giving %s a label: %w", hostPath.Path, err) } case v1.HostPathFileOrCreate: @@ -73,7 +73,8 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) } } // unconditionally label a newly created volume - if err := libpod.LabelVolumePath(hostPath.Path); err != nil { + + if err := libpod.LabelVolumePath(hostPath.Path, mountLabel); err != nil { return nil, fmt.Errorf("giving %s a label: %w", hostPath.Path, err) } case v1.HostPathSocket: @@ -232,10 +233,10 @@ func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name stri } // Create a KubeVolume from one of the supported VolumeSource -func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName string) (*KubeVolume, error) { +func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName, mountLabel string) (*KubeVolume, error) { switch { case volumeSource.HostPath != nil: - return VolumeFromHostPath(volumeSource.HostPath) + return VolumeFromHostPath(volumeSource.HostPath, mountLabel) case volumeSource.PersistentVolumeClaim != nil: return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim) case volumeSource.ConfigMap != nil: @@ -250,11 +251,11 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, s } // Create a map of volume name to KubeVolume -func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (map[string]*KubeVolume, error) { +func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, mountLabel string) (map[string]*KubeVolume, error) { volumes := make(map[string]*KubeVolume) for _, specVolume := range specVolumes { - volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name) + volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name, mountLabel) if err != nil { return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err) } diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 9f36008864..ece40ab6cd 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -519,5 +519,15 @@ EOF run_podman rm -f -t 0 -a } +@test "podman run with building volume and selinux file label" { + skip_if_no_selinux + run_podman create --security-opt label=filetype:usr_t --volume myvol:/myvol $IMAGE top + run_podman volume inspect myvol --format '{{ .Mountpoint }}' + path=${output} + run ls -Zd $path + is "$output" "system_u:object_r:usr_t:s0 $path" "volume should be labeled with usr_t type" + run_podman volume rm myvol --force +} + # vim: filetype=sh diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 9769c9f0eb..593f889bda 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -559,18 +559,24 @@ EOF @test "podman kube generate filetype" { YAML=$PODMAN_TMPDIR/test.yml - run_podman create --pod new:pod1 --security-opt label=level:s0:c1,c2 --security-opt label=filetype:usr_t --name test1 $IMAGE true + run_podman create --pod new:pod1 --security-opt label=level:s0:c1,c2 --security-opt label=filetype:usr_t -v myvol:/myvol --name test1 $IMAGE true run_podman kube generate pod1 -f $YAML run cat $YAML is "$output" ".*filetype: usr_t" "Generated YAML file should contain filetype usr_t" run_podman pod rm --force pod1 + run_podman volume rm myvol --force run_podman kube play $YAML if selinux_enabled; then run_podman inspect pod1-test1 --format "{{ .MountLabel }}" is "$output" "system_u:object_r:usr_t:s0:c1,c2" "Generated container should use filetype usr_t" + run_podman volume inspect myvol --format '{{ .Mountpoint }}' + path=${output} + run ls -Zd $path + is "$output" "system_u:object_r:usr_t:s0 $path" "volume should be labeled with usr_t type" fi run_podman kube down $YAML + run_podman volume rm myvol --force } # kube play --wait=true, where we clear up the created containers, pods, and volumes when a kill or sigterm is triggered