From edb0287cb1435b2a89f79a67141b1482f698e208 Mon Sep 17 00:00:00 2001 From: John Mcgrath Date: Wed, 6 Mar 2024 00:39:23 -0600 Subject: [PATCH] DisableServiceLinks admission controller --- pkg/kubeapiserver/options/plugins.go | 3 + .../disableservicelinks/admission.go | 76 +++++++++++++ .../disableservicelinks/admission_test.go | 101 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 plugin/pkg/admission/disableservicelinks/admission.go create mode 100644 plugin/pkg/admission/disableservicelinks/admission_test.go diff --git a/pkg/kubeapiserver/options/plugins.go b/pkg/kubeapiserver/options/plugins.go index fa2c9b5f426..845dc78102a 100644 --- a/pkg/kubeapiserver/options/plugins.go +++ b/pkg/kubeapiserver/options/plugins.go @@ -31,6 +31,7 @@ import ( certsubjectrestriction "k8s.io/kubernetes/plugin/pkg/admission/certificates/subjectrestriction" "k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds" "k8s.io/kubernetes/plugin/pkg/admission/deny" + "k8s.io/kubernetes/plugin/pkg/admission/disableservicelinks" "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit" "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration" "k8s.io/kubernetes/plugin/pkg/admission/gc" @@ -93,6 +94,7 @@ var AllOrderedPlugins = []string{ certsubjectrestriction.PluginName, // CertificateSubjectRestriction defaultingressclass.PluginName, // DefaultIngressClass denyserviceexternalips.PluginName, // DenyServiceExternalIPs + disableservicelinks.PluginName, // DisableServiceLinks // new admission plugins should generally be inserted above here // webhook, resourcequota, and deny plugins must go at the end @@ -114,6 +116,7 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { defaultingressclass.Register(plugins) denyserviceexternalips.Register(plugins) deny.Register(plugins) // DEPRECATED as no real meaning + disableservicelinks.Register(plugins) eventratelimit.Register(plugins) extendedresourcetoleration.Register(plugins) gc.Register(plugins) diff --git a/plugin/pkg/admission/disableservicelinks/admission.go b/plugin/pkg/admission/disableservicelinks/admission.go new file mode 100644 index 00000000000..322ba0548da --- /dev/null +++ b/plugin/pkg/admission/disableservicelinks/admission.go @@ -0,0 +1,76 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package disableservicelinks + +import ( + "context" + "fmt" + "io" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apiserver/pkg/admission" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/utils/ptr" +) + +// PluginName indicates name of admission plugin. +const PluginName = "DisableServiceLinks" + +// Register is called by the apiserver to register the plugin factory. +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return newDisableServiceLinks(), nil + }) +} + +// newDisableServiceLinks creates a new instance of the DisableServiceLinks admission controller. +func newDisableServiceLinks() *plugin { + return &plugin{ + Handler: admission.NewHandler(admission.Create, admission.Update), + } +} + +// Make sure we are implementing the interface. +var _ admission.MutationInterface = &plugin{} + +type plugin struct { + *admission.Handler +} + +// Admit updates the EnableServiceLinks of a pod and set it to false. +func (p *plugin) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error { + op := attributes.GetOperation() + + // noop admission.Update for future support + if op == admission.Update { + return nil + } + + // Ignore all calls to subresources or resources other than pods. + if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != core.Resource("pods") { + return nil + } + + pod, ok := attributes.GetObject().(*core.Pod) + if !ok { + return errors.NewBadRequest(fmt.Sprintf("expected *core.Pod but got %T", attributes.GetObject())) + } + + pod.Spec.EnableServiceLinks = ptr.To(false) + + return nil +} diff --git a/plugin/pkg/admission/disableservicelinks/admission_test.go b/plugin/pkg/admission/disableservicelinks/admission_test.go new file mode 100644 index 00000000000..9fdc6d07442 --- /dev/null +++ b/plugin/pkg/admission/disableservicelinks/admission_test.go @@ -0,0 +1,101 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package disableservicelinks + +import ( + "context" + "testing" + + "k8s.io/apiserver/pkg/admission" + admissiontesting "k8s.io/apiserver/pkg/admission/testing" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/utils/ptr" +) + +func TestAdmit(t *testing.T) { + + plugin := admissiontesting.WithReinvocationTesting(t, newDisableServiceLinks()) + + tests := []struct { + description string + requestedPod core.Pod + expectedPod core.Pod + operation admission.Operation + }{ + { + description: "Create empty pod with default value of Spec.EnableServiceLinks", + requestedPod: core.Pod{ + Spec: core.PodSpec{}, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{EnableServiceLinks: ptr.To(false)}, + }, + operation: admission.Create, + }, + { + description: "Create empty pod with Spec.EnableServiceLinks set to true", + requestedPod: core.Pod{ + Spec: core.PodSpec{EnableServiceLinks: ptr.To(true)}, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{EnableServiceLinks: ptr.To(false)}, + }, + operation: admission.Create, + }, + { + description: "Update empty pod with Spec.EnableServiceLinks set to true", + requestedPod: core.Pod{ + Spec: core.PodSpec{EnableServiceLinks: ptr.To(true)}, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{EnableServiceLinks: ptr.To(true)}, + }, + operation: admission.Update, + }, + } + for i, test := range tests { + err := plugin.Admit(context.TODO(), admission.NewAttributesRecord(&test.requestedPod, nil, + core.Kind("Pod").WithVersion("version"), "foo", "name", + core.Resource("pods").WithVersion("version"), "", test.operation, + nil, false, nil), nil) + + if err != nil { + t.Errorf("[%d: %s] unexpected error %v for pod %+v", i, test.description, err, test.requestedPod) + } + + if !helper.Semantic.DeepEqual(test.expectedPod.Spec, test.requestedPod.Spec) { + t.Errorf("[%d: %s] expected %t got %t", i, test.description, *test.expectedPod.Spec.EnableServiceLinks, *test.requestedPod.Spec.EnableServiceLinks) + } + } +} + +func TestHandles(t *testing.T) { + plugin := newDisableServiceLinks() + tests := map[admission.Operation]bool{ + admission.Create: true, + admission.Update: true, + admission.Delete: false, + admission.Connect: false, + } + for op, expected := range tests { + result := plugin.Handles(op) + if result != expected { + t.Errorf("Unexpected result for operation %s: %v\n", op, result) + } + } +}