...

Source file src/k8s.io/kubernetes/pkg/volume/projected/projected.go

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

     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  }
   431  

View as plain text