1
16
17 package serviceaccount
18
19 import (
20 "context"
21 "fmt"
22 "io"
23 "math/rand"
24 "strconv"
25 "strings"
26 "time"
27
28 corev1 "k8s.io/api/core/v1"
29 "k8s.io/apimachinery/pkg/api/errors"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/util/sets"
32 "k8s.io/apiserver/pkg/admission"
33 genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
34 "k8s.io/apiserver/pkg/storage/names"
35 "k8s.io/client-go/informers"
36 "k8s.io/client-go/kubernetes"
37 corev1listers "k8s.io/client-go/listers/core/v1"
38 podutil "k8s.io/kubernetes/pkg/api/pod"
39 api "k8s.io/kubernetes/pkg/apis/core"
40 "k8s.io/kubernetes/pkg/serviceaccount"
41 "k8s.io/utils/pointer"
42 )
43
44 const (
45
46 DefaultServiceAccountName = "default"
47
48
49
50 EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets"
51
52
53 ServiceAccountVolumeName = "kube-api-access"
54
55
56
57 DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
58
59
60 PluginName = "ServiceAccount"
61 )
62
63
64 func Register(plugins *admission.Plugins) {
65 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
66 serviceAccountAdmission := NewServiceAccount()
67 return serviceAccountAdmission, nil
68 })
69 }
70
71 var _ = admission.Interface(&Plugin{})
72
73
74 type Plugin struct {
75 *admission.Handler
76
77
78 LimitSecretReferences bool
79
80 MountServiceAccountToken bool
81
82 client kubernetes.Interface
83
84 serviceAccountLister corev1listers.ServiceAccountLister
85
86 generateName func(string) string
87 }
88
89 var _ admission.MutationInterface = &Plugin{}
90 var _ admission.ValidationInterface = &Plugin{}
91 var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
92 var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
93
94
95
96
97
98
99
100 func NewServiceAccount() *Plugin {
101 return &Plugin{
102 Handler: admission.NewHandler(admission.Create, admission.Update),
103
104 LimitSecretReferences: false,
105
106 MountServiceAccountToken: true,
107
108 generateName: names.SimpleNameGenerator.GenerateName,
109 }
110 }
111
112
113 func (s *Plugin) SetExternalKubeClientSet(cl kubernetes.Interface) {
114 s.client = cl
115 }
116
117
118 func (s *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
119 serviceAccountInformer := f.Core().V1().ServiceAccounts()
120 s.serviceAccountLister = serviceAccountInformer.Lister()
121 s.SetReadyFunc(func() bool {
122 return serviceAccountInformer.Informer().HasSynced()
123 })
124 }
125
126
127 func (s *Plugin) ValidateInitialization() error {
128 if s.client == nil {
129 return fmt.Errorf("missing client")
130 }
131 if s.serviceAccountLister == nil {
132 return fmt.Errorf("missing serviceAccountLister")
133 }
134 return nil
135 }
136
137
138 func (s *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
139 if shouldIgnore(a) {
140 return nil
141 }
142 if a.GetOperation() != admission.Create {
143
144 return nil
145 }
146 pod := a.GetObject().(*api.Pod)
147
148
149
150
151 if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
152 return s.Validate(ctx, a, o)
153 }
154
155
156 if len(pod.Spec.ServiceAccountName) == 0 {
157 pod.Spec.ServiceAccountName = DefaultServiceAccountName
158 }
159
160 serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
161 if err != nil {
162 return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %w", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
163 }
164 if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
165 s.mountServiceAccountToken(serviceAccount, pod)
166 }
167 if len(pod.Spec.ImagePullSecrets) == 0 {
168 pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
169 for i := 0; i < len(serviceAccount.ImagePullSecrets); i++ {
170 pod.Spec.ImagePullSecrets[i].Name = serviceAccount.ImagePullSecrets[i].Name
171 }
172 }
173
174 return s.Validate(ctx, a, o)
175 }
176
177
178 func (s *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
179 if shouldIgnore(a) {
180 return nil
181 }
182
183 pod := a.GetObject().(*api.Pod)
184
185 if a.GetOperation() == admission.Update && a.GetSubresource() == "ephemeralcontainers" {
186 return s.limitEphemeralContainerSecretReferences(pod, a)
187 }
188
189 if a.GetOperation() != admission.Create {
190
191 return nil
192 }
193
194
195 if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
196 if len(pod.Spec.ServiceAccountName) != 0 {
197 return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
198 }
199 hasSecrets := false
200 podutil.VisitPodSecretNames(pod, func(name string) bool {
201 hasSecrets = true
202 return false
203 }, podutil.AllContainers)
204 if hasSecrets {
205 return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets"))
206 }
207 for _, v := range pod.Spec.Volumes {
208 if proj := v.Projected; proj != nil {
209 for _, projSource := range proj.Sources {
210 if projSource.ServiceAccountToken != nil {
211 return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not use ServiceAccountToken volume projections"))
212 }
213 }
214 }
215 }
216 return nil
217 }
218
219
220 if len(pod.Spec.ServiceAccountName) == 0 {
221 return admission.NewForbidden(a, fmt.Errorf("no service account specified for pod %s/%s", a.GetNamespace(), pod.Name))
222 }
223
224 serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
225 if err != nil {
226 return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
227 }
228
229 if s.enforceMountableSecrets(serviceAccount) {
230 if err := s.limitSecretReferences(serviceAccount, pod); err != nil {
231 return admission.NewForbidden(a, err)
232 }
233 }
234
235 return nil
236 }
237
238 func shouldIgnore(a admission.Attributes) bool {
239 if a.GetResource().GroupResource() != api.Resource("pods") || (a.GetSubresource() != "" && a.GetSubresource() != "ephemeralcontainers") {
240 return true
241 }
242 obj := a.GetObject()
243 if obj == nil {
244 return true
245 }
246 _, ok := obj.(*api.Pod)
247 if !ok {
248 return true
249 }
250
251 return false
252 }
253
254 func shouldAutomount(sa *corev1.ServiceAccount, pod *api.Pod) bool {
255
256 if pod.Spec.AutomountServiceAccountToken != nil {
257 return *pod.Spec.AutomountServiceAccountToken
258 }
259
260 if sa.AutomountServiceAccountToken != nil {
261 return *sa.AutomountServiceAccountToken
262 }
263
264 return true
265 }
266
267
268
269 func (s *Plugin) enforceMountableSecrets(serviceAccount *corev1.ServiceAccount) bool {
270 if s.LimitSecretReferences {
271 return true
272 }
273
274 if value, ok := serviceAccount.Annotations[EnforceMountableSecretsAnnotation]; ok {
275 enforceMountableSecretCheck, _ := strconv.ParseBool(value)
276 return enforceMountableSecretCheck
277 }
278
279 return false
280 }
281
282
283 func (s *Plugin) getServiceAccount(namespace string, name string) (*corev1.ServiceAccount, error) {
284 serviceAccount, err := s.serviceAccountLister.ServiceAccounts(namespace).Get(name)
285 if err == nil {
286 return serviceAccount, nil
287 }
288 if !errors.IsNotFound(err) {
289 return nil, err
290 }
291
292
293 numAttempts := 1
294 if name == DefaultServiceAccountName {
295
296 numAttempts = 10
297 }
298 retryInterval := time.Duration(rand.Int63n(100)+int64(100)) * time.Millisecond
299 for i := 0; i < numAttempts; i++ {
300 if i != 0 {
301 time.Sleep(retryInterval)
302 }
303 serviceAccount, err := s.client.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
304 if err == nil {
305 return serviceAccount, nil
306 }
307 if !errors.IsNotFound(err) {
308 return nil, err
309 }
310 }
311
312 return nil, errors.NewNotFound(api.Resource("serviceaccount"), name)
313 }
314
315 func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, pod *api.Pod) error {
316
317 mountableSecrets := sets.NewString()
318 for _, s := range serviceAccount.Secrets {
319 mountableSecrets.Insert(s.Name)
320 }
321 for _, volume := range pod.Spec.Volumes {
322 source := volume.VolumeSource
323 if source.Secret == nil {
324 continue
325 }
326 secretName := source.Secret.SecretName
327 if !mountableSecrets.Has(secretName) {
328 return fmt.Errorf("volume with secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", secretName, serviceAccount.Name)
329 }
330 }
331
332 for _, container := range pod.Spec.InitContainers {
333 for _, env := range container.Env {
334 if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
335 if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
336 return fmt.Errorf("init container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
337 }
338 }
339 }
340 for _, envFrom := range container.EnvFrom {
341 if envFrom.SecretRef != nil {
342 if !mountableSecrets.Has(envFrom.SecretRef.Name) {
343 return fmt.Errorf("init container %s with envFrom referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
344 }
345 }
346 }
347 }
348
349 for _, container := range pod.Spec.Containers {
350 for _, env := range container.Env {
351 if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
352 if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
353 return fmt.Errorf("container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
354 }
355 }
356 }
357 for _, envFrom := range container.EnvFrom {
358 if envFrom.SecretRef != nil {
359 if !mountableSecrets.Has(envFrom.SecretRef.Name) {
360 return fmt.Errorf("container %s with envFrom referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
361 }
362 }
363 }
364 }
365
366
367 pullSecrets := sets.NewString()
368 for _, s := range serviceAccount.ImagePullSecrets {
369 pullSecrets.Insert(s.Name)
370 }
371 for i, pullSecretRef := range pod.Spec.ImagePullSecrets {
372 if !pullSecrets.Has(pullSecretRef.Name) {
373 return fmt.Errorf(`imagePullSecrets[%d].name="%s" is not allowed because service account %s does not reference that imagePullSecret`, i, pullSecretRef.Name, serviceAccount.Name)
374 }
375 }
376 return nil
377 }
378
379 func (s *Plugin) limitEphemeralContainerSecretReferences(pod *api.Pod, a admission.Attributes) error {
380
381 if len(pod.Spec.ServiceAccountName) == 0 {
382 return admission.NewForbidden(a, fmt.Errorf("no service account specified for pod %s/%s", a.GetNamespace(), pod.Name))
383 }
384
385 serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
386 if err != nil {
387 return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %w", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
388 }
389 if !s.enforceMountableSecrets(serviceAccount) {
390 return nil
391 }
392
393 mountableSecrets := sets.NewString()
394 for _, s := range serviceAccount.Secrets {
395 mountableSecrets.Insert(s.Name)
396 }
397 for _, container := range pod.Spec.EphemeralContainers {
398 for _, env := range container.Env {
399 if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
400 if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
401 return fmt.Errorf("ephemeral container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
402 }
403 }
404 }
405 for _, envFrom := range container.EnvFrom {
406 if envFrom.SecretRef != nil {
407 if !mountableSecrets.Has(envFrom.SecretRef.Name) {
408 return fmt.Errorf("ephemeral container %s with envFrom referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
409 }
410 }
411 }
412 }
413 return nil
414 }
415
416 func (s *Plugin) mountServiceAccountToken(serviceAccount *corev1.ServiceAccount, pod *api.Pod) {
417
418 tokenVolumeName := ""
419 hasTokenVolume := false
420 allVolumeNames := sets.NewString()
421 for _, volume := range pod.Spec.Volumes {
422 allVolumeNames.Insert(volume.Name)
423 if strings.HasPrefix(volume.Name, ServiceAccountVolumeName+"-") {
424 tokenVolumeName = volume.Name
425 hasTokenVolume = true
426 break
427 }
428 }
429
430
431 if len(tokenVolumeName) == 0 {
432 tokenVolumeName = s.generateName(ServiceAccountVolumeName + "-")
433 }
434
435
436 volumeMount := api.VolumeMount{
437 Name: tokenVolumeName,
438 ReadOnly: true,
439 MountPath: DefaultAPITokenMountPath,
440 }
441
442
443 needsTokenVolume := false
444 for i, container := range pod.Spec.InitContainers {
445 existingContainerMount := false
446 for _, volumeMount := range container.VolumeMounts {
447
448 if volumeMount.MountPath == DefaultAPITokenMountPath {
449 existingContainerMount = true
450 break
451 }
452 }
453 if !existingContainerMount {
454 pod.Spec.InitContainers[i].VolumeMounts = append(pod.Spec.InitContainers[i].VolumeMounts, volumeMount)
455 needsTokenVolume = true
456 }
457 }
458 for i, container := range pod.Spec.Containers {
459 existingContainerMount := false
460 for _, volumeMount := range container.VolumeMounts {
461
462 if volumeMount.MountPath == DefaultAPITokenMountPath {
463 existingContainerMount = true
464 break
465 }
466 }
467 if !existingContainerMount {
468 pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, volumeMount)
469 needsTokenVolume = true
470 }
471 }
472
473
474 if !hasTokenVolume && needsTokenVolume {
475 pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{
476 Name: tokenVolumeName,
477 VolumeSource: api.VolumeSource{
478 Projected: TokenVolumeSource(),
479 },
480 })
481 }
482 }
483
484
485 func TokenVolumeSource() *api.ProjectedVolumeSource {
486 return &api.ProjectedVolumeSource{
487
488 DefaultMode: pointer.Int32(corev1.ProjectedVolumeSourceDefaultMode),
489 Sources: []api.VolumeProjection{
490 {
491 ServiceAccountToken: &api.ServiceAccountTokenProjection{
492 Path: "token",
493 ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds,
494 },
495 },
496 {
497 ConfigMap: &api.ConfigMapProjection{
498 LocalObjectReference: api.LocalObjectReference{
499 Name: "kube-root-ca.crt",
500 },
501 Items: []api.KeyToPath{
502 {
503 Key: "ca.crt",
504 Path: "ca.crt",
505 },
506 },
507 },
508 },
509 {
510 DownwardAPI: &api.DownwardAPIProjection{
511 Items: []api.DownwardAPIVolumeFile{
512 {
513 Path: "namespace",
514 FieldRef: &api.ObjectFieldSelector{
515 APIVersion: "v1",
516 FieldPath: "metadata.namespace",
517 },
518 },
519 },
520 },
521 },
522 },
523 }
524 }
525
View as plain text