1
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
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
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
237
238 return volume.SetVolumeOwnership(s, dir, mounterArgs.FsGroup, nil , 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
327
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