k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/projected/projected.go (about) 1 /* 2 Copyright 2017 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 projected 18 19 import ( 20 "fmt" 21 22 authenticationv1 "k8s.io/api/authentication/v1" 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/types" 27 utilerrors "k8s.io/apimachinery/pkg/util/errors" 28 "k8s.io/klog/v2" 29 "k8s.io/kubernetes/pkg/volume" 30 "k8s.io/kubernetes/pkg/volume/configmap" 31 "k8s.io/kubernetes/pkg/volume/downwardapi" 32 "k8s.io/kubernetes/pkg/volume/secret" 33 volumeutil "k8s.io/kubernetes/pkg/volume/util" 34 utilstrings "k8s.io/utils/strings" 35 ) 36 37 // ProbeVolumePlugins is the entry point for plugin detection in a package. 38 func ProbeVolumePlugins() []volume.VolumePlugin { 39 return []volume.VolumePlugin{&projectedPlugin{}} 40 } 41 42 const ( 43 projectedPluginName = "kubernetes.io/projected" 44 ) 45 46 type projectedPlugin struct { 47 host volume.VolumeHost 48 kvHost volume.KubeletVolumeHost 49 getSecret func(namespace, name string) (*v1.Secret, error) 50 getConfigMap func(namespace, name string) (*v1.ConfigMap, error) 51 getServiceAccountToken func(namespace, name string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) 52 deleteServiceAccountToken func(podUID types.UID) 53 } 54 55 var _ volume.VolumePlugin = &projectedPlugin{} 56 57 func wrappedVolumeSpec() volume.Spec { 58 return volume.Spec{ 59 Volume: &v1.Volume{ 60 VolumeSource: v1.VolumeSource{ 61 EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}, 62 }, 63 }, 64 } 65 } 66 67 func getPath(uid types.UID, volName string, host volume.VolumeHost) string { 68 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(projectedPluginName), volName) 69 } 70 71 func (plugin *projectedPlugin) Init(host volume.VolumeHost) error { 72 plugin.host = host 73 plugin.kvHost = host.(volume.KubeletVolumeHost) 74 plugin.getSecret = host.GetSecretFunc() 75 plugin.getConfigMap = host.GetConfigMapFunc() 76 plugin.getServiceAccountToken = host.GetServiceAccountTokenFunc() 77 plugin.deleteServiceAccountToken = host.DeleteServiceAccountTokenFunc() 78 return nil 79 } 80 81 func (plugin *projectedPlugin) GetPluginName() string { 82 return projectedPluginName 83 } 84 85 func (plugin *projectedPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 86 _, _, err := getVolumeSource(spec) 87 if err != nil { 88 return "", err 89 } 90 91 return spec.Name(), nil 92 } 93 94 func (plugin *projectedPlugin) CanSupport(spec *volume.Spec) bool { 95 return spec.Volume != nil && spec.Volume.Projected != nil 96 } 97 98 func (plugin *projectedPlugin) RequiresRemount(spec *volume.Spec) bool { 99 return true 100 } 101 102 func (plugin *projectedPlugin) SupportsMountOption() bool { 103 return false 104 } 105 106 func (plugin *projectedPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 107 return false, nil 108 } 109 110 func (plugin *projectedPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { 111 return &projectedVolumeMounter{ 112 projectedVolume: &projectedVolume{ 113 volName: spec.Name(), 114 sources: spec.Volume.Projected.Sources, 115 podUID: pod.UID, 116 plugin: plugin, 117 MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))), 118 }, 119 source: *spec.Volume.Projected, 120 pod: pod, 121 opts: &opts, 122 }, nil 123 } 124 125 func (plugin *projectedPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 126 return &projectedVolumeUnmounter{ 127 &projectedVolume{ 128 volName: volName, 129 podUID: podUID, 130 plugin: plugin, 131 MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(podUID, volName, plugin.host))), 132 }, 133 }, nil 134 } 135 136 func (plugin *projectedPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 137 projectedVolume := &v1.Volume{ 138 Name: volumeName, 139 VolumeSource: v1.VolumeSource{ 140 Projected: &v1.ProjectedVolumeSource{}, 141 }, 142 } 143 144 return volume.ReconstructedVolume{ 145 Spec: volume.NewSpecFromVolume(projectedVolume), 146 }, nil 147 } 148 149 type projectedVolume struct { 150 volName string 151 sources []v1.VolumeProjection 152 podUID types.UID 153 plugin *projectedPlugin 154 volume.MetricsProvider 155 } 156 157 var _ volume.Volume = &projectedVolume{} 158 159 func (sv *projectedVolume) GetPath() string { 160 return getPath(sv.podUID, sv.volName, sv.plugin.host) 161 } 162 163 type projectedVolumeMounter struct { 164 *projectedVolume 165 166 source v1.ProjectedVolumeSource 167 pod *v1.Pod 168 opts *volume.VolumeOptions 169 } 170 171 var _ volume.Mounter = &projectedVolumeMounter{} 172 173 func (sv *projectedVolume) GetAttributes() volume.Attributes { 174 return volume.Attributes{ 175 ReadOnly: true, 176 Managed: true, 177 SELinuxRelabel: true, 178 } 179 180 } 181 182 func (s *projectedVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { 183 return s.SetUpAt(s.GetPath(), mounterArgs) 184 } 185 186 func (s *projectedVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 187 klog.V(3).Infof("Setting up volume %v for pod %v at %v", s.volName, s.pod.UID, dir) 188 189 wrapped, err := s.plugin.host.NewWrapperMounter(s.volName, wrappedVolumeSpec(), s.pod, *s.opts) 190 if err != nil { 191 return err 192 } 193 194 data, err := s.collectData(mounterArgs) 195 if err != nil { 196 klog.Errorf("Error preparing data for projected volume %v for pod %v/%v: %s", s.volName, s.pod.Namespace, s.pod.Name, err.Error()) 197 return err 198 } 199 200 setupSuccess := false 201 if err := wrapped.SetUpAt(dir, mounterArgs); err != nil { 202 return err 203 } 204 205 if err := volumeutil.MakeNestedMountpoints(s.volName, dir, *s.pod); err != nil { 206 return err 207 } 208 209 defer func() { 210 // Clean up directories if setup fails 211 if !setupSuccess { 212 unmounter, unmountCreateErr := s.plugin.NewUnmounter(s.volName, s.podUID) 213 if unmountCreateErr != nil { 214 klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", s.volName, unmountCreateErr) 215 return 216 } 217 tearDownErr := unmounter.TearDown() 218 if tearDownErr != nil { 219 klog.Errorf("error tearing down volume %s with : %v", s.volName, tearDownErr) 220 } 221 } 222 }() 223 224 writerContext := fmt.Sprintf("pod %v/%v volume %v", s.pod.Namespace, s.pod.Name, s.volName) 225 writer, err := volumeutil.NewAtomicWriter(dir, writerContext) 226 if err != nil { 227 klog.Errorf("Error creating atomic writer: %v", err) 228 return err 229 } 230 231 setPerms := func(_ string) error { 232 // This may be the first time writing and new files get created outside the timestamp subdirectory: 233 // change the permissions on the whole volume and not only in the timestamp directory. 234 return volume.SetVolumeOwnership(s, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(s.plugin, nil)) 235 } 236 err = writer.Write(data, setPerms) 237 if err != nil { 238 klog.Errorf("Error writing payload to dir: %v", err) 239 return err 240 } 241 242 setupSuccess = true 243 return nil 244 } 245 246 func (s *projectedVolumeMounter) collectData(mounterArgs volume.MounterArgs) (map[string]volumeutil.FileProjection, error) { 247 if s.source.DefaultMode == nil { 248 return nil, fmt.Errorf("no defaultMode used, not even the default value for it") 249 } 250 251 kubeClient := s.plugin.host.GetKubeClient() 252 if kubeClient == nil { 253 return nil, fmt.Errorf("cannot setup projected volume %v because kube client is not configured", s.volName) 254 } 255 256 errlist := []error{} 257 payload := make(map[string]volumeutil.FileProjection) 258 for _, source := range s.source.Sources { 259 switch { 260 case source.Secret != nil: 261 optional := source.Secret.Optional != nil && *source.Secret.Optional 262 secretapi, err := s.plugin.getSecret(s.pod.Namespace, source.Secret.Name) 263 if err != nil { 264 if !(errors.IsNotFound(err) && optional) { 265 klog.Errorf("Couldn't get secret %v/%v: %v", s.pod.Namespace, source.Secret.Name, err) 266 errlist = append(errlist, err) 267 continue 268 } 269 secretapi = &v1.Secret{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Namespace: s.pod.Namespace, 272 Name: source.Secret.Name, 273 }, 274 } 275 } 276 secretPayload, err := secret.MakePayload(source.Secret.Items, secretapi, s.source.DefaultMode, optional) 277 if err != nil { 278 klog.Errorf("Couldn't get secret payload %v/%v: %v", s.pod.Namespace, source.Secret.Name, err) 279 errlist = append(errlist, err) 280 continue 281 } 282 for k, v := range secretPayload { 283 payload[k] = v 284 } 285 case source.ConfigMap != nil: 286 optional := source.ConfigMap.Optional != nil && *source.ConfigMap.Optional 287 configMap, err := s.plugin.getConfigMap(s.pod.Namespace, source.ConfigMap.Name) 288 if err != nil { 289 if !(errors.IsNotFound(err) && optional) { 290 klog.Errorf("Couldn't get configMap %v/%v: %v", s.pod.Namespace, source.ConfigMap.Name, err) 291 errlist = append(errlist, err) 292 continue 293 } 294 configMap = &v1.ConfigMap{ 295 ObjectMeta: metav1.ObjectMeta{ 296 Namespace: s.pod.Namespace, 297 Name: source.ConfigMap.Name, 298 }, 299 } 300 } 301 configMapPayload, err := configmap.MakePayload(source.ConfigMap.Items, configMap, s.source.DefaultMode, optional) 302 if err != nil { 303 klog.Errorf("Couldn't get configMap payload %v/%v: %v", s.pod.Namespace, source.ConfigMap.Name, err) 304 errlist = append(errlist, err) 305 continue 306 } 307 for k, v := range configMapPayload { 308 payload[k] = v 309 } 310 case source.DownwardAPI != nil: 311 downwardAPIPayload, err := downwardapi.CollectData(source.DownwardAPI.Items, s.pod, s.plugin.host, s.source.DefaultMode) 312 if err != nil { 313 errlist = append(errlist, err) 314 continue 315 } 316 for k, v := range downwardAPIPayload { 317 payload[k] = v 318 } 319 case source.ServiceAccountToken != nil: 320 tp := source.ServiceAccountToken 321 322 // When FsGroup is set, we depend on SetVolumeOwnership to 323 // change from 0600 to 0640. 324 mode := *s.source.DefaultMode 325 if mounterArgs.FsUser != nil || mounterArgs.FsGroup != nil { 326 mode = 0600 327 } 328 329 var auds []string 330 if len(tp.Audience) != 0 { 331 auds = []string{tp.Audience} 332 } 333 tr, err := s.plugin.getServiceAccountToken(s.pod.Namespace, s.pod.Spec.ServiceAccountName, &authenticationv1.TokenRequest{ 334 Spec: authenticationv1.TokenRequestSpec{ 335 Audiences: auds, 336 ExpirationSeconds: tp.ExpirationSeconds, 337 BoundObjectRef: &authenticationv1.BoundObjectReference{ 338 APIVersion: "v1", 339 Kind: "Pod", 340 Name: s.pod.Name, 341 UID: s.pod.UID, 342 }, 343 }, 344 }) 345 if err != nil { 346 errlist = append(errlist, err) 347 continue 348 } 349 payload[tp.Path] = volumeutil.FileProjection{ 350 Data: []byte(tr.Status.Token), 351 Mode: mode, 352 FsUser: mounterArgs.FsUser, 353 } 354 case source.ClusterTrustBundle != nil: 355 allowEmpty := false 356 if source.ClusterTrustBundle.Optional != nil && *source.ClusterTrustBundle.Optional { 357 allowEmpty = true 358 } 359 360 var trustAnchors []byte 361 if source.ClusterTrustBundle.Name != nil { 362 var err error 363 trustAnchors, err = s.plugin.kvHost.GetTrustAnchorsByName(*source.ClusterTrustBundle.Name, allowEmpty) 364 if err != nil { 365 errlist = append(errlist, err) 366 continue 367 } 368 } else if source.ClusterTrustBundle.SignerName != nil { 369 var err error 370 trustAnchors, err = s.plugin.kvHost.GetTrustAnchorsBySigner(*source.ClusterTrustBundle.SignerName, source.ClusterTrustBundle.LabelSelector, allowEmpty) 371 if err != nil { 372 errlist = append(errlist, err) 373 continue 374 } 375 } else { 376 errlist = append(errlist, fmt.Errorf("ClusterTrustBundle projection requires either name or signerName to be set")) 377 continue 378 } 379 380 mode := *s.source.DefaultMode 381 if mounterArgs.FsUser != nil || mounterArgs.FsGroup != nil { 382 mode = 0600 383 } 384 385 payload[source.ClusterTrustBundle.Path] = volumeutil.FileProjection{ 386 Data: trustAnchors, 387 Mode: mode, 388 FsUser: mounterArgs.FsUser, 389 } 390 } 391 } 392 return payload, utilerrors.NewAggregate(errlist) 393 } 394 395 type projectedVolumeUnmounter struct { 396 *projectedVolume 397 } 398 399 var _ volume.Unmounter = &projectedVolumeUnmounter{} 400 401 func (c *projectedVolumeUnmounter) TearDown() error { 402 return c.TearDownAt(c.GetPath()) 403 } 404 405 func (c *projectedVolumeUnmounter) TearDownAt(dir string) error { 406 klog.V(3).Infof("Tearing down volume %v for pod %v at %v", c.volName, c.podUID, dir) 407 408 wrapped, err := c.plugin.host.NewWrapperUnmounter(c.volName, wrappedVolumeSpec(), c.podUID) 409 if err != nil { 410 return err 411 } 412 if err = wrapped.TearDownAt(dir); err != nil { 413 return err 414 } 415 416 c.plugin.deleteServiceAccountToken(c.podUID) 417 return nil 418 } 419 420 func getVolumeSource(spec *volume.Spec) (*v1.ProjectedVolumeSource, bool, error) { 421 if spec.Volume != nil && spec.Volume.Projected != nil { 422 return spec.Volume.Projected, spec.ReadOnly, nil 423 } 424 425 return nil, false, fmt.Errorf("Spec does not reference a projected volume type") 426 }