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 }