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

View File

@ -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 {

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)
// 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())
}

View File

@ -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 {

View File

@ -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
}

View File

@ -109,14 +109,17 @@ 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{})
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 {
if err == syscall.ENOTSUP {

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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.

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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