update to inject only the list of excluded resources.

This commit is contained in:
Jiahui Feng 2024-03-05 10:27:35 -08:00
parent e257522889
commit 6b03166bed
7 changed files with 51 additions and 113 deletions

View File

@ -70,7 +70,7 @@ func (c *Config) New(proxyTransport *http.Transport, egressSelector *egressselec
cloudConfig,
discoveryRESTMapper,
quotainstall.NewQuotaConfigurationForAdmission(),
exclusion.NewFilter(),
exclusion.Excluded(),
)
admissionPostStartHook := func(context genericapiserver.PostStartHookContext) error {

View File

@ -1,42 +0,0 @@
/*
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 exclusion
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/resourcefilter"
)
// NewFilter creates a resource filter with the built-in exclusion list.
func NewFilter() resourcefilter.Interface {
return &filter{excluded: sets.New[schema.GroupResource](excluded...)}
}
type filter struct {
excluded sets.Set[schema.GroupResource]
}
func (f *filter) ShouldHandle(a admission.Attributes) bool {
gvr := a.GetResource()
// ignore the version for the decision-making
// because putting different versions into different category
// is almost always a mistake.
gr := gvr.GroupResource()
return !f.excluded.Has(gr)
}

View File

@ -18,9 +18,9 @@ package admission
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/admission/resourcefilter"
quota "k8s.io/apiserver/pkg/quota/v1"
)
@ -33,10 +33,10 @@ type WantsCloudConfig interface {
// PluginInitializer is used for initialization of the Kubernetes specific admission plugins.
type PluginInitializer struct {
cloudConfig []byte
restMapper meta.RESTMapper
quotaConfiguration quota.Configuration
resourceFilter resourcefilter.Interface
cloudConfig []byte
restMapper meta.RESTMapper
quotaConfiguration quota.Configuration
excludedAdmissionResources []schema.GroupResource
}
var _ admission.PluginInitializer = &PluginInitializer{}
@ -48,13 +48,13 @@ func NewPluginInitializer(
cloudConfig []byte,
restMapper meta.RESTMapper,
quotaConfiguration quota.Configuration,
resourceFilter resourcefilter.Interface,
excludedAdmissionResources []schema.GroupResource,
) *PluginInitializer {
return &PluginInitializer{
cloudConfig: cloudConfig,
restMapper: restMapper,
quotaConfiguration: quotaConfiguration,
resourceFilter: resourceFilter,
cloudConfig: cloudConfig,
restMapper: restMapper,
quotaConfiguration: quotaConfiguration,
excludedAdmissionResources: excludedAdmissionResources,
}
}
@ -73,7 +73,7 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
wants.SetQuotaConfiguration(i.quotaConfiguration)
}
if wants, ok := plugin.(initializer.WantsResourceFilter); ok {
wants.SetResourceFilter(i.resourceFilter)
if wants, ok := plugin.(initializer.WantsExcludedAdmissionResources); ok {
wants.SetExcludedAdmissionResources(i.excludedAdmissionResources)
}
}

View File

@ -18,8 +18,8 @@ package initializer
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/resourcefilter"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/cel/openapi/resolver"
quota "k8s.io/apiserver/pkg/quota/v1"
@ -91,9 +91,9 @@ type WantsSchemaResolver interface {
admission.InitializationValidator
}
// WantsResourceFilter defines a function which sets the ResourceFilter for
// an admission plugin that needs it.
type WantsResourceFilter interface {
SetResourceFilter(filter resourcefilter.Interface)
// WantsExcludedAdmissionResources defines a function which sets the ExcludedAdmissionResources
// for an admission plugin that needs it.
type WantsExcludedAdmissionResources interface {
SetExcludedAdmissionResources(excludedAdmissionResources []schema.GroupResource)
admission.InitializationValidator
}

View File

@ -21,12 +21,14 @@ import (
"errors"
"fmt"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
"k8s.io/apiserver/pkg/admission/resourcefilter"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/informers"
@ -37,6 +39,15 @@ import (
type sourceFactory[H any] func(informers.SharedInformerFactory, kubernetes.Interface, dynamic.Interface, meta.RESTMapper) Source[H]
type dispatcherFactory[H any] func(authorizer.Authorizer, *matching.Matcher) Dispatcher[H]
// admissionResources is the list of resources related to CEL-based admission
// features.
var admissionResources = []schema.GroupResource{
{Group: admissionregistrationv1.GroupName, Resource: "validatingadmissionpolicies"},
{Group: admissionregistrationv1.GroupName, Resource: "validatingadmissionpolicybindings"},
{Group: admissionregistrationv1.GroupName, Resource: "mutatingadmissionpolicies"},
{Group: admissionregistrationv1.GroupName, Resource: "mutatingadmissionpolicybindings"},
}
// AdmissionPolicyManager is an abstract admission plugin with all the
// infrastructure to define Admit or Validate on-top.
type Plugin[H any] struct {
@ -49,14 +60,14 @@ type Plugin[H any] struct {
dispatcher Dispatcher[H]
matcher *matching.Matcher
informerFactory informers.SharedInformerFactory
client kubernetes.Interface
restMapper meta.RESTMapper
dynamicClient dynamic.Interface
resourceFilter resourcefilter.Interface // optional
stopCh <-chan struct{}
authorizer authorizer.Authorizer
enabled bool
informerFactory informers.SharedInformerFactory
client kubernetes.Interface
restMapper meta.RESTMapper
dynamicClient dynamic.Interface
excludedResources sets.Set[schema.GroupResource]
stopCh <-chan struct{}
authorizer authorizer.Authorizer
enabled bool
}
var (
@ -66,7 +77,7 @@ var (
_ initializer.WantsDynamicClient = &Plugin[any]{}
_ initializer.WantsDrainedNotification = &Plugin[any]{}
_ initializer.WantsAuthorizer = &Plugin[any]{}
_ initializer.WantsResourceFilter = &Plugin[any]{}
_ initializer.WantsExcludedAdmissionResources = &Plugin[any]{}
_ admission.InitializationValidator = &Plugin[any]{}
)
@ -79,6 +90,9 @@ func NewPlugin[H any](
Handler: handler,
sourceFactory: sourceFactory,
dispatcherFactory: dispatcherFactory,
// always exclude admission/mutating policies and bindings
excludedResources: sets.New(admissionResources...),
}
}
@ -114,8 +128,8 @@ func (c *Plugin[H]) SetEnabled(enabled bool) {
c.enabled = enabled
}
func (c *Plugin[H]) SetResourceFilter(filter resourcefilter.Interface) {
c.resourceFilter = filter
func (c *Plugin[H]) SetExcludedAdmissionResources(excludedResources []schema.GroupResource) {
c.excludedResources.Insert(excludedResources...)
}
// ValidateInitialization - once clientset and informer factory are provided, creates and starts the admission controller
@ -184,7 +198,7 @@ func (c *Plugin[H]) Dispatch(
) (err error) {
if !c.enabled {
return nil
} else if isPolicyResource(a) || (c.resourceFilter != nil && !c.resourceFilter.ShouldHandle(a)) {
} else if c.shouldIgnoreResource(a) {
return nil
} else if !c.WaitForReady() {
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
@ -193,14 +207,9 @@ func (c *Plugin[H]) Dispatch(
return c.dispatcher.Dispatch(ctx, a, o, c.source.Hooks())
}
func isPolicyResource(attr admission.Attributes) bool {
gvk := attr.GetResource()
if gvk.Group == "admissionregistration.k8s.io" {
if gvk.Resource == "validatingadmissionpolicies" || gvk.Resource == "validatingadmissionpolicybindings" {
return true
} else if gvk.Resource == "mutatingadmissionpolicies" || gvk.Resource == "mutatingadmissionpolicybindings" {
return true
}
}
return false
func (c *Plugin[H]) shouldIgnoreResource(attr admission.Attributes) bool {
gvr := attr.GetResource()
// exclusion decision ignores the version.
gr := gvr.GroupResource()
return c.excludedResources.Has(gr)
}

View File

@ -74,7 +74,7 @@ type Plugin struct {
var _ admission.Interface = &Plugin{}
var _ admission.ValidationInterface = &Plugin{}
var _ initializer.WantsFeatures = &Plugin{}
var _ initializer.WantsResourceFilter = &Plugin{}
var _ initializer.WantsExcludedAdmissionResources = &Plugin{}
func NewPlugin(_ io.Reader) *Plugin {
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)

View File

@ -1,29 +0,0 @@
/*
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 resourcefilter
import (
"k8s.io/apiserver/pkg/admission"
)
// Interface is a resource filter that takes an Attributes and
// check if it should be handled or ignored by the admission plugin.
type Interface interface {
// ShouldHandle returns true if the admission plugin should handle the request,
// considering the given Attributes, or false otherwise.
ShouldHandle(admission.Attributes) bool
}