Must use mountlabel when creating builtin volumes

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh
2023-02-21 16:18:15 -05:00
parent 80be8c3d64
commit b5a99e0816
13 changed files with 120 additions and 24 deletions

View File

@ -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. // WithVolumeOptions sets the options of the volume.
func WithVolumeOptions(options map[string]string) VolumeCreateOption { func WithVolumeOptions(options map[string]string) VolumeCreateOption {
return func(volume *Volume) error { return func(volume *Volume) error {

View File

@ -107,6 +107,18 @@ func (p *Pod) Name() string {
return p.config.Name 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. // Namespace returns the pod's libpod namespace.
// Namespaces are used to logically separate containers and pods in the state. // Namespaces are used to logically separate containers and pods in the state.
func (p *Pod) Namespace() string { func (p *Pod) Namespace() string {

View File

@ -495,7 +495,10 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
logrus.Debugf("Creating new volume %s for container", vol.Name) logrus.Debugf("Creating new volume %s for container", vol.Name)
// The volume does not exist, so we need to create it. // 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 { if isAnonymous {
volOptions = append(volOptions, withSetAnon()) volOptions = append(volOptions, withSetAnon())
} }

View File

@ -20,6 +20,7 @@ import (
"github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/stringid"
pluginapi "github.com/docker/go-plugins-helpers/volume" pluginapi "github.com/docker/go-plugins-helpers/volume"
"github.com/opencontainers/selinux/go-selinux"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -122,6 +123,13 @@ func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, opti
storageConfig := storage.ContainerOptions{ storageConfig := storage.ContainerOptions{
LabelOpts: []string{"filetype:container_file_t:s0"}, 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 { 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) 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 { 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) 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 return nil, err
} }
if volume.config.DisableQuota { if volume.config.DisableQuota {

View File

@ -25,7 +25,7 @@ func deleteSystemdCgroup(path string, resources *spec.LinuxResources) error {
} }
// No equivalent on FreeBSD? // No equivalent on FreeBSD?
func LabelVolumePath(path string) error { func LabelVolumePath(path, mountLabel string) error {
return nil return nil
} }

View File

@ -109,13 +109,16 @@ var lvpReleaseLabel = label.ReleaseLabel
// LabelVolumePath takes a mount path for a volume and gives it an // LabelVolumePath takes a mount path for a volume and gives it an
// selinux label of either shared or not // selinux label of either shared or not
func LabelVolumePath(path string) error { func LabelVolumePath(path, mountLabel string) error {
_, mountLabel, err := lvpInitLabels([]string{}) if mountLabel == "" {
if err != nil { var err error
return fmt.Errorf("getting default mountlabels: %w", err) _, mountLabel, err = lvpInitLabels([]string{})
} if err != nil {
if err := lvpReleaseLabel(mountLabel); err != nil { return fmt.Errorf("getting default mountlabels: %w", err)
return fmt.Errorf("releasing label %q: %w", mountLabel, err) }
if err := lvpReleaseLabel(mountLabel); err != nil {
return fmt.Errorf("releasing label %q: %w", mountLabel, err)
}
} }
if err := lvpRelabel(path, mountLabel, true); err != nil { if err := lvpRelabel(path, mountLabel, true); err != nil {

View File

@ -34,6 +34,6 @@ func TestLabelVolumePath(t *testing.T) {
} }
// LabelVolumePath should not return an error if the operation is unsupported. // LabelVolumePath should not return an error if the operation is unsupported.
err := LabelVolumePath("/foo/bar") err := LabelVolumePath("/foo/bar", "")
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -22,6 +22,6 @@ func Unmount(mount string) {
// LabelVolumePath takes a mount path for a volume and gives it an // LabelVolumePath takes a mount path for a volume and gives it an
// selinux label of either shared or not // selinux label of either shared or not
func LabelVolumePath(path string) error { func LabelVolumePath(path, mountLabel string) error {
return errors.New("not implemented LabelVolumePath") return errors.New("not implemented LabelVolumePath")
} }

View File

@ -67,6 +67,8 @@ type VolumeConfig struct {
// StorageImageID is the ID of the image the volume was based off of. // StorageImageID is the ID of the image the volume was based off of.
// Only used for image volumes. // Only used for image volumes.
StorageImageID string `json:"storageImageID,omitempty"` 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. // VolumeState holds the volume's mutable state.

View File

@ -34,6 +34,7 @@ import (
"github.com/coreos/go-systemd/v22/daemon" "github.com/coreos/go-systemd/v22/daemon"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/selinux/go-selinux"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
yamlv3 "gopkg.in/yaml.v3" 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 { if err != nil {
return nil, err return nil, err
} }
@ -535,7 +536,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
configMaps = append(configMaps, cm) 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -544,7 +550,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
// defined by a configmap or secret // defined by a configmap or secret
for _, v := range volumes { for _, v := range volumes {
if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional { 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 err != nil {
if errors.Is(err, define.ErrVolumeExists) { if errors.Is(err, define.ErrVolumeExists) {
// Volume for this configmap already exists do not // 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. // 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 var report entities.PlayKubeReport
opts := make(map[string]string) opts := make(map[string]string)
@ -987,6 +997,7 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste
libpod.WithVolumeName(name), libpod.WithVolumeName(name),
libpod.WithVolumeLabels(pvcYAML.Labels), libpod.WithVolumeLabels(pvcYAML.Labels),
libpod.WithVolumeIgnoreIfExist(), libpod.WithVolumeIgnoreIfExist(),
libpod.WithVolumeMountLabel(mountLabel),
} }
// Get pvc annotations and create remaining podman volume options if available. // 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 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
}

View File

@ -51,7 +51,7 @@ type KubeVolume struct {
} }
// Create a KubeVolume from an HostPathVolumeSource // 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 { if hostPath.Type != nil {
switch *hostPath.Type { switch *hostPath.Type {
case v1.HostPathDirectoryOrCreate: case v1.HostPathDirectoryOrCreate:
@ -59,7 +59,7 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error)
return nil, err return nil, err
} }
// Label a newly created volume // 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) return nil, fmt.Errorf("giving %s a label: %w", hostPath.Path, err)
} }
case v1.HostPathFileOrCreate: case v1.HostPathFileOrCreate:
@ -73,7 +73,8 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error)
} }
} }
// unconditionally label a newly created volume // 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) return nil, fmt.Errorf("giving %s a label: %w", hostPath.Path, err)
} }
case v1.HostPathSocket: case v1.HostPathSocket:
@ -232,10 +233,10 @@ func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name stri
} }
// Create a KubeVolume from one of the supported VolumeSource // 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 { switch {
case volumeSource.HostPath != nil: case volumeSource.HostPath != nil:
return VolumeFromHostPath(volumeSource.HostPath) return VolumeFromHostPath(volumeSource.HostPath, mountLabel)
case volumeSource.PersistentVolumeClaim != nil: case volumeSource.PersistentVolumeClaim != nil:
return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim) return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim)
case volumeSource.ConfigMap != nil: 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 // 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) volumes := make(map[string]*KubeVolume)
for _, specVolume := range specVolumes { 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 { if err != nil {
return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err) return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err)
} }

View File

@ -519,5 +519,15 @@ EOF
run_podman rm -f -t 0 -a 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 # vim: filetype=sh

View File

@ -559,18 +559,24 @@ EOF
@test "podman kube generate filetype" { @test "podman kube generate filetype" {
YAML=$PODMAN_TMPDIR/test.yml 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_podman kube generate pod1 -f $YAML
run cat $YAML run cat $YAML
is "$output" ".*filetype: usr_t" "Generated YAML file should contain filetype usr_t" is "$output" ".*filetype: usr_t" "Generated YAML file should contain filetype usr_t"
run_podman pod rm --force pod1 run_podman pod rm --force pod1
run_podman volume rm myvol --force
run_podman kube play $YAML run_podman kube play $YAML
if selinux_enabled; then if selinux_enabled; then
run_podman inspect pod1-test1 --format "{{ .MountLabel }}" 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" 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 fi
run_podman kube down $YAML 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 # kube play --wait=true, where we clear up the created containers, pods, and volumes when a kill or sigterm is triggered