k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/secret/secret.go (about)

     1  /*
     2  Copyright 2015 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 secret
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"k8s.io/klog/v2"
    23  	"k8s.io/mount-utils"
    24  	utilstrings "k8s.io/utils/strings"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/kubernetes/pkg/volume"
    31  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    32  )
    33  
    34  // ProbeVolumePlugins is the entry point for plugin detection in a package.
    35  func ProbeVolumePlugins() []volume.VolumePlugin {
    36  	return []volume.VolumePlugin{&secretPlugin{}}
    37  }
    38  
    39  const (
    40  	secretPluginName = "kubernetes.io/secret"
    41  )
    42  
    43  // secretPlugin implements the VolumePlugin interface.
    44  type secretPlugin struct {
    45  	host      volume.VolumeHost
    46  	getSecret func(namespace, name string) (*v1.Secret, error)
    47  }
    48  
    49  var _ volume.VolumePlugin = &secretPlugin{}
    50  
    51  func wrappedVolumeSpec() volume.Spec {
    52  	return volume.Spec{
    53  		Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}}},
    54  	}
    55  }
    56  
    57  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    58  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(secretPluginName), volName)
    59  }
    60  
    61  func (plugin *secretPlugin) Init(host volume.VolumeHost) error {
    62  	plugin.host = host
    63  	plugin.getSecret = host.GetSecretFunc()
    64  	return nil
    65  }
    66  
    67  func (plugin *secretPlugin) GetPluginName() string {
    68  	return secretPluginName
    69  }
    70  
    71  func (plugin *secretPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    72  	volumeSource, _ := getVolumeSource(spec)
    73  	if volumeSource == nil {
    74  		return "", fmt.Errorf("Spec does not reference a Secret volume type")
    75  	}
    76  
    77  	return volumeSource.SecretName, nil
    78  }
    79  
    80  func (plugin *secretPlugin) CanSupport(spec *volume.Spec) bool {
    81  	return spec.Volume != nil && spec.Volume.Secret != nil
    82  }
    83  
    84  func (plugin *secretPlugin) RequiresRemount(spec *volume.Spec) bool {
    85  	return true
    86  }
    87  
    88  func (plugin *secretPlugin) SupportsMountOption() bool {
    89  	return false
    90  }
    91  
    92  func (plugin *secretPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
    93  	return false, nil
    94  }
    95  
    96  func (plugin *secretPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
    97  	return &secretVolumeMounter{
    98  		secretVolume: &secretVolume{
    99  			spec.Name(),
   100  			pod.UID,
   101  			plugin,
   102  			plugin.host.GetMounter(plugin.GetPluginName()),
   103  			volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))),
   104  		},
   105  		source:    *spec.Volume.Secret,
   106  		pod:       *pod,
   107  		opts:      &opts,
   108  		getSecret: plugin.getSecret,
   109  	}, nil
   110  }
   111  
   112  func (plugin *secretPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   113  	return &secretVolumeUnmounter{
   114  		&secretVolume{
   115  			volName,
   116  			podUID,
   117  			plugin,
   118  			plugin.host.GetMounter(plugin.GetPluginName()),
   119  			volume.NewCachedMetrics(volume.NewMetricsDu(getPath(podUID, volName, plugin.host))),
   120  		},
   121  	}, nil
   122  }
   123  
   124  func (plugin *secretPlugin) ConstructVolumeSpec(volName, mountPath string) (volume.ReconstructedVolume, error) {
   125  	secretVolume := &v1.Volume{
   126  		Name: volName,
   127  		VolumeSource: v1.VolumeSource{
   128  			Secret: &v1.SecretVolumeSource{
   129  				SecretName: volName,
   130  			},
   131  		},
   132  	}
   133  	return volume.ReconstructedVolume{
   134  		Spec: volume.NewSpecFromVolume(secretVolume),
   135  	}, nil
   136  }
   137  
   138  type secretVolume struct {
   139  	volName string
   140  	podUID  types.UID
   141  	plugin  *secretPlugin
   142  	mounter mount.Interface
   143  	volume.MetricsProvider
   144  }
   145  
   146  var _ volume.Volume = &secretVolume{}
   147  
   148  func (sv *secretVolume) GetPath() string {
   149  	return getPath(sv.podUID, sv.volName, sv.plugin.host)
   150  }
   151  
   152  // secretVolumeMounter handles retrieving secrets from the API server
   153  // and placing them into the volume on the host.
   154  type secretVolumeMounter struct {
   155  	*secretVolume
   156  
   157  	source    v1.SecretVolumeSource
   158  	pod       v1.Pod
   159  	opts      *volume.VolumeOptions
   160  	getSecret func(namespace, name string) (*v1.Secret, error)
   161  }
   162  
   163  var _ volume.Mounter = &secretVolumeMounter{}
   164  
   165  func (sv *secretVolume) GetAttributes() volume.Attributes {
   166  	return volume.Attributes{
   167  		ReadOnly:       true,
   168  		Managed:        true,
   169  		SELinuxRelabel: true,
   170  	}
   171  }
   172  
   173  func (b *secretVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
   174  	return b.SetUpAt(b.GetPath(), mounterArgs)
   175  }
   176  
   177  func (b *secretVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   178  	klog.V(3).Infof("Setting up volume %v for pod %v at %v", b.volName, b.pod.UID, dir)
   179  
   180  	// Wrap EmptyDir, let it do the setup.
   181  	wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, *b.opts)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	optional := b.source.Optional != nil && *b.source.Optional
   187  	secret, err := b.getSecret(b.pod.Namespace, b.source.SecretName)
   188  	if err != nil {
   189  		if !(errors.IsNotFound(err) && optional) {
   190  			klog.Errorf("Couldn't get secret %v/%v: %v", b.pod.Namespace, b.source.SecretName, err)
   191  			return err
   192  		}
   193  		secret = &v1.Secret{
   194  			ObjectMeta: metav1.ObjectMeta{
   195  				Namespace: b.pod.Namespace,
   196  				Name:      b.source.SecretName,
   197  			},
   198  		}
   199  	}
   200  
   201  	totalBytes := totalSecretBytes(secret)
   202  	klog.V(3).Infof("Received secret %v/%v containing (%v) pieces of data, %v total bytes",
   203  		b.pod.Namespace,
   204  		b.source.SecretName,
   205  		len(secret.Data),
   206  		totalBytes)
   207  
   208  	payload, err := MakePayload(b.source.Items, secret, b.source.DefaultMode, optional)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	setupSuccess := false
   214  	if err := wrapped.SetUpAt(dir, mounterArgs); err != nil {
   215  		return err
   216  	}
   217  	if err := volumeutil.MakeNestedMountpoints(b.volName, dir, b.pod); err != nil {
   218  		return err
   219  	}
   220  
   221  	defer func() {
   222  		// Clean up directories if setup fails
   223  		if !setupSuccess {
   224  			unmounter, unmountCreateErr := b.plugin.NewUnmounter(b.volName, b.podUID)
   225  			if unmountCreateErr != nil {
   226  				klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", b.volName, unmountCreateErr)
   227  				return
   228  			}
   229  			tearDownErr := unmounter.TearDown()
   230  			if tearDownErr != nil {
   231  				klog.Errorf("error tearing down volume %s with : %v", b.volName, tearDownErr)
   232  			}
   233  		}
   234  	}()
   235  
   236  	writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
   237  	writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
   238  	if err != nil {
   239  		klog.Errorf("Error creating atomic writer: %v", err)
   240  		return err
   241  	}
   242  
   243  	setPerms := func(_ string) error {
   244  		// This may be the first time writing and new files get created outside the timestamp subdirectory:
   245  		// change the permissions on the whole volume and not only in the timestamp directory.
   246  		return volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
   247  	}
   248  	err = writer.Write(payload, setPerms)
   249  	if err != nil {
   250  		klog.Errorf("Error writing payload to dir: %v", err)
   251  		return err
   252  	}
   253  
   254  	setupSuccess = true
   255  	return nil
   256  }
   257  
   258  // MakePayload function is exported so that it can be called from the projection volume driver
   259  func MakePayload(mappings []v1.KeyToPath, secret *v1.Secret, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) {
   260  	if defaultMode == nil {
   261  		return nil, fmt.Errorf("no defaultMode used, not even the default value for it")
   262  	}
   263  
   264  	payload := make(map[string]volumeutil.FileProjection, len(secret.Data))
   265  	var fileProjection volumeutil.FileProjection
   266  
   267  	if len(mappings) == 0 {
   268  		for name, data := range secret.Data {
   269  			fileProjection.Data = []byte(data)
   270  			fileProjection.Mode = *defaultMode
   271  			payload[name] = fileProjection
   272  		}
   273  	} else {
   274  		for _, ktp := range mappings {
   275  			content, ok := secret.Data[ktp.Key]
   276  			if !ok {
   277  				if optional {
   278  					continue
   279  				}
   280  				errMsg := fmt.Sprintf("references non-existent secret key: %s", ktp.Key)
   281  				klog.Errorf(errMsg)
   282  				return nil, fmt.Errorf(errMsg)
   283  			}
   284  
   285  			fileProjection.Data = []byte(content)
   286  			if ktp.Mode != nil {
   287  				fileProjection.Mode = *ktp.Mode
   288  			} else {
   289  				fileProjection.Mode = *defaultMode
   290  			}
   291  			payload[ktp.Path] = fileProjection
   292  		}
   293  	}
   294  	return payload, nil
   295  }
   296  
   297  func totalSecretBytes(secret *v1.Secret) int {
   298  	totalSize := 0
   299  	for _, bytes := range secret.Data {
   300  		totalSize += len(bytes)
   301  	}
   302  
   303  	return totalSize
   304  }
   305  
   306  // secretVolumeUnmounter handles cleaning up secret volumes.
   307  type secretVolumeUnmounter struct {
   308  	*secretVolume
   309  }
   310  
   311  var _ volume.Unmounter = &secretVolumeUnmounter{}
   312  
   313  func (c *secretVolumeUnmounter) TearDown() error {
   314  	return c.TearDownAt(c.GetPath())
   315  }
   316  
   317  func (c *secretVolumeUnmounter) TearDownAt(dir string) error {
   318  	return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID)
   319  }
   320  
   321  func getVolumeSource(spec *volume.Spec) (*v1.SecretVolumeSource, bool) {
   322  	var readOnly bool
   323  	var volumeSource *v1.SecretVolumeSource
   324  
   325  	if spec.Volume != nil && spec.Volume.Secret != nil {
   326  		volumeSource = spec.Volume.Secret
   327  		readOnly = spec.ReadOnly
   328  	}
   329  
   330  	return volumeSource, readOnly
   331  }