k8s.io/kubernetes@v1.29.3/pkg/volume/util/selinux.go (about) 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 !v1helper.ContainsAccessMode(volumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) { 181 return false 182 } 183 return true 184 } 185 186 // AddSELinuxMountOption adds -o context="XYZ" mount option to a given list 187 func AddSELinuxMountOption(options []string, seLinuxContext string) []string { 188 if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { 189 return options 190 } 191 // Use double quotes to support a comma "," in the SELinux context string. 192 // For example: dirsync,context="system_u:object_r:container_file_t:s0:c15,c25",noatime 193 return append(options, fmt.Sprintf("context=%q", seLinuxContext)) 194 }