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