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  }