Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: add mutatingadmissionpolicy plugin pkg
- Loading branch information
1 parent
abae58e
commit fc84831
Showing
9 changed files
with
744 additions
and
0 deletions.
There are no files selected for viewing
82 changes: 82 additions & 0 deletions
82
staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
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 ( | ||
"k8s.io/api/admissionregistration/v1beta1" | ||
"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() *v1beta1.ParamKind { | ||
return v.Spec.ParamKind | ||
} | ||
|
||
func (v *mutatingAdmissionPolicyAccessor) GetMatchConstraints() *v1beta1.MatchResources { | ||
return v.Spec.MatchConstraints | ||
} | ||
|
||
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() *v1beta1.MatchResources { | ||
return v.Spec.MatchResources | ||
} | ||
|
||
func (v *mutatingAdmissionPolicyBindingAccessor) GetParamRef() *v1beta1.ParamRef { | ||
return v.Spec.ParamRef | ||
} |
200 changes: 200 additions & 0 deletions
200
staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* | ||
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" | ||
"fmt" | ||
|
||
"k8s.io/api/admissionregistration/v1beta1" | ||
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" | ||
"k8s.io/apimachinery/pkg/types" | ||
utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||
"k8s.io/apiserver/pkg/admission" | ||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic" | ||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching" | ||
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) generic.Dispatcher[PolicyHook] { | ||
return generic.NewPolicyDispatcher[*Policy, *PolicyBinding, PolicyEvaluator]( | ||
NewMutatingAdmissionPolicyAccessor, | ||
NewMutatingAdmissionPolicyBindingAccessor, | ||
m, | ||
dispatch(m), | ||
) | ||
} | ||
|
||
func dispatch(m *matching.Matcher) func( | ||
ctx context.Context, | ||
a admission.Attributes, | ||
o admission.ObjectInterfaces, | ||
versionedAttributes webhookgeneric.VersionedAttributeAccessor, | ||
invocations []generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator], | ||
) error { | ||
return func( | ||
ctx context.Context, | ||
a admission.Attributes, | ||
o admission.ObjectInterfaces, | ||
versionedAttributes webhookgeneric.VersionedAttributeAccessor, | ||
invocations []generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator], | ||
) error { | ||
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()) | ||
}() | ||
|
||
// Should loop through invocations, handling possible error and invoking | ||
// evaluator to apply patch, also should handle re-invocations | ||
|
||
for _, invocation := range invocations { | ||
invocationKey, err := keyFor(invocation) | ||
if err != nil { | ||
// This should never happen. It occurs if there is a programming | ||
// error causing the Param not to be a valid object. | ||
utilruntime.HandleError(err) | ||
return k8serrors.NewInternalError(err) | ||
} | ||
|
||
if reinvokeCtx.IsReinvoke() && !policyReinvokeCtx.ShouldReinvoke(invocationKey) { | ||
continue | ||
} | ||
|
||
versionedAttr, err := versionedAttributes.VersionedAttribute(invocation.Kind) | ||
if err != nil { | ||
return k8serrors.NewInternalError(err) | ||
} | ||
lastVersionedAttr = versionedAttr | ||
|
||
changed, err := dispatchOne(ctx, m, a, o, versionedAttr, invocation) | ||
if err != nil { | ||
switch err.(type) { | ||
case *k8serrors.StatusError: | ||
return err | ||
default: | ||
return k8serrors.NewInternalError(err) | ||
} | ||
} | ||
|
||
if changed { | ||
// Patch had changed the object. Prepare to reinvoke all previous webhooks that are eligible for re-invocation. | ||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins() | ||
reinvokeCtx.SetShouldReinvoke() | ||
} | ||
if invocation.Policy.Spec.ReinvocationPolicy != nil && *invocation.Policy.Spec.ReinvocationPolicy == v1beta1.IfNeededReinvocationPolicy { | ||
policyReinvokeCtx.AddReinvocablePolicyToPreviouslyInvoked(invocationKey) | ||
} | ||
} | ||
|
||
if lastVersionedAttr != nil && lastVersionedAttr.VersionedObject != nil && lastVersionedAttr.Dirty { | ||
return o.GetObjectConvertor().Convert(lastVersionedAttr.VersionedObject, lastVersionedAttr.Attributes.GetObject(), nil) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func dispatchOne( | ||
ctx context.Context, | ||
m *matching.Matcher, | ||
a admission.Attributes, | ||
o admission.ObjectInterfaces, | ||
versionedAttributes *admission.VersionedAttributes, | ||
invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator], | ||
) (changed bool, err error) { | ||
var namespace *v1.Namespace | ||
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 = m.GetNamespace(namespaceName) | ||
if err != nil { | ||
return false, k8serrors.NewInternalError(fmt.Errorf("failed to get namespace %s: %v", namespaceName, err)) | ||
} | ||
} | ||
|
||
if invocation.Evaluator == nil { | ||
// internal error | ||
return false, k8serrors.NewInternalError(fmt.Errorf("policy evaluator is nil")) | ||
} | ||
|
||
patch, err := invocation.Evaluator( | ||
ctx, | ||
invocation.Resource, | ||
versionedAttributes, | ||
o, | ||
invocation.Param, | ||
namespace, | ||
celconfig.RuntimeCELCostBudget, | ||
) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
newVersionedObject := patch.GetPatchedObject() | ||
changed = !apiequality.Semantic.DeepEqual(versionedAttributes.VersionedObject, newVersionedObject) | ||
versionedAttributes.Dirty = true | ||
versionedAttributes.VersionedObject = newVersionedObject | ||
o.GetObjectDefaulter().Default(newVersionedObject) | ||
return changed, nil | ||
} | ||
|
||
func keyFor(invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator]) (key, error) { | ||
var paramUID types.UID | ||
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 = paramAccessor.GetUID() | ||
} | ||
|
||
return key{ | ||
PolicyUID: invocation.Policy.GetUID(), | ||
BindingUID: invocation.Binding.GetUID(), | ||
ParamUID: paramUID, | ||
}, nil | ||
} |
23 changes: 23 additions & 0 deletions
23
staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
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 "k8s.io/apimachinery/pkg/runtime" | ||
|
||
type Application interface { | ||
GetPatchedObject() runtime.Object | ||
} |
32 changes: 32 additions & 0 deletions
32
staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/noop.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
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 "k8s.io/apimachinery/pkg/runtime" | ||
|
||
type noopPatchObject struct { | ||
obj runtime.Object | ||
} | ||
|
||
func NewNoop(obj runtime.Object) Application { | ||
return &noopPatchObject{ | ||
obj: obj, | ||
} | ||
} | ||
func (p *noopPatchObject) GetPatchedObject() runtime.Object { | ||
return p.obj | ||
} |
24 changes: 24 additions & 0 deletions
24
staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
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 | ||
|
||
func NewSMD() Application { | ||
|
||
} | ||
|
||
type smdPatchObject struct { | ||
} |
Oops, something went wrong.