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, cloudConfig,
discoveryRESTMapper, discoveryRESTMapper,
quotainstall.NewQuotaConfigurationForAdmission(), quotainstall.NewQuotaConfigurationForAdmission(),
exclusion.NewFilter(), exclusion.Excluded(),
) )
admissionPostStartHook := func(context genericapiserver.PostStartHookContext) error { 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 ( import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/admission/resourcefilter"
quota "k8s.io/apiserver/pkg/quota/v1" 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. // PluginInitializer is used for initialization of the Kubernetes specific admission plugins.
type PluginInitializer struct { type PluginInitializer struct {
cloudConfig []byte cloudConfig []byte
restMapper meta.RESTMapper restMapper meta.RESTMapper
quotaConfiguration quota.Configuration quotaConfiguration quota.Configuration
resourceFilter resourcefilter.Interface excludedAdmissionResources []schema.GroupResource
} }
var _ admission.PluginInitializer = &PluginInitializer{} var _ admission.PluginInitializer = &PluginInitializer{}
@ -48,13 +48,13 @@ func NewPluginInitializer(
cloudConfig []byte, cloudConfig []byte,
restMapper meta.RESTMapper, restMapper meta.RESTMapper,
quotaConfiguration quota.Configuration, quotaConfiguration quota.Configuration,
resourceFilter resourcefilter.Interface, excludedAdmissionResources []schema.GroupResource,
) *PluginInitializer { ) *PluginInitializer {
return &PluginInitializer{ return &PluginInitializer{
cloudConfig: cloudConfig, cloudConfig: cloudConfig,
restMapper: restMapper, restMapper: restMapper,
quotaConfiguration: quotaConfiguration, quotaConfiguration: quotaConfiguration,
resourceFilter: resourceFilter, excludedAdmissionResources: excludedAdmissionResources,
} }
} }
@ -73,7 +73,7 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
wants.SetQuotaConfiguration(i.quotaConfiguration) wants.SetQuotaConfiguration(i.quotaConfiguration)
} }
if wants, ok := plugin.(initializer.WantsResourceFilter); ok { if wants, ok := plugin.(initializer.WantsExcludedAdmissionResources); ok {
wants.SetResourceFilter(i.resourceFilter) wants.SetExcludedAdmissionResources(i.excludedAdmissionResources)
} }
} }

View File

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

View File

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

View File

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