k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/network/denyserviceexternalips/admission.go (about) 1 /* 2 Copyright 2020 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 denyserviceexternalips 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 24 "k8s.io/apimachinery/pkg/api/errors" 25 "k8s.io/apiserver/pkg/admission" 26 "k8s.io/klog/v2" 27 "k8s.io/kubernetes/pkg/apis/core" 28 ) 29 30 const ( 31 // PluginName is the name of this admission controller plugin 32 PluginName = "DenyServiceExternalIPs" 33 ) 34 35 // Register registers a plugin 36 func Register(plugins *admission.Plugins) { 37 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { 38 plugin := newPlugin() 39 return plugin, nil 40 }) 41 } 42 43 // externalIPsDenierPlugin holds state for and implements the admission plugin. 44 type externalIPsDenierPlugin struct { 45 *admission.Handler 46 } 47 48 var _ admission.Interface = &externalIPsDenierPlugin{} 49 var _ admission.ValidationInterface = &externalIPsDenierPlugin{} 50 51 // newPlugin creates a new admission plugin. 52 func newPlugin() *externalIPsDenierPlugin { 53 return &externalIPsDenierPlugin{ 54 Handler: admission.NewHandler(admission.Create, admission.Update), 55 } 56 } 57 58 // Admit ensures that modifications of the Service.Spec.ExternalIPs field are 59 // denied 60 func (plug *externalIPsDenierPlugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { 61 if attr.GetResource().GroupResource() != core.Resource("services") { 62 return nil 63 } 64 65 if len(attr.GetSubresource()) != 0 { 66 return nil 67 } 68 69 // if we can't convert then we don't handle this object so just return 70 newSvc, ok := attr.GetObject().(*core.Service) 71 if !ok { 72 klog.V(3).Infof("Expected Service resource, got: %v", attr.GetKind()) 73 return errors.NewInternalError(fmt.Errorf("Expected Service resource, got: %v", attr.GetKind())) 74 } 75 76 var oldSvc *core.Service 77 if old := attr.GetOldObject(); old != nil { 78 tmp, ok := old.(*core.Service) 79 if !ok { 80 klog.V(3).Infof("Expected Service resource, got: %v", attr.GetKind()) 81 return errors.NewInternalError(fmt.Errorf("Expected Service resource, got: %v", attr.GetKind())) 82 } 83 oldSvc = tmp 84 } 85 86 if isSubset(newSvc, oldSvc) { 87 return nil 88 } 89 90 klog.V(4).Infof("Denying new use of ExternalIPs on Service %s/%s", newSvc.Namespace, newSvc.Name) 91 return admission.NewForbidden(attr, fmt.Errorf("Use of external IPs is denied by admission control")) 92 } 93 94 func isSubset(newSvc, oldSvc *core.Service) bool { 95 // If new has none, it's a subset. 96 if len(newSvc.Spec.ExternalIPs) == 0 { 97 return true 98 } 99 // If we have some but it's not an update, it's not a subset. 100 if oldSvc == nil { 101 return false 102 } 103 oldIPs := map[string]bool{} 104 for _, ip := range oldSvc.Spec.ExternalIPs { 105 oldIPs[ip] = true 106 } 107 // Every IP in newSvc must be in oldSvc 108 for _, ip := range newSvc.Spec.ExternalIPs { 109 if oldIPs[ip] == false { 110 return false 111 } 112 } 113 return true 114 }