...

Source file src/k8s.io/kubernetes/pkg/volume/util/selinux.go

Documentation: k8s.io/kubernetes/pkg/volume/util

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package util
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/opencontainers/selinux/go-selinux"
    23  	"github.com/opencontainers/selinux/go-selinux/label"
    24  	v1 "k8s.io/api/core/v1"
    25  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    26  	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
    27  	"k8s.io/kubernetes/pkg/features"
    28  	"k8s.io/kubernetes/pkg/volume"
    29  )
    30  
    31  // SELinuxLabelTranslator translates v1.SELinuxOptions of a process to SELinux file label.
    32  type SELinuxLabelTranslator interface {
    33  	// SELinuxOptionsToFileLabel returns SELinux file label for given SELinuxOptions
    34  	// of a container process.
    35  	// When Role, User or Type are empty, they're read from the system defaults.
    36  	// It returns "" and no error on platforms that do not have SELinux enabled
    37  	// or don't support SELinux at all.
    38  	SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error)
    39  
    40  	// SELinuxEnabled returns true when the OS has enabled SELinux support.
    41  	SELinuxEnabled() bool
    42  }
    43  
    44  // Real implementation of the interface.
    45  // On Linux with SELinux enabled it translates. Otherwise it always returns an empty string and no error.
    46  type translator struct{}
    47  
    48  var _ SELinuxLabelTranslator = &translator{}
    49  
    50  // NewSELinuxLabelTranslator returns new SELinuxLabelTranslator for the platform.
    51  func NewSELinuxLabelTranslator() SELinuxLabelTranslator {
    52  	return &translator{}
    53  }
    54  
    55  // SELinuxOptionsToFileLabel returns SELinux file label for given SELinuxOptions
    56  // of a container process.
    57  // When Role, User or Type are empty, they're read from the system defaults.
    58  // It returns "" and no error on platforms that do not have SELinux enabled
    59  // or don't support SELinux at all.
    60  func (l *translator) SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error) {
    61  	if opts == nil {
    62  		return "", nil
    63  	}
    64  
    65  	args := contextOptions(opts)
    66  	if len(args) == 0 {
    67  		return "", nil
    68  	}
    69  
    70  	processLabel, fileLabel, err := label.InitLabels(args)
    71  	if err != nil {
    72  		// In theory, this should be unreachable. InitLabels can fail only when args contain an unknown option,
    73  		// and all options returned by contextOptions are known.
    74  		return "", err
    75  	}
    76  	// InitLabels() may allocate a new unique SELinux label in kubelet memory. The label is *not* allocated
    77  	// in the container runtime. Clear it to avoid memory problems.
    78  	// ReleaseLabel on non-allocated label is NOOP.
    79  	selinux.ReleaseLabel(processLabel)
    80  
    81  	return fileLabel, nil
    82  }
    83  
    84  // Convert SELinuxOptions to []string accepted by label.InitLabels
    85  func contextOptions(opts *v1.SELinuxOptions) []string {
    86  	if opts == nil {
    87  		return nil
    88  	}
    89  	args := make([]string, 0, 3)
    90  	if opts.User != "" {
    91  		args = append(args, "user:"+opts.User)
    92  	}
    93  	if opts.Role != "" {
    94  		args = append(args, "role:"+opts.Role)
    95  	}
    96  	if opts.Type != "" {
    97  		args = append(args, "type:"+opts.Type)
    98  	}
    99  	if opts.Level != "" {
   100  		args = append(args, "level:"+opts.Level)
   101  	}
   102  	return args
   103  }
   104  
   105  func (l *translator) SELinuxEnabled() bool {
   106  	return selinux.GetEnabled()
   107  }
   108  
   109  // Fake implementation of the interface for unit tests.
   110  type fakeTranslator struct{}
   111  
   112  var _ SELinuxLabelTranslator = &fakeTranslator{}
   113  
   114  // NewFakeSELinuxLabelTranslator returns a fake translator for unit tests.
   115  // It imitates a real translator on platforms that do not have SELinux enabled
   116  // or don't support SELinux at all.
   117  func NewFakeSELinuxLabelTranslator() SELinuxLabelTranslator {
   118  	return &fakeTranslator{}
   119  }
   120  
   121  // SELinuxOptionsToFileLabel returns SELinux file label for given options.
   122  func (l *fakeTranslator) SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error) {
   123  	if opts == nil {
   124  		return "", nil
   125  	}
   126  	// Fill empty values from "system defaults" (taken from Fedora Linux).
   127  	user := opts.User
   128  	if user == "" {
   129  		user = "system_u"
   130  	}
   131  
   132  	role := opts.Role
   133  	if role == "" {
   134  		role = "object_r"
   135  	}
   136  
   137  	// opts is context of the *process* to run in a container. Translate
   138  	// process type "container_t" to file label type "container_file_t".
   139  	// (The rest of the context is the same for processes and files).
   140  	fileType := opts.Type
   141  	if fileType == "" || fileType == "container_t" {
   142  		fileType = "container_file_t"
   143  	}
   144  
   145  	level := opts.Level
   146  	if level == "" {
   147  		// If empty, level is allocated randomly.
   148  		level = "s0:c998,c999"
   149  	}
   150  
   151  	ctx := fmt.Sprintf("%s:%s:%s:%s", user, role, fileType, level)
   152  	return ctx, nil
   153  }
   154  
   155  func (l *fakeTranslator) SELinuxEnabled() bool {
   156  	return true
   157  }
   158  
   159  // SupportsSELinuxContextMount checks if the given volumeSpec supports with mount -o context
   160  func SupportsSELinuxContextMount(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) (bool, error) {
   161  	plugin, _ := volumePluginMgr.FindPluginBySpec(volumeSpec)
   162  	if plugin != nil {
   163  		return plugin.SupportsSELinuxContextMount(volumeSpec)
   164  	}
   165  
   166  	return false, nil
   167  }
   168  
   169  // VolumeSupportsSELinuxMount returns true if given volume access mode can support mount with SELinux mount options.
   170  func VolumeSupportsSELinuxMount(volumeSpec *volume.Spec) bool {
   171  	if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
   172  		return false
   173  	}
   174  	if volumeSpec.PersistentVolume == nil {
   175  		return false
   176  	}
   177  	if len(volumeSpec.PersistentVolume.Spec.AccessModes) != 1 {
   178  		return false
   179  	}
   180  	if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMount) {
   181  		return true
   182  	}
   183  	// Only SELinuxMountReadWriteOncePod feature enabled
   184  	if !v1helper.ContainsAccessMode(volumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) {
   185  		return false
   186  	}
   187  	return true
   188  }
   189  
   190  // AddSELinuxMountOption adds -o context="XYZ" mount option to a given list
   191  func AddSELinuxMountOption(options []string, seLinuxContext string) []string {
   192  	if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
   193  		return options
   194  	}
   195  	// Use double quotes to support a comma "," in the SELinux context string.
   196  	// For example: dirsync,context="system_u:object_r:container_file_t:s0:c15,c25",noatime
   197  	return append(options, fmt.Sprintf("context=%q", seLinuxContext))
   198  }
   199  

View as plain text