mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 18:53:35 +00:00
rebase: update K8s packages to v0.32.1
Update K8s packages in go.mod to v0.32.1 Signed-off-by: Praveen M <m.praveen@ibm.com>
This commit is contained in:
1
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/accessor.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/accessor.go
generated
vendored
@ -26,6 +26,7 @@ type PolicyAccessor interface {
|
||||
GetNamespace() string
|
||||
GetParamKind() *v1.ParamKind
|
||||
GetMatchConstraints() *v1.MatchResources
|
||||
GetFailurePolicy() *v1.FailurePolicyType
|
||||
}
|
||||
|
||||
type BindingAccessor interface {
|
||||
|
3
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/interfaces.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/interfaces.go
generated
vendored
@ -49,6 +49,9 @@ type Source[H Hook] interface {
|
||||
// Dispatcher dispatches evaluates an admission request against the currently
|
||||
// active hooks returned by the source.
|
||||
type Dispatcher[H Hook] interface {
|
||||
// Start the dispatcher. This method should be called only once at startup.
|
||||
Start(ctx context.Context) error
|
||||
|
||||
// Dispatch a request to the policies. Dispatcher may choose not to
|
||||
// call a hook, either because the rules of the hook does not match, or
|
||||
// the namespaceSelector or the objectSelector of the hook does not
|
||||
|
12
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go
generated
vendored
12
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go
generated
vendored
@ -36,8 +36,9 @@ import (
|
||||
)
|
||||
|
||||
// H is the Hook type generated by the source and consumed by the dispatcher.
|
||||
// !TODO: Just pass in a Plugin[H] with accessors to all this information
|
||||
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, kubernetes.Interface) Dispatcher[H]
|
||||
|
||||
// admissionResources is the list of resources related to CEL-based admission
|
||||
// features.
|
||||
@ -170,7 +171,7 @@ func (c *Plugin[H]) ValidateInitialization() error {
|
||||
}
|
||||
|
||||
c.source = c.sourceFactory(c.informerFactory, c.client, c.dynamicClient, c.restMapper)
|
||||
c.dispatcher = c.dispatcherFactory(c.authorizer, c.matcher)
|
||||
c.dispatcher = c.dispatcherFactory(c.authorizer, c.matcher, c.client)
|
||||
|
||||
pluginContext, pluginContextCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
@ -181,10 +182,15 @@ func (c *Plugin[H]) ValidateInitialization() error {
|
||||
go func() {
|
||||
err := c.source.Run(pluginContext)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
utilruntime.HandleError(fmt.Errorf("policy source context unexpectedly closed: %v", err))
|
||||
utilruntime.HandleError(fmt.Errorf("policy source context unexpectedly closed: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
err := c.dispatcher.Start(pluginContext)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
utilruntime.HandleError(fmt.Errorf("policy dispatcher context unexpectedly closed: %w", err))
|
||||
}
|
||||
|
||||
c.SetReadyFunc(func() bool {
|
||||
return namespaceInformer.Informer().HasSynced() && c.source.HasSynced()
|
||||
})
|
||||
|
157
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_dispatcher.go
generated
vendored
157
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_dispatcher.go
generated
vendored
@ -36,7 +36,7 @@ import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// A policy invocation is a single policy-binding-param tuple from a Policy Hook
|
||||
// PolicyInvocation is a single policy-binding-param tuple from a Policy Hook
|
||||
// in the context of a specific request. The params have already been resolved
|
||||
// and any error in configuration or setting up the invocation is stored in
|
||||
// the Error field.
|
||||
@ -62,10 +62,6 @@ type PolicyInvocation[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
|
||||
// Params fetched by the binding to use to evaluate the policy
|
||||
Param runtime.Object
|
||||
|
||||
// Error is set if there was an error with the policy or binding or its
|
||||
// params, etc
|
||||
Error error
|
||||
}
|
||||
|
||||
// dispatcherDelegate is called during a request with a pre-filtered list
|
||||
@ -76,7 +72,7 @@ type PolicyInvocation[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
//
|
||||
// The delegate provides the "validation" or "mutation" aspect of dispatcher functionality
|
||||
// (in contrast to generic.PolicyDispatcher which only selects active policies and params)
|
||||
type dispatcherDelegate[P, B runtime.Object, E Evaluator] func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, versionedAttributes webhookgeneric.VersionedAttributeAccessor, invocations []PolicyInvocation[P, B, E]) error
|
||||
type dispatcherDelegate[P, B runtime.Object, E Evaluator] func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, versionedAttributes webhookgeneric.VersionedAttributeAccessor, invocations []PolicyInvocation[P, B, E]) ([]PolicyError, *apierrors.StatusError)
|
||||
|
||||
type policyDispatcher[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
newPolicyAccessor func(P) PolicyAccessor
|
||||
@ -104,7 +100,10 @@ func NewPolicyDispatcher[P runtime.Object, B runtime.Object, E Evaluator](
|
||||
// request. It then resolves all params and creates an Invocation for each
|
||||
// matching policy-binding-param tuple. The delegate is then called with the
|
||||
// list of tuples.
|
||||
//
|
||||
func (d *policyDispatcher[P, B, E]) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: MatchConditions expressions are not evaluated here. The dispatcher delegate
|
||||
// is expected to ignore the result of any policies whose match conditions dont pass.
|
||||
// This may be possible to refactor so matchconditions are checked here instead.
|
||||
@ -117,29 +116,33 @@ func (d *policyDispatcher[P, B, E]) Dispatch(ctx context.Context, a admission.At
|
||||
objectInterfaces: o,
|
||||
}
|
||||
|
||||
var policyErrors []PolicyError
|
||||
addConfigError := func(err error, definition PolicyAccessor, binding BindingAccessor) {
|
||||
var message error
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err)
|
||||
} else {
|
||||
message = fmt.Errorf("failed to configure binding: %w", err)
|
||||
}
|
||||
|
||||
policyErrors = append(policyErrors, PolicyError{
|
||||
Policy: definition,
|
||||
Binding: binding,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
for _, hook := range hooks {
|
||||
policyAccessor := d.newPolicyAccessor(hook.Policy)
|
||||
matches, matchGVR, matchGVK, err := d.matcher.DefinitionMatches(a, o, policyAccessor)
|
||||
if err != nil {
|
||||
// There was an error evaluating if this policy matches anything.
|
||||
utilruntime.HandleError(err)
|
||||
relevantHooks = append(relevantHooks, PolicyInvocation[P, B, E]{
|
||||
Policy: hook.Policy,
|
||||
Error: err,
|
||||
})
|
||||
addConfigError(err, policyAccessor, nil)
|
||||
continue
|
||||
} else if !matches {
|
||||
continue
|
||||
} else if hook.ConfigurationError != nil {
|
||||
// The policy matches but there is a configuration error with the
|
||||
// policy itself
|
||||
relevantHooks = append(relevantHooks, PolicyInvocation[P, B, E]{
|
||||
Policy: hook.Policy,
|
||||
Error: hook.ConfigurationError,
|
||||
Resource: matchGVR,
|
||||
Kind: matchGVK,
|
||||
})
|
||||
utilruntime.HandleError(hook.ConfigurationError)
|
||||
addConfigError(hook.ConfigurationError, policyAccessor, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -148,19 +151,22 @@ func (d *policyDispatcher[P, B, E]) Dispatch(ctx context.Context, a admission.At
|
||||
matches, err = d.matcher.BindingMatches(a, o, bindingAccessor)
|
||||
if err != nil {
|
||||
// There was an error evaluating if this binding matches anything.
|
||||
utilruntime.HandleError(err)
|
||||
relevantHooks = append(relevantHooks, PolicyInvocation[P, B, E]{
|
||||
Policy: hook.Policy,
|
||||
Binding: binding,
|
||||
Error: err,
|
||||
Resource: matchGVR,
|
||||
Kind: matchGVK,
|
||||
})
|
||||
addConfigError(err, policyAccessor, bindingAccessor)
|
||||
continue
|
||||
} else if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
// here the binding matches.
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls.
|
||||
if _, err = versionedAttrAccessor.VersionedAttribute(matchGVK); err != nil {
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls.
|
||||
addConfigError(err, policyAccessor, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect params for this binding
|
||||
params, err := CollectParams(
|
||||
policyAccessor.GetParamKind(),
|
||||
@ -171,14 +177,7 @@ func (d *policyDispatcher[P, B, E]) Dispatch(ctx context.Context, a admission.At
|
||||
)
|
||||
if err != nil {
|
||||
// There was an error collecting params for this binding.
|
||||
utilruntime.HandleError(err)
|
||||
relevantHooks = append(relevantHooks, PolicyInvocation[P, B, E]{
|
||||
Policy: hook.Policy,
|
||||
Binding: binding,
|
||||
Error: err,
|
||||
Resource: matchGVR,
|
||||
Kind: matchGVK,
|
||||
})
|
||||
addConfigError(err, policyAccessor, bindingAccessor)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -194,23 +193,72 @@ func (d *policyDispatcher[P, B, E]) Dispatch(ctx context.Context, a admission.At
|
||||
Evaluator: hook.Evaluator,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls
|
||||
_, err = versionedAttrAccessor.VersionedAttribute(matchGVK)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
if len(relevantHooks) > 0 {
|
||||
extraPolicyErrors, statusError := d.delegate(ctx, a, o, versionedAttrAccessor, relevantHooks)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
policyErrors = append(policyErrors, extraPolicyErrors...)
|
||||
}
|
||||
|
||||
var filteredErrors []PolicyError
|
||||
for _, e := range policyErrors {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy v1.FailurePolicyType
|
||||
if fp := e.Policy.GetFailurePolicy(); fp == nil {
|
||||
policy = v1.Fail
|
||||
} else {
|
||||
policy = *fp
|
||||
}
|
||||
|
||||
switch policy {
|
||||
case v1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
continue
|
||||
case v1.Fail:
|
||||
filteredErrors = append(filteredErrors, e)
|
||||
default:
|
||||
filteredErrors = append(filteredErrors, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
if len(filteredErrors) > 0 {
|
||||
|
||||
forbiddenErr := admission.NewForbidden(a, fmt.Errorf("admission request denied by policy"))
|
||||
|
||||
// The forbiddenErr is always a StatusError.
|
||||
var err *apierrors.StatusError
|
||||
if !errors.As(forbiddenErr, &err) {
|
||||
// Should never happen.
|
||||
return apierrors.NewInternalError(fmt.Errorf("failed to create status error"))
|
||||
}
|
||||
err.ErrStatus.Message = ""
|
||||
|
||||
for _, policyError := range filteredErrors {
|
||||
message := policyError.Error()
|
||||
|
||||
// If this is the first denied decision, use its message and reason
|
||||
// for the status error message.
|
||||
if err.ErrStatus.Message == "" {
|
||||
err.ErrStatus.Message = message
|
||||
if policyError.Reason != "" {
|
||||
err.ErrStatus.Reason = policyError.Reason
|
||||
}
|
||||
}
|
||||
|
||||
// Add the denied decision's message to the status error's details
|
||||
err.ErrStatus.Details.Causes = append(
|
||||
err.ErrStatus.Details.Causes,
|
||||
metav1.StatusCause{Message: message})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return d.delegate(ctx, a, o, versionedAttrAccessor, relevantHooks)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns params to use to evaluate a policy-binding with given param
|
||||
@ -352,3 +400,18 @@ func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionK
|
||||
v.versionedAttrs[gvk] = versionedAttr
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
type PolicyError struct {
|
||||
Policy PolicyAccessor
|
||||
Binding BindingAccessor
|
||||
Message error
|
||||
Reason metav1.StatusReason
|
||||
}
|
||||
|
||||
func (c PolicyError) Error() string {
|
||||
if c.Binding != nil {
|
||||
return fmt.Sprintf("policy '%s' with binding '%s' denied request: %s", c.Policy.GetName(), c.Binding.GetName(), c.Message.Error())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("policy %q denied request: %s", c.Policy.GetName(), c.Message.Error())
|
||||
}
|
||||
|
18
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_source.go
generated
vendored
18
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_source.go
generated
vendored
@ -41,6 +41,13 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Interval for refreshing policies.
|
||||
// TODO: Consider reducing this to a shorter duration or replacing this entirely
|
||||
// with checks that detect when a policy change took effect.
|
||||
const policyRefreshIntervalDefault = 1 * time.Second
|
||||
|
||||
var policyRefreshInterval = policyRefreshIntervalDefault
|
||||
|
||||
type policySource[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
ctx context.Context
|
||||
policyInformer generic.Informer[P]
|
||||
@ -122,6 +129,15 @@ func NewPolicySource[P runtime.Object, B runtime.Object, E Evaluator](
|
||||
return res
|
||||
}
|
||||
|
||||
// SetPolicyRefreshIntervalForTests allows the refresh interval to be overridden during tests.
|
||||
// This should only be called from tests.
|
||||
func SetPolicyRefreshIntervalForTests(interval time.Duration) func() {
|
||||
policyRefreshInterval = interval
|
||||
return func() {
|
||||
policyRefreshInterval = policyRefreshIntervalDefault
|
||||
}
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) Run(ctx context.Context) error {
|
||||
if s.ctx != nil {
|
||||
return fmt.Errorf("policy source already running")
|
||||
@ -178,7 +194,7 @@ func (s *policySource[P, B, E]) Run(ctx context.Context) error {
|
||||
// and needs to be recompiled
|
||||
go func() {
|
||||
// Loop every 1 second until context is cancelled, refreshing policies
|
||||
wait.Until(s.refreshPolicies, 1*time.Second, ctx.Done())
|
||||
wait.Until(s.refreshPolicies, policyRefreshInterval, ctx.Done())
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
13
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go
generated
vendored
13
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go
generated
vendored
@ -45,7 +45,6 @@ import (
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
)
|
||||
|
||||
// PolicyTestContext is everything you need to unit test a policy plugin
|
||||
@ -196,18 +195,6 @@ func NewPolicyTestContext[P, B runtime.Object, E Evaluator](
|
||||
plugin.SetEnabled(true)
|
||||
|
||||
featureGate := featuregate.NewFeatureGate()
|
||||
err = featureGate.Add(map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
//!TODO: move this to validating specific tests
|
||||
features.ValidatingAdmissionPolicy: {
|
||||
Default: true, PreRelease: featuregate.Beta}})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = featureGate.SetFromMap(map[string]bool{string(features.ValidatingAdmissionPolicy): true})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
testContext, testCancel := context.WithCancel(context.Background())
|
||||
genericInitializer := initializer.New(
|
||||
nativeClient,
|
||||
|
144
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
generated
vendored
Normal file
144
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
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 mutating
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
)
|
||||
|
||||
func NewMutatingAdmissionPolicyAccessor(obj *Policy) generic.PolicyAccessor {
|
||||
return &mutatingAdmissionPolicyAccessor{
|
||||
Policy: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMutatingAdmissionPolicyBindingAccessor(obj *PolicyBinding) generic.BindingAccessor {
|
||||
return &mutatingAdmissionPolicyBindingAccessor{
|
||||
PolicyBinding: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type mutatingAdmissionPolicyAccessor struct {
|
||||
*Policy
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetParamKind() *v1.ParamKind {
|
||||
pk := v.Spec.ParamKind
|
||||
if pk == nil {
|
||||
return nil
|
||||
}
|
||||
return &v1.ParamKind{
|
||||
APIVersion: pk.APIVersion,
|
||||
Kind: pk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResources {
|
||||
return convertV1alpha1ResourceRulesToV1(v.Spec.MatchConstraints)
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return toV1FailurePolicy(v.Spec.FailurePolicy)
|
||||
}
|
||||
|
||||
func toV1FailurePolicy(failurePolicy *v1alpha1.FailurePolicyType) *v1.FailurePolicyType {
|
||||
if failurePolicy == nil {
|
||||
return nil
|
||||
}
|
||||
fp := v1.FailurePolicyType(*failurePolicy)
|
||||
return &fp
|
||||
}
|
||||
|
||||
type mutatingAdmissionPolicyBindingAccessor struct {
|
||||
*PolicyBinding
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetPolicyName() types.NamespacedName {
|
||||
return types.NamespacedName{
|
||||
Namespace: "",
|
||||
Name: v.Spec.PolicyName,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetMatchResources() *v1.MatchResources {
|
||||
return convertV1alpha1ResourceRulesToV1(v.Spec.MatchResources)
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef {
|
||||
if v.Spec.ParamRef == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nfa *v1.ParameterNotFoundActionType
|
||||
if v.Spec.ParamRef.ParameterNotFoundAction != nil {
|
||||
nfa = new(v1.ParameterNotFoundActionType)
|
||||
*nfa = v1.ParameterNotFoundActionType(*v.Spec.ParamRef.ParameterNotFoundAction)
|
||||
}
|
||||
|
||||
return &v1.ParamRef{
|
||||
Name: v.Spec.ParamRef.Name,
|
||||
Namespace: v.Spec.ParamRef.Namespace,
|
||||
Selector: v.Spec.ParamRef.Selector,
|
||||
ParameterNotFoundAction: nfa,
|
||||
}
|
||||
}
|
||||
|
||||
func convertV1alpha1ResourceRulesToV1(mc *v1alpha1.MatchResources) *v1.MatchResources {
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res v1.MatchResources
|
||||
res.NamespaceSelector = mc.NamespaceSelector
|
||||
res.ObjectSelector = mc.ObjectSelector
|
||||
for _, ex := range mc.ExcludeResourceRules {
|
||||
res.ExcludeResourceRules = append(res.ExcludeResourceRules, v1.NamedRuleWithOperations{
|
||||
ResourceNames: ex.ResourceNames,
|
||||
RuleWithOperations: ex.RuleWithOperations,
|
||||
})
|
||||
}
|
||||
for _, ex := range mc.ResourceRules {
|
||||
res.ResourceRules = append(res.ResourceRules, v1.NamedRuleWithOperations{
|
||||
ResourceNames: ex.ResourceNames,
|
||||
RuleWithOperations: ex.RuleWithOperations,
|
||||
})
|
||||
}
|
||||
if mc.MatchPolicy != nil {
|
||||
mp := v1.MatchPolicyType(*mc.MatchPolicy)
|
||||
res.MatchPolicy = &mp
|
||||
}
|
||||
return &res
|
||||
}
|
81
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go
generated
vendored
Normal file
81
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
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 mutating
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// compilePolicy compiles the policy into a PolicyEvaluator
|
||||
// any error is stored and delayed until invocation.
|
||||
//
|
||||
// Each individual mutation is compiled into MutationEvaluationFunc and
|
||||
// returned is a PolicyEvaluator in the same order as the mutations appeared in the policy.
|
||||
func compilePolicy(policy *Policy) PolicyEvaluator {
|
||||
opts := plugincel.OptionalVariableDeclarations{HasParams: policy.Spec.ParamKind != nil, StrictCost: true, HasAuthorizer: true}
|
||||
compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
|
||||
if err != nil {
|
||||
return PolicyEvaluator{Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("failed to initialize CEL compiler: %v", err),
|
||||
}}
|
||||
}
|
||||
|
||||
// Compile and store variables
|
||||
compiler.CompileAndStoreVariables(convertv1alpha1Variables(policy.Spec.Variables), opts, environment.StoredExpressions)
|
||||
|
||||
// Compile matchers
|
||||
var matcher matchconditions.Matcher = nil
|
||||
matchConditions := policy.Spec.MatchConditions
|
||||
if len(matchConditions) > 0 {
|
||||
matchExpressionAccessors := make([]plugincel.ExpressionAccessor, len(matchConditions))
|
||||
for i := range matchConditions {
|
||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||
}
|
||||
matcher = matchconditions.NewMatcher(compiler.CompileCondition(matchExpressionAccessors, opts, environment.StoredExpressions), toV1FailurePolicy(policy.Spec.FailurePolicy), "policy", "validate", policy.Name)
|
||||
}
|
||||
|
||||
// Compiler patchers
|
||||
var patchers []patch.Patcher
|
||||
patchOptions := opts
|
||||
patchOptions.HasPatchTypes = true
|
||||
for _, m := range policy.Spec.Mutations {
|
||||
switch m.PatchType {
|
||||
case v1alpha1.PatchTypeJSONPatch:
|
||||
if m.JSONPatch != nil {
|
||||
accessor := &patch.JSONPatchCondition{Expression: m.JSONPatch.Expression}
|
||||
compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
|
||||
patchers = append(patchers, patch.NewJSONPatcher(compileResult))
|
||||
}
|
||||
case v1alpha1.PatchTypeApplyConfiguration:
|
||||
if m.ApplyConfiguration != nil {
|
||||
accessor := &patch.ApplyConfigurationCondition{Expression: m.ApplyConfiguration.Expression}
|
||||
compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
|
||||
patchers = append(patchers, patch.NewApplyConfigurationPatcher(compileResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PolicyEvaluator{Matcher: matcher, Mutators: patchers, CompositionEnv: compiler.CompositionEnv}
|
||||
}
|
295
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
generated
vendored
Normal file
295
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
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 mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionauthorizer "k8s.io/apiserver/pkg/admission/plugin/authorizer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
webhookgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
func NewDispatcher(a authorizer.Authorizer, m *matching.Matcher, tcm patch.TypeConverterManager) generic.Dispatcher[PolicyHook] {
|
||||
res := &dispatcher{
|
||||
matcher: m,
|
||||
authz: a,
|
||||
typeConverterManager: tcm,
|
||||
}
|
||||
res.Dispatcher = generic.NewPolicyDispatcher[*Policy, *PolicyBinding, PolicyEvaluator](
|
||||
NewMutatingAdmissionPolicyAccessor,
|
||||
NewMutatingAdmissionPolicyBindingAccessor,
|
||||
m,
|
||||
res.dispatchInvocations,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
type dispatcher struct {
|
||||
matcher *matching.Matcher
|
||||
authz authorizer.Authorizer
|
||||
typeConverterManager patch.TypeConverterManager
|
||||
generic.Dispatcher[PolicyHook]
|
||||
}
|
||||
|
||||
func (d *dispatcher) Start(ctx context.Context) error {
|
||||
go d.typeConverterManager.Run(ctx)
|
||||
return d.Dispatcher.Start(ctx)
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatchInvocations(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedAttributes webhookgeneric.VersionedAttributeAccessor,
|
||||
invocations []generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator],
|
||||
) ([]generic.PolicyError, *k8serrors.StatusError) {
|
||||
var lastVersionedAttr *admission.VersionedAttributes
|
||||
|
||||
reinvokeCtx := a.GetReinvocationContext()
|
||||
var policyReinvokeCtx *policyReinvokeContext
|
||||
if v := reinvokeCtx.Value(PluginName); v != nil {
|
||||
policyReinvokeCtx = v.(*policyReinvokeContext)
|
||||
} else {
|
||||
policyReinvokeCtx = &policyReinvokeContext{}
|
||||
reinvokeCtx.SetValue(PluginName, policyReinvokeCtx)
|
||||
}
|
||||
|
||||
if reinvokeCtx.IsReinvoke() && policyReinvokeCtx.IsOutputChangedSinceLastPolicyInvocation(a.GetObject()) {
|
||||
// If the object has changed, we know the in-tree plugin re-invocations have mutated the object,
|
||||
// and we need to reinvoke all eligible policies.
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
}
|
||||
defer func() {
|
||||
policyReinvokeCtx.SetLastPolicyInvocationOutput(a.GetObject())
|
||||
}()
|
||||
|
||||
var policyErrors []generic.PolicyError
|
||||
addConfigError := func(err error, invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator], reason metav1.StatusReason) {
|
||||
policyErrors = append(policyErrors, generic.PolicyError{
|
||||
Message: err,
|
||||
Policy: NewMutatingAdmissionPolicyAccessor(invocation.Policy),
|
||||
Binding: NewMutatingAdmissionPolicyBindingAccessor(invocation.Binding),
|
||||
Reason: reason,
|
||||
})
|
||||
}
|
||||
|
||||
// There is at least one invocation to invoke. Make sure we have a namespace
|
||||
// object if the incoming object is not cluster scoped to pass into the evaluator.
|
||||
var namespace *v1.Namespace
|
||||
var err error
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = d.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "", Resource: "namespaces"}, namespaceName)
|
||||
}
|
||||
}
|
||||
|
||||
authz := admissionauthorizer.NewCachingAuthorizer(d.authz)
|
||||
|
||||
// Should loop through invocations, handling possible error and invoking
|
||||
// evaluator to apply patch, also should handle re-invocations
|
||||
for _, invocation := range invocations {
|
||||
if invocation.Evaluator.CompositionEnv != nil {
|
||||
ctx = invocation.Evaluator.CompositionEnv.CreateContext(ctx)
|
||||
}
|
||||
if len(invocation.Evaluator.Mutators) != len(invocation.Policy.Spec.Mutations) {
|
||||
// This would be a bug. The compiler should always return exactly as
|
||||
// many evaluators as there are mutations
|
||||
return nil, k8serrors.NewInternalError(fmt.Errorf("expected %v compiled evaluators for policy %v, got %v",
|
||||
len(invocation.Policy.Spec.Mutations), invocation.Policy.Name, len(invocation.Evaluator.Mutators)))
|
||||
}
|
||||
|
||||
versionedAttr, err := versionedAttributes.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
// This should never happen, we pre-warm versoined attribute
|
||||
// accessors before starting the dispatcher
|
||||
return nil, k8serrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if invocation.Evaluator.Matcher != nil {
|
||||
matchResults := invocation.Evaluator.Matcher.Match(ctx, versionedAttr, invocation.Param, authz)
|
||||
if matchResults.Error != nil {
|
||||
addConfigError(matchResults.Error, invocation, metav1.StatusReasonInvalid)
|
||||
continue
|
||||
}
|
||||
|
||||
// if preconditions are not met, then skip mutations
|
||||
if !matchResults.Matches {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
invocationKey, invocationKeyErr := keyFor(invocation)
|
||||
if invocationKeyErr != nil {
|
||||
// This should never happen. It occurs if there is a programming
|
||||
// error causing the Param not to be a valid object.
|
||||
return nil, k8serrors.NewInternalError(invocationKeyErr)
|
||||
}
|
||||
if reinvokeCtx.IsReinvoke() && !policyReinvokeCtx.ShouldReinvoke(invocationKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
objectBeforeMutations := versionedAttr.VersionedObject
|
||||
// Mutations for a single invocation of a MutatingAdmissionPolicy are evaluated
|
||||
// in order.
|
||||
for mutationIndex := range invocation.Policy.Spec.Mutations {
|
||||
lastVersionedAttr = versionedAttr
|
||||
if versionedAttr.VersionedObject == nil { // Do not call patchers if there is no object to patch.
|
||||
continue
|
||||
}
|
||||
|
||||
patcher := invocation.Evaluator.Mutators[mutationIndex]
|
||||
optionalVariables := cel.OptionalVariableBindings{VersionedParams: invocation.Param, Authorizer: authz}
|
||||
err = d.dispatchOne(ctx, patcher, o, versionedAttr, namespace, invocation.Resource, optionalVariables)
|
||||
if err != nil {
|
||||
var statusError *k8serrors.StatusError
|
||||
if errors.As(err, &statusError) {
|
||||
return nil, statusError
|
||||
}
|
||||
|
||||
addConfigError(err, invocation, metav1.StatusReasonInvalid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(objectBeforeMutations, versionedAttr.VersionedObject) {
|
||||
// The mutation has changed the object. Prepare to reinvoke all previous mutations that are eligible for re-invocation.
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
}
|
||||
if invocation.Policy.Spec.ReinvocationPolicy == v1alpha1.IfNeededReinvocationPolicy {
|
||||
policyReinvokeCtx.AddReinvocablePolicyToPreviouslyInvoked(invocationKey)
|
||||
}
|
||||
}
|
||||
|
||||
if lastVersionedAttr != nil && lastVersionedAttr.VersionedObject != nil && lastVersionedAttr.Dirty {
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
if err := o.GetObjectConvertor().Convert(lastVersionedAttr.VersionedObject, lastVersionedAttr.Attributes.GetObject(), nil); err != nil {
|
||||
return nil, k8serrors.NewInternalError(fmt.Errorf("failed to convert object: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return policyErrors, nil
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatchOne(
|
||||
ctx context.Context,
|
||||
patcher patch.Patcher,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedAttributes *admission.VersionedAttributes,
|
||||
namespace *v1.Namespace,
|
||||
resource schema.GroupVersionResource,
|
||||
optionalVariables cel.OptionalVariableBindings,
|
||||
) (err error) {
|
||||
if patcher == nil {
|
||||
// internal error. this should not happen
|
||||
return k8serrors.NewInternalError(fmt.Errorf("policy evaluator is nil"))
|
||||
}
|
||||
|
||||
// Find type converter for the invoked Group-Version.
|
||||
typeConverter := d.typeConverterManager.GetTypeConverter(versionedAttributes.VersionedKind)
|
||||
if typeConverter == nil {
|
||||
// This can happen if the request is for a resource whose schema
|
||||
// has not been registered with the type converter manager.
|
||||
return k8serrors.NewServiceUnavailable(fmt.Sprintf("Resource kind %s not found. There can be a delay between when CustomResourceDefinitions are created and when they are available.", versionedAttributes.VersionedKind))
|
||||
}
|
||||
|
||||
patchRequest := patch.Request{
|
||||
MatchedResource: resource,
|
||||
VersionedAttributes: versionedAttributes,
|
||||
ObjectInterfaces: o,
|
||||
OptionalVariables: optionalVariables,
|
||||
Namespace: namespace,
|
||||
TypeConverter: typeConverter,
|
||||
}
|
||||
newVersionedObject, err := patcher.Patch(ctx, patchRequest, celconfig.RuntimeCELCostBudget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch versionedAttributes.VersionedObject.(type) {
|
||||
case *unstructured.Unstructured:
|
||||
// No conversion needed before defaulting for the patch object if the admitted object is unstructured.
|
||||
default:
|
||||
// Before defaulting, if the admitted object is a typed object, convert unstructured patch result back to a typed object.
|
||||
newVersionedObject, err = o.GetObjectConvertor().ConvertToVersion(newVersionedObject, versionedAttributes.GetKind().GroupVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
o.GetObjectDefaulter().Default(newVersionedObject)
|
||||
|
||||
versionedAttributes.Dirty = true
|
||||
versionedAttributes.VersionedObject = newVersionedObject
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyFor(invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator]) (key, error) {
|
||||
var paramUID types.NamespacedName
|
||||
if invocation.Param != nil {
|
||||
paramAccessor, err := meta.Accessor(invocation.Param)
|
||||
if err != nil {
|
||||
// This should never happen, as the param should have been validated
|
||||
// before being passed to the plugin.
|
||||
return key{}, err
|
||||
}
|
||||
paramUID = types.NamespacedName{
|
||||
Name: paramAccessor.GetName(),
|
||||
Namespace: paramAccessor.GetNamespace(),
|
||||
}
|
||||
}
|
||||
|
||||
return key{
|
||||
PolicyUID: types.NamespacedName{
|
||||
Name: invocation.Policy.GetName(),
|
||||
Namespace: invocation.Policy.GetNamespace(),
|
||||
},
|
||||
BindingUID: types.NamespacedName{
|
||||
Name: invocation.Binding.GetName(),
|
||||
Namespace: invocation.Binding.GetNamespace(),
|
||||
},
|
||||
ParamUID: paramUID,
|
||||
}, nil
|
||||
}
|
45
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
generated
vendored
Normal file
45
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
)
|
||||
|
||||
// Patcher provides a patch function to perform a mutation to an object in the admission chain.
|
||||
type Patcher interface {
|
||||
// Patch returns a copy of the object in the request, modified to change specified by the patch.
|
||||
// The original object in the request MUST NOT be modified in-place.
|
||||
Patch(ctx context.Context, request Request, runtimeCELCostBudget int64) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Request defines the arguments required by a patcher.
|
||||
type Request struct {
|
||||
MatchedResource schema.GroupVersionResource
|
||||
VersionedAttributes *admission.VersionedAttributes
|
||||
ObjectInterfaces admission.ObjectInterfaces
|
||||
OptionalVariables cel.OptionalVariableBindings
|
||||
Namespace *v1.Namespace
|
||||
TypeConverter managedfields.TypeConverter
|
||||
}
|
192
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/json_patch.go
generated
vendored
Normal file
192
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/json_patch.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
gojson "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/cel/mutation"
|
||||
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// JSONPatchCondition contains the inputs needed to compile and evaluate a cel expression
|
||||
// that returns a JSON patch value.
|
||||
type JSONPatchCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
var _ plugincel.ExpressionAccessor = &JSONPatchCondition{}
|
||||
|
||||
func (v *JSONPatchCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *JSONPatchCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.ListType(jsonPatchType)}
|
||||
}
|
||||
|
||||
var jsonPatchType = types.NewObjectType("JSONPatch")
|
||||
|
||||
// NewJSONPatcher creates a patcher that performs a JSON Patch mutation.
|
||||
func NewJSONPatcher(patchEvaluator plugincel.MutatingEvaluator) Patcher {
|
||||
return &jsonPatcher{patchEvaluator}
|
||||
}
|
||||
|
||||
type jsonPatcher struct {
|
||||
PatchEvaluator plugincel.MutatingEvaluator
|
||||
}
|
||||
|
||||
func (e *jsonPatcher) Patch(ctx context.Context, r Request, runtimeCELCostBudget int64) (runtime.Object, error) {
|
||||
admissionRequest := plugincel.CreateAdmissionRequest(
|
||||
r.VersionedAttributes.Attributes,
|
||||
metav1.GroupVersionResource(r.MatchedResource),
|
||||
metav1.GroupVersionKind(r.VersionedAttributes.VersionedKind))
|
||||
|
||||
compileErrors := e.PatchEvaluator.CompilationErrors()
|
||||
if len(compileErrors) > 0 {
|
||||
return nil, errors.Join(compileErrors...)
|
||||
}
|
||||
patchObj, _, err := e.evaluatePatchExpression(ctx, e.PatchEvaluator, runtimeCELCostBudget, r, admissionRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o := r.ObjectInterfaces
|
||||
jsonSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), json.SerializerOptions{Pretty: false, Strict: true})
|
||||
objJS, err := runtime.Encode(jsonSerializer, r.VersionedAttributes.VersionedObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create JSON patch: %w", err)
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
// If a json patch fails a test operation, the patch must not be applied
|
||||
return r.VersionedAttributes.VersionedObject, nil
|
||||
}
|
||||
return nil, fmt.Errorf("JSON Patch: %w", err)
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
if _, ok := r.VersionedAttributes.VersionedObject.(*unstructured.Unstructured); ok {
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = o.GetObjectCreater().New(r.VersionedAttributes.VersionedKind)
|
||||
if err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return newVersionedObject, nil
|
||||
}
|
||||
|
||||
func (e *jsonPatcher) evaluatePatchExpression(ctx context.Context, patchEvaluator plugincel.MutatingEvaluator, remainingBudget int64, r Request, admissionRequest *admissionv1.AdmissionRequest) (jsonpatch.Patch, int64, error) {
|
||||
var err error
|
||||
var eval plugincel.EvaluationResult
|
||||
eval, remainingBudget, err = patchEvaluator.ForInput(ctx, r.VersionedAttributes, admissionRequest, r.OptionalVariables, r.Namespace, remainingBudget)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if eval.Error != nil {
|
||||
return nil, -1, eval.Error
|
||||
}
|
||||
refVal := eval.EvalResult
|
||||
|
||||
// the return type can be any valid CEL value.
|
||||
// Scalars, maps and lists are used to set the value when the path points to a field of that type.
|
||||
// ObjectVal is used when the path points to a struct. A map like "{"field1": 1, "fieldX": bool}" is not
|
||||
// possible in Kubernetes CEL because maps and lists may not have mixed types.
|
||||
|
||||
iter, ok := refVal.(traits.Lister)
|
||||
if !ok {
|
||||
// Should never happen since compiler checks return type.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array")
|
||||
}
|
||||
result := jsonpatch.Patch{}
|
||||
for it := iter.Iterator(); it.HasNext() == types.True; {
|
||||
v := it.Next()
|
||||
patchObj, err := v.ConvertToNative(reflect.TypeOf(&mutation.JSONPatchVal{}))
|
||||
if err != nil {
|
||||
// Should never happen since return type is checked by compiler.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array of JSONPatch: %w", err)
|
||||
}
|
||||
op, ok := patchObj.(*mutation.JSONPatchVal)
|
||||
if !ok {
|
||||
// Should never happen since return type is checked by compiler.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array of JSONPatch, got element of %T", patchObj)
|
||||
}
|
||||
|
||||
// Construct a JSON Patch from the evaluated CEL expression
|
||||
resultOp := jsonpatch.Operation{}
|
||||
resultOp["op"] = pointer.To(gojson.RawMessage(strconv.Quote(op.Op)))
|
||||
resultOp["path"] = pointer.To(gojson.RawMessage(strconv.Quote(op.Path)))
|
||||
if len(op.From) > 0 {
|
||||
resultOp["from"] = pointer.To(gojson.RawMessage(strconv.Quote(op.From)))
|
||||
}
|
||||
if op.Val != nil {
|
||||
if objVal, ok := op.Val.(*dynamic.ObjectVal); ok {
|
||||
// TODO: Object initializers are insufficiently type checked.
|
||||
// In the interim, we use this sanity check to detect type mismatches
|
||||
// between field names and Object initializers. For example,
|
||||
// "Object.spec{ selector: Object.spec.wrong{}}" is detected as a mismatch.
|
||||
// Before beta, attaching full type information both to Object initializers and
|
||||
// the "object" and "oldObject" variables is needed. This will allow CEL to
|
||||
// perform comprehensive runtime type checking.
|
||||
err := objVal.CheckTypeNamesMatchFieldPathNames()
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("type mismatch: %w", err)
|
||||
}
|
||||
}
|
||||
// CEL data literals representing arbitrary JSON values can be serialized to JSON for use in
|
||||
// JSON Patch if first converted to pb.Value.
|
||||
v, err := op.Val.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("JSONPath valueExpression evaluated to a type that could not marshal to JSON: %w", err)
|
||||
}
|
||||
b, err := gojson.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("JSONPath valueExpression evaluated to a type that could not marshal to JSON: %w", err)
|
||||
}
|
||||
resultOp["value"] = pointer.To[gojson.RawMessage](b)
|
||||
}
|
||||
|
||||
result = append(result, resultOp)
|
||||
}
|
||||
|
||||
return result, remainingBudget, nil
|
||||
}
|
217
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
generated
vendored
Normal file
217
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
|
||||
)
|
||||
|
||||
// ApplyConfigurationCondition contains the inputs needed to compile and evaluate a cel expression
|
||||
// that returns an apply configuration
|
||||
type ApplyConfigurationCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
var _ plugincel.ExpressionAccessor = &ApplyConfigurationCondition{}
|
||||
|
||||
func (v *ApplyConfigurationCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *ApplyConfigurationCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{applyConfigObjectType}
|
||||
}
|
||||
|
||||
var applyConfigObjectType = celtypes.NewObjectType("Object")
|
||||
|
||||
// NewApplyConfigurationPatcher creates a patcher that performs an applyConfiguration mutation.
|
||||
func NewApplyConfigurationPatcher(expressionEvaluator plugincel.MutatingEvaluator) Patcher {
|
||||
return &applyConfigPatcher{expressionEvaluator: expressionEvaluator}
|
||||
}
|
||||
|
||||
type applyConfigPatcher struct {
|
||||
expressionEvaluator plugincel.MutatingEvaluator
|
||||
}
|
||||
|
||||
func (e *applyConfigPatcher) Patch(ctx context.Context, r Request, runtimeCELCostBudget int64) (runtime.Object, error) {
|
||||
admissionRequest := plugincel.CreateAdmissionRequest(
|
||||
r.VersionedAttributes.Attributes,
|
||||
metav1.GroupVersionResource(r.MatchedResource),
|
||||
metav1.GroupVersionKind(r.VersionedAttributes.VersionedKind))
|
||||
|
||||
compileErrors := e.expressionEvaluator.CompilationErrors()
|
||||
if len(compileErrors) > 0 {
|
||||
return nil, errors.Join(compileErrors...)
|
||||
}
|
||||
eval, _, err := e.expressionEvaluator.ForInput(ctx, r.VersionedAttributes, admissionRequest, r.OptionalVariables, r.Namespace, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eval.Error != nil {
|
||||
return nil, eval.Error
|
||||
}
|
||||
v := eval.EvalResult
|
||||
|
||||
// The compiler ensures that the return type is an ObjectVal with type name of "Object".
|
||||
objVal, ok := v.(*dynamic.ObjectVal)
|
||||
if !ok {
|
||||
// Should not happen since the compiler type checks the return type.
|
||||
return nil, fmt.Errorf("unsupported return type from ApplyConfiguration expression: %v", v.Type())
|
||||
}
|
||||
// TODO: Object initializers are insufficiently type checked.
|
||||
// In the interim, we use this sanity check to detect type mismatches
|
||||
// between field names and Object initializers. For example,
|
||||
// "Object.spec{ selector: Object.spec.wrong{}}" is detected as a mismatch.
|
||||
// Before beta, attaching full type information both to Object initializers and
|
||||
// the "object" and "oldObject" variables is needed. This will allow CEL to
|
||||
// perform comprehensive runtime type checking.
|
||||
err = objVal.CheckTypeNamesMatchFieldPathNames()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("type mismatch: %w", err)
|
||||
}
|
||||
|
||||
value, ok := objVal.Value().(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid return type: %T", v)
|
||||
}
|
||||
|
||||
patchObject := unstructured.Unstructured{Object: value}
|
||||
patchObject.SetGroupVersionKind(r.VersionedAttributes.VersionedObject.GetObjectKind().GroupVersionKind())
|
||||
patched, err := ApplyStructuredMergeDiff(r.TypeConverter, r.VersionedAttributes.VersionedObject, &patchObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error applying patch: %w", err)
|
||||
}
|
||||
return patched, nil
|
||||
}
|
||||
|
||||
// ApplyStructuredMergeDiff applies a structured merge diff to an object and returns a copy of the object
|
||||
// with the patch applied.
|
||||
func ApplyStructuredMergeDiff(
|
||||
typeConverter managedfields.TypeConverter,
|
||||
originalObject runtime.Object,
|
||||
patch *unstructured.Unstructured,
|
||||
) (runtime.Object, error) {
|
||||
if patch.GroupVersionKind() != originalObject.GetObjectKind().GroupVersionKind() {
|
||||
return nil, fmt.Errorf("patch (%v) and original object (%v) are not of the same gvk", patch.GroupVersionKind().String(), originalObject.GetObjectKind().GroupVersionKind().String())
|
||||
} else if typeConverter == nil {
|
||||
return nil, fmt.Errorf("type converter must not be nil")
|
||||
}
|
||||
|
||||
patchObjTyped, err := typeConverter.ObjectToTyped(patch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert patch object to typed object: %w", err)
|
||||
}
|
||||
|
||||
err = validatePatch(patchObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ApplyConfiguration: %w", err)
|
||||
}
|
||||
|
||||
liveObjTyped, err := typeConverter.ObjectToTyped(originalObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert original object to typed object: %w", err)
|
||||
}
|
||||
|
||||
newObjTyped, err := liveObjTyped.Merge(patchObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to merge patch: %w", err)
|
||||
}
|
||||
|
||||
// Our mutating admission policy sets the fields but does not track ownership.
|
||||
// Newly introduced fields in the patch won't be tracked by a field manager
|
||||
// (so if the original object is updated again but the mutating policy is
|
||||
// not active, the fields will be dropped).
|
||||
//
|
||||
// This necessarily means that changes to an object by a mutating policy
|
||||
// are only preserved if the policy was active at the time of the change.
|
||||
// (If the policy is not active, the changes may be dropped.)
|
||||
|
||||
newObj, err := typeConverter.TypedToObject(newObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert typed object to object: %w", err)
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// validatePatch searches an apply configuration for any arrays, maps or structs elements that are atomic and returns
|
||||
// an error if any are found.
|
||||
// This prevents accidental removal of fields that can occur when the user intends to modify some
|
||||
// fields in an atomic type, not realizing that all fields not explicitly set in the new value
|
||||
// of the atomic will be removed.
|
||||
func validatePatch(v *typed.TypedValue) error {
|
||||
atomics := findAtomics(nil, v.Schema(), v.TypeRef(), v.AsValue())
|
||||
if len(atomics) > 0 {
|
||||
return fmt.Errorf("may not mutate atomic arrays, maps or structs: %v", strings.Join(atomics, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAtomics returns field paths for any atomic arrays, maps or structs found when traversing the given value.
|
||||
func findAtomics(path []fieldpath.PathElement, s *schema.Schema, tr schema.TypeRef, v value.Value) (atomics []string) {
|
||||
if a, ok := s.Resolve(tr); ok { // Validation pass happens before this and checks that all schemas can be resolved
|
||||
if v.IsMap() && a.Map != nil {
|
||||
if a.Map.ElementRelationship == schema.Atomic {
|
||||
atomics = append(atomics, pathString(path))
|
||||
}
|
||||
v.AsMap().Iterate(func(key string, val value.Value) bool {
|
||||
pe := fieldpath.PathElement{FieldName: &key}
|
||||
if sf, ok := a.Map.FindField(key); ok {
|
||||
tr = sf.Type
|
||||
atomics = append(atomics, findAtomics(append(path, pe), s, tr, val)...)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if v.IsList() && a.List != nil {
|
||||
if a.List.ElementRelationship == schema.Atomic {
|
||||
atomics = append(atomics, pathString(path))
|
||||
}
|
||||
list := v.AsList()
|
||||
for i := 0; i < list.Length(); i++ {
|
||||
pe := fieldpath.PathElement{Index: &i}
|
||||
atomics = append(atomics, findAtomics(append(path, pe), s, a.List.ElementType, list.At(i))...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return atomics
|
||||
}
|
||||
|
||||
func pathString(path []fieldpath.PathElement) string {
|
||||
sb := strings.Builder{}
|
||||
for _, p := range path {
|
||||
sb.WriteString(p.String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
187
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go
generated
vendored
Normal file
187
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/openapi"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
type TypeConverterManager interface {
|
||||
// GetTypeConverter returns a type converter for the given GVK
|
||||
GetTypeConverter(gvk schema.GroupVersionKind) managedfields.TypeConverter
|
||||
Run(ctx context.Context)
|
||||
}
|
||||
|
||||
func NewTypeConverterManager(
|
||||
staticTypeConverter managedfields.TypeConverter,
|
||||
openapiClient openapi.Client,
|
||||
) TypeConverterManager {
|
||||
return &typeConverterManager{
|
||||
staticTypeConverter: staticTypeConverter,
|
||||
openapiClient: openapiClient,
|
||||
typeConverterMap: make(map[schema.GroupVersion]typeConverterCacheEntry),
|
||||
lastFetchedPaths: make(map[schema.GroupVersion]openapi.GroupVersion),
|
||||
}
|
||||
}
|
||||
|
||||
type typeConverterCacheEntry struct {
|
||||
typeConverter managedfields.TypeConverter
|
||||
entry openapi.GroupVersion
|
||||
}
|
||||
|
||||
// typeConverterManager helps us make sure we have an up to date schema and
|
||||
// type converter for our openapi models. It should be connfigured to use a
|
||||
// static type converter for natively typed schemas, and fetches the schema
|
||||
// for CRDs/other over the network on demand (trying to reduce network calls where necessary)
|
||||
type typeConverterManager struct {
|
||||
// schemaCache is used to cache the schema for a given GVK
|
||||
staticTypeConverter managedfields.TypeConverter
|
||||
|
||||
// discoveryClient is used to fetch the schema for a given GVK
|
||||
openapiClient openapi.Client
|
||||
|
||||
lock sync.RWMutex
|
||||
|
||||
typeConverterMap map[schema.GroupVersion]typeConverterCacheEntry
|
||||
lastFetchedPaths map[schema.GroupVersion]openapi.GroupVersion
|
||||
}
|
||||
|
||||
func (t *typeConverterManager) Run(ctx context.Context) {
|
||||
// Loop every 5s refershing the OpenAPI schema list to know which
|
||||
// schemas have been invalidated. This should use e-tags under the hood
|
||||
_ = wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(_ context.Context) (done bool, err error) {
|
||||
paths, err := t.openapiClient.Paths()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to fetch openapi paths: %w", err))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// The /openapi/v3 endpoint contains a list of paths whose ServerRelativeURL
|
||||
// value changes every time the schema is updated. So we poll /openapi/v3
|
||||
// to get the "version number" for each schema, and invalidate our cache
|
||||
// if the version number has changed since we pulled it.
|
||||
parsedPaths := make(map[schema.GroupVersion]openapi.GroupVersion, len(paths))
|
||||
for path, entry := range paths {
|
||||
if !strings.HasPrefix(path, "apis/") && !strings.HasPrefix(path, "api/") {
|
||||
continue
|
||||
}
|
||||
path = strings.TrimPrefix(path, "apis/")
|
||||
path = strings.TrimPrefix(path, "api/")
|
||||
|
||||
gv, err := schema.ParseGroupVersion(path)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to parse group version %q: %w", path, err))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
parsedPaths[gv] = entry
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.lastFetchedPaths = parsedPaths
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *typeConverterManager) GetTypeConverter(gvk schema.GroupVersionKind) managedfields.TypeConverter {
|
||||
// Check to see if the static type converter handles this GVK
|
||||
if t.staticTypeConverter != nil {
|
||||
//!TODO: Add ability to check existence to type converter
|
||||
// working around for now but seeing if getting a typed version of an
|
||||
// empty object returns error
|
||||
stub := &unstructured.Unstructured{}
|
||||
stub.SetGroupVersionKind(gvk)
|
||||
|
||||
if _, err := t.staticTypeConverter.ObjectToTyped(stub); err == nil {
|
||||
return t.staticTypeConverter
|
||||
}
|
||||
}
|
||||
|
||||
gv := gvk.GroupVersion()
|
||||
|
||||
existing, entry, err := func() (managedfields.TypeConverter, openapi.GroupVersion, error) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
// If schema is not supported by static type converter, ask discovery
|
||||
// for the schema
|
||||
entry, ok := t.lastFetchedPaths[gv]
|
||||
if !ok {
|
||||
// If we can't get the schema, we can't do anything
|
||||
return nil, nil, fmt.Errorf("no schema for %v", gvk)
|
||||
}
|
||||
|
||||
// If the entry schema has not changed, used the same type converter
|
||||
if existing, ok := t.typeConverterMap[gv]; ok && existing.entry.ServerRelativeURL() == entry.ServerRelativeURL() {
|
||||
// If we have a type converter for this GVK, return it
|
||||
return existing.typeConverter, existing.entry, nil
|
||||
}
|
||||
|
||||
return nil, entry, nil
|
||||
}()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return nil
|
||||
} else if existing != nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
schBytes, err := entry.Schema(runtime.ContentTypeJSON)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to get schema for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
var sch spec3.OpenAPI
|
||||
if err := json.Unmarshal(schBytes, &sch); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to unmarshal schema for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// The schema has changed, or there is no entry for it, generate
|
||||
// a new type converter for this GV
|
||||
tc, err := managedfields.NewTypeConverter(sch.Components.Schemas, false)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to create type converter for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.typeConverterMap[gv] = typeConverterCacheEntry{
|
||||
typeConverter: tc,
|
||||
entry: entry,
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
151
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go
generated
vendored
Normal file
151
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
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 mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"io"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "MutatingAdmissionPolicy"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(configFile), nil
|
||||
})
|
||||
}
|
||||
|
||||
type Policy = v1alpha1.MutatingAdmissionPolicy
|
||||
type PolicyBinding = v1alpha1.MutatingAdmissionPolicyBinding
|
||||
type PolicyMutation = v1alpha1.Mutation
|
||||
type PolicyHook = generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]
|
||||
|
||||
type Mutator struct {
|
||||
}
|
||||
type MutationEvaluationFunc func(
|
||||
ctx context.Context,
|
||||
matchedResource schema.GroupVersionResource,
|
||||
versionedAttr *admission.VersionedAttributes,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedParams runtime.Object,
|
||||
namespace *corev1.Namespace,
|
||||
typeConverter managedfields.TypeConverter,
|
||||
runtimeCELCostBudget int64,
|
||||
authorizer authorizer.Authorizer,
|
||||
) (runtime.Object, error)
|
||||
|
||||
type PolicyEvaluator struct {
|
||||
Matcher matchconditions.Matcher
|
||||
Mutators []patch.Patcher
|
||||
CompositionEnv *cel.CompositionEnv
|
||||
Error error
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Plugin[PolicyHook]
|
||||
}
|
||||
|
||||
var _ admission.Interface = &Plugin{}
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
|
||||
// NewPlugin returns a generic admission webhook plugin.
|
||||
func NewPlugin(_ io.Reader) *Plugin {
|
||||
// There is no request body to mutate for DELETE, so this plugin never handles that operation.
|
||||
handler := admission.NewHandler(admission.Create, admission.Update, admission.Connect)
|
||||
res := &Plugin{}
|
||||
res.Plugin = generic.NewPlugin(
|
||||
handler,
|
||||
func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] {
|
||||
return generic.NewPolicySource(
|
||||
f.Admissionregistration().V1alpha1().MutatingAdmissionPolicies().Informer(),
|
||||
f.Admissionregistration().V1alpha1().MutatingAdmissionPolicyBindings().Informer(),
|
||||
NewMutatingAdmissionPolicyAccessor,
|
||||
NewMutatingAdmissionPolicyBindingAccessor,
|
||||
compilePolicy,
|
||||
//!TODO: Create a way to share param informers between
|
||||
// mutating/validating plugins
|
||||
f,
|
||||
dynamicClient,
|
||||
restMapper,
|
||||
)
|
||||
},
|
||||
func(a authorizer.Authorizer, m *matching.Matcher, client kubernetes.Interface) generic.Dispatcher[PolicyHook] {
|
||||
return NewDispatcher(a, m, patch.NewTypeConverterManager(nil, client.Discovery().OpenAPIV3()))
|
||||
},
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Plugin.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
func (a *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
a.Plugin.SetEnabled(featureGates.Enabled(features.MutatingAdmissionPolicy))
|
||||
}
|
||||
|
||||
// Variable is a named expression for composition.
|
||||
type Variable struct {
|
||||
Name string
|
||||
Expression string
|
||||
}
|
||||
|
||||
func (v *Variable) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *Variable) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.AnyType, celgo.DynType}
|
||||
}
|
||||
|
||||
func (v *Variable) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func convertv1alpha1Variables(variables []v1alpha1.Variable) []cel.NamedExpressionAccessor {
|
||||
namedExpressions := make([]cel.NamedExpressionAccessor, len(variables))
|
||||
for i, variable := range variables {
|
||||
namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression}
|
||||
}
|
||||
return namedExpressions
|
||||
}
|
76
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go
generated
vendored
Normal file
76
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go
generated
vendored
Normal file
@ -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 mutating
|
||||
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
type key struct {
|
||||
PolicyUID types.NamespacedName
|
||||
BindingUID types.NamespacedName
|
||||
ParamUID types.NamespacedName
|
||||
MutationIndex int
|
||||
}
|
||||
|
||||
type policyReinvokeContext struct {
|
||||
// lastPolicyOutput holds the result of the last Policy admission plugin call
|
||||
lastPolicyOutput runtime.Object
|
||||
// previouslyInvokedReinvocablePolicys holds the set of policies that have been invoked and
|
||||
// should be reinvoked if a later mutation occurs
|
||||
previouslyInvokedReinvocablePolicies sets.Set[key]
|
||||
// reinvokePolicies holds the set of Policies that should be reinvoked
|
||||
reinvokePolicies sets.Set[key]
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) ShouldReinvoke(policy key) bool {
|
||||
return rc.reinvokePolicies.Has(policy)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) IsOutputChangedSinceLastPolicyInvocation(object runtime.Object) bool {
|
||||
return !apiequality.Semantic.DeepEqual(rc.lastPolicyOutput, object)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) SetLastPolicyInvocationOutput(object runtime.Object) {
|
||||
if object == nil {
|
||||
rc.lastPolicyOutput = nil
|
||||
return
|
||||
}
|
||||
rc.lastPolicyOutput = object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) AddReinvocablePolicyToPreviouslyInvoked(policy key) {
|
||||
if rc.previouslyInvokedReinvocablePolicies == nil {
|
||||
rc.previouslyInvokedReinvocablePolicies = sets.New[key]()
|
||||
}
|
||||
rc.previouslyInvokedReinvocablePolicies.Insert(policy)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() {
|
||||
if len(rc.previouslyInvokedReinvocablePolicies) > 0 {
|
||||
if rc.reinvokePolicies == nil {
|
||||
rc.reinvokePolicies = sets.New[key]()
|
||||
}
|
||||
for s := range rc.previouslyInvokedReinvocablePolicies {
|
||||
rc.reinvokePolicies.Insert(s)
|
||||
}
|
||||
rc.previouslyInvokedReinvocablePolicies = sets.New[key]()
|
||||
}
|
||||
}
|
4
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/accessor.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/accessor.go
generated
vendored
@ -54,6 +54,10 @@ func (v *validatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResou
|
||||
return v.Spec.MatchConstraints
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return v.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
type validatingAdmissionPolicyBindingAccessor struct {
|
||||
*v1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
152
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/caching_authorizer.go
generated
vendored
152
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/caching_authorizer.go
generated
vendored
@ -1,152 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
type authzResult struct {
|
||||
authorized authorizer.Decision
|
||||
reason string
|
||||
err error
|
||||
}
|
||||
|
||||
type cachingAuthorizer struct {
|
||||
authorizer authorizer.Authorizer
|
||||
decisions map[string]authzResult
|
||||
}
|
||||
|
||||
func newCachingAuthorizer(in authorizer.Authorizer) authorizer.Authorizer {
|
||||
return &cachingAuthorizer{
|
||||
authorizer: in,
|
||||
decisions: make(map[string]authzResult),
|
||||
}
|
||||
}
|
||||
|
||||
// The attribute accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ authorizer.Attributes = (interface {
|
||||
GetUser() user.Info
|
||||
GetVerb() string
|
||||
IsReadOnly() bool
|
||||
GetNamespace() string
|
||||
GetResource() string
|
||||
GetSubresource() string
|
||||
GetName() string
|
||||
GetAPIGroup() string
|
||||
GetAPIVersion() string
|
||||
IsResourceRequest() bool
|
||||
GetPath() string
|
||||
GetFieldSelector() (fields.Requirements, error)
|
||||
GetLabelSelector() (labels.Requirements, error)
|
||||
})(nil)
|
||||
|
||||
// The user info accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ user.Info = (interface {
|
||||
GetName() string
|
||||
GetUID() string
|
||||
GetGroups() []string
|
||||
GetExtra() map[string][]string
|
||||
})(nil)
|
||||
|
||||
// Authorize returns an authorization decision by delegating to another Authorizer. If an equivalent
|
||||
// check has already been performed, a cached result is returned. Not safe for concurrent use.
|
||||
func (ca *cachingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
type SerializableAttributes struct {
|
||||
authorizer.AttributesRecord
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
serializableAttributes := SerializableAttributes{
|
||||
AttributesRecord: authorizer.AttributesRecord{
|
||||
Verb: a.GetVerb(),
|
||||
Namespace: a.GetNamespace(),
|
||||
APIGroup: a.GetAPIGroup(),
|
||||
APIVersion: a.GetAPIVersion(),
|
||||
Resource: a.GetResource(),
|
||||
Subresource: a.GetSubresource(),
|
||||
Name: a.GetName(),
|
||||
ResourceRequest: a.IsResourceRequest(),
|
||||
Path: a.GetPath(),
|
||||
},
|
||||
}
|
||||
// in the error case, we won't honor this field selector, so the cache doesn't need it.
|
||||
if fieldSelector, err := a.GetFieldSelector(); len(fieldSelector) > 0 {
|
||||
serializableAttributes.FieldSelectorRequirements, serializableAttributes.FieldSelectorParsingErr = fieldSelector, err
|
||||
}
|
||||
if labelSelector, _ := a.GetLabelSelector(); len(labelSelector) > 0 {
|
||||
// the labels requirements have private elements so those don't help us serialize to a unique key
|
||||
serializableAttributes.LabelSelector = labelSelector.String()
|
||||
}
|
||||
|
||||
if u := a.GetUser(); u != nil {
|
||||
di := &user.DefaultInfo{
|
||||
Name: u.GetName(),
|
||||
UID: u.GetUID(),
|
||||
}
|
||||
|
||||
// Differently-ordered groups or extras could cause otherwise-equivalent checks to
|
||||
// have distinct cache keys.
|
||||
if groups := u.GetGroups(); len(groups) > 0 {
|
||||
di.Groups = make([]string, len(groups))
|
||||
copy(di.Groups, groups)
|
||||
sort.Strings(di.Groups)
|
||||
}
|
||||
|
||||
if extra := u.GetExtra(); len(extra) > 0 {
|
||||
di.Extra = make(map[string][]string, len(extra))
|
||||
for k, vs := range extra {
|
||||
vdupe := make([]string, len(vs))
|
||||
copy(vdupe, vs)
|
||||
sort.Strings(vdupe)
|
||||
di.Extra[k] = vdupe
|
||||
}
|
||||
}
|
||||
|
||||
serializableAttributes.User = di
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if err := json.NewEncoder(&b).Encode(serializableAttributes); err != nil {
|
||||
return authorizer.DecisionNoOpinion, "", err
|
||||
}
|
||||
key := b.String()
|
||||
|
||||
if cached, ok := ca.decisions[key]; ok {
|
||||
return cached.authorized, cached.reason, cached.err
|
||||
}
|
||||
|
||||
authorized, reason, err := ca.authorizer.Authorize(ctx, a)
|
||||
|
||||
ca.decisions[key] = authzResult{
|
||||
authorized: authorized,
|
||||
reason: reason,
|
||||
err: err,
|
||||
}
|
||||
|
||||
return authorized, reason, err
|
||||
}
|
7
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/dispatcher.go
generated
vendored
7
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/dispatcher.go
generated
vendored
@ -30,6 +30,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionauthorizer "k8s.io/apiserver/pkg/admission/plugin/authorizer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
@ -63,6 +64,10 @@ type policyDecisionWithMetadata struct {
|
||||
Binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
func (c *dispatcher) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispatch implements generic.Dispatcher.
|
||||
func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []PolicyHook) error {
|
||||
|
||||
@ -109,7 +114,7 @@ func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o adm
|
||||
}
|
||||
}
|
||||
|
||||
authz := newCachingAuthorizer(c.authz)
|
||||
authz := admissionauthorizer.NewCachingAuthorizer(c.authz)
|
||||
|
||||
for _, hook := range hooks {
|
||||
// versionedAttributes will be set to non-nil inside of the loop, but
|
||||
|
2
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/errors.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/errors.go
generated
vendored
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
2
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/metrics.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/metrics.go
generated
vendored
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
20
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go
generated
vendored
20
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go
generated
vendored
@ -36,7 +36,6 @@ import (
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -93,13 +92,12 @@ type Plugin struct {
|
||||
|
||||
var _ admission.Interface = &Plugin{}
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
var _ initializer.WantsFeatures = &Plugin{}
|
||||
var _ initializer.WantsExcludedAdmissionResources = &Plugin{}
|
||||
|
||||
func NewPlugin(_ io.Reader) *Plugin {
|
||||
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
|
||||
|
||||
return &Plugin{
|
||||
p := &Plugin{
|
||||
Plugin: generic.NewPlugin(
|
||||
handler,
|
||||
func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] {
|
||||
@ -114,11 +112,13 @@ func NewPlugin(_ io.Reader) *Plugin {
|
||||
restMapper,
|
||||
)
|
||||
},
|
||||
func(a authorizer.Authorizer, m *matching.Matcher) generic.Dispatcher[PolicyHook] {
|
||||
func(a authorizer.Authorizer, m *matching.Matcher, client kubernetes.Interface) generic.Dispatcher[PolicyHook] {
|
||||
return NewDispatcher(a, generic.NewPolicyMatcher(m))
|
||||
},
|
||||
),
|
||||
}
|
||||
p.SetEnabled(true)
|
||||
return p
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes.
|
||||
@ -126,10 +126,6 @@ func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admi
|
||||
return a.Plugin.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
func (a *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
a.Plugin.SetEnabled(featureGates.Enabled(features.ValidatingAdmissionPolicy))
|
||||
}
|
||||
|
||||
func compilePolicy(policy *Policy) Validator {
|
||||
hasParam := false
|
||||
if policy.Spec.ParamKind != nil {
|
||||
@ -155,13 +151,13 @@ func compilePolicy(policy *Policy) Validator {
|
||||
for i := range matchConditions {
|
||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||
}
|
||||
matcher = matchconditions.NewMatcher(filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "policy", "validate", policy.Name)
|
||||
matcher = matchconditions.NewMatcher(filterCompiler.CompileCondition(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "policy", "validate", policy.Name)
|
||||
}
|
||||
res := NewValidator(
|
||||
filterCompiler.Compile(convertv1Validations(policy.Spec.Validations), optionalVars, environment.StoredExpressions),
|
||||
filterCompiler.CompileCondition(convertv1Validations(policy.Spec.Validations), optionalVars, environment.StoredExpressions),
|
||||
matcher,
|
||||
filterCompiler.Compile(convertv1AuditAnnotations(policy.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
|
||||
filterCompiler.Compile(convertv1MessageExpressions(policy.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
|
||||
filterCompiler.CompileCondition(convertv1AuditAnnotations(policy.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
|
||||
filterCompiler.CompileCondition(convertv1MessageExpressions(policy.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
|
||||
failurePolicy,
|
||||
)
|
||||
|
||||
|
11
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go
generated
vendored
11
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go
generated
vendored
@ -41,13 +41,13 @@ import (
|
||||
// validator implements the Validator interface
|
||||
type validator struct {
|
||||
celMatcher matchconditions.Matcher
|
||||
validationFilter cel.Filter
|
||||
auditAnnotationFilter cel.Filter
|
||||
messageFilter cel.Filter
|
||||
validationFilter cel.ConditionEvaluator
|
||||
auditAnnotationFilter cel.ConditionEvaluator
|
||||
messageFilter cel.ConditionEvaluator
|
||||
failPolicy *v1.FailurePolicyType
|
||||
}
|
||||
|
||||
func NewValidator(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failPolicy *v1.FailurePolicyType) Validator {
|
||||
func NewValidator(validationFilter cel.ConditionEvaluator, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.ConditionEvaluator, failPolicy *v1.FailurePolicyType) Validator {
|
||||
return &validator{
|
||||
celMatcher: celMatcher,
|
||||
validationFilter: validationFilter,
|
||||
@ -122,6 +122,7 @@ func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVe
|
||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, ns, remainingBudget)
|
||||
for i, evalResult := range evalResults {
|
||||
var decision = &decisions[i]
|
||||
decision.Elapsed = evalResult.Elapsed
|
||||
// TODO: move this to generics
|
||||
validation, ok := evalResult.ExpressionAccessor.(*ValidationCondition)
|
||||
if !ok {
|
||||
@ -146,6 +147,7 @@ func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVe
|
||||
decision.Message = fmt.Sprintf("failed messageExpression: %s", err)
|
||||
} else if evalResult.EvalResult != celtypes.True {
|
||||
decision.Action = ActionDeny
|
||||
decision.Evaluation = EvalDeny
|
||||
if validation.Reason == nil {
|
||||
decision.Reason = metav1.StatusReasonInvalid
|
||||
} else {
|
||||
@ -210,6 +212,7 @@ func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVe
|
||||
continue
|
||||
}
|
||||
var auditAnnotationResult = &auditAnnotationResults[i]
|
||||
auditAnnotationResult.Elapsed = evalResult.Elapsed
|
||||
// TODO: move this to generics
|
||||
validation, ok := evalResult.ExpressionAccessor.(*AuditAnnotationCondition)
|
||||
if !ok {
|
||||
|
Reference in New Issue
Block a user