Merge pull request #119215 from alexzielenski/apiserver/policy/namespaceParamRef-alpha

KEP-3488: Per namespace policy params
This commit is contained in:
Kubernetes Prow Robot
2023-07-20 13:18:10 -07:00
committed by GitHub
31 changed files with 2121 additions and 525 deletions

View File

@@ -547,15 +547,23 @@
"x-kubernetes-map-type": "atomic"
},
"io.k8s.api.admissionregistration.v1alpha1.ParamRef": {
"description": "ParamRef references a parameter resource",
"description": "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.",
"properties": {
"name": {
"description": "Name of the resource being referenced.",
"description": "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.",
"type": "string"
},
"namespace": {
"description": "Namespace of the referenced resource. Should be empty for the cluster-scoped resources",
"description": "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.",
"type": "string"
},
"parameterNotFoundAction": {
"description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`",
"type": "string"
},
"selector": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset."
}
},
"type": "object",
@@ -609,7 +617,7 @@
]
},
"io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyBinding": {
"description": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.",
"description": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
@@ -678,7 +686,7 @@
},
"paramRef": {
"$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ParamRef",
"description": "ParamRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied."
"description": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param."
},
"policyName": {
"description": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.",

View File

@@ -185,15 +185,27 @@
"x-kubernetes-map-type": "atomic"
},
"io.k8s.api.admissionregistration.v1alpha1.ParamRef": {
"description": "ParamRef references a parameter resource",
"description": "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.",
"properties": {
"name": {
"description": "Name of the resource being referenced.",
"description": "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.",
"type": "string"
},
"namespace": {
"description": "Namespace of the referenced resource. Should be empty for the cluster-scoped resources",
"description": "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.",
"type": "string"
},
"parameterNotFoundAction": {
"description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`",
"type": "string"
},
"selector": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector"
}
],
"description": "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset."
}
},
"type": "object",
@@ -267,7 +279,7 @@
]
},
"io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyBinding": {
"description": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.",
"description": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
@@ -364,7 +376,7 @@
"$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1alpha1.ParamRef"
}
],
"description": "ParamRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied."
"description": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param."
},
"policyName": {
"description": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.",

View File

@@ -98,5 +98,14 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
obj.MatchPolicy = &m
}
},
func(obj *admissionregistration.ParamRef, c fuzz.Continue) {
c.FuzzNoCustom(obj) // fuzz self without calling this function again
// Populate required field
if obj.ParameterNotFoundAction == nil {
v := admissionregistration.DenyAction
obj.ParameterNotFoundAction = &v
}
},
}
}

View File

@@ -76,6 +76,18 @@ const (
AllScopes ScopeType = "*"
)
// ParameterNotFoundActionType specifies a failure policy that defines how a binding
// is evaluated when the param referred by its perNamespaceParamRef is not found.
type ParameterNotFoundActionType string
const (
// Allow means all requests will be admitted if no param resources
// could be found.
AllowAction ParameterNotFoundActionType = "Allow"
// Deny means all requests will be denied if no param resources are found.
DenyAction ParameterNotFoundActionType = "Deny"
)
// FailurePolicyType specifies the type of failure policy
type FailurePolicyType string
@@ -401,6 +413,15 @@ type AuditAnnotation struct {
// ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources.
// ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.
//
// For a given admission request, each binding will cause its policy to be
// evaluated N times, where N is 1 for policies/bindings that don't use
// params, otherwise N is the number of parameters selected by the binding.
//
// The CEL expressions of a policy must have a computed CEL cost below the maximum
// CEL budget. Each evaluation of the policy is given an independent CEL cost budget.
// Adding/removing policies, bindings, or params can not affect whether a
// given (policy, binding, param) combination is within its own CEL budget.
type ValidatingAdmissionPolicyBinding struct {
metav1.TypeMeta
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.
@@ -430,9 +451,10 @@ type ValidatingAdmissionPolicyBindingSpec struct {
// Required.
PolicyName string
// ParamRef specifies the parameter resource used to configure the admission control policy.
// paramRef specifies the parameter resource used to configure the admission control policy.
// It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy.
// If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied.
// If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.
// +optional
ParamRef *ParamRef
@@ -486,14 +508,63 @@ type ValidatingAdmissionPolicyBindingSpec struct {
ValidationActions []ValidationAction
}
// ParamRef references a parameter resource
// ParamRef describes how to locate the params to be used as input to
// expressions of rules applied by a policy binding.
// +structType=atomic
type ParamRef struct {
// Name of the resource being referenced.
// name is the name of the resource being referenced.
//
// One of `name` or `selector` must be set, but `name` and `selector` are
// mutually exclusive properties. If one is set, the other must be unset.
//
// A single parameter used for all admission requests can be configured
// by setting the `name` field, leaving `selector` blank, and setting namespace
// if `paramKind` is namespace-scoped.
//
// +optional
Name string
// Namespace of the referenced resource.
// Should be empty for the cluster-scoped resources
// namespace is the namespace of the referenced resource. Allows limiting
// the search for params to a specific namespace. Applies to both `name` and
// `selector` fields.
//
// A per-namespace parameter may be used by specifying a namespace-scoped
// `paramKind` in the policy and leaving this field empty.
//
// - If `paramKind` is cluster-scoped, this field MUST be unset. Setting this
// field results in a configuration error.
//
// - If `paramKind` is namespace-scoped, the namespace of the object being
// evaluated for admission will be used when this field is left unset. Take
// care that if this is left empty the binding must not match any cluster-scoped
// resources, which will result in an error.
//
// +optional
Namespace string
// selector can be used to match multiple param objects based on their labels.
// Supply selector: {} to match all resources of the ParamKind.
//
// If multiple params are found, they are all evaluated with the policy expressions
// and the results are ANDed together.
//
// One of `name` or `selector` must be set, but `name` and `selector` are
// mutually exclusive properties. If one is set, the other must be unset.
//
// +optional
Selector *metav1.LabelSelector
// parameterNotFoundAction controls the behavior of the binding when the resource
// exists, and name or selector is valid, but there are no parameters
// matched by the binding. If the value is set to `Allow`, then no
// matched parameters will be treated as successful validation by the binding.
// If set to `Deny`, then no matched parameters will be subject to the
// `failurePolicy` of the policy.
//
// Allowed values are `Allow` or `Deny`
//
// Required
ParameterNotFoundAction *ParameterNotFoundActionType
}
// MatchResources decides whether to run the admission control policy on an object based

View File

@@ -49,3 +49,11 @@ func SetDefaults_MatchResources(obj *admissionregistrationv1alpha1.MatchResource
obj.ObjectSelector = &selector
}
}
// SetDefaults_ParamRef sets defaults for ParamRef
func SetDefaults_ParamRef(obj *admissionregistrationv1alpha1.ParamRef) {
if obj.ParameterNotFoundAction == nil {
v := admissionregistrationv1alpha1.DenyAction
obj.ParameterNotFoundAction = &v
}
}

View File

@@ -116,3 +116,58 @@ func TestDefaultAdmissionPolicy(t *testing.T) {
})
}
}
func TestDefaultAdmissionPolicyBinding(t *testing.T) {
denyAction := v1alpha1.DenyAction
equivalent := v1alpha1.Equivalent
tests := []struct {
name string
original runtime.Object
expected runtime.Object
}{
{
name: "ValidatingAdmissionPolicyBinding.ParamRef.ParameterNotFoundAction",
original: &v1alpha1.ValidatingAdmissionPolicyBinding{
Spec: v1alpha1.ValidatingAdmissionPolicyBindingSpec{
ParamRef: &v1alpha1.ParamRef{},
},
},
expected: &v1alpha1.ValidatingAdmissionPolicyBinding{
Spec: v1alpha1.ValidatingAdmissionPolicyBindingSpec{
ParamRef: &v1alpha1.ParamRef{
ParameterNotFoundAction: &denyAction,
},
},
},
},
{
name: "ValidatingAdmissionPolicyBinding.MatchResources",
original: &v1alpha1.ValidatingAdmissionPolicyBinding{
Spec: v1alpha1.ValidatingAdmissionPolicyBindingSpec{
MatchResources: &v1alpha1.MatchResources{},
},
},
expected: &v1alpha1.ValidatingAdmissionPolicyBinding{
Spec: v1alpha1.ValidatingAdmissionPolicyBindingSpec{
MatchResources: &v1alpha1.MatchResources{
NamespaceSelector: &metav1.LabelSelector{},
ObjectSelector: &metav1.LabelSelector{},
MatchPolicy: &equivalent,
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
original := test.original
expected := test.expected
legacyscheme.Scheme.Default(original)
if !apiequality.Semantic.DeepEqual(original, expected) {
t.Error(cmp.Diff(expected, original))
}
})
}
}

View File

@@ -397,6 +397,8 @@ func Convert_admissionregistration_ParamKind_To_v1alpha1_ParamKind(in *admission
func autoConvert_v1alpha1_ParamRef_To_admissionregistration_ParamRef(in *v1alpha1.ParamRef, out *admissionregistration.ParamRef, s conversion.Scope) error {
out.Name = in.Name
out.Namespace = in.Namespace
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.ParameterNotFoundAction = (*admissionregistration.ParameterNotFoundActionType)(unsafe.Pointer(in.ParameterNotFoundAction))
return nil
}
@@ -408,6 +410,8 @@ func Convert_v1alpha1_ParamRef_To_admissionregistration_ParamRef(in *v1alpha1.Pa
func autoConvert_admissionregistration_ParamRef_To_v1alpha1_ParamRef(in *admissionregistration.ParamRef, out *v1alpha1.ParamRef, s conversion.Scope) error {
out.Name = in.Name
out.Namespace = in.Namespace
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.ParameterNotFoundAction = (*v1alpha1.ParameterNotFoundActionType)(unsafe.Pointer(in.ParameterNotFoundAction))
return nil
}

View File

@@ -62,6 +62,9 @@ func SetObjectDefaults_ValidatingAdmissionPolicy(in *v1alpha1.ValidatingAdmissio
}
func SetObjectDefaults_ValidatingAdmissionPolicyBinding(in *v1alpha1.ValidatingAdmissionPolicyBinding) {
if in.Spec.ParamRef != nil {
SetDefaults_ParamRef(in.Spec.ParamRef)
}
if in.Spec.MatchResources != nil {
SetDefaults_MatchResources(in.Spec.MatchResources)
for i := range in.Spec.MatchResources.ResourceRules {

View File

@@ -1151,9 +1151,38 @@ func validateParamRef(pr *admissionregistration.ParamRef, fldPath *field.Path) f
if pr == nil {
return allErrors
}
for _, msg := range path.ValidatePathSegmentName(pr.Name, false) {
allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), pr.Name, msg))
if len(pr.Name) > 0 {
for _, msg := range path.ValidatePathSegmentName(pr.Name, false) {
allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), pr.Name, msg))
}
if pr.Selector != nil {
allErrors = append(allErrors, field.Forbidden(fldPath.Child("name"), `name and selector are mutually exclusive`))
}
}
if pr.Selector != nil {
labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{}
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(pr.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...)
if len(pr.Name) > 0 {
allErrors = append(allErrors, field.Forbidden(fldPath.Child("selector"), `name and selector are mutually exclusive`))
}
}
if len(pr.Name) == 0 && pr.Selector == nil {
allErrors = append(allErrors, field.Required(fldPath, `one of name or selector must be specified`))
}
if pr.ParameterNotFoundAction == nil || len(*pr.ParameterNotFoundAction) == 0 {
allErrors = append(allErrors, field.Required(fldPath.Child("parameterNotFoundAction"), ""))
} else {
if *pr.ParameterNotFoundAction != admissionregistration.DenyAction && *pr.ParameterNotFoundAction != admissionregistration.AllowAction {
allErrors = append(allErrors, field.NotSupported(fldPath.Child("parameterNotFoundAction"), pr.ParameterNotFoundAction, []string{string(admissionregistration.DenyAction), string(admissionregistration.AllowAction)}))
}
}
return allErrors
}

View File

@@ -32,6 +32,8 @@ import (
"k8s.io/kubernetes/pkg/apis/admissionregistration"
)
func ptr[T any](v T) *T { return &v }
func strPtr(s string) *string { return &s }
func int32Ptr(i int32) *int32 { return &i }
@@ -3517,7 +3519,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
MatchResources: &admissionregistration.MatchResources{
MatchPolicy: func() *admissionregistration.MatchPolicyType {
@@ -3537,7 +3540,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
MatchResources: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{{
@@ -3591,7 +3595,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
}, MatchResources: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{{
RuleWithOperations: admissionregistration.RuleWithOperations{
@@ -3616,7 +3621,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
}, MatchResources: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{{
RuleWithOperations: admissionregistration.RuleWithOperations{
@@ -3641,7 +3647,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3668,7 +3675,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3704,7 +3712,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3731,7 +3740,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3758,7 +3768,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3794,7 +3805,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3821,7 +3833,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3848,7 +3861,8 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny, admissionregistration.Deny},
},
@@ -3863,12 +3877,80 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.ValidationAction("illegal")},
},
},
expectedError: `Unsupported value: "illegal": supported values: "Audit", "Deny", "Warn"`,
}, {
name: "paramRef selector must not be set when name is set",
config: &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
},
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
},
},
expectedError: `spec.paramRef.name: Forbidden: name and selector are mutually exclusive, spec.paramRef.selector: Forbidden: name and selector are mutually exclusive`,
}, {
name: "paramRef parameterNotFoundAction must be set",
config: &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
},
},
},
expectedError: "spec.paramRef.parameterNotFoundAction: Required value",
}, {
name: "paramRef parameterNotFoundAction must be an valid value",
config: &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.ParameterNotFoundActionType("invalid")),
},
},
},
expectedError: `spec.paramRef.parameterNotFoundAction: Unsupported value: "invalid": supported values: "Deny", "Allow"`,
}, {
name: "paramRef one of name or selector",
config: &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
ParamRef: &admissionregistration.ParamRef{
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
},
},
expectedError: `one of name or selector must be specified`,
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -3903,7 +3985,8 @@ func TestValidateValidatingAdmissionPolicyBindingUpdate(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3937,7 +4020,8 @@ func TestValidateValidatingAdmissionPolicyBindingUpdate(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
@@ -3973,7 +4057,8 @@ func TestValidateValidatingAdmissionPolicyBindingUpdate(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "xyzlimit-scale.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "xyzlimit-scale-setting.example.com",
Name: "xyzlimit-scale-setting.example.com",
ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{

View File

@@ -295,6 +295,16 @@ func (in *ParamKind) DeepCopy() *ParamKind {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ParamRef) DeepCopyInto(out *ParamRef) {
*out = *in
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.ParameterNotFoundAction != nil {
in, out := &in.ParameterNotFoundAction, &out.ParameterNotFoundAction
*out = new(ParameterNotFoundActionType)
**out = **in
}
return
}
@@ -502,7 +512,7 @@ func (in *ValidatingAdmissionPolicyBindingSpec) DeepCopyInto(out *ValidatingAdmi
if in.ParamRef != nil {
in, out := &in.ParamRef, &out.ParamRef
*out = new(ParamRef)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.MatchResources != nil {
in, out := &in.MatchResources, &out.MatchResources

View File

@@ -2269,23 +2269,37 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ParamRef(ref common.Referen
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ParamRef references a parameter resource",
Description: "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name of the resource being referenced.",
Description: "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.",
Type: []string{"string"},
Format: "",
},
},
"namespace": {
SchemaProps: spec.SchemaProps{
Description: "Namespace of the referenced resource. Should be empty for the cluster-scoped resources",
Description: "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.",
Type: []string{"string"},
Format: "",
},
},
"selector": {
SchemaProps: spec.SchemaProps{
Description: "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"),
},
},
"parameterNotFoundAction": {
SchemaProps: spec.SchemaProps{
Description: "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`\n\nPossible enum values:\n - `\"Allow\"` Ignore means that an error finding params for a binding is ignored\n - `\"Deny\"` Fail means that an error finding params for a binding is ignored",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"Allow", "Deny"},
},
},
},
},
VendorExtensible: spec.VendorExtensible{
@@ -2294,6 +2308,8 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ParamRef(ref common.Referen
},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
}
}
@@ -2385,7 +2401,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.",
Description: "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
@@ -2490,7 +2506,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi
},
"paramRef": {
SchemaProps: spec.SchemaProps{
Description: "ParamRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied.",
Description: "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.",
Ref: ref("k8s.io/api/admissionregistration/v1alpha1.ParamRef"),
},
},

View File

@@ -1673,13 +1673,18 @@ func printValidatingAdmissionPolicyBinding(obj *admissionregistration.Validating
Object: runtime.RawExtension{Object: obj},
}
paramName := "<unset>"
if obj.Spec.ParamRef != nil {
if obj.Spec.ParamRef.Namespace != "" {
paramName = obj.Spec.ParamRef.Namespace + "/" + obj.Spec.ParamRef.Name
} else {
paramName = obj.Spec.ParamRef.Name
if pr := obj.Spec.ParamRef; pr != nil {
if len(pr.Name) > 0 {
if pr.Namespace != "" {
paramName = pr.Namespace + "/" + pr.Name
} else {
// Can't tell from here if param is cluster-scoped, so all
// params without names get * namespace
paramName = "*/" + pr.Name
}
} else if pr.Selector != nil {
paramName = pr.Selector.String()
}
}
row.Cells = append(row.Cells, obj.Name, obj.Spec.PolicyName, paramName, translateTimestampSince(obj.CreationTimestamp))
return []metav1.TableRow{row}, nil

View File

@@ -85,15 +85,21 @@ func (v *validatingAdmissionPolicyBindingStrategy) authorize(ctx context.Context
}
}
paramRef := binding.Spec.ParamRef
var attrs authorizer.AttributesRecord
// require that the user can read (verb "get") the referred resource.
attrs := authorizer.AttributesRecord{
paramRef := binding.Spec.ParamRef
verb := "get"
if len(paramRef.Name) == 0 {
verb = "list"
}
attrs = authorizer.AttributesRecord{
User: user,
Verb: "get",
Verb: verb,
ResourceRequest: true,
Name: paramRef.Name,
Namespace: paramRef.Namespace,
Namespace: paramRef.Namespace, // if empty, no namespace indicates get across all namespaces
APIGroup: apiGroup,
APIVersion: apiVersion,
Resource: resource,
@@ -104,7 +110,8 @@ func (v *validatingAdmissionPolicyBindingStrategy) authorize(ctx context.Context
return err
}
if d != authorizer.DecisionAllow {
return fmt.Errorf(`user %v does not have "get" permission on the object referenced by paramRef`, user)
return fmt.Errorf(`user %v does not have "%v" permission on the object referenced by paramRef`, verb, user)
}
return nil
}

View File

@@ -103,19 +103,46 @@ func TestAuthorization(t *testing.T) {
strategy := NewStrategy(tc.auth, tc.policyGetter, tc.resourceResolver)
t.Run("create", func(t *testing.T) {
ctx := request.WithUser(context.Background(), tc.userInfo)
errs := strategy.Validate(ctx, validPolicyBinding())
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
for _, obj := range validPolicyBindings() {
errs := strategy.Validate(ctx, obj)
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
}
}
})
t.Run("update", func(t *testing.T) {
ctx := request.WithUser(context.Background(), tc.userInfo)
obj := validPolicyBinding()
objWithChangedParamRef := obj.DeepCopy()
objWithChangedParamRef.Spec.ParamRef.Name = "changed"
errs := strategy.ValidateUpdate(ctx, obj, objWithChangedParamRef)
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
for _, obj := range validPolicyBindings() {
objWithChangedParamRef := obj.DeepCopy()
if pr := objWithChangedParamRef.Spec.ParamRef; pr != nil {
if len(pr.Name) > 0 {
pr.Name = "changed"
}
if pr.Selector != nil {
pr.Selector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"changed": "value",
},
}
}
if len(pr.Namespace) > 0 {
pr.Namespace = "othernamespace"
}
if pr.ParameterNotFoundAction == nil || *pr.ParameterNotFoundAction == admissionregistration.AllowAction {
v := admissionregistration.DenyAction
pr.ParameterNotFoundAction = &v
} else {
v := admissionregistration.AllowAction
pr.ParameterNotFoundAction = &v
}
}
errs := strategy.ValidateUpdate(ctx, obj, objWithChangedParamRef)
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
}
}
})
})

View File

@@ -37,121 +37,175 @@ import (
)
func TestCreate(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
configuration := validPolicyBinding()
test.TestCreate(
// valid
configuration,
// invalid
newPolicyBinding(""),
)
for _, configuration := range validPolicyBindings() {
t.Run(configuration.Name, func(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestCreate(
// valid
configuration,
// invalid
newPolicyBinding(""),
)
})
}
}
func TestUpdate(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestUpdate(
// valid
validPolicyBinding(),
// updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
object.Labels = map[string]string{"c": "d"}
return object
},
// invalid updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
object.Name = ""
return object
},
)
for _, b := range validPolicyBindings() {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
t.Run(b.Name, func(t *testing.T) {
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestUpdate(
// valid
b,
// updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
object.Labels = map[string]string{"c": "d"}
return object
},
// invalid updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
object.Name = ""
return object
},
)
})
}
}
func TestGet(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestGet(validPolicyBinding())
for _, b := range validPolicyBindings() {
t.Run(b.Name, func(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestGet(b)
})
}
}
func TestList(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestList(validPolicyBinding())
for _, b := range validPolicyBindings() {
t.Run(b.Name, func(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestList(b)
})
}
}
func TestDelete(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestDelete(validPolicyBinding())
for _, b := range validPolicyBindings() {
t.Run(b.Name, func(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestDelete(b)
})
}
}
func TestWatch(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestWatch(
validPolicyBinding(),
[]labels.Set{},
[]labels.Set{
{"hoo": "bar"},
},
[]fields.Set{
{"metadata.name": "foo"},
},
[]fields.Set{
{"metadata.name": "nomatch"},
},
)
for _, b := range validPolicyBindings() {
t.Run(b.Name, func(t *testing.T) {
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
test.TestWatch(
b,
[]labels.Set{},
[]labels.Set{
{"hoo": "bar"},
},
[]fields.Set{
{"metadata.name": b.Name},
},
[]fields.Set{
{"metadata.name": "nomatch"},
},
)
})
}
}
func validPolicyBinding() *admissionregistration.ValidatingAdmissionPolicyBinding {
return &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "param-test",
func validPolicyBindings() []*admissionregistration.ValidatingAdmissionPolicyBinding {
denyAction := admissionregistration.DenyAction
return []*admissionregistration.ValidatingAdmissionPolicyBinding{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "replica-limit-test.example.com",
ParameterNotFoundAction: &denyAction,
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-clusterwide",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "replica-limit-test.example.com",
Namespace: "default",
ParameterNotFoundAction: &denyAction,
},
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"CREATE"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-selector",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
},
ParameterNotFoundAction: &denyAction,
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-selector-clusterwide",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Namespace: "mynamespace",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
},
ParameterNotFoundAction: &denyAction,
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
}
@@ -166,7 +220,8 @@ func newPolicyBinding(name string) *admissionregistration.ValidatingAdmissionPol
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "param-test",
Name: "param-test",
Namespace: "default",
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
MatchResources: &admissionregistration.MatchResources{},

View File

@@ -35,32 +35,87 @@ func TestPolicyBindingStrategy(t *testing.T) {
t.Errorf("PolicyBinding should not allow create on update")
}
configuration := validPolicyBinding()
strategy.PrepareForCreate(ctx, configuration)
errs := strategy.Validate(ctx, configuration)
if len(errs) != 0 {
t.Errorf("Unexpected error validating %v", errs)
}
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{Name: ""},
}
strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
errs = strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
if len(errs) == 0 {
t.Errorf("Expected a validation error")
for _, configuration := range validPolicyBindings() {
strategy.PrepareForCreate(ctx, configuration)
errs := strategy.Validate(ctx, configuration)
if len(errs) != 0 {
t.Errorf("Unexpected error validating %v", errs)
}
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{Name: ""},
}
strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
errs = strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
if len(errs) == 0 {
t.Errorf("Expected a validation error")
}
}
}
func validPolicyBinding() *admissionregistration.ValidatingAdmissionPolicyBinding {
return &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "replica-limit-test.example.com",
func validPolicyBindings() []*admissionregistration.ValidatingAdmissionPolicyBinding {
denyAction := admissionregistration.DenyAction
return []*admissionregistration.ValidatingAdmissionPolicyBinding{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "replica-limit-test.example.com",
ParameterNotFoundAction: &denyAction,
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-clusterwide",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Name: "replica-limit-test.example.com",
Namespace: "default",
ParameterNotFoundAction: &denyAction,
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-selector",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
},
ParameterNotFoundAction: &denyAction,
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-selector-clusterwide",
},
Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
PolicyName: "replicalimit-policy.example.com",
ParamRef: &admissionregistration.ParamRef{
Namespace: "mynamespace",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
},
ParameterNotFoundAction: &denyAction,
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
},
}
}

View File

@@ -546,99 +546,102 @@ func init() {
}
var fileDescriptor_c3be8d256e3ae3cf = []byte{
// 1457 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcd, 0x6f, 0x1b, 0x45,
0x1b, 0xcf, 0xc6, 0x6e, 0x12, 0x8f, 0xf3, 0x61, 0xcf, 0xdb, 0xaa, 0x6e, 0xf4, 0xd6, 0x8e, 0x56,
0xd5, 0xab, 0x46, 0x7a, 0xd9, 0x25, 0x69, 0xa1, 0x80, 0x90, 0x50, 0xb6, 0x5f, 0xf4, 0x23, 0x4d,
0x34, 0x45, 0x89, 0x84, 0xa8, 0xc4, 0x78, 0x77, 0x62, 0x4f, 0xed, 0xfd, 0x60, 0x67, 0x6d, 0x1a,
0x81, 0x44, 0x25, 0x2e, 0x70, 0xe3, 0xc0, 0x85, 0x2b, 0xff, 0x09, 0xb7, 0x1e, 0x7b, 0x2c, 0x07,
0x2c, 0x6a, 0x2e, 0xfc, 0x05, 0x20, 0xe5, 0x02, 0x9a, 0xd9, 0xd9, 0x4f, 0x3b, 0xc4, 0x2e, 0x81,
0x9b, 0xf7, 0xf9, 0xf8, 0xfd, 0xe6, 0x79, 0xe6, 0x79, 0x66, 0x9e, 0x31, 0x40, 0x9d, 0xb7, 0x98,
0x46, 0x5d, 0xbd, 0xd3, 0x6b, 0x12, 0xdf, 0x21, 0x01, 0x61, 0x7a, 0x9f, 0x38, 0x96, 0xeb, 0xeb,
0x52, 0x81, 0x3d, 0xaa, 0x63, 0xcb, 0xa6, 0x8c, 0x51, 0xd7, 0xf1, 0x49, 0x8b, 0xb2, 0xc0, 0xc7,
0x01, 0x75, 0x1d, 0xbd, 0xbf, 0x81, 0xbb, 0x5e, 0x1b, 0x6f, 0xe8, 0x2d, 0xe2, 0x10, 0x1f, 0x07,
0xc4, 0xd2, 0x3c, 0xdf, 0x0d, 0x5c, 0xb8, 0x1e, 0xba, 0x6a, 0xd8, 0xa3, 0xda, 0x58, 0x57, 0x2d,
0x72, 0x5d, 0x7d, 0xad, 0x45, 0x83, 0x76, 0xaf, 0xa9, 0x99, 0xae, 0xad, 0xb7, 0xdc, 0x96, 0xab,
0x0b, 0x84, 0x66, 0xef, 0x40, 0x7c, 0x89, 0x0f, 0xf1, 0x2b, 0x44, 0x5e, 0xbd, 0x32, 0xc1, 0xa2,
0xf2, 0xcb, 0x59, 0xbd, 0x9a, 0x38, 0xd9, 0xd8, 0x6c, 0x53, 0x87, 0xf8, 0x87, 0xba, 0xd7, 0x69,
0x71, 0x01, 0xd3, 0x6d, 0x12, 0xe0, 0x71, 0x5e, 0xfa, 0x71, 0x5e, 0x7e, 0xcf, 0x09, 0xa8, 0x4d,
0x46, 0x1c, 0xde, 0x3c, 0xc9, 0x81, 0x99, 0x6d, 0x62, 0xe3, 0xbc, 0x9f, 0xca, 0xc0, 0xca, 0x56,
0xcf, 0xa2, 0xc1, 0x96, 0xe3, 0xb8, 0x81, 0x08, 0x02, 0x5e, 0x04, 0x85, 0x0e, 0x39, 0xac, 0x29,
0x6b, 0xca, 0xe5, 0x92, 0x51, 0x7e, 0x36, 0x68, 0xcc, 0x0c, 0x07, 0x8d, 0xc2, 0x3d, 0x72, 0x88,
0xb8, 0x1c, 0x6e, 0x81, 0x95, 0x3e, 0xee, 0xf6, 0xc8, 0xcd, 0x27, 0x9e, 0x4f, 0x44, 0x0a, 0x6a,
0xb3, 0xc2, 0xf4, 0xbc, 0x34, 0x5d, 0xd9, 0xcb, 0xaa, 0x51, 0xde, 0x5e, 0xed, 0x82, 0x6a, 0xf2,
0xb5, 0x8f, 0x7d, 0x87, 0x3a, 0x2d, 0xf8, 0x7f, 0xb0, 0x70, 0x40, 0x49, 0xd7, 0x42, 0xe4, 0x40,
0x02, 0x56, 0x24, 0xe0, 0xc2, 0x2d, 0x29, 0x47, 0xb1, 0x05, 0x5c, 0x07, 0xf3, 0x9f, 0x86, 0x8e,
0xb5, 0x82, 0x30, 0x5e, 0x91, 0xc6, 0xf3, 0x12, 0x0f, 0x45, 0x7a, 0xf5, 0x00, 0x2c, 0x6f, 0xe3,
0xc0, 0x6c, 0x5f, 0x77, 0x1d, 0x8b, 0x8a, 0x08, 0xd7, 0x40, 0xd1, 0xc1, 0x36, 0x91, 0x21, 0x2e,
0x4a, 0xcf, 0xe2, 0x03, 0x6c, 0x13, 0x24, 0x34, 0x70, 0x13, 0x00, 0x92, 0x8f, 0x0f, 0x4a, 0x3b,
0x90, 0x0a, 0x2d, 0x65, 0xa5, 0xfe, 0x58, 0x94, 0x44, 0x88, 0x30, 0xb7, 0xe7, 0x9b, 0x84, 0xc1,
0x27, 0xa0, 0xca, 0xe1, 0x98, 0x87, 0x4d, 0xf2, 0x90, 0x74, 0x89, 0x19, 0xb8, 0xbe, 0x60, 0x2d,
0x6f, 0x5e, 0xd1, 0x92, 0x3a, 0x8d, 0x77, 0x4c, 0xf3, 0x3a, 0x2d, 0x2e, 0x60, 0x1a, 0x2f, 0x0c,
0xad, 0xbf, 0xa1, 0xdd, 0xc7, 0x4d, 0xd2, 0x8d, 0x5c, 0x8d, 0x73, 0xc3, 0x41, 0xa3, 0xfa, 0x20,
0x8f, 0x88, 0x46, 0x49, 0xa0, 0x0b, 0x96, 0xdd, 0xe6, 0x63, 0x62, 0x06, 0x31, 0xed, 0xec, 0xab,
0xd3, 0xc2, 0xe1, 0xa0, 0xb1, 0xbc, 0x93, 0x81, 0x43, 0x39, 0x78, 0xf8, 0x05, 0x58, 0xf2, 0x65,
0xdc, 0xa8, 0xd7, 0x25, 0xac, 0x56, 0x58, 0x2b, 0x5c, 0x2e, 0x6f, 0x1a, 0xda, 0xc4, 0xed, 0xa8,
0xf1, 0xc0, 0x2c, 0xee, 0xbc, 0x4f, 0x83, 0xf6, 0x8e, 0x47, 0x42, 0x3d, 0x33, 0xce, 0xc9, 0xc4,
0x2f, 0xa1, 0x34, 0x01, 0xca, 0xf2, 0xc1, 0x6f, 0x15, 0x70, 0x96, 0x3c, 0x31, 0xbb, 0x3d, 0x8b,
0x64, 0xec, 0x6a, 0xc5, 0x53, 0x5b, 0xc8, 0x7f, 0xe5, 0x42, 0xce, 0xde, 0x1c, 0xc3, 0x83, 0xc6,
0xb2, 0xc3, 0x1b, 0xa0, 0x6c, 0xf3, 0xa2, 0xd8, 0x75, 0xbb, 0xd4, 0x3c, 0xac, 0xcd, 0x8b, 0x52,
0x52, 0x87, 0x83, 0x46, 0x79, 0x3b, 0x11, 0x1f, 0x0d, 0x1a, 0x2b, 0xa9, 0xcf, 0x0f, 0x0e, 0x3d,
0x82, 0xd2, 0x6e, 0xea, 0x0b, 0x05, 0x9c, 0x3f, 0x66, 0x55, 0xf0, 0x5a, 0x92, 0x79, 0x51, 0x1a,
0x35, 0x65, 0xad, 0x70, 0xb9, 0x64, 0x54, 0xd3, 0x19, 0x13, 0x0a, 0x94, 0xb5, 0x83, 0x5f, 0x2a,
0x00, 0xfa, 0x23, 0x78, 0xb2, 0x50, 0xae, 0x4d, 0x92, 0x2f, 0x6d, 0x4c, 0x92, 0x56, 0x65, 0x92,
0xe0, 0xa8, 0x0e, 0x8d, 0xa1, 0x53, 0x31, 0x28, 0xed, 0x62, 0x1f, 0xdb, 0xf7, 0xa8, 0x63, 0xf1,
0xbe, 0xc3, 0x1e, 0xdd, 0x23, 0xbe, 0xe8, 0x3b, 0x25, 0xdb, 0x77, 0x5b, 0xbb, 0x77, 0xa4, 0x06,
0xa5, 0xac, 0x78, 0x37, 0x77, 0xa8, 0x63, 0xc9, 0x2e, 0x8d, 0xbb, 0x99, 0xe3, 0x21, 0xa1, 0x51,
0x1f, 0x81, 0x05, 0x41, 0xc1, 0x0f, 0x8e, 0x93, 0x7b, 0x5f, 0x07, 0xa5, 0xb8, 0x9f, 0x24, 0x68,
0x55, 0x9a, 0x95, 0xe2, 0xde, 0x43, 0x89, 0x8d, 0xfa, 0x9d, 0x02, 0x16, 0xf9, 0x96, 0x5d, 0x6f,
0x13, 0xb3, 0xc3, 0x8f, 0xb2, 0xaf, 0x14, 0x00, 0x49, 0xfe, 0x80, 0x0b, 0xf7, 0xa5, 0xbc, 0xf9,
0xee, 0x14, 0x85, 0x38, 0x72, 0x4a, 0x26, 0xd9, 0x1d, 0x51, 0x31, 0x34, 0x86, 0x53, 0xfd, 0x69,
0x16, 0x5c, 0xd8, 0xc3, 0x5d, 0x6a, 0xe1, 0x80, 0x3a, 0xad, 0xad, 0x88, 0x2e, 0x2c, 0x2b, 0xf8,
0x31, 0x58, 0xe0, 0x1d, 0x6f, 0xe1, 0x00, 0xcb, 0x63, 0xe9, 0xf5, 0xc9, 0xce, 0x87, 0xf0, 0x30,
0xd8, 0x26, 0x01, 0x4e, 0xb6, 0x27, 0x91, 0xa1, 0x18, 0x15, 0x3e, 0x06, 0x45, 0xe6, 0x11, 0x53,
0x16, 0xd5, 0xfb, 0x53, 0xc4, 0x7e, 0xec, 0xaa, 0x1f, 0x7a, 0xc4, 0x4c, 0x36, 0x8e, 0x7f, 0x21,
0xc1, 0x01, 0x7d, 0x30, 0xc7, 0x02, 0x1c, 0xf4, 0x98, 0xb8, 0x12, 0xca, 0x9b, 0x77, 0x4f, 0x85,
0x4d, 0x20, 0x1a, 0xcb, 0x92, 0x6f, 0x2e, 0xfc, 0x46, 0x92, 0x49, 0xfd, 0x5d, 0x01, 0x6b, 0xc7,
0xfa, 0x1a, 0xd4, 0xb1, 0x78, 0x3d, 0xfc, 0xf3, 0x69, 0xfe, 0x24, 0x93, 0xe6, 0x9d, 0xd3, 0x08,
0x5c, 0x2e, 0xfe, 0xb8, 0x6c, 0xab, 0xbf, 0x29, 0xe0, 0xd2, 0x49, 0xce, 0xf7, 0x29, 0x0b, 0xe0,
0x47, 0x23, 0xd1, 0x6b, 0x13, 0x5e, 0x42, 0x94, 0x85, 0xb1, 0xc7, 0x83, 0x40, 0x24, 0x49, 0x45,
0xee, 0x81, 0x33, 0x34, 0x20, 0x36, 0x3f, 0xb6, 0x78, 0x77, 0xdd, 0x3b, 0xc5, 0xd0, 0x8d, 0x25,
0xc9, 0x7b, 0xe6, 0x0e, 0x67, 0x40, 0x21, 0x91, 0xfa, 0x75, 0xe1, 0xe4, 0xc0, 0x79, 0x9e, 0xf8,
0x61, 0xe6, 0x09, 0xe1, 0x83, 0xe4, 0xc0, 0x89, 0xb7, 0x71, 0x37, 0xd6, 0xa0, 0x94, 0x15, 0x7c,
0x04, 0x16, 0x3c, 0x79, 0x54, 0x8d, 0xb9, 0xb1, 0x4f, 0x8a, 0x28, 0x3a, 0xe5, 0x8c, 0x45, 0x9e,
0xad, 0xe8, 0x0b, 0xc5, 0x90, 0xb0, 0x07, 0x96, 0xed, 0xcc, 0x88, 0x22, 0x5b, 0xe5, 0xed, 0x29,
0x48, 0xb2, 0x33, 0x4e, 0x38, 0x1c, 0x64, 0x65, 0x28, 0x47, 0x02, 0xf7, 0x41, 0xb5, 0x2f, 0x33,
0xe6, 0x3a, 0x5b, 0x66, 0x78, 0xcf, 0x14, 0xc5, 0x35, 0xb5, 0xce, 0x47, 0x9a, 0xbd, 0xbc, 0xf2,
0x68, 0xd0, 0xa8, 0xe4, 0x85, 0x68, 0x14, 0x43, 0xfd, 0x55, 0x01, 0x17, 0x8f, 0xdd, 0x8b, 0x7f,
0xa1, 0xfa, 0x68, 0xb6, 0xfa, 0x6e, 0x9c, 0x4a, 0xf5, 0x8d, 0x2f, 0xbb, 0xef, 0xe7, 0xfe, 0x22,
0x54, 0x51, 0x6f, 0x18, 0x94, 0xbc, 0xe8, 0x26, 0x95, 0xb1, 0x5e, 0x9d, 0xb6, 0x78, 0xb8, 0xaf,
0xb1, 0xc4, 0xaf, 0xba, 0xf8, 0x13, 0x25, 0xa8, 0xf0, 0x33, 0x50, 0xb1, 0xe5, 0x2c, 0xcd, 0x01,
0xa8, 0x13, 0x44, 0xf3, 0xc2, 0xdf, 0xa8, 0xa0, 0xb3, 0xc3, 0x41, 0xa3, 0xb2, 0x9d, 0x83, 0x45,
0x23, 0x44, 0xb0, 0x0b, 0xca, 0x49, 0x05, 0x44, 0x03, 0xe6, 0x1b, 0xaf, 0x90, 0x72, 0xd7, 0x31,
0xfe, 0x23, 0x73, 0x5c, 0x4e, 0x64, 0x0c, 0xa5, 0xe1, 0xe1, 0x7d, 0xb0, 0x74, 0x80, 0x69, 0xb7,
0xe7, 0x13, 0x39, 0xba, 0x15, 0x45, 0x03, 0xff, 0x8f, 0x8f, 0x55, 0xb7, 0xd2, 0x8a, 0xa3, 0x41,
0xa3, 0x9a, 0x11, 0x88, 0xf1, 0x2d, 0xeb, 0x0c, 0x9f, 0x2a, 0xa0, 0x82, 0xb3, 0x0f, 0x2d, 0x56,
0x3b, 0x23, 0x22, 0x78, 0x67, 0x8a, 0x08, 0x72, 0x6f, 0x35, 0xa3, 0x26, 0xc3, 0xa8, 0xe4, 0x14,
0x0c, 0x8d, 0xb0, 0xc1, 0xcf, 0xc1, 0x8a, 0x9d, 0x79, 0x07, 0xb1, 0xda, 0x9c, 0x58, 0xc0, 0xd4,
0x5b, 0x17, 0x23, 0x24, 0x6f, 0xbe, 0xac, 0x9c, 0xa1, 0x3c, 0x15, 0xb4, 0x40, 0xa9, 0x8f, 0x7d,
0x8a, 0x9b, 0x7c, 0x24, 0x9f, 0x17, 0xbc, 0x57, 0xa6, 0xda, 0xba, 0xd0, 0x37, 0x19, 0xc5, 0x22,
0x09, 0x43, 0x09, 0xb0, 0xfa, 0xc3, 0x2c, 0x68, 0x9c, 0x70, 0x95, 0xc3, 0xbb, 0x00, 0xba, 0x4d,
0x46, 0xfc, 0x3e, 0xb1, 0x6e, 0x87, 0xaf, 0xe1, 0x68, 0xd6, 0x2c, 0x24, 0xe3, 0xd5, 0xce, 0x88,
0x05, 0x1a, 0xe3, 0x05, 0x6d, 0xb0, 0x18, 0xa4, 0x26, 0xbf, 0x69, 0x66, 0x67, 0x19, 0x58, 0x7a,
0x70, 0x34, 0x2a, 0xc3, 0x41, 0x23, 0x33, 0x4a, 0xa2, 0x0c, 0x3c, 0x34, 0x01, 0x30, 0x93, 0xdd,
0x0b, 0x1b, 0x40, 0x9f, 0xec, 0x38, 0x4b, 0xf6, 0x2c, 0xbe, 0x82, 0x52, 0xdb, 0x95, 0x82, 0x55,
0xff, 0x50, 0x00, 0x48, 0xba, 0x02, 0x5e, 0x02, 0xa9, 0x07, 0xaf, 0xbc, 0xc5, 0x8a, 0x1c, 0x02,
0xa5, 0xe4, 0xfc, 0x3d, 0x6e, 0x13, 0xc6, 0x70, 0x2b, 0x1a, 0x99, 0xe3, 0xf7, 0xf8, 0x76, 0x28,
0x46, 0x91, 0x1e, 0xee, 0x83, 0x39, 0x9f, 0x60, 0xe6, 0x3a, 0xf2, 0xe5, 0xfe, 0x1e, 0x1f, 0xab,
0x90, 0x90, 0x1c, 0x0d, 0x1a, 0x1b, 0x93, 0xfc, 0x5f, 0xa2, 0xc9, 0x29, 0x4c, 0x38, 0x21, 0x09,
0x07, 0x6f, 0x83, 0xaa, 0xe4, 0x48, 0x2d, 0x38, 0xec, 0xda, 0x0b, 0x72, 0x35, 0xd5, 0xed, 0xbc,
0x01, 0x1a, 0xf5, 0x51, 0xef, 0x82, 0x85, 0xa8, 0xba, 0x60, 0x0d, 0x14, 0x53, 0xd7, 0x77, 0x18,
0xb8, 0x90, 0xe4, 0x12, 0x33, 0x3b, 0x3e, 0x31, 0xc6, 0xce, 0xb3, 0x97, 0xf5, 0x99, 0xe7, 0x2f,
0xeb, 0x33, 0x2f, 0x5e, 0xd6, 0x67, 0x9e, 0x0e, 0xeb, 0xca, 0xb3, 0x61, 0x5d, 0x79, 0x3e, 0xac,
0x2b, 0x2f, 0x86, 0x75, 0xe5, 0xe7, 0x61, 0x5d, 0xf9, 0xe6, 0x97, 0xfa, 0xcc, 0x87, 0xeb, 0x13,
0xff, 0xdf, 0xf5, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb2, 0xbb, 0x16, 0x34, 0x34, 0x13, 0x00,
0x00,
// 1509 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcb, 0x6f, 0x1b, 0x45,
0x18, 0xcf, 0xc6, 0x6e, 0x12, 0x8f, 0xf3, 0xf2, 0xd0, 0x2a, 0x6e, 0xa0, 0xde, 0x68, 0x55, 0xa1,
0x46, 0x82, 0x35, 0x49, 0x0b, 0x85, 0x0a, 0x09, 0x65, 0xfb, 0xa2, 0x8f, 0x3c, 0x34, 0x45, 0x89,
0x84, 0x40, 0x62, 0xb2, 0x3b, 0x71, 0xa6, 0xf6, 0x3e, 0xd8, 0x59, 0x9b, 0x46, 0x20, 0x51, 0x89,
0x0b, 0xdc, 0x38, 0x70, 0xe1, 0xca, 0x9f, 0xc0, 0x7f, 0xc0, 0xad, 0xc7, 0x1e, 0xcb, 0x01, 0x8b,
0x9a, 0x0b, 0x7f, 0x01, 0x48, 0xb9, 0x80, 0x66, 0x76, 0xf6, 0x69, 0x9b, 0xd8, 0x25, 0x70, 0xf3,
0x7c, 0x8f, 0xdf, 0xf7, 0x98, 0xef, 0xfb, 0xf6, 0x1b, 0x03, 0xd4, 0x7c, 0x9b, 0xe9, 0xd4, 0xad,
0x37, 0xdb, 0xfb, 0xc4, 0x77, 0x48, 0x40, 0x58, 0xbd, 0x43, 0x1c, 0xcb, 0xf5, 0xeb, 0x92, 0x81,
0x3d, 0x5a, 0xc7, 0x96, 0x4d, 0x19, 0xa3, 0xae, 0xe3, 0x93, 0x06, 0x65, 0x81, 0x8f, 0x03, 0xea,
0x3a, 0xf5, 0xce, 0x1a, 0x6e, 0x79, 0x87, 0x78, 0xad, 0xde, 0x20, 0x0e, 0xf1, 0x71, 0x40, 0x2c,
0xdd, 0xf3, 0xdd, 0xc0, 0x85, 0xab, 0xa1, 0xaa, 0x8e, 0x3d, 0xaa, 0x0f, 0x54, 0xd5, 0x23, 0xd5,
0xe5, 0xd7, 0x1b, 0x34, 0x38, 0x6c, 0xef, 0xeb, 0xa6, 0x6b, 0xd7, 0x1b, 0x6e, 0xc3, 0xad, 0x0b,
0x84, 0xfd, 0xf6, 0x81, 0x38, 0x89, 0x83, 0xf8, 0x15, 0x22, 0x2f, 0x5f, 0x1e, 0xc1, 0xa9, 0xbc,
0x3b, 0xcb, 0x57, 0x12, 0x25, 0x1b, 0x9b, 0x87, 0xd4, 0x21, 0xfe, 0x51, 0xdd, 0x6b, 0x36, 0x38,
0x81, 0xd5, 0x6d, 0x12, 0xe0, 0x41, 0x5a, 0xf5, 0x61, 0x5a, 0x7e, 0xdb, 0x09, 0xa8, 0x4d, 0xfa,
0x14, 0xde, 0x3a, 0x49, 0x81, 0x99, 0x87, 0xc4, 0xc6, 0x79, 0x3d, 0x8d, 0x81, 0x85, 0x8d, 0xb6,
0x45, 0x83, 0x0d, 0xc7, 0x71, 0x03, 0x11, 0x04, 0xbc, 0x00, 0x0a, 0x4d, 0x72, 0x54, 0x55, 0x56,
0x94, 0x4b, 0x25, 0xa3, 0xfc, 0xa4, 0xab, 0x4e, 0xf4, 0xba, 0x6a, 0xe1, 0x1e, 0x39, 0x42, 0x9c,
0x0e, 0x37, 0xc0, 0x42, 0x07, 0xb7, 0xda, 0xe4, 0xe6, 0x23, 0xcf, 0x27, 0x22, 0x05, 0xd5, 0x49,
0x21, 0xba, 0x24, 0x45, 0x17, 0x76, 0xb3, 0x6c, 0x94, 0x97, 0xd7, 0x5a, 0xa0, 0x92, 0x9c, 0xf6,
0xb0, 0xef, 0x50, 0xa7, 0x01, 0x5f, 0x03, 0x33, 0x07, 0x94, 0xb4, 0x2c, 0x44, 0x0e, 0x24, 0xe0,
0xa2, 0x04, 0x9c, 0xb9, 0x25, 0xe9, 0x28, 0x96, 0x80, 0xab, 0x60, 0xfa, 0xb3, 0x50, 0xb1, 0x5a,
0x10, 0xc2, 0x0b, 0x52, 0x78, 0x5a, 0xe2, 0xa1, 0x88, 0xaf, 0x1d, 0x80, 0xf9, 0x4d, 0x1c, 0x98,
0x87, 0xd7, 0x5d, 0xc7, 0xa2, 0x22, 0xc2, 0x15, 0x50, 0x74, 0xb0, 0x4d, 0x64, 0x88, 0xb3, 0x52,
0xb3, 0xb8, 0x85, 0x6d, 0x82, 0x04, 0x07, 0xae, 0x03, 0x40, 0xf2, 0xf1, 0x41, 0x29, 0x07, 0x52,
0xa1, 0xa5, 0xa4, 0xb4, 0x9f, 0x8b, 0xd2, 0x10, 0x22, 0xcc, 0x6d, 0xfb, 0x26, 0x61, 0xf0, 0x11,
0xa8, 0x70, 0x38, 0xe6, 0x61, 0x93, 0x3c, 0x20, 0x2d, 0x62, 0x06, 0xae, 0x2f, 0xac, 0x96, 0xd7,
0x2f, 0xeb, 0x49, 0x9d, 0xc6, 0x37, 0xa6, 0x7b, 0xcd, 0x06, 0x27, 0x30, 0x9d, 0x17, 0x86, 0xde,
0x59, 0xd3, 0xef, 0xe3, 0x7d, 0xd2, 0x8a, 0x54, 0x8d, 0x73, 0xbd, 0xae, 0x5a, 0xd9, 0xca, 0x23,
0xa2, 0x7e, 0x23, 0xd0, 0x05, 0xf3, 0xee, 0xfe, 0x43, 0x62, 0x06, 0xb1, 0xd9, 0xc9, 0x17, 0x37,
0x0b, 0x7b, 0x5d, 0x75, 0x7e, 0x3b, 0x03, 0x87, 0x72, 0xf0, 0xf0, 0x4b, 0x30, 0xe7, 0xcb, 0xb8,
0x51, 0xbb, 0x45, 0x58, 0xb5, 0xb0, 0x52, 0xb8, 0x54, 0x5e, 0x37, 0xf4, 0x91, 0xdb, 0x51, 0xe7,
0x81, 0x59, 0x5c, 0x79, 0x8f, 0x06, 0x87, 0xdb, 0x1e, 0x09, 0xf9, 0xcc, 0x38, 0x27, 0x13, 0x3f,
0x87, 0xd2, 0x06, 0x50, 0xd6, 0x1e, 0xfc, 0x4e, 0x01, 0x67, 0xc9, 0x23, 0xb3, 0xd5, 0xb6, 0x48,
0x46, 0xae, 0x5a, 0x3c, 0x35, 0x47, 0x5e, 0x91, 0x8e, 0x9c, 0xbd, 0x39, 0xc0, 0x0e, 0x1a, 0x68,
0x1d, 0xde, 0x00, 0x65, 0x9b, 0x17, 0xc5, 0x8e, 0xdb, 0xa2, 0xe6, 0x51, 0x75, 0x5a, 0x94, 0x92,
0xd6, 0xeb, 0xaa, 0xe5, 0xcd, 0x84, 0x7c, 0xdc, 0x55, 0x17, 0x52, 0xc7, 0x0f, 0x8e, 0x3c, 0x82,
0xd2, 0x6a, 0xda, 0x33, 0x05, 0x2c, 0x0d, 0xf1, 0x0a, 0x5e, 0x4d, 0x32, 0x2f, 0x4a, 0xa3, 0xaa,
0xac, 0x14, 0x2e, 0x95, 0x8c, 0x4a, 0x3a, 0x63, 0x82, 0x81, 0xb2, 0x72, 0xf0, 0x2b, 0x05, 0x40,
0xbf, 0x0f, 0x4f, 0x16, 0xca, 0xd5, 0x51, 0xf2, 0xa5, 0x0f, 0x48, 0xd2, 0xb2, 0x4c, 0x12, 0xec,
0xe7, 0xa1, 0x01, 0xe6, 0x34, 0x0c, 0x4a, 0x3b, 0xd8, 0xc7, 0xf6, 0x3d, 0xea, 0x58, 0xbc, 0xef,
0xb0, 0x47, 0x77, 0x89, 0x2f, 0xfa, 0x4e, 0xc9, 0xf6, 0xdd, 0xc6, 0xce, 0x1d, 0xc9, 0x41, 0x29,
0x29, 0xde, 0xcd, 0x4d, 0xea, 0x58, 0xb2, 0x4b, 0xe3, 0x6e, 0xe6, 0x78, 0x48, 0x70, 0xb4, 0x1f,
0x27, 0xc1, 0x8c, 0xb0, 0xc1, 0x27, 0xc7, 0xc9, 0xcd, 0x5f, 0x07, 0xa5, 0xb8, 0xa1, 0x24, 0x6a,
0x45, 0x8a, 0x95, 0xe2, 0xe6, 0x43, 0x89, 0x0c, 0xfc, 0x18, 0xcc, 0xb0, 0xa8, 0xcd, 0x0a, 0x2f,
0xde, 0x66, 0xb3, 0x7c, 0xd6, 0xc5, 0x0d, 0x16, 0x43, 0xc2, 0x00, 0x2c, 0x79, 0xdc, 0x7b, 0x12,
0x10, 0x7f, 0xcb, 0x0d, 0x6e, 0xb9, 0x6d, 0xc7, 0xda, 0x30, 0x79, 0xf6, 0xaa, 0x45, 0xe1, 0xdd,
0xb5, 0x5e, 0x57, 0x5d, 0xda, 0x19, 0x2c, 0x72, 0xdc, 0x55, 0x5f, 0x1e, 0xc2, 0x12, 0x65, 0x36,
0x0c, 0x5a, 0xfb, 0x5e, 0x01, 0xb3, 0x5c, 0xe2, 0xfa, 0x21, 0x31, 0x9b, 0x7c, 0x40, 0x7f, 0xad,
0x00, 0x48, 0xf2, 0x63, 0x3b, 0xac, 0xb6, 0xf2, 0xfa, 0xbb, 0x63, 0xb4, 0x57, 0xdf, 0xec, 0x4f,
0x6a, 0xa6, 0x8f, 0xc5, 0xd0, 0x00, 0x9b, 0xda, 0x2f, 0x93, 0xe0, 0xfc, 0x2e, 0x6e, 0x51, 0x0b,
0x07, 0xd4, 0x69, 0x6c, 0x44, 0xe6, 0xc2, 0x66, 0x81, 0x9f, 0x80, 0x19, 0x9e, 0x60, 0x0b, 0x07,
0x58, 0x0e, 0xdb, 0x37, 0x46, 0xbb, 0x8e, 0x70, 0xc4, 0x6d, 0x92, 0x00, 0x27, 0x45, 0x97, 0xd0,
0x50, 0x8c, 0x0a, 0x1f, 0x82, 0x22, 0xf3, 0x88, 0x29, 0x5b, 0xe5, 0xfd, 0x31, 0x62, 0x1f, 0xea,
0xf5, 0x03, 0x8f, 0x98, 0x49, 0x35, 0xf2, 0x13, 0x12, 0x36, 0xa0, 0x0f, 0xa6, 0x58, 0x80, 0x83,
0x36, 0x93, 0xa5, 0x75, 0xf7, 0x54, 0xac, 0x09, 0x44, 0x63, 0x5e, 0xda, 0x9b, 0x0a, 0xcf, 0x48,
0x5a, 0xd2, 0xfe, 0x54, 0xc0, 0xca, 0x50, 0x5d, 0x83, 0x3a, 0x16, 0xaf, 0x87, 0xff, 0x3e, 0xcd,
0x9f, 0x66, 0xd2, 0xbc, 0x7d, 0x1a, 0x81, 0x4b, 0xe7, 0x87, 0x65, 0x5b, 0xfb, 0x43, 0x01, 0x17,
0x4f, 0x52, 0xbe, 0x4f, 0x59, 0x00, 0x3f, 0xea, 0x8b, 0x5e, 0x1f, 0xb1, 0xe7, 0x29, 0x0b, 0x63,
0x8f, 0xd7, 0x9b, 0x88, 0x92, 0x8a, 0xdc, 0x03, 0x67, 0x68, 0x40, 0x6c, 0x3e, 0x8c, 0x79, 0x77,
0xdd, 0x3b, 0xc5, 0xd0, 0x8d, 0x39, 0x69, 0xf7, 0xcc, 0x1d, 0x6e, 0x01, 0x85, 0x86, 0xb4, 0x6f,
0x0a, 0x27, 0x07, 0xce, 0xf3, 0xc4, 0x47, 0xb4, 0x27, 0x88, 0x5b, 0xc9, 0x14, 0x8d, 0xaf, 0x71,
0x27, 0xe6, 0xa0, 0x94, 0x14, 0x1f, 0x90, 0x9e, 0x9c, 0xbf, 0x03, 0xf6, 0x90, 0x93, 0x22, 0x8a,
0x46, 0x77, 0x38, 0x20, 0xa3, 0x13, 0x8a, 0x21, 0x61, 0x1b, 0xcc, 0xdb, 0x99, 0xc5, 0x4b, 0xb6,
0xca, 0x3b, 0x63, 0x18, 0xc9, 0x6e, 0x6e, 0xe1, 0xca, 0x93, 0xa5, 0xa1, 0x9c, 0x11, 0xb8, 0x07,
0x2a, 0x1d, 0x99, 0x31, 0xd7, 0x09, 0xa7, 0x66, 0xb8, 0x6d, 0x94, 0x8c, 0x55, 0xbe, 0xa8, 0xed,
0xe6, 0x99, 0xc7, 0x5d, 0x75, 0x31, 0x4f, 0x44, 0xfd, 0x18, 0xda, 0xef, 0x0a, 0xb8, 0x30, 0xf4,
0x2e, 0xfe, 0x87, 0xea, 0xa3, 0xd9, 0xea, 0xbb, 0x71, 0x2a, 0xd5, 0x37, 0xb8, 0xec, 0x7e, 0x98,
0xfa, 0x87, 0x50, 0x45, 0xbd, 0x61, 0x50, 0xf2, 0xa2, 0xfd, 0x40, 0xc6, 0x7a, 0x65, 0xdc, 0xe2,
0xe1, 0xba, 0xc6, 0x1c, 0xff, 0x7e, 0xc7, 0x47, 0x94, 0xa0, 0xc2, 0xcf, 0xc1, 0xa2, 0x2d, 0x5f,
0x08, 0x1c, 0x80, 0x3a, 0x41, 0xb4, 0x05, 0xfd, 0x8b, 0x0a, 0x3a, 0xdb, 0xeb, 0xaa, 0x8b, 0x9b,
0x39, 0x58, 0xd4, 0x67, 0x08, 0xb6, 0x40, 0x39, 0xa9, 0x80, 0x68, 0x6d, 0x7e, 0xf3, 0x05, 0x52,
0xee, 0x3a, 0xc6, 0x4b, 0x32, 0xc7, 0xe5, 0x84, 0xc6, 0x50, 0x1a, 0x1e, 0xde, 0x07, 0x73, 0x07,
0x98, 0xb6, 0xda, 0x3e, 0x91, 0x0b, 0x69, 0xb8, 0x41, 0xbc, 0xca, 0x97, 0xc5, 0x5b, 0x69, 0xc6,
0x71, 0x57, 0xad, 0x64, 0x08, 0x62, 0x5b, 0xc8, 0x2a, 0xc3, 0xc7, 0x0a, 0x58, 0xc4, 0xd9, 0xe7,
0x23, 0xab, 0x9e, 0x11, 0x11, 0x5c, 0x1b, 0x23, 0x82, 0xdc, 0x0b, 0xd4, 0xa8, 0xca, 0x30, 0x16,
0x73, 0x0c, 0x86, 0xfa, 0xac, 0xc1, 0x2f, 0xc0, 0x82, 0x9d, 0x79, 0xdd, 0xb1, 0xea, 0x94, 0x70,
0x60, 0xec, 0xab, 0x8b, 0x11, 0x92, 0x97, 0x6c, 0x96, 0xce, 0x50, 0xde, 0x14, 0xb4, 0x40, 0xa9,
0x83, 0x7d, 0x8a, 0xf7, 0xf9, 0x43, 0x63, 0x5a, 0xd8, 0xbd, 0x3c, 0xd6, 0xd5, 0x85, 0xba, 0xc9,
0x7e, 0x19, 0x51, 0x18, 0x4a, 0x80, 0xb5, 0x9f, 0x26, 0x81, 0x7a, 0xc2, 0xa7, 0x1c, 0xde, 0x05,
0xd0, 0xdd, 0x67, 0xc4, 0xef, 0x10, 0xeb, 0x76, 0xf8, 0xc6, 0x8f, 0x36, 0xe8, 0x42, 0xb2, 0x5e,
0x6d, 0xf7, 0x49, 0xa0, 0x01, 0x5a, 0xd0, 0x06, 0xb3, 0x41, 0x6a, 0xf3, 0x1b, 0xe7, 0x45, 0x20,
0x03, 0x4b, 0x2f, 0x8e, 0xc6, 0x62, 0xaf, 0xab, 0x66, 0x56, 0x49, 0x94, 0x81, 0x87, 0x26, 0x00,
0x66, 0x72, 0x7b, 0x61, 0x03, 0xd4, 0x47, 0x1b, 0x67, 0xc9, 0x9d, 0xc5, 0x9f, 0xa0, 0xd4, 0x75,
0xa5, 0x60, 0xb5, 0xbf, 0x14, 0x00, 0x92, 0xae, 0x80, 0x17, 0x41, 0xea, 0x19, 0x2f, 0xbf, 0x62,
0x45, 0x0e, 0x81, 0x52, 0x74, 0xb8, 0x0a, 0xa6, 0x6d, 0xc2, 0x18, 0x6e, 0x44, 0xef, 0x80, 0xf8,
0x5f, 0x86, 0xcd, 0x90, 0x8c, 0x22, 0x3e, 0xdc, 0x03, 0x53, 0x3e, 0xc1, 0xcc, 0x75, 0xe4, 0xff,
0x11, 0xef, 0xf1, 0xb5, 0x0a, 0x09, 0xca, 0x71, 0x57, 0x5d, 0x1b, 0xe5, 0x5f, 0x20, 0x5d, 0x6e,
0x61, 0x42, 0x09, 0x49, 0x38, 0x78, 0x1b, 0x54, 0xa4, 0x8d, 0x94, 0xc3, 0x61, 0xd7, 0x9e, 0x97,
0xde, 0x54, 0x36, 0xf3, 0x02, 0xa8, 0x5f, 0x47, 0xbb, 0x0b, 0x66, 0xa2, 0xea, 0x82, 0x55, 0x50,
0x4c, 0x7d, 0xbe, 0xc3, 0xc0, 0x05, 0x25, 0x97, 0x98, 0xc9, 0xc1, 0x89, 0x31, 0xb6, 0x9f, 0x3c,
0xaf, 0x4d, 0x3c, 0x7d, 0x5e, 0x9b, 0x78, 0xf6, 0xbc, 0x36, 0xf1, 0xb8, 0x57, 0x53, 0x9e, 0xf4,
0x6a, 0xca, 0xd3, 0x5e, 0x4d, 0x79, 0xd6, 0xab, 0x29, 0xbf, 0xf6, 0x6a, 0xca, 0xb7, 0xbf, 0xd5,
0x26, 0x3e, 0x5c, 0x1d, 0xf9, 0x5f, 0xbc, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xad, 0xe2, 0x61,
0x96, 0x0a, 0x14, 0x00, 0x00,
}
func (m *AuditAnnotation) Marshal() (dAtA []byte, err error) {
@@ -917,6 +920,25 @@ func (m *ParamRef) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.ParameterNotFoundAction != nil {
i -= len(*m.ParameterNotFoundAction)
copy(dAtA[i:], *m.ParameterNotFoundAction)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ParameterNotFoundAction)))
i--
dAtA[i] = 0x22
}
if m.Selector != nil {
{
size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x1a
}
i -= len(m.Namespace)
copy(dAtA[i:], m.Namespace)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace)))
@@ -1581,6 +1603,14 @@ func (m *ParamRef) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
l = len(m.Namespace)
n += 1 + l + sovGenerated(uint64(l))
if m.Selector != nil {
l = m.Selector.Size()
n += 1 + l + sovGenerated(uint64(l))
}
if m.ParameterNotFoundAction != nil {
l = len(*m.ParameterNotFoundAction)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@@ -1875,6 +1905,8 @@ func (this *ParamRef) String() string {
s := strings.Join([]string{`&ParamRef{`,
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`,
`Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`,
`ParameterNotFoundAction:` + valueToStringGenerated(this.ParameterNotFoundAction) + `,`,
`}`,
}, "")
return s
@@ -2934,6 +2966,75 @@ func (m *ParamRef) Unmarshal(dAtA []byte) error {
}
m.Namespace = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Selector == nil {
m.Selector = &v1.LabelSelector{}
}
if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ParameterNotFoundAction", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := ParameterNotFoundActionType(dAtA[iNdEx:postIndex])
m.ParameterNotFoundAction = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@@ -227,16 +227,59 @@ message ParamKind {
optional string kind = 2;
}
// ParamRef references a parameter resource
// ParamRef describes how to locate the params to be used as input to
// expressions of rules applied by a policy binding.
// +structType=atomic
message ParamRef {
// Name of the resource being referenced.
// `name` is the name of the resource being referenced.
//
// `name` and `selector` are mutually exclusive properties. If one is set,
// the other must be unset.
//
// +optional
optional string name = 1;
// Namespace of the referenced resource.
// Should be empty for the cluster-scoped resources
// namespace is the namespace of the referenced resource. Allows limiting
// the search for params to a specific namespace. Applies to both `name` and
// `selector` fields.
//
// A per-namespace parameter may be used by specifying a namespace-scoped
// `paramKind` in the policy and leaving this field empty.
//
// - If `paramKind` is cluster-scoped, this field MUST be unset. Setting this
// field results in a configuration error.
//
// - If `paramKind` is namespace-scoped, the namespace of the object being
// evaluated for admission will be used when this field is left unset. Take
// care that if this is left empty the binding must not match any cluster-scoped
// resources, which will result in an error.
//
// +optional
optional string namespace = 2;
// selector can be used to match multiple param objects based on their labels.
// Supply selector: {} to match all resources of the ParamKind.
//
// If multiple params are found, they are all evaluated with the policy expressions
// and the results are ANDed together.
//
// One of `name` or `selector` must be set, but `name` and `selector` are
// mutually exclusive properties. If one is set, the other must be unset.
//
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 3;
// `parameterNotFoundAction` controls the behavior of the binding when the resource
// exists, and name or selector is valid, but there are no parameters
// matched by the binding. If the value is set to `Allow`, then no
// matched parameters will be treated as successful validation by the binding.
// If set to `Deny`, then no matched parameters will be subject to the
// `failurePolicy` of the policy.
//
// Allowed values are `Allow` or `Deny`
// Default to `Deny`
// +optional
optional string parameterNotFoundAction = 4;
}
// TypeChecking contains results of type checking the expressions in the
@@ -267,6 +310,15 @@ message ValidatingAdmissionPolicy {
// ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources.
// ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.
//
// For a given admission request, each binding will cause its policy to be
// evaluated N times, where N is 1 for policies/bindings that don't use
// params, otherwise N is the number of parameters selected by the binding.
//
// The CEL expressions of a policy must have a computed CEL cost below the maximum
// CEL budget. Each evaluation of the policy is given an independent CEL cost budget.
// Adding/removing policies, bindings, or params can not affect whether a
// given (policy, binding, param) combination is within its own CEL budget.
message ValidatingAdmissionPolicyBinding {
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.
// +optional
@@ -294,9 +346,10 @@ message ValidatingAdmissionPolicyBindingSpec {
// Required.
optional string policyName = 1;
// ParamRef specifies the parameter resource used to configure the admission control policy.
// paramRef specifies the parameter resource used to configure the admission control policy.
// It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy.
// If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied.
// If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.
// +optional
optional ParamRef paramRef = 2;

View File

@@ -39,6 +39,18 @@ const (
AllScopes ScopeType = v1.AllScopes
)
// ParameterNotFoundActionType specifies a failure policy that defines how a binding
// is evaluated when the param referred by its perNamespaceParamRef is not found.
// +enum
type ParameterNotFoundActionType string
const (
// Ignore means that an error finding params for a binding is ignored
AllowAction ParameterNotFoundActionType = "Allow"
// Fail means that an error finding params for a binding is ignored
DenyAction ParameterNotFoundActionType = "Deny"
)
// FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled.
// +enum
type FailurePolicyType string
@@ -363,6 +375,15 @@ type AuditAnnotation struct {
// ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources.
// ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.
//
// For a given admission request, each binding will cause its policy to be
// evaluated N times, where N is 1 for policies/bindings that don't use
// params, otherwise N is the number of parameters selected by the binding.
//
// The CEL expressions of a policy must have a computed CEL cost below the maximum
// CEL budget. Each evaluation of the policy is given an independent CEL cost budget.
// Adding/removing policies, bindings, or params can not affect whether a
// given (policy, binding, param) combination is within its own CEL budget.
type ValidatingAdmissionPolicyBinding struct {
metav1.TypeMeta `json:",inline"`
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.
@@ -393,9 +414,10 @@ type ValidatingAdmissionPolicyBindingSpec struct {
// Required.
PolicyName string `json:"policyName,omitempty" protobuf:"bytes,1,rep,name=policyName"`
// ParamRef specifies the parameter resource used to configure the admission control policy.
// paramRef specifies the parameter resource used to configure the admission control policy.
// It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy.
// If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied.
// If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.
// +optional
ParamRef *ParamRef `json:"paramRef,omitempty" protobuf:"bytes,2,rep,name=paramRef"`
@@ -450,15 +472,59 @@ type ValidatingAdmissionPolicyBindingSpec struct {
ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"`
}
// ParamRef references a parameter resource
// ParamRef describes how to locate the params to be used as input to
// expressions of rules applied by a policy binding.
// +structType=atomic
type ParamRef struct {
// Name of the resource being referenced.
// `name` is the name of the resource being referenced.
//
// `name` and `selector` are mutually exclusive properties. If one is set,
// the other must be unset.
//
// +optional
Name string `json:"name,omitempty" protobuf:"bytes,1,rep,name=name"`
// Namespace of the referenced resource.
// Should be empty for the cluster-scoped resources
// namespace is the namespace of the referenced resource. Allows limiting
// the search for params to a specific namespace. Applies to both `name` and
// `selector` fields.
//
// A per-namespace parameter may be used by specifying a namespace-scoped
// `paramKind` in the policy and leaving this field empty.
//
// - If `paramKind` is cluster-scoped, this field MUST be unset. Setting this
// field results in a configuration error.
//
// - If `paramKind` is namespace-scoped, the namespace of the object being
// evaluated for admission will be used when this field is left unset. Take
// care that if this is left empty the binding must not match any cluster-scoped
// resources, which will result in an error.
//
// +optional
Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,rep,name=namespace"`
// selector can be used to match multiple param objects based on their labels.
// Supply selector: {} to match all resources of the ParamKind.
//
// If multiple params are found, they are all evaluated with the policy expressions
// and the results are ANDed together.
//
// One of `name` or `selector` must be set, but `name` and `selector` are
// mutually exclusive properties. If one is set, the other must be unset.
//
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,rep,name=selector"`
// `parameterNotFoundAction` controls the behavior of the binding when the resource
// exists, and name or selector is valid, but there are no parameters
// matched by the binding. If the value is set to `Allow`, then no
// matched parameters will be treated as successful validation by the binding.
// If set to `Deny`, then no matched parameters will be subject to the
// `failurePolicy` of the policy.
//
// Allowed values are `Allow` or `Deny`
// Default to `Deny`
// +optional
ParameterNotFoundAction *ParameterNotFoundActionType `json:"parameterNotFoundAction,omitempty" protobuf:"bytes,4,rep,name=parameterNotFoundAction"`
}
// MatchResources decides whether to run the admission control policy on an object based

View File

@@ -80,9 +80,11 @@ func (ParamKind) SwaggerDoc() map[string]string {
}
var map_ParamRef = map[string]string{
"": "ParamRef references a parameter resource",
"name": "Name of the resource being referenced.",
"namespace": "Namespace of the referenced resource. Should be empty for the cluster-scoped resources",
"": "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.",
"name": "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.",
"namespace": "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.",
"selector": "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.",
"parameterNotFoundAction": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`",
}
func (ParamRef) SwaggerDoc() map[string]string {
@@ -110,7 +112,7 @@ func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string {
}
var map_ValidatingAdmissionPolicyBinding = map[string]string{
"": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.",
"": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.",
"metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.",
"spec": "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.",
}
@@ -132,7 +134,7 @@ func (ValidatingAdmissionPolicyBindingList) SwaggerDoc() map[string]string {
var map_ValidatingAdmissionPolicyBindingSpec = map[string]string{
"": "ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding.",
"policyName": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.",
"paramRef": "ParamRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied.",
"paramRef": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.",
"matchResources": "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.",
"validationActions": "validationActions declares how Validations of the referenced ValidatingAdmissionPolicy are enforced. If a validation evaluates to false it is always enforced according to these actions.\n\nFailures defined by the ValidatingAdmissionPolicy's FailurePolicy are enforced according to these actions only if the FailurePolicy is set to Fail, otherwise the failures are ignored. This includes compilation errors, runtime errors and misconfigurations of the policy.\n\nvalidationActions is declared as a set of action values. Order does not matter. validationActions may not contain duplicates of the same action.\n\nThe supported actions values are:\n\n\"Deny\" specifies that a validation failure results in a denied request.\n\n\"Warn\" specifies that a validation failure is reported to the request client in HTTP Warning headers, with a warning code of 299. Warnings can be sent both for allowed or denied admission responses.\n\n\"Audit\" specifies that a validation failure is included in the published audit event for the request. The audit event will contain a `validation.policy.admission.k8s.io/validation_failure` audit annotation with a value containing the details of the validation failures, formatted as a JSON list of objects, each with the following fields: - message: The validation failure message string - policy: The resource name of the ValidatingAdmissionPolicy - binding: The resource name of the ValidatingAdmissionPolicyBinding - expressionIndex: The index of the failed validations in the ValidatingAdmissionPolicy - validationActions: The enforcement actions enacted for the validation failure Example audit annotation: `\"validation.policy.admission.k8s.io/validation_failure\": \"[{\"message\": \"Invalid value\", {\"policy\": \"policy.example.com\", {\"binding\": \"policybinding.example.com\", {\"expressionIndex\": \"1\", {\"validationActions\": [\"Audit\"]}]\"`\n\nClients should expect to handle additional values by ignoring any values not recognized.\n\n\"Deny\" and \"Warn\" may not be used together since this combination needlessly duplicates the validation failure both in the API response body and the HTTP warning headers.\n\nRequired.",
}

View File

@@ -160,6 +160,16 @@ func (in *ParamKind) DeepCopy() *ParamKind {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ParamRef) DeepCopyInto(out *ParamRef) {
*out = *in
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.ParameterNotFoundAction != nil {
in, out := &in.ParameterNotFoundAction, &out.ParameterNotFoundAction
*out = new(ParameterNotFoundActionType)
**out = **in
}
return
}
@@ -288,7 +298,7 @@ func (in *ValidatingAdmissionPolicyBindingSpec) DeepCopyInto(out *ValidatingAdmi
if in.ParamRef != nil {
in, out := &in.ParamRef, &out.ParamRef
*out = new(ParamRef)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.MatchResources != nil {
in, out := &in.MatchResources, &out.MatchResources

View File

@@ -47,7 +47,22 @@
"policyName": "policyNameValue",
"paramRef": {
"name": "nameValue",
"namespace": "namespaceValue"
"namespace": "namespaceValue",
"selector": {
"matchLabels": {
"matchLabelsKey": "matchLabelsValue"
},
"matchExpressions": [
{
"key": "keyValue",
"operator": "operatorValue",
"values": [
"valuesValue"
]
}
]
},
"parameterNotFoundAction": "parameterNotFoundActionValue"
},
"matchResources": {
"namespaceSelector": {

View File

@@ -78,6 +78,15 @@ spec:
paramRef:
name: nameValue
namespace: namespaceValue
parameterNotFoundAction: parameterNotFoundActionValue
selector:
matchExpressions:
- key: keyValue
operator: operatorValue
values:
- valuesValue
matchLabels:
matchLabelsKey: matchLabelsValue
policyName: policyNameValue
validationActions:
- validationActionsValue

View File

@@ -36,6 +36,7 @@ import (
"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/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utiljson "k8s.io/apimachinery/pkg/util/json"
@@ -70,6 +71,13 @@ var (
Kind: paramsGVK.Kind + "List",
}, &unstructured.UnstructuredList{})
res.AddKnownTypeWithName(clusterScopedParamsGVK, &unstructured.Unstructured{})
res.AddKnownTypeWithName(schema.GroupVersionKind{
Group: clusterScopedParamsGVK.Group,
Version: clusterScopedParamsGVK.Version,
Kind: clusterScopedParamsGVK.Kind + "List",
}, &unstructured.UnstructuredList{})
if err := v1alpha1.AddToScheme(res); err != nil {
panic(err)
}
@@ -80,6 +88,13 @@ var (
return res
}()
clusterScopedParamsGVK schema.GroupVersionKind = schema.GroupVersionKind{
Group: "example.com",
Version: "v1",
Kind: "ClusterScopedParamsConfig",
}
paramsGVK schema.GroupVersionKind = schema.GroupVersionKind{
Group: "example.com",
Version: "v1",
@@ -95,6 +110,7 @@ var (
})
res.Add(paramsGVK, meta.RESTScopeNamespace)
res.Add(clusterScopedParamsGVK, meta.RESTScopeRoot)
res.Add(definitionGVK, meta.RESTScopeRoot)
res.Add(bindingGVK, meta.RESTScopeRoot)
res.Add(v1.SchemeGroupVersion.WithKind("ConfigMap"), meta.RESTScopeNamespace)
@@ -133,6 +149,7 @@ var (
"kind": paramsGVK.Kind,
"metadata": map[string]interface{}{
"name": "replicas-test.example.com",
"namespace": "default",
"resourceVersion": "1",
},
"maxReplicas": int64(3),
@@ -149,6 +166,8 @@ var (
ParamRef: &v1alpha1.ParamRef{
Name: fakeParams.GetName(),
Namespace: fakeParams.GetNamespace(),
// fake object tracker does not populate defaults
ParameterNotFoundAction: ptrTo(v1alpha1.DenyAction),
},
ValidationActions: []v1alpha1.ValidationAction{v1alpha1.Deny},
},
@@ -196,6 +215,40 @@ var (
}
)
func newParam(name, namespace string, labels map[string]string) *unstructured.Unstructured {
if len(namespace) == 0 {
namespace = metav1.NamespaceDefault
}
res := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": paramsGVK.GroupVersion().String(),
"kind": paramsGVK.Kind,
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
"resourceVersion": "1",
},
},
}
res.SetLabels(labels)
return res
}
func newClusterScopedParam(name string, labels map[string]string) *unstructured.Unstructured {
res := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": clusterScopedParamsGVK.GroupVersion().String(),
"kind": clusterScopedParamsGVK.Kind,
"metadata": map[string]interface{}{
"name": name,
"resourceVersion": "1",
},
},
}
res.SetLabels(labels)
return res
}
// Interface which has fake compile functionality for use in tests
// So that we can test the controller without pulling in any CEL functionality
type fakeCompiler struct {
@@ -562,7 +615,14 @@ func (c *celAdmissionController) getCurrentObject(obj runtime.Object) (runtime.O
}
// Param type. Just check informer for its GVK
item, err := paramInformer.Get(accessor.GetName())
var item runtime.Object
var err error
if namespace := accessor.GetNamespace(); len(namespace) > 0 {
item, err = paramInformer.Namespaced(namespace).Get(accessor.GetName())
} else {
item, err = paramInformer.Get(accessor.GetName())
}
if err != nil {
if k8serrors.IsNotFound(err) {
return nil, nil
@@ -940,7 +1000,9 @@ func TestReconfigureBinding(t *testing.T) {
"apiVersion": paramsGVK.GroupVersion().String(),
"kind": paramsGVK.Kind,
"metadata": map[string]interface{}{
"name": "replicas-test2.example.com",
"name": "replicas-test2.example.com",
// fake object tracker does not populate missing namespace
"namespace": "default",
"resourceVersion": "2",
},
"maxReplicas": int64(35),
@@ -976,8 +1038,9 @@ func TestReconfigureBinding(t *testing.T) {
Spec: v1alpha1.ValidatingAdmissionPolicyBindingSpec{
PolicyName: denyPolicy.Name,
ParamRef: &v1alpha1.ParamRef{
Name: fakeParams2.GetName(),
Namespace: fakeParams2.GetNamespace(),
Name: fakeParams2.GetName(),
Namespace: fakeParams2.GetNamespace(),
ParameterNotFoundAction: ptrTo(v1alpha1.DenyAction),
},
ValidationActions: []v1alpha1.ValidationAction{v1alpha1.Deny},
},
@@ -1019,7 +1082,7 @@ func TestReconfigureBinding(t *testing.T) {
&admission.RuntimeObjectInterfaces{},
)
require.ErrorContains(t, err, `failed to configure binding: replicas-test2.example.com not found`)
require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction")
// Add the missing params
require.NoError(t, paramTracker.Add(fakeParams2))
@@ -1275,10 +1338,80 @@ func TestInvalidParamSourceInstanceName(t *testing.T) {
// expect the specific error to be that the param was not found, not that CRD
// is not existing
require.ErrorContains(t, err,
`failed to configure binding: replicas-test.example.com not found`)
"no params found for policy binding with `Deny` parameterNotFoundAction")
require.Len(t, passedParams, 0)
}
// Show that policy still gets evaluated with `nil` param if paramRef & namespaceParamRef
// are both unset
func TestEmptyParamRef(t *testing.T) {
reset()
testContext, testContextCancel := context.WithCancel(context.Background())
defer testContextCancel()
compiler := &fakeCompiler{}
validator := &fakeValidator{}
matcher := &fakeMatcher{
DefaultMatch: true,
}
handler, _, tracker, controller := setupFakeTest(t, compiler, matcher)
datalock := sync.Mutex{}
numCompiles := 0
compiler.RegisterDefinition(denyPolicy, func([]cel.ExpressionAccessor, cel.OptionalVariableDeclarations) cel.Filter {
datalock.Lock()
numCompiles += 1
datalock.Unlock()
return &fakeFilter{
keyId: denyPolicy.Spec.Validations[0].Expression,
}
})
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
// Versioned params must be nil to pass the test
if versionedParams != nil {
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionAdmit,
},
},
}
}
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionDeny,
Message: "Denied",
},
},
}
})
require.NoError(t, tracker.Create(definitionsGVR, denyPolicy, denyPolicy.Namespace))
require.NoError(t, tracker.Create(bindingsGVR, denyBindingWithNoParamRef, denyBindingWithNoParamRef.Namespace))
// Wait for controller to reconcile given objects
require.NoError(t,
waitForReconcile(
testContext, controller,
denyBindingWithNoParamRef, denyPolicy))
err := handler.Validate(
testContext,
// Object is irrelevant/unchecked for this test. Just test that
// the evaluator is executed, and returns a denial
attributeRecord(nil, fakeParams, admission.Create),
&admission.RuntimeObjectInterfaces{},
)
require.ErrorContains(t, err, `Denied`)
require.Equal(t, 1, numCompiles)
}
// Shows that a definition with no param source works just fine, and has
// nil params passed to its evaluator.
//
@@ -1574,7 +1707,7 @@ func TestNativeTypeParam(t *testing.T) {
},
ObjectMeta: metav1.ObjectMeta{
Name: "replicas-test.example.com",
Namespace: "",
Namespace: "default",
ResourceVersion: "1",
},
Data: map[string]string{
@@ -1807,6 +1940,615 @@ func TestAllValidationActions(t *testing.T) {
require.ErrorContains(t, err, "I'm sorry Dave")
}
func TestNamespaceParamRefName(t *testing.T) {
reset()
testContext, testContextCancel := context.WithCancel(context.Background())
defer testContextCancel()
compiler := &fakeCompiler{}
validator := &fakeValidator{}
matcher := &fakeMatcher{
DefaultMatch: true,
}
handler, _, tracker, controller := setupFakeTest(t, compiler, matcher)
compiles := atomic.Int64{}
evaluations := atomic.Int64{}
// Use ConfigMap native-typed param
nativeTypeParamPolicy := *denyPolicy
nativeTypeParamPolicy.Spec.ParamKind = &v1alpha1.ParamKind{
APIVersion: "v1",
Kind: "ConfigMap",
}
namespaceParamBinding := *denyBinding
namespaceParamBinding.Spec.ParamRef = &v1alpha1.ParamRef{
Name: "replicas-test.example.com",
}
compiler.RegisterDefinition(&nativeTypeParamPolicy, func([]cel.ExpressionAccessor, cel.OptionalVariableDeclarations) cel.Filter {
compiles.Add(1)
return &fakeFilter{
keyId: nativeTypeParamPolicy.Spec.Validations[0].Expression,
}
})
lock := sync.Mutex{}
observedParamNamespaces := []string{}
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
lock.Lock()
defer lock.Unlock()
evaluations.Add(1)
if p, ok := versionedParams.(*v1.ConfigMap); ok {
observedParamNamespaces = append(observedParamNamespaces, p.Namespace)
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionDeny,
Message: "correct type",
},
},
}
}
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionDeny,
Message: "Incorrect param type",
},
},
}
})
configMapParam := &v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "replicas-test.example.com",
Namespace: "default",
ResourceVersion: "1",
},
Data: map[string]string{
"coolkey": "default",
},
}
configMapParam2 := &v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "replicas-test.example.com",
Namespace: "mynamespace",
ResourceVersion: "1",
},
Data: map[string]string{
"coolkey": "mynamespace",
},
}
configMapParam3 := &v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "replicas-test.example.com",
Namespace: "othernamespace",
ResourceVersion: "1",
},
Data: map[string]string{
"coolkey": "othernamespace",
},
}
require.NoError(t, tracker.Create(definitionsGVR, &nativeTypeParamPolicy, nativeTypeParamPolicy.Namespace))
require.NoError(t, tracker.Create(bindingsGVR, &namespaceParamBinding, namespaceParamBinding.Namespace))
require.NoError(t, tracker.Add(configMapParam))
require.NoError(t, tracker.Add(configMapParam2))
require.NoError(t, tracker.Add(configMapParam3))
// Wait for controller to reconcile given objects
require.NoError(t,
waitForReconcile(
testContext, controller,
&namespaceParamBinding, &nativeTypeParamPolicy, configMapParam, configMapParam2, configMapParam3))
// Object is irrelevant/unchecked for this test. Just test that
// the evaluator is executed with correct namespace, and returns admit
// meaning the params passed was a configmap
err := handler.Validate(
testContext,
attributeRecord(nil, configMapParam, admission.Create),
&admission.RuntimeObjectInterfaces{},
)
func() {
lock.Lock()
defer lock.Unlock()
require.ErrorContains(t, err, "correct type")
require.EqualValues(t, 1, compiles.Load())
require.EqualValues(t, 1, evaluations.Load())
}()
err = handler.Validate(
testContext,
attributeRecord(nil, configMapParam2, admission.Create),
&admission.RuntimeObjectInterfaces{},
)
func() {
lock.Lock()
defer lock.Unlock()
require.ErrorContains(t, err, "correct type")
require.EqualValues(t, 1, compiles.Load())
require.EqualValues(t, 2, evaluations.Load())
}()
err = handler.Validate(
testContext,
attributeRecord(nil, configMapParam3, admission.Create),
&admission.RuntimeObjectInterfaces{},
)
func() {
lock.Lock()
defer lock.Unlock()
require.ErrorContains(t, err, "correct type")
require.EqualValues(t, 1, compiles.Load())
require.EqualValues(t, 3, evaluations.Load())
}()
err = handler.Validate(
testContext,
attributeRecord(nil, configMapParam, admission.Create),
&admission.RuntimeObjectInterfaces{},
)
func() {
lock.Lock()
defer lock.Unlock()
require.ErrorContains(t, err, "correct type")
require.EqualValues(t, []string{"default", "mynamespace", "othernamespace", "default"}, observedParamNamespaces)
require.EqualValues(t, 1, compiles.Load())
require.EqualValues(t, 4, evaluations.Load())
}()
}
func TestParamRef(t *testing.T) {
for _, paramIsClusterScoped := range []bool{false, true} {
for _, nameIsSet := range []bool{false, true} {
for _, namespaceIsSet := range []bool{false, true} {
if paramIsClusterScoped && namespaceIsSet {
// Skip invalid configuration
continue
}
for _, selectorIsSet := range []bool{false, true} {
if selectorIsSet && nameIsSet {
// SKip invalid configuration
continue
}
for _, denyNotFound := range []bool{false, true} {
name := "ParamRef"
if paramIsClusterScoped {
name = "ClusterScoped" + name
}
if nameIsSet {
name = name + "WithName"
} else if selectorIsSet {
name = name + "WithLabelSelector"
} else {
name = name + "WithEverythingSelector"
}
if namespaceIsSet {
name = name + "WithNamespace"
}
if denyNotFound {
name = name + "DenyNotFound"
} else {
name = name + "AllowNotFound"
}
t.Run(name, func(t *testing.T) {
// Test creating a policy with a cluster or namesapce-scoped param
// and binding with the provided configuration. Test will ensure
// that the provided configuration is capable of matching
// params as expected, and not matching params when not expected.
// Also ensures the NotFound setting works as expected with this particular
// configuration of ParamRef when all the previously
// matched params are deleted.
testParamRefCase(t, paramIsClusterScoped, nameIsSet, namespaceIsSet, selectorIsSet, denyNotFound)
})
}
}
}
}
}
}
// testParamRefCase constructs a ParamRef and policy with appropriate ParamKind
// for the given parameters, then constructs a scenario with several matching/non-matching params
// of varying names, namespaces, labels.
//
// Test then selects subset of params that should match provided configuration
// and ensuers those params are the only ones used.
//
// Also ensures NotFound action is enforced correctly by deleting all found
// params and ensuring the Action is used.
//
// This test is not meant to test every possible scenario of matching/not matching:
// only that each ParamRef CAN be evaluated correctly for both cluster scoped
// and namespace-scoped request kinds, and that the failure action is correctly
// applied.
func testParamRefCase(t *testing.T, paramIsClusterScoped, nameIsSet, namespaceIsSet, selectorIsSet, denyNotFound bool) {
// Create a cluster scoped and a namespace scoped CRD
policy := *denyPolicy
binding := *denyBinding
binding.Spec.ParamRef = &v1alpha1.ParamRef{}
paramRef := binding.Spec.ParamRef
shouldErrorOnClusterScopedRequests := !namespaceIsSet && !paramIsClusterScoped
matchingParamName := "replicas-test.example.com"
matchingNamespace := "mynamespace"
nonMatchingNamespace := "othernamespace"
matchingLabels := labels.Set{"doesitmatch": "yes"}
nonmatchingLabels := labels.Set{"doesitmatch": "no"}
otherNonmatchingLabels := labels.Set{"notaffiliated": "no"}
if paramIsClusterScoped {
policy.Spec.ParamKind = &v1alpha1.ParamKind{
APIVersion: clusterScopedParamsGVK.GroupVersion().String(),
Kind: clusterScopedParamsGVK.Kind,
}
} else {
policy.Spec.ParamKind = &v1alpha1.ParamKind{
APIVersion: paramsGVK.GroupVersion().String(),
Kind: paramsGVK.Kind,
}
}
if nameIsSet {
paramRef.Name = matchingParamName
} else if selectorIsSet {
paramRef.Selector = metav1.SetAsLabelSelector(matchingLabels)
} else {
paramRef.Selector = &metav1.LabelSelector{}
}
if namespaceIsSet {
paramRef.Namespace = matchingNamespace
}
if denyNotFound {
paramRef.ParameterNotFoundAction = ptrTo(v1alpha1.DenyAction)
} else {
paramRef.ParameterNotFoundAction = ptrTo(v1alpha1.AllowAction)
}
compiler := &fakeCompiler{}
validator := &fakeValidator{}
matcher := &fakeMatcher{
DefaultMatch: true,
}
var matchedParams []runtime.Object
paramLock := sync.Mutex{}
observeParam := func(p runtime.Object) {
paramLock.Lock()
defer paramLock.Unlock()
matchedParams = append(matchedParams, p)
}
getAndResetObservedParams := func() []runtime.Object {
paramLock.Lock()
defer paramLock.Unlock()
oldParams := matchedParams
matchedParams = nil
return oldParams
}
compiler.RegisterDefinition(&policy, func(ea []cel.ExpressionAccessor, ovd cel.OptionalVariableDeclarations) cel.Filter {
return &fakeFilter{
keyId: policy.Spec.Validations[0].Expression,
}
})
validator.RegisterDefinition(&policy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
observeParam(versionedParams)
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionDeny,
Message: "Denied by policy",
},
},
}
})
handler, paramTracker, tracker, controller := setupFakeTest(t, compiler, matcher)
// Create library of params to try to fool the controller
params := []*unstructured.Unstructured{
newParam(matchingParamName, v1.NamespaceDefault, nonmatchingLabels),
newParam(matchingParamName, matchingNamespace, nonmatchingLabels),
newParam(matchingParamName, nonMatchingNamespace, nonmatchingLabels),
newParam(matchingParamName+"1", v1.NamespaceDefault, matchingLabels),
newParam(matchingParamName+"1", matchingNamespace, matchingLabels),
newParam(matchingParamName+"1", nonMatchingNamespace, matchingLabels),
newParam(matchingParamName+"2", v1.NamespaceDefault, otherNonmatchingLabels),
newParam(matchingParamName+"2", matchingNamespace, otherNonmatchingLabels),
newParam(matchingParamName+"2", nonMatchingNamespace, otherNonmatchingLabels),
newParam(matchingParamName+"3", v1.NamespaceDefault, otherNonmatchingLabels),
newParam(matchingParamName+"3", matchingNamespace, matchingLabels),
newParam(matchingParamName+"3", nonMatchingNamespace, matchingLabels),
newClusterScopedParam(matchingParamName, matchingLabels),
newClusterScopedParam(matchingParamName+"1", nonmatchingLabels),
newClusterScopedParam(matchingParamName+"2", otherNonmatchingLabels),
newClusterScopedParam(matchingParamName+"3", matchingLabels),
newClusterScopedParam(matchingParamName+"4", nonmatchingLabels),
newClusterScopedParam(matchingParamName+"5", otherNonmatchingLabels),
}
require.NoError(t, tracker.Create(definitionsGVR, &policy, policy.Namespace))
require.NoError(t, tracker.Create(bindingsGVR, &binding, binding.Namespace))
require.NoError(t, waitForReconcile(context.TODO(), controller, &policy, &binding))
for _, p := range params {
paramTracker.Add(p)
}
namespacedRequestObject := newParam("some param", nonMatchingNamespace, nil)
clusterScopedRequestObject := newClusterScopedParam("other param", nil)
// Validate a namespaced object, and verify that the params being validated
// are the ones we would expect
var expectedParamsForNamespacedRequest []*unstructured.Unstructured
for _, p := range params {
if p.GetAPIVersion() != policy.Spec.ParamKind.APIVersion || p.GetKind() != policy.Spec.ParamKind.Kind {
continue
} else if len(paramRef.Name) > 0 && p.GetName() != paramRef.Name {
continue
} else if len(paramRef.Namespace) > 0 && p.GetNamespace() != paramRef.Namespace {
continue
}
if !paramIsClusterScoped {
if len(paramRef.Namespace) == 0 && p.GetNamespace() != namespacedRequestObject.GetNamespace() {
continue
}
}
if paramRef.Selector != nil {
ls := p.GetLabels()
matched := true
for k, v := range paramRef.Selector.MatchLabels {
if l, hasLabel := ls[k]; !hasLabel {
matched = false
break
} else if l != v {
matched = false
break
}
}
// Empty selector matches everything
if len(paramRef.Selector.MatchExpressions) == 0 && len(paramRef.Selector.MatchLabels) == 0 {
matched = true
}
if !matched {
continue
}
}
expectedParamsForNamespacedRequest = append(expectedParamsForNamespacedRequest, p)
require.NoError(t, waitForReconcile(context.TODO(), controller, p))
}
require.NotEmpty(t, expectedParamsForNamespacedRequest, "all test cases should match at least one param")
require.ErrorContains(t, handler.Validate(context.TODO(), attributeRecord(nil, namespacedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{}), "Denied by policy")
require.ElementsMatch(t, expectedParamsForNamespacedRequest, getAndResetObservedParams(), "should exactly match expected params")
// Validate a cluster-scoped object, and verify that the params being validated
// are the ones we would expect
var expectedParamsForClusterScopedRequest []*unstructured.Unstructured
for _, p := range params {
if shouldErrorOnClusterScopedRequests {
continue
} else if p.GetAPIVersion() != policy.Spec.ParamKind.APIVersion || p.GetKind() != policy.Spec.ParamKind.Kind {
continue
} else if len(paramRef.Name) > 0 && p.GetName() != paramRef.Name {
continue
} else if len(paramRef.Namespace) > 0 && p.GetNamespace() != paramRef.Namespace {
continue
} else if !paramIsClusterScoped && len(paramRef.Namespace) == 0 && p.GetNamespace() != v1.NamespaceDefault {
continue
}
if paramRef.Selector != nil {
ls := p.GetLabels()
matched := true
for k, v := range paramRef.Selector.MatchLabels {
if l, hasLabel := ls[k]; !hasLabel {
matched = false
break
} else if l != v {
matched = false
break
}
}
// Empty selector matches everything
if len(paramRef.Selector.MatchExpressions) == 0 && len(paramRef.Selector.MatchLabels) == 0 {
matched = true
}
if !matched {
continue
}
}
expectedParamsForClusterScopedRequest = append(expectedParamsForClusterScopedRequest, p)
require.NoError(t, waitForReconcile(context.TODO(), controller, p))
}
err := handler.Validate(context.TODO(), attributeRecord(nil, clusterScopedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{})
if shouldErrorOnClusterScopedRequests {
// Cannot validate cliuster-scoped resources against a paramRef that sets namespace
require.ErrorContains(t, err, "failed to configure binding: cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
} else {
require.NotEmpty(t, expectedParamsForClusterScopedRequest, "all test cases should match at least one param")
require.ErrorContains(t, err, "Denied by policy")
}
require.ElementsMatch(t, expectedParamsForClusterScopedRequest, getAndResetObservedParams(), "should exactly match expected params")
// Remove all params matched by namespaced, and cluster-scoped validation.
// Validate again to make sure NotFoundAction is respected
var deleted []runtime.Object
for _, p := range expectedParamsForNamespacedRequest {
if paramIsClusterScoped {
require.NoError(t, paramTracker.Delete(paramsGVK.GroupVersion().WithResource("clusterscopedparamsconfigs"), p.GetNamespace(), p.GetName()))
} else {
require.NoError(t, paramTracker.Delete(paramsGVK.GroupVersion().WithResource("paramsconfigs"), p.GetNamespace(), p.GetName()))
}
deleted = append(deleted, p)
}
for _, p := range expectedParamsForClusterScopedRequest {
// Tracker.Delete docs says it wont raise error for not found, but its implmenetation
// pretty plainly does...
rsrsc := "paramsconfigs"
if paramIsClusterScoped {
rsrsc = "clusterscopedparamsconfigs"
}
if err := paramTracker.Delete(paramsGVK.GroupVersion().WithResource(rsrsc), p.GetNamespace(), p.GetName()); err != nil && !k8serrors.IsNotFound(err) {
require.NoError(t, err)
deleted = append(deleted, p)
}
}
require.NoError(t, waitForReconcileDeletion(context.TODO(), controller, deleted...))
controller.refreshPolicies()
// Check that NotFound is working correctly for both namespaeed & non-namespaced
// request object
err = handler.Validate(context.TODO(), attributeRecord(nil, namespacedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{})
if denyNotFound {
require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction")
} else {
require.NoError(t, err, "Allow not found expects no error when no params found. Policy should have been skipped")
}
require.Empty(t, getAndResetObservedParams(), "policy should not have been evaluated")
err = handler.Validate(context.TODO(), attributeRecord(nil, clusterScopedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{})
if shouldErrorOnClusterScopedRequests {
require.ErrorContains(t, err, "failed to configure binding: cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
} else if denyNotFound {
require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction")
} else {
require.NoError(t, err, "Allow not found expects no error when no params found. Policy should have been skipped")
}
require.Empty(t, getAndResetObservedParams(), "policy should not have been evaluated")
}
// If the ParamKind is ClusterScoped, and namespace param is used.
// This is a Configuration Error of the policy
func TestNamespaceParamRefClusterScopedParamError(t *testing.T) {
reset()
testContext, testContextCancel := context.WithCancel(context.Background())
defer testContextCancel()
compiler := &fakeCompiler{}
validator := &fakeValidator{}
matcher := &fakeMatcher{
DefaultMatch: true,
}
handler, _, tracker, controller := setupFakeTest(t, compiler, matcher)
compiles := atomic.Int64{}
evaluations := atomic.Int64{}
// Use ValidatingAdmissionPolicy for param type since it is cluster-scoped
nativeTypeParamPolicy := *denyPolicy
nativeTypeParamPolicy.Spec.ParamKind = &v1alpha1.ParamKind{
APIVersion: "admissionregistration.k8s.io/v1alpha1",
Kind: "ValidatingAdmissionPolicy",
}
namespaceParamBinding := *denyBinding
namespaceParamBinding.Spec.ParamRef = &v1alpha1.ParamRef{
Name: "other-param-to-use-with-no-label.example.com",
Namespace: "mynamespace",
}
compiler.RegisterDefinition(&nativeTypeParamPolicy, func([]cel.ExpressionAccessor, cel.OptionalVariableDeclarations) cel.Filter {
compiles.Add(1)
return &fakeFilter{
keyId: nativeTypeParamPolicy.Spec.Validations[0].Expression,
}
})
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
evaluations.Add(1)
if _, ok := versionedParams.(*v1alpha1.ValidatingAdmissionPolicy); ok {
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionAdmit,
Message: "correct type",
},
},
}
}
return ValidateResult{
Decisions: []PolicyDecision{
{
Action: ActionDeny,
Message: fmt.Sprintf("Incorrect param type %T", versionedParams),
},
},
}
})
require.NoError(t, tracker.Create(definitionsGVR, &nativeTypeParamPolicy, nativeTypeParamPolicy.Namespace))
require.NoError(t, tracker.Create(bindingsGVR, &namespaceParamBinding, namespaceParamBinding.Namespace))
// Wait for controller to reconcile given objects
require.NoError(t,
waitForReconcile(
testContext, controller,
&namespaceParamBinding, &nativeTypeParamPolicy))
// Object is irrelevant/unchecked for this test. Just test that
// the evaluator is executed with correct namespace, and returns admit
// meaning the params passed was a configmap
err := handler.Validate(
testContext,
attributeRecord(nil, fakeParams, admission.Create),
&admission.RuntimeObjectInterfaces{},
)
require.ErrorContains(t, err, "paramRef.namespace must not be provided for a cluster-scoped `paramKind`")
require.EqualValues(t, 1, compiles.Load())
require.EqualValues(t, 0, evaluations.Load())
}
func TestAuditAnnotations(t *testing.T) {
testContext, testContextCancel := context.WithCancel(context.Background())
defer testContextCancel()

View File

@@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utiljson "k8s.io/apimachinery/pkg/util/json"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
@@ -72,8 +73,8 @@ type celAdmissionController struct {
// against all of its registered bindings.
type policyData struct {
definitionInfo
paramController generic.Controller[runtime.Object]
bindings []bindingInfo
paramInfo
bindings []bindingInfo
}
// contains the cel PolicyDecisions along with the ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
@@ -116,6 +117,9 @@ type paramInfo struct {
// Function to call to stop the informer and clean up the controller
stop func()
// Whether this param is cluster or namespace scoped
scope meta.RESTScope
// Policy Definitions which refer to this param CRD
dependentDefinitions sets.Set[namespacedName]
}
@@ -134,6 +138,7 @@ func NewAdmissionController(
restMapper,
client,
dynamicClient,
informerFactory,
nil,
NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)),
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
@@ -232,6 +237,12 @@ func (c *celAdmissionController) Validate(
authz := newCachingAuthorizer(c.authz)
for _, definitionInfo := range policyDatas {
// versionedAttributes will be set to non-nil inside of the loop, but
// is scoped outside of the param loop so we only convert once. We defer
// conversion so that it is only performed when we know a policy matches,
// saving the cost of converting non-matching requests.
var versionedAttr *admission.VersionedAttributes
definition := definitionInfo.lastReconciledValue
matches, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
if err != nil {
@@ -263,63 +274,23 @@ func (c *celAdmissionController) Validate(
continue
}
var param runtime.Object
// versionedAttributes will be set to non-nil inside of the loop, but
// is scoped outside of the param loop so we only convert once. We defer
// conversion so that it is only performed when we know a policy matches,
// saving the cost of converting non-matching requests.
var versionedAttr *admission.VersionedAttributes
// If definition has paramKind, paramRef is required in binding.
// If definition has no paramKind, paramRef set in binding will be ignored.
paramKind := definition.Spec.ParamKind
paramRef := binding.Spec.ParamRef
if paramKind != nil && paramRef != nil {
paramController := definitionInfo.paramController
if paramController == nil {
addConfigError(fmt.Errorf("paramKind kind `%v` not known",
paramKind.String()), definition, binding)
continue
}
// If the param informer for this admission policy has not yet
// had time to perform an initial listing, don't attempt to use
// it.
timeoutCtx, cancel := context.WithTimeout(c.policyController.context, 1*time.Second)
defer cancel()
if !cache.WaitForCacheSync(timeoutCtx.Done(), paramController.HasSynced) {
addConfigError(fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
paramKind.String()), definition, binding)
continue
}
if len(paramRef.Namespace) == 0 {
param, err = paramController.Informer().Get(paramRef.Name)
} else {
param, err = paramController.Informer().Namespaced(paramRef.Namespace).Get(paramRef.Name)
}
params, err := c.collectParams(definition.Spec.ParamKind, definitionInfo.paramInfo, binding.Spec.ParamRef, a.GetNamespace())
if err != nil {
addConfigError(err, definition, binding)
continue
} else if versionedAttr == nil && len(params) > 0 {
// As optimization versionedAttr creation is deferred until
// first use. Since > 0 params, we will validate
va, err := admission.NewVersionedAttributes(a, matchKind, o)
if err != nil {
// Apply failure policy
addConfigError(err, definition, binding)
if k8serrors.IsInvalid(err) {
// Param mis-configured
// require to set paramRef.namespace for namespaced resource and unset paramRef.namespace for cluster scoped resource
continue
} else if k8serrors.IsNotFound(err) {
// Param not yet available. User may need to wait a bit
// before being able to use it for validation.
continue
}
// There was a bad internal error
utilruntime.HandleError(err)
wrappedErr := fmt.Errorf("failed to convert object version: %w", err)
addConfigError(wrappedErr, definition, binding)
continue
}
versionedAttr = va
}
var validationResults []ValidateResult
var namespace *v1.Namespace
namespaceName := a.GetNamespace()
@@ -338,72 +309,79 @@ func (c *celAdmissionController) Validate(
}
}
if versionedAttr == nil {
va, err := admission.NewVersionedAttributes(a, matchKind, o)
if err != nil {
wrappedErr := fmt.Errorf("failed to convert object version: %w", err)
addConfigError(wrappedErr, definition, binding)
continue
}
versionedAttr = va
}
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, namespace, celconfig.RuntimeCELCostBudget, authz)
for i, decision := range validationResult.Decisions {
switch decision.Action {
case ActionAdmit:
if decision.Evaluation == EvalError {
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
}
case ActionDeny:
for _, action := range binding.Spec.ValidationActions {
switch action {
case v1alpha1.Deny:
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
Definition: definition,
Binding: binding,
PolicyDecision: decision,
})
celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
case v1alpha1.Audit:
c.publishValidationFailureAnnotation(binding, i, decision, versionedAttr)
celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
case v1alpha1.Warn:
warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message))
celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
}
}
default:
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
decision.Action, binding.Name, definition.Name)
}
}
for _, auditAnnotation := range validationResult.AuditAnnotations {
switch auditAnnotation.Action {
case AuditAnnotationActionPublish:
value := auditAnnotation.Value
if len(auditAnnotation.Value) > maxAuditAnnotationValueLength {
value = value[:maxAuditAnnotationValueLength]
}
auditAnnotationCollector.add(auditAnnotation.Key, value)
case AuditAnnotationActionError:
// When failurePolicy=fail, audit annotation errors result in deny
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
Definition: definition,
Binding: binding,
PolicyDecision: PolicyDecision{
Action: ActionDeny,
Evaluation: EvalError,
Message: auditAnnotation.Error,
Elapsed: auditAnnotation.Elapsed,
for _, param := range params {
var p runtime.Object = param
if p != nil && p.GetObjectKind().GroupVersionKind().Empty() {
// Make sure param has TypeMeta populated
// This is a simple hack to make sure typeMeta is
// available to CEL without making copies of objects, etc.
p = &wrappedParam{
TypeMeta: metav1.TypeMeta{
APIVersion: definition.Spec.ParamKind.APIVersion,
Kind: definition.Spec.ParamKind.Kind,
},
})
celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, "active")
case AuditAnnotationActionExclude: // skip it
default:
return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action)
nested: param,
}
}
validationResults = append(validationResults, bindingInfo.validator.Validate(ctx, versionedAttr, p, namespace, celconfig.RuntimeCELCostBudget, authz))
}
for _, validationResult := range validationResults {
for i, decision := range validationResult.Decisions {
switch decision.Action {
case ActionAdmit:
if decision.Evaluation == EvalError {
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
}
case ActionDeny:
for _, action := range binding.Spec.ValidationActions {
switch action {
case v1alpha1.Deny:
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
Definition: definition,
Binding: binding,
PolicyDecision: decision,
})
celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
case v1alpha1.Audit:
c.publishValidationFailureAnnotation(binding, i, decision, versionedAttr)
celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
case v1alpha1.Warn:
warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message))
celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
}
}
default:
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
decision.Action, binding.Name, definition.Name)
}
}
for _, auditAnnotation := range validationResult.AuditAnnotations {
switch auditAnnotation.Action {
case AuditAnnotationActionPublish:
value := auditAnnotation.Value
if len(auditAnnotation.Value) > maxAuditAnnotationValueLength {
value = value[:maxAuditAnnotationValueLength]
}
auditAnnotationCollector.add(auditAnnotation.Key, value)
case AuditAnnotationActionError:
// When failurePolicy=fail, audit annotation errors result in deny
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
Definition: definition,
Binding: binding,
PolicyDecision: PolicyDecision{
Action: ActionDeny,
Evaluation: EvalError,
Message: auditAnnotation.Error,
Elapsed: auditAnnotation.Elapsed,
},
})
celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, "active")
case AuditAnnotationActionExclude: // skip it
default:
return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action)
}
}
}
}
@@ -432,6 +410,123 @@ func (c *celAdmissionController) Validate(
return nil
}
// Returns objects to use to evaluate the policy
func (c *celAdmissionController) collectParams(
paramKind *v1alpha1.ParamKind,
info paramInfo,
paramRef *v1alpha1.ParamRef,
namespace string,
) ([]runtime.Object, error) {
// If definition has paramKind, paramRef is required in binding.
// If definition has no paramKind, paramRef set in binding will be ignored.
var params []runtime.Object
var paramStore generic.NamespacedLister[runtime.Object]
// Make sure the param kind is ready to use
if paramKind != nil && paramRef != nil {
if info.controller == nil {
return nil, fmt.Errorf("paramKind kind `%v` not known",
paramKind.String())
}
// Set up cluster-scoped, or namespaced access to the params
// "default" if not provided, and paramKind is namespaced
paramStore = info.controller.Informer()
if info.scope.Name() == meta.RESTScopeNameNamespace {
paramsNamespace := namespace
if len(paramRef.Namespace) > 0 {
paramsNamespace = paramRef.Namespace
} else if len(paramsNamespace) == 0 {
// You must supply namespace if your matcher can possibly
// match a cluster-scoped resource
return nil, fmt.Errorf("cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
}
paramStore = info.controller.Informer().Namespaced(paramsNamespace)
}
// If the param informer for this admission policy has not yet
// had time to perform an initial listing, don't attempt to use
// it.
timeoutCtx, cancel := context.WithTimeout(c.policyController.context, 1*time.Second)
defer cancel()
if !cache.WaitForCacheSync(timeoutCtx.Done(), info.controller.HasSynced) {
return nil, fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
paramKind.String())
}
}
// Find params to use with policy
switch {
case paramKind == nil:
// ParamKind is unset. Ignore any globalParamRef or namespaceParamRef
// setting.
return []runtime.Object{nil}, nil
case paramRef == nil:
// Policy ParamKind is set, but binding does not use it.
// Validate with nil params
return []runtime.Object{nil}, nil
case len(paramRef.Namespace) > 0 && info.scope.Name() == meta.RESTScopeRoot.Name():
// Not allowed to set namespace for cluster-scoped param
return nil, fmt.Errorf("paramRef.namespace must not be provided for a cluster-scoped `paramKind`")
case len(paramRef.Name) > 0:
if paramRef.Selector != nil {
// This should be validated, but just in case.
return nil, fmt.Errorf("paramRef.name and paramRef.selector are mutually exclusive")
}
switch param, err := paramStore.Get(paramRef.Name); {
case err == nil:
params = []runtime.Object{param}
case k8serrors.IsNotFound(err):
// Param not yet available. User may need to wait a bit
// before being able to use it for validation.
//
// Set params to nil to prepare for not found action
params = nil
case k8serrors.IsInvalid(err):
// Param mis-configured
// require to set namespace for namespaced resource
// and unset namespace for cluster scoped resource
return nil, err
default:
// Internal error
utilruntime.HandleError(err)
return nil, err
}
case paramRef.Selector != nil:
// Select everything by default if empty name and selector
selector, err := metav1.LabelSelectorAsSelector(paramRef.Selector)
if err != nil {
// Cannot parse label selector: configuration error
return nil, err
}
paramList, err := paramStore.List(selector)
if err != nil {
// There was a bad internal error
utilruntime.HandleError(err)
return nil, err
}
// Successfully grabbed params
params = paramList
default:
// Should be unreachable due to validation
return nil, fmt.Errorf("one of name or selector must be provided")
}
// Apply fail action for params not found case
if len(params) == 0 && paramRef.ParameterNotFoundAction != nil && *paramRef.ParameterNotFoundAction == v1alpha1.DenyAction {
return nil, errors.New("no params found for policy binding with `Deny` parameterNotFoundAction")
}
return params, nil
}
func (c *celAdmissionController) publishValidationFailureAnnotation(binding *v1alpha1.ValidatingAdmissionPolicyBinding, expressionIndex int, decision PolicyDecision, attributes admission.Attributes) {
key := "validation.policy.admission.k8s.io/validation_failure"
// Marshal to a list of failures since, in the future, we may need to support multiple failures
@@ -507,3 +602,48 @@ func (a auditAnnotationCollector) publish(policyName string, attributes admissio
}
}
}
// A workaround to fact that native types do not have TypeMeta populated, which
// is needed for CEL expressions to be able to access the value.
type wrappedParam struct {
metav1.TypeMeta
nested runtime.Object
}
func (w *wrappedParam) MarshalJSON() ([]byte, error) {
return nil, errors.New("MarshalJSON unimplemented for wrappedParam")
}
func (w *wrappedParam) UnmarshalJSON(data []byte) error {
return errors.New("UnmarshalJSON unimplemented for wrappedParam")
}
func (w *wrappedParam) ToUnstructured() interface{} {
res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(w.nested)
if err != nil {
return nil
}
metaRes, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&w.TypeMeta)
if err != nil {
return nil
}
for k, v := range metaRes {
res[k] = v
}
return res
}
func (w *wrappedParam) DeepCopyObject() runtime.Object {
return &wrappedParam{
TypeMeta: w.TypeMeta,
nested: w.nested.DeepCopyObject(),
}
}
func (w *wrappedParam) GetObjectKind() schema.ObjectKind {
return w
}

View File

@@ -40,7 +40,6 @@ import (
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
k8sscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/cache"
)
@@ -48,6 +47,7 @@ type policyController struct {
once sync.Once
context context.Context
dynamicClient dynamic.Interface
informerFactory informers.SharedInformerFactory
restMapper meta.RESTMapper
policyDefinitionsController generic.Controller[*v1alpha1.ValidatingAdmissionPolicy]
policyBindingController generic.Controller[*v1alpha1.ValidatingAdmissionPolicyBinding]
@@ -61,13 +61,10 @@ type policyController struct {
newValidator
// Lock which protects:
// - cachedPolicies
// - paramCRDControllers
// - definitionInfo
// - bindingInfos
// - definitionsToBindings
// All other fields should be assumed constant
client kubernetes.Interface
// Lock which protects
// All Below fields
// All above fields should be assumed constant
mutex sync.RWMutex
cachedPolicies []policyData
@@ -88,8 +85,6 @@ type policyController struct {
// All keys must have at least one dependent binding
// All binding names MUST exist as a key bindingInfos
definitionsToBindings map[namespacedName]sets.Set[namespacedName]
client kubernetes.Interface
}
type newValidator func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failurePolicy *v1.FailurePolicyType) Validator
@@ -98,6 +93,7 @@ func newPolicyController(
restMapper meta.RESTMapper,
client kubernetes.Interface,
dynamicClient dynamic.Interface,
informerFactory informers.SharedInformerFactory,
filterCompiler cel.FilterCompiler,
matcher Matcher,
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
@@ -128,9 +124,10 @@ func newPolicyController(
Name: "cel-policy-bindings",
},
),
restMapper: restMapper,
dynamicClient: dynamicClient,
client: client,
restMapper: restMapper,
dynamicClient: dynamicClient,
informerFactory: informerFactory,
client: client,
}
return res
}
@@ -235,7 +232,6 @@ func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string,
// Skip setting up controller for empty param type
return nil
}
// find GVR for params
// Parse param source into a GVK
@@ -262,101 +258,75 @@ func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string,
return info.configurationError
}
paramInfo := c.ensureParamInfo(paramSource, paramsGVR)
paramInfo.dependentDefinitions.Insert(nn)
return nil
}
// Ensures that there is an informer started for the given GVK to be used as a
// param
func (c *policyController) ensureParamInfo(paramSource *v1alpha1.ParamKind, mapping *meta.RESTMapping) *paramInfo {
if info, ok := c.paramsCRDControllers[*paramSource]; ok {
// If a param controller is already active for this paramsource, make
// sure it is tracking this policy's dependency upon it
info.dependentDefinitions.Insert(nn)
return info
}
// We are not watching this param. Start an informer for it.
instanceContext, instanceCancel := context.WithCancel(c.context)
var informer cache.SharedIndexInformer
// Try to see if our provided informer factory has an informer for this type.
// We assume the informer is already started, and starts all types associated
// with it.
if genericInformer, err := c.informerFactory.ForResource(mapping.Resource); err == nil {
informer = genericInformer.Informer()
// Ensure the informer is started
// Use policyController's context rather than the instance context.
// PolicyController context is expected to last until app shutdown
// This is due to behavior of informerFactory which would cause the
// informer to stop running once the context is cancelled, and
// never started again.
c.informerFactory.Start(c.context.Done())
} else {
instanceContext, instanceCancel := context.WithCancel(c.context)
var informer cache.SharedIndexInformer
// Informer Factory is optional
if c.client != nil {
// Create temporary informer factory
// Cannot use the k8s shared informer factory for dynamic params informer.
// Would leak unnecessary informers when we are done since we would have to
// call informerFactory.Start() with a longer-lived stopCh than necessary.
// SharedInformerFactory does not support temporary usage.
dynamicFactory := informers.NewSharedInformerFactory(c.client, 10*time.Minute)
// Look for a typed informer. If it does not exist
genericInformer, err := dynamicFactory.ForResource(paramsGVR.Resource)
// Ignore error. We fallback to dynamic informer if there is no
// typed informer
if err != nil {
informer = nil
} else {
informer = genericInformer.Informer()
// Set transformer on the informer to workaround inconsistency
// where typed objects have TypeMeta wiped out but dynamic
// objects keep kind/apiVersion fields
informer.SetTransform(func(i interface{}) (interface{}, error) {
// Ensure param is populated with its GVK for consistency
// (CRD dynamic informer always returns objects with kind/apiversion,
// but native types do not include populated TypeMeta.
if param := i.(runtime.Object); param != nil {
if param.GetObjectKind().GroupVersionKind().Empty() {
// https://github.com/kubernetes/client-go/issues/413#issue-324586398
gvks, _, _ := k8sscheme.Scheme.ObjectKinds(param)
for _, gvk := range gvks {
if len(gvk.Kind) == 0 {
continue
}
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
continue
}
param.GetObjectKind().SetGroupVersionKind(gvk)
break
}
}
}
return i, nil
})
}
}
if informer == nil {
// Dynamic JSON informer fallback.
// Cannot use shared dynamic informer since it would be impossible
// to clean CRD informers properly with multiple dependents
// (cannot start ahead of time, and cannot track dependencies via stopCh)
informer = dynamicinformer.NewFilteredDynamicInformer(
c.dynamicClient,
paramsGVR.Resource,
corev1.NamespaceAll,
// Use same interval as is used for k8s typed sharedInformerFactory
// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
10*time.Minute,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
nil,
).Informer()
}
controller := generic.NewController(
generic.NewInformer[runtime.Object](informer),
c.reconcileParams,
generic.ControllerOptions{
Workers: 1,
Name: paramSource.String() + "-controller",
},
)
c.paramsCRDControllers[*paramSource] = &paramInfo{
controller: controller,
stop: instanceCancel,
dependentDefinitions: sets.New(nn),
}
go controller.Run(instanceContext)
// Dynamic JSON informer fallback.
// Cannot use shared dynamic informer since it would be impossible
// to clean CRD informers properly with multiple dependents
// (cannot start ahead of time, and cannot track dependencies via stopCh)
informer = dynamicinformer.NewFilteredDynamicInformer(
c.dynamicClient,
mapping.Resource,
corev1.NamespaceAll,
// Use same interval as is used for k8s typed sharedInformerFactory
// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
10*time.Minute,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
nil,
).Informer()
go informer.Run(instanceContext.Done())
}
return nil
controller := generic.NewController(
generic.NewInformer[runtime.Object](informer),
c.reconcileParams,
generic.ControllerOptions{
Workers: 1,
Name: paramSource.String() + "-controller",
},
)
ret := &paramInfo{
controller: controller,
stop: instanceCancel,
scope: mapping.Scope,
dependentDefinitions: sets.New[namespacedName](),
}
c.paramsCRDControllers[*paramSource] = ret
go controller.Run(instanceContext)
return ret
}
func (c *policyController) reconcilePolicyBinding(namespace, name string, binding *v1alpha1.ValidatingAdmissionPolicyBinding) error {
@@ -494,17 +464,17 @@ func (c *policyController) latestPolicyData() []policyData {
bindingInfos = append(bindingInfos, *bindingInfo)
}
var paramController generic.Controller[runtime.Object]
var pInfo paramInfo
if paramKind := definitionInfo.lastReconciledValue.Spec.ParamKind; paramKind != nil {
if info, ok := c.paramsCRDControllers[*paramKind]; ok {
paramController = info.controller
pInfo = *info
}
}
res = append(res, policyData{
definitionInfo: *definitionInfo,
paramController: paramController,
bindings: bindingInfos,
definitionInfo: *definitionInfo,
paramInfo: pInfo,
bindings: bindingInfos,
})
}

View File

@@ -18,11 +18,18 @@ limitations under the License.
package v1alpha1
import (
v1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
// ParamRefApplyConfiguration represents an declarative configuration of the ParamRef type for use
// with apply.
type ParamRefApplyConfiguration struct {
Name *string `json:"name,omitempty"`
Namespace *string `json:"namespace,omitempty"`
Name *string `json:"name,omitempty"`
Namespace *string `json:"namespace,omitempty"`
Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"`
ParameterNotFoundAction *v1alpha1.ParameterNotFoundActionType `json:"parameterNotFoundAction,omitempty"`
}
// ParamRefApplyConfiguration constructs an declarative configuration of the ParamRef type for use with
@@ -46,3 +53,19 @@ func (b *ParamRefApplyConfiguration) WithNamespace(value string) *ParamRefApplyC
b.Namespace = &value
return b
}
// WithSelector sets the Selector field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Selector field is set to the value of the last call.
func (b *ParamRefApplyConfiguration) WithSelector(value *v1.LabelSelectorApplyConfiguration) *ParamRefApplyConfiguration {
b.Selector = value
return b
}
// WithParameterNotFoundAction sets the ParameterNotFoundAction field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ParameterNotFoundAction field is set to the value of the last call.
func (b *ParamRefApplyConfiguration) WithParameterNotFoundAction(value v1alpha1.ParameterNotFoundActionType) *ParamRefApplyConfiguration {
b.ParameterNotFoundAction = &value
return b
}

View File

@@ -366,6 +366,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: namespace
type:
scalar: string
- name: parameterNotFoundAction
type:
scalar: string
- name: selector
type:
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector
elementRelationship: atomic
- name: io.k8s.api.admissionregistration.v1alpha1.TypeChecking
map:

View File

@@ -2026,7 +2026,7 @@ func Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated(t *testing
return false, nil
}
if !strings.Contains(err.Error(), "failed to configure binding: test not found") {
if !strings.Contains(err.Error(), "failed to configure binding: no params found for policy binding with `Deny` parameterNotFoundAction") {
return false, err
}
@@ -2049,7 +2049,7 @@ func Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated(t *testing
_, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{})
// cache not synced with new object yet, try again
if strings.Contains(err.Error(), "failed to configure binding: test not found") {
if strings.Contains(err.Error(), "failed to configure binding: no params found for policy binding with `Deny` parameterNotFoundAction") {
return false, nil
}