Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split serviceaccount admission into mutation and validation #55252

Merged
merged 1 commit into from
Nov 9, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 64 additions & 31 deletions plugin/pkg/admission/serviceaccount/admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ type serviceAccount struct {
secretLister corelisters.SecretLister
}

var _ admission.MutationInterface = &serviceAccount{}
var _ admission.ValidationInterface = &serviceAccount{}
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&serviceAccount{})
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&serviceAccount{})

Expand Down Expand Up @@ -132,18 +134,56 @@ func (a *serviceAccount) ValidateInitialization() error {
}

func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
if a.GetResource().GroupResource() != api.Resource("pods") {
if shouldIgnore(a) {
return nil
}
obj := a.GetObject()
if obj == nil {
return nil
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
}
pod, ok := obj.(*api.Pod)
if !ok {
if updateInitialized {
// related pod spec fields are immutable after the pod is initialized
return nil
}

pod := a.GetObject().(*api.Pod)

// Don't modify the spec of mirror pods.
// That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
// That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
return s.Validate(a)
}

// Set the default service account if needed
if len(pod.Spec.ServiceAccountName) == 0 {
pod.Spec.ServiceAccountName = DefaultServiceAccountName
}

serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
}
if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil {
if _, ok := err.(errors.APIStatus); ok {
return err
}
return admission.NewForbidden(a, err)
}
}
if len(pod.Spec.ImagePullSecrets) == 0 {
pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets)
}

return s.Validate(a)
}

func (s *serviceAccount) Validate(a admission.Attributes) (err error) {
if shouldIgnore(a) {
return nil
}
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
Expand All @@ -153,9 +193,9 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
return nil
}

// Don't modify the spec of mirror pods.
// That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
// That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
pod := a.GetObject().(*api.Pod)

// Mirror pods have restrictions on what they can reference
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
if len(pod.Spec.ServiceAccountName) != 0 {
return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
Expand All @@ -171,42 +211,35 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
return nil
}

// Set the default service account if needed
if len(pod.Spec.ServiceAccountName) == 0 {
pod.Spec.ServiceAccountName = DefaultServiceAccountName
}

// Ensure the referenced service account exists
serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
}
if serviceAccount == nil {
// TODO: convert to a ServerTimeout error (or other error that sends a Retry-After header)
return admission.NewForbidden(a, fmt.Errorf("service account %s/%s was not found, retry after the service account is created", a.GetNamespace(), pod.Spec.ServiceAccountName))
}

if s.enforceMountableSecrets(serviceAccount) {
if err := s.limitSecretReferences(serviceAccount, pod); err != nil {
return admission.NewForbidden(a, err)
}
}

if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil {
if _, ok := err.(errors.APIStatus); ok {
return err
}
return admission.NewForbidden(a, err)
}
}
return nil
}

if len(pod.Spec.ImagePullSecrets) == 0 {
pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets)
func shouldIgnore(a admission.Attributes) bool {
if a.GetResource().GroupResource() != api.Resource("pods") {
return true
}
obj := a.GetObject()
if obj == nil {
return true
}
_, ok := obj.(*api.Pod)
if !ok {
return true
}

return nil
return false
}

func shouldAutomount(sa *api.ServiceAccount, pod *api.Pod) bool {
Expand Down Expand Up @@ -267,7 +300,7 @@ func (s *serviceAccount) getServiceAccount(namespace string, name string) (*api.
}
}

return nil, nil
return nil, errors.NewNotFound(api.Resource("serviceaccount"), name)
}

// getReferencedServiceAccountToken returns the name of the first referenced secret which is a ServiceAccountToken for the service account
Expand Down