...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages

     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 alwayspullimages contains an admission controller that modifies every new Pod to force
    18  // the image pull policy to Always. This is useful in a multitenant cluster so that users can be
    19  // assured that their private images can only be used by those who have the credentials to pull
    20  // them. Without this admission controller, once an image has been pulled to a node, any pod from
    21  // any user can use it simply by knowing the image's name (assuming the Pod is scheduled onto the
    22  // right node), without any authorization check against the image. With this admission controller
    23  // enabled, images are always pulled prior to starting containers, which means valid credentials are
    24  // required.
    25  package alwayspullimages
    26  
    27  import (
    28  	"context"
    29  	"io"
    30  
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    33  	"k8s.io/apimachinery/pkg/util/sets"
    34  	"k8s.io/apimachinery/pkg/util/validation/field"
    35  	"k8s.io/apiserver/pkg/admission"
    36  	"k8s.io/klog/v2"
    37  	api "k8s.io/kubernetes/pkg/apis/core"
    38  	"k8s.io/kubernetes/pkg/apis/core/pods"
    39  )
    40  
    41  // PluginName indicates name of admission plugin.
    42  const PluginName = "AlwaysPullImages"
    43  
    44  // Register registers a plugin
    45  func Register(plugins *admission.Plugins) {
    46  	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    47  		return NewAlwaysPullImages(), nil
    48  	})
    49  }
    50  
    51  // AlwaysPullImages is an implementation of admission.Interface.
    52  // It looks at all new pods and overrides each container's image pull policy to Always.
    53  type AlwaysPullImages struct {
    54  	*admission.Handler
    55  }
    56  
    57  var _ admission.MutationInterface = &AlwaysPullImages{}
    58  var _ admission.ValidationInterface = &AlwaysPullImages{}
    59  
    60  // Admit makes an admission decision based on the request attributes
    61  func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
    62  	// Ignore all calls to subresources or resources other than pods.
    63  	if shouldIgnore(attributes) {
    64  		return nil
    65  	}
    66  	pod, ok := attributes.GetObject().(*api.Pod)
    67  	if !ok {
    68  		return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
    69  	}
    70  
    71  	pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
    72  		c.ImagePullPolicy = api.PullAlways
    73  		return true
    74  	})
    75  
    76  	return nil
    77  }
    78  
    79  // Validate makes sure that all containers are set to always pull images
    80  func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
    81  	if shouldIgnore(attributes) {
    82  		return nil
    83  	}
    84  
    85  	pod, ok := attributes.GetObject().(*api.Pod)
    86  	if !ok {
    87  		return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
    88  	}
    89  
    90  	var allErrs []error
    91  	pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool {
    92  		if c.ImagePullPolicy != api.PullAlways {
    93  			allErrs = append(allErrs, admission.NewForbidden(attributes,
    94  				field.NotSupported(p.Child("imagePullPolicy"), c.ImagePullPolicy, []string{string(api.PullAlways)}),
    95  			))
    96  		}
    97  		return true
    98  	})
    99  	if len(allErrs) > 0 {
   100  		return utilerrors.NewAggregate(allErrs)
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  // check if it's update and it doesn't change the images referenced by the pod spec
   107  func isUpdateWithNoNewImages(attributes admission.Attributes) bool {
   108  	if attributes.GetOperation() != admission.Update {
   109  		return false
   110  	}
   111  
   112  	pod, ok := attributes.GetObject().(*api.Pod)
   113  	if !ok {
   114  		klog.Warningf("Resource was marked with kind Pod but pod was unable to be converted.")
   115  		return false
   116  	}
   117  
   118  	oldPod, ok := attributes.GetOldObject().(*api.Pod)
   119  	if !ok {
   120  		klog.Warningf("Resource was marked with kind Pod but old pod was unable to be converted.")
   121  		return false
   122  	}
   123  
   124  	oldImages := sets.NewString()
   125  	pods.VisitContainersWithPath(&oldPod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
   126  		oldImages.Insert(c.Image)
   127  		return true
   128  	})
   129  
   130  	hasNewImage := false
   131  	pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
   132  		if !oldImages.Has(c.Image) {
   133  			hasNewImage = true
   134  		}
   135  		return !hasNewImage
   136  	})
   137  	return !hasNewImage
   138  }
   139  
   140  func shouldIgnore(attributes admission.Attributes) bool {
   141  	// Ignore all calls to subresources or resources other than pods.
   142  	if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
   143  		return true
   144  	}
   145  
   146  	if isUpdateWithNoNewImages(attributes) {
   147  		return true
   148  	}
   149  	return false
   150  }
   151  
   152  // NewAlwaysPullImages creates a new always pull images admission control handler
   153  func NewAlwaysPullImages() *AlwaysPullImages {
   154  	return &AlwaysPullImages{
   155  		Handler: admission.NewHandler(admission.Create, admission.Update),
   156  	}
   157  }
   158  

View as plain text