k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/alwayspullimages/admission.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 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 }