ValidatingAdmissionPolicy: Variable Composition (#118642)

* [API REVIEW] Variable Composition

* lazy map.

* variable composition implementation.

* check variables during VAP validation.

* generated: ./hack/update-vendor.sh

* generated: UPDATE_COMPATIBILITY_FIXTURE_DATA

(cd staging/src/k8s.io/api/ && env UPDATE_COMPATIBILITY_FIXTURE_DATA=true go test)

* cost calucation.

* tests for cost calculations.

* e2e test for variables.

* fix doc for Validation.Expression.

* generated: ./hack/update-codegen.sh

* fix missing utilruntime import.

* generated: ./hack/update-openapi-spec.sh
This commit is contained in:
Jiahui Feng
2023-07-13 17:13:28 -07:00
committed by GitHub
parent 1e21da87b8
commit b635f2a401
36 changed files with 2105 additions and 131 deletions

View File

@@ -770,6 +770,19 @@
}, },
"type": "array", "type": "array",
"x-kubernetes-list-type": "atomic" "x-kubernetes-list-type": "atomic"
},
"variables": {
"description": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.",
"items": {
"$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.Variable"
},
"type": "array",
"x-kubernetes-list-map-keys": [
"name"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
} }
}, },
"type": "object" "type": "object"
@@ -804,7 +817,7 @@
"description": "Validation specifies the CEL expression which is used to apply the validation.", "description": "Validation specifies the CEL expression which is used to apply the validation.",
"properties": { "properties": {
"expression": { "expression": {
"description": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", "description": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.",
"type": "string" "type": "string"
}, },
"message": { "message": {
@@ -825,6 +838,24 @@
], ],
"type": "object" "type": "object"
}, },
"io.k8s.api.admissionregistration.v1alpha1.Variable": {
"description": "Variable is the definition of a variable that is used for composition.",
"properties": {
"expression": {
"description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.",
"type": "string"
},
"name": {
"description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`",
"type": "string"
}
},
"required": [
"name",
"expression"
],
"type": "object"
},
"io.k8s.api.apiserverinternal.v1alpha1.ServerStorageVersion": { "io.k8s.api.apiserverinternal.v1alpha1.ServerStorageVersion": {
"description": "An API server instance reports the version it can decode and the version it encodes objects to when persisting objects in the backend.", "description": "An API server instance reports the version it can decode and the version it encodes objects to when persisting objects in the backend.",
"properties": { "properties": {

View File

@@ -490,6 +490,24 @@
}, },
"type": "array", "type": "array",
"x-kubernetes-list-type": "atomic" "x-kubernetes-list-type": "atomic"
},
"variables": {
"description": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1alpha1.Variable"
}
],
"default": {}
},
"type": "array",
"x-kubernetes-list-map-keys": [
"name"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
} }
}, },
"type": "object" "type": "object"
@@ -534,7 +552,7 @@
"properties": { "properties": {
"expression": { "expression": {
"default": "", "default": "",
"description": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", "description": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.",
"type": "string" "type": "string"
}, },
"message": { "message": {
@@ -555,6 +573,26 @@
], ],
"type": "object" "type": "object"
}, },
"io.k8s.api.admissionregistration.v1alpha1.Variable": {
"description": "Variable is the definition of a variable that is used for composition.",
"properties": {
"expression": {
"default": "",
"description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.",
"type": "string"
},
"name": {
"default": "",
"description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`",
"type": "string"
}
},
"required": [
"name",
"expression"
],
"type": "object"
},
"io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": {
"description": "APIResource specifies the name of a resource and whether it is namespaced.", "description": "APIResource specifies the name of a resource and whether it is namespaced.",
"properties": { "properties": {

View File

@@ -247,6 +247,16 @@ type ValidatingAdmissionPolicySpec struct {
// A maximum of 20 auditAnnotation are allowed per ValidatingAdmissionPolicy. // A maximum of 20 auditAnnotation are allowed per ValidatingAdmissionPolicy.
// +optional // +optional
AuditAnnotations []AuditAnnotation AuditAnnotations []AuditAnnotation
// Variables contain definitions of variables that can be used in composition of other expressions.
// Each variable is defined as a named CEL expression.
// The variables defined here will be available under `variables` in other expressions of the policy
// except MatchConditions because MatchConditions are evaluated before the rest of the policy.
//
// The expression of a variable can refer to other variables defined earlier in the list but not those after.
// Thus, Variables must be sorted by the order of first appearance and acyclic.
// +optional
Variables []Variable
} }
// ParamKind is a tuple of Group Kind and Version. // ParamKind is a tuple of Group Kind and Version.
@@ -271,6 +281,8 @@ type Validation struct {
//'oldObject' - The existing object. The value is null for CREATE requests. //'oldObject' - The existing object. The value is null for CREATE requests.
//'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). //'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)).
//'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. //'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind.
//'variables' - Map of composited variables, from its name to its lazily evaluated value.
// For example, a variable named 'foo' can be accessed as 'variables.foo'
// - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
// - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
@@ -334,6 +346,19 @@ type Validation struct {
MessageExpression string MessageExpression string
} }
// Variable is the definition of a variable that is used for composition. A variable is defined as a named expression.
// +structType=atomic
type Variable struct {
// Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables.
// The variable can be accessed in other expressions through `variables`
// For example, if name is "foo", the variable will be available as `variables.foo`
Name string
// Expression is the expression that will be evaluated as the value of the variable.
// The CEL expression has access to the same identifiers as the CEL expressions in Validation.
Expression string
}
// AuditAnnotation describes how to produce an audit annotation for an API request. // AuditAnnotation describes how to produce an audit annotation for an API request.
type AuditAnnotation struct { type AuditAnnotation struct {
// key specifies the audit annotation key. The audit annotation keys of // key specifies the audit annotation key. The audit annotation keys of
@@ -1065,6 +1090,8 @@ type MatchCondition struct {
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
// 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the // 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
// request resource. // request resource.
// 'variables' - Map of composited variables, from its name to its lazily evaluated value.
// For example, a variable named 'foo' can be access as 'variables.foo'
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
// //
// Required. // Required.

View File

@@ -199,6 +199,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddGeneratedConversionFunc((*v1alpha1.Variable)(nil), (*admissionregistration.Variable)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_Variable_To_admissionregistration_Variable(a.(*v1alpha1.Variable), b.(*admissionregistration.Variable), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*admissionregistration.Variable)(nil), (*v1alpha1.Variable)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_admissionregistration_Variable_To_v1alpha1_Variable(a.(*admissionregistration.Variable), b.(*v1alpha1.Variable), scope)
}); err != nil {
return err
}
return nil return nil
} }
@@ -625,6 +635,7 @@ func autoConvert_v1alpha1_ValidatingAdmissionPolicySpec_To_admissionregistration
out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy))
out.AuditAnnotations = *(*[]admissionregistration.AuditAnnotation)(unsafe.Pointer(&in.AuditAnnotations)) out.AuditAnnotations = *(*[]admissionregistration.AuditAnnotation)(unsafe.Pointer(&in.AuditAnnotations))
out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions)) out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
out.Variables = *(*[]admissionregistration.Variable)(unsafe.Pointer(&in.Variables))
return nil return nil
} }
@@ -648,6 +659,7 @@ func autoConvert_admissionregistration_ValidatingAdmissionPolicySpec_To_v1alpha1
out.MatchConditions = *(*[]v1alpha1.MatchCondition)(unsafe.Pointer(&in.MatchConditions)) out.MatchConditions = *(*[]v1alpha1.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
out.FailurePolicy = (*v1alpha1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) out.FailurePolicy = (*v1alpha1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy))
out.AuditAnnotations = *(*[]v1alpha1.AuditAnnotation)(unsafe.Pointer(&in.AuditAnnotations)) out.AuditAnnotations = *(*[]v1alpha1.AuditAnnotation)(unsafe.Pointer(&in.AuditAnnotations))
out.Variables = *(*[]v1alpha1.Variable)(unsafe.Pointer(&in.Variables))
return nil return nil
} }
@@ -705,3 +717,25 @@ func autoConvert_admissionregistration_Validation_To_v1alpha1_Validation(in *adm
func Convert_admissionregistration_Validation_To_v1alpha1_Validation(in *admissionregistration.Validation, out *v1alpha1.Validation, s conversion.Scope) error { func Convert_admissionregistration_Validation_To_v1alpha1_Validation(in *admissionregistration.Validation, out *v1alpha1.Validation, s conversion.Scope) error {
return autoConvert_admissionregistration_Validation_To_v1alpha1_Validation(in, out, s) return autoConvert_admissionregistration_Validation_To_v1alpha1_Validation(in, out, s)
} }
func autoConvert_v1alpha1_Variable_To_admissionregistration_Variable(in *v1alpha1.Variable, out *admissionregistration.Variable, s conversion.Scope) error {
out.Name = in.Name
out.Expression = in.Expression
return nil
}
// Convert_v1alpha1_Variable_To_admissionregistration_Variable is an autogenerated conversion function.
func Convert_v1alpha1_Variable_To_admissionregistration_Variable(in *v1alpha1.Variable, out *admissionregistration.Variable, s conversion.Scope) error {
return autoConvert_v1alpha1_Variable_To_admissionregistration_Variable(in, out, s)
}
func autoConvert_admissionregistration_Variable_To_v1alpha1_Variable(in *admissionregistration.Variable, out *v1alpha1.Variable, s conversion.Scope) error {
out.Name = in.Name
out.Expression = in.Expression
return nil
}
// Convert_admissionregistration_Variable_To_v1alpha1_Variable is an autogenerated conversion function.
func Convert_admissionregistration_Variable_To_v1alpha1_Variable(in *admissionregistration.Variable, out *v1alpha1.Variable, s conversion.Scope) error {
return autoConvert_admissionregistration_Variable_To_v1alpha1_Variable(in, out, s)
}

View File

@@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/api/validation/path"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
utilvalidation "k8s.io/apimachinery/pkg/util/validation" utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
@@ -723,6 +724,14 @@ func validateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmiss
func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, opts validationOptions, fldPath *field.Path) field.ErrorList { func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, opts validationOptions, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList var allErrors field.ErrorList
var compiler plugincel.Compiler // composition compiler is stateful, create one lazily per policy
getCompiler := func() plugincel.Compiler {
if compiler == nil {
needsComposition := len(spec.Variables) > 0
compiler = createCompiler(needsComposition)
}
return compiler
}
if spec.FailurePolicy == nil { if spec.FailurePolicy == nil {
allErrors = append(allErrors, field.Required(fldPath.Child("failurePolicy"), "")) allErrors = append(allErrors, field.Required(fldPath.Child("failurePolicy"), ""))
} else if !supportedFailurePolicies.Has(string(*spec.FailurePolicy)) { } else if !supportedFailurePolicies.Has(string(*spec.FailurePolicy)) {
@@ -744,12 +753,17 @@ func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissi
if !opts.ignoreMatchConditions { if !opts.ignoreMatchConditions {
allErrors = append(allErrors, validateMatchConditions(spec.MatchConditions, opts, fldPath.Child("matchConditions"))...) allErrors = append(allErrors, validateMatchConditions(spec.MatchConditions, opts, fldPath.Child("matchConditions"))...)
} }
if len(spec.Variables) > 0 {
for i, variable := range spec.Variables {
allErrors = append(allErrors, validateVariable(getCompiler(), &variable, spec.ParamKind, opts, fldPath.Child("variables").Index(i))...)
}
}
if len(spec.Validations) == 0 && len(spec.AuditAnnotations) == 0 { if len(spec.Validations) == 0 && len(spec.AuditAnnotations) == 0 {
allErrors = append(allErrors, field.Required(fldPath.Child("validations"), "validations or auditAnnotations must contain at least one item")) allErrors = append(allErrors, field.Required(fldPath.Child("validations"), "validations or auditAnnotations must contain at least one item"))
allErrors = append(allErrors, field.Required(fldPath.Child("auditAnnotations"), "validations or auditAnnotations must contain at least one item")) allErrors = append(allErrors, field.Required(fldPath.Child("auditAnnotations"), "validations or auditAnnotations must contain at least one item"))
} else { } else {
for i, validation := range spec.Validations { for i, validation := range spec.Validations {
allErrors = append(allErrors, validateValidation(&validation, spec.ParamKind, opts, fldPath.Child("validations").Index(i))...) allErrors = append(allErrors, validateValidation(getCompiler(), &validation, spec.ParamKind, opts, fldPath.Child("validations").Index(i))...)
} }
if spec.AuditAnnotations != nil { if spec.AuditAnnotations != nil {
keys := sets.NewString() keys := sets.NewString()
@@ -757,7 +771,7 @@ func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissi
allErrors = append(allErrors, field.Invalid(fldPath.Child("auditAnnotations"), spec.AuditAnnotations, fmt.Sprintf("must not have more than %d auditAnnotations", maxAuditAnnotations))) allErrors = append(allErrors, field.Invalid(fldPath.Child("auditAnnotations"), spec.AuditAnnotations, fmt.Sprintf("must not have more than %d auditAnnotations", maxAuditAnnotations)))
} }
for i, auditAnnotation := range spec.AuditAnnotations { for i, auditAnnotation := range spec.AuditAnnotations {
allErrors = append(allErrors, validateAuditAnnotation(meta, &auditAnnotation, spec.ParamKind, opts, fldPath.Child("auditAnnotations").Index(i))...) allErrors = append(allErrors, validateAuditAnnotation(getCompiler(), meta, &auditAnnotation, spec.ParamKind, opts, fldPath.Child("auditAnnotations").Index(i))...)
if keys.Has(auditAnnotation.Key) { if keys.Has(auditAnnotation.Key) {
allErrors = append(allErrors, field.Duplicate(fldPath.Child("auditAnnotations").Index(i).Child("key"), auditAnnotation.Key)) allErrors = append(allErrors, field.Duplicate(fldPath.Child("auditAnnotations").Index(i).Child("key"), auditAnnotation.Key))
} }
@@ -935,7 +949,42 @@ func validateMatchCondition(v *admissionregistration.MatchCondition, opts valida
return allErrors return allErrors
} }
func validateValidation(v *admissionregistration.Validation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { func validateVariable(compiler plugincel.Compiler, v *admissionregistration.Variable, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList
if len(v.Name) == 0 || strings.TrimSpace(v.Name) == "" {
allErrors = append(allErrors, field.Required(fldPath.Child("name"), "name is not specified"))
} else {
if !isCELIdentifier(v.Name) {
allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), v.Name, "name is not a valid CEL identifier"))
}
}
if len(v.Expression) == 0 || strings.TrimSpace(v.Expression) == "" {
allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified"))
} else {
if compiler, ok := compiler.(*plugincel.CompositedCompiler); ok {
envType := environment.NewExpressions
if opts.preexistingExpressions.validationExpressions.Has(v.Expression) {
envType = environment.StoredExpressions
}
variable := &validatingadmissionpolicy.Variable{
Name: v.Name,
Expression: v.Expression,
}
result := compiler.CompileAndStoreVariable(variable, plugincel.OptionalVariableDeclarations{
HasParams: paramKind != nil,
HasAuthorizer: true,
}, envType)
if result.Error != nil {
allErrors = append(allErrors, convertCELErrorToValidationError(fldPath.Child("expression"), variable, result.Error))
}
} else {
allErrors = append(allErrors, field.InternalError(fldPath, fmt.Errorf("variable composition is not allowed")))
}
}
return allErrors
}
func validateValidation(compiler plugincel.Compiler, v *admissionregistration.Validation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList var allErrors field.ErrorList
trimmedExpression := strings.TrimSpace(v.Expression) trimmedExpression := strings.TrimSpace(v.Expression)
trimmedMsg := strings.TrimSpace(v.Message) trimmedMsg := strings.TrimSpace(v.Message)
@@ -943,14 +992,14 @@ func validateValidation(v *admissionregistration.Validation, paramKind *admissio
if len(trimmedExpression) == 0 { if len(trimmedExpression) == 0 {
allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified")) allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified"))
} else { } else {
allErrors = append(allErrors, validateValidationExpression(v.Expression, paramKind != nil, opts, fldPath.Child("expression"))...) allErrors = append(allErrors, validateValidationExpression(compiler, v.Expression, paramKind != nil, opts, fldPath.Child("expression"))...)
} }
if len(v.MessageExpression) > 0 && len(trimmedMessageExpression) == 0 { if len(v.MessageExpression) > 0 && len(trimmedMessageExpression) == 0 {
allErrors = append(allErrors, field.Invalid(fldPath.Child("messageExpression"), v.MessageExpression, "must be non-empty if specified")) allErrors = append(allErrors, field.Invalid(fldPath.Child("messageExpression"), v.MessageExpression, "must be non-empty if specified"))
} else if len(trimmedMessageExpression) != 0 { } else if len(trimmedMessageExpression) != 0 {
// use v.MessageExpression instead of trimmedMessageExpression so that // use v.MessageExpression instead of trimmedMessageExpression so that
// the compiler output shows the correct column. // the compiler output shows the correct column.
allErrors = append(allErrors, validateMessageExpression(v.MessageExpression, opts, fldPath.Child("messageExpression"))...) allErrors = append(allErrors, validateMessageExpression(compiler, v.MessageExpression, opts, fldPath.Child("messageExpression"))...)
} }
if len(v.Message) > 0 && len(trimmedMsg) == 0 { if len(v.Message) > 0 && len(trimmedMsg) == 0 {
allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must be non-empty if specified")) allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must be non-empty if specified"))
@@ -965,31 +1014,35 @@ func validateValidation(v *admissionregistration.Validation, paramKind *admissio
return allErrors return allErrors
} }
func validateCELCondition(expression plugincel.ExpressionAccessor, variables plugincel.OptionalVariableDeclarations, envType environment.Type, fldPath *field.Path) field.ErrorList { func validateCELCondition(compiler plugincel.Compiler, expression plugincel.ExpressionAccessor, variables plugincel.OptionalVariableDeclarations, envType environment.Type, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList var allErrors field.ErrorList
result := compiler.CompileCELExpression(expression, variables, envType) result := compiler.CompileCELExpression(expression, variables, envType)
if result.Error != nil { if result.Error != nil {
switch result.Error.Type { allErrors = append(allErrors, convertCELErrorToValidationError(fldPath, expression, result.Error))
case cel.ErrorTypeRequired:
allErrors = append(allErrors, field.Required(fldPath, result.Error.Detail))
case cel.ErrorTypeInvalid:
allErrors = append(allErrors, field.Invalid(fldPath, expression.GetExpression(), result.Error.Detail))
case cel.ErrorTypeInternal:
allErrors = append(allErrors, field.InternalError(fldPath, result.Error))
default:
allErrors = append(allErrors, field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", result.Error)))
}
} }
return allErrors return allErrors
} }
func validateValidationExpression(expression string, hasParams bool, opts validationOptions, fldPath *field.Path) field.ErrorList { func convertCELErrorToValidationError(fldPath *field.Path, expression plugincel.ExpressionAccessor, err error) *field.Error {
if celErr, ok := err.(*cel.Error); ok {
switch celErr.Type {
case cel.ErrorTypeRequired:
return field.Required(fldPath, celErr.Detail)
case cel.ErrorTypeInvalid:
return field.Invalid(fldPath, expression.GetExpression(), celErr.Detail)
case cel.ErrorTypeInternal:
return field.InternalError(fldPath, celErr)
}
}
return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err))
}
func validateValidationExpression(compiler plugincel.Compiler, expression string, hasParams bool, opts validationOptions, fldPath *field.Path) field.ErrorList {
envType := environment.NewExpressions envType := environment.NewExpressions
if opts.preexistingExpressions.validationExpressions.Has(expression) { if opts.preexistingExpressions.validationExpressions.Has(expression) {
envType = environment.StoredExpressions envType = environment.StoredExpressions
} }
return validateCELCondition(&validatingadmissionpolicy.ValidationCondition{ return validateCELCondition(compiler, &validatingadmissionpolicy.ValidationCondition{
Expression: expression, Expression: expression,
}, plugincel.OptionalVariableDeclarations{ }, plugincel.OptionalVariableDeclarations{
HasParams: hasParams, HasParams: hasParams,
@@ -1002,7 +1055,7 @@ func validateMatchConditionsExpression(expression string, opts validationOptions
if opts.preexistingExpressions.matchConditionExpressions.Has(expression) { if opts.preexistingExpressions.matchConditionExpressions.Has(expression) {
envType = environment.StoredExpressions envType = environment.StoredExpressions
} }
return validateCELCondition(&matchconditions.MatchCondition{ return validateCELCondition(statelessCELCompiler, &matchconditions.MatchCondition{
Expression: expression, Expression: expression,
}, plugincel.OptionalVariableDeclarations{ }, plugincel.OptionalVariableDeclarations{
HasParams: opts.allowParamsInMatchConditions, HasParams: opts.allowParamsInMatchConditions,
@@ -1010,12 +1063,12 @@ func validateMatchConditionsExpression(expression string, opts validationOptions
}, envType, fldPath) }, envType, fldPath)
} }
func validateMessageExpression(expression string, opts validationOptions, fldPath *field.Path) field.ErrorList { func validateMessageExpression(compiler plugincel.Compiler, expression string, opts validationOptions, fldPath *field.Path) field.ErrorList {
envType := environment.NewExpressions envType := environment.NewExpressions
if opts.preexistingExpressions.validationMessageExpressions.Has(expression) { if opts.preexistingExpressions.validationMessageExpressions.Has(expression) {
envType = environment.StoredExpressions envType = environment.StoredExpressions
} }
return validateCELCondition(&validatingadmissionpolicy.MessageExpressionCondition{ return validateCELCondition(compiler, &validatingadmissionpolicy.MessageExpressionCondition{
MessageExpression: expression, MessageExpression: expression,
}, plugincel.OptionalVariableDeclarations{ }, plugincel.OptionalVariableDeclarations{
HasParams: opts.allowParamsInMatchConditions, HasParams: opts.allowParamsInMatchConditions,
@@ -1023,7 +1076,7 @@ func validateMessageExpression(expression string, opts validationOptions, fldPat
}, envType, fldPath) }, envType, fldPath)
} }
func validateAuditAnnotation(meta metav1.ObjectMeta, v *admissionregistration.AuditAnnotation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { func validateAuditAnnotation(compiler plugincel.Compiler, meta metav1.ObjectMeta, v *admissionregistration.AuditAnnotation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList var allErrors field.ErrorList
if len(meta.GetName()) != 0 { if len(meta.GetName()) != 0 {
name := meta.GetName() name := meta.GetName()
@@ -1166,4 +1219,38 @@ func validateFieldRef(fieldRef string, fldPath *field.Path) field.ErrorList {
return nil return nil
} }
var compiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) // statelessCELCompiler does not support variable composition (and thus is stateless). It should be used when
// variable composition is not allowed, for example, when validating MatchConditions.
var statelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
func createCompiler(allowComposition bool) plugincel.Compiler {
if !allowComposition {
return statelessCELCompiler
}
compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
if err != nil {
// should never happen, but cannot panic either.
utilruntime.HandleError(err)
return plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
}
return compiler
}
var celIdentRegex = regexp.MustCompile("^[_a-zA-Z][_a-zA-Z0-9]*$")
var celReserved = sets.NewString("true", "false", "null", "in",
"as", "break", "const", "continue", "else",
"for", "function", "if", "import", "let",
"loop", "package", "namespace", "return",
"var", "void", "while")
func isCELIdentifier(name string) bool {
// IDENT ::= [_a-zA-Z][_a-zA-Z0-9]* - RESERVED
// BOOL_LIT ::= "true" | "false"
// NULL_LIT ::= "null"
// RESERVED ::= BOOL_LIT | NULL_LIT | "in"
// | "as" | "break" | "const" | "continue" | "else"
// | "for" | "function" | "if" | "import" | "let"
// | "loop" | "package" | "namespace" | "return"
// | "var" | "void" | "while"
return celIdentRegex.MatchString(name) && !celReserved.Has(name)
}

View File

@@ -2864,7 +2864,128 @@ func TestValidateValidatingAdmissionPolicy(t *testing.T) {
}, },
}, },
expectedError: `undeclared reference to 'params'`, expectedError: `undeclared reference to 'params'`,
}} }, {
name: "variable composition empty name",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
Variables: []admissionregistration.Variable{
{
Name: " ",
Expression: "true",
},
},
Validations: []admissionregistration.Validation{
{
Expression: "true",
},
},
},
},
expectedError: `spec.variables[0].name: Required value: name is not specified`,
}, {
name: "variable composition name is not a valid identifier",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
Variables: []admissionregistration.Variable{
{
Name: "4ever",
Expression: "true",
},
},
Validations: []admissionregistration.Validation{
{
Expression: "true",
},
},
},
},
expectedError: `spec.variables[0].name: Invalid value: "4ever": name is not a valid CEL identifier`,
}, {
name: "variable composition cannot compile",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
Variables: []admissionregistration.Variable{
{
Name: "foo",
Expression: "114 + '514'", // compile error: type confusion
},
},
Validations: []admissionregistration.Validation{
{
Expression: "true",
},
},
},
},
expectedError: `spec.variables[0].expression: Invalid value: "114 + '514'": compilation failed: ERROR: <input>:1:5: found no matching overload for '_+_' applied to '(int, string)`,
}, {
name: "validation referred to non-existing variable",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
Variables: []admissionregistration.Variable{
{
Name: "foo",
Expression: "1 + 1",
},
{
Name: "bar",
Expression: "variables.foo + 1",
},
},
Validations: []admissionregistration.Validation{
{
Expression: "variables.foo > 1", // correct
},
{
Expression: "variables.replicas == 2", // replicas undefined
},
},
},
},
expectedError: `spec.validations[1].expression: Invalid value: "variables.replicas == 2": compilation failed: ERROR: <input>:1:10: undefined field 'replicas'`,
}, {
name: "variables wrong order",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
Variables: []admissionregistration.Variable{
{
Name: "correct",
Expression: "object",
},
{
Name: "bar", // should go below foo
Expression: "variables.foo + 1",
},
{
Name: "foo",
Expression: "1 + 1",
},
},
Validations: []admissionregistration.Validation{
{
Expression: "variables.foo > 1", // correct
},
},
},
},
expectedError: `spec.variables[1].expression: Invalid value: "variables.foo + 1": compilation failed: ERROR: <input>:1:10: undefined field 'foo'`,
},
}
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
errs := ValidateValidatingAdmissionPolicy(test.config) errs := ValidateValidatingAdmissionPolicy(test.config)
@@ -3298,9 +3419,9 @@ func TestValidateValidatingAdmissionPolicyUpdate(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
compiler = plugincel.NewCompiler(extended) statelessCELCompiler = plugincel.NewCompiler(extended)
defer func() { defer func() {
compiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) statelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
}() }()
for _, test := range tests { for _, test := range tests {

View File

@@ -595,6 +595,11 @@ func (in *ValidatingAdmissionPolicySpec) DeepCopyInto(out *ValidatingAdmissionPo
*out = make([]AuditAnnotation, len(*in)) *out = make([]AuditAnnotation, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Variables != nil {
in, out := &in.Variables, &out.Variables
*out = make([]Variable, len(*in))
copy(*out, *in)
}
return return
} }
@@ -787,6 +792,22 @@ func (in *Validation) DeepCopy() *Validation {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Variable) DeepCopyInto(out *Variable) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Variable.
func (in *Variable) DeepCopy() *Variable {
if in == nil {
return nil
}
out := new(Variable)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) { func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
*out = *in *out = *in

View File

@@ -62,6 +62,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/api/admissionregistration/v1alpha1.ValidatingAdmissionPolicySpec": schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySpec(ref), "k8s.io/api/admissionregistration/v1alpha1.ValidatingAdmissionPolicySpec": schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySpec(ref),
"k8s.io/api/admissionregistration/v1alpha1.ValidatingAdmissionPolicyStatus": schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyStatus(ref), "k8s.io/api/admissionregistration/v1alpha1.ValidatingAdmissionPolicyStatus": schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyStatus(ref),
"k8s.io/api/admissionregistration/v1alpha1.Validation": schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref), "k8s.io/api/admissionregistration/v1alpha1.Validation": schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref),
"k8s.io/api/admissionregistration/v1alpha1.Variable": schema_k8sio_api_admissionregistration_v1alpha1_Variable(ref),
"k8s.io/api/admissionregistration/v1beta1.MatchCondition": schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref), "k8s.io/api/admissionregistration/v1beta1.MatchCondition": schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref),
"k8s.io/api/admissionregistration/v1beta1.MutatingWebhook": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref), "k8s.io/api/admissionregistration/v1beta1.MutatingWebhook": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref),
"k8s.io/api/admissionregistration/v1beta1.MutatingWebhookConfiguration": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfiguration(ref), "k8s.io/api/admissionregistration/v1beta1.MutatingWebhookConfiguration": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfiguration(ref),
@@ -2661,11 +2662,35 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySp
}, },
}, },
}, },
"variables": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []interface{}{
"name",
},
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge",
},
},
SchemaProps: spec.SchemaProps{
Description: "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/api/admissionregistration/v1alpha1.Variable"),
},
},
},
},
},
}, },
}, },
}, },
Dependencies: []string{ Dependencies: []string{
"k8s.io/api/admissionregistration/v1alpha1.AuditAnnotation", "k8s.io/api/admissionregistration/v1alpha1.MatchCondition", "k8s.io/api/admissionregistration/v1alpha1.MatchResources", "k8s.io/api/admissionregistration/v1alpha1.ParamKind", "k8s.io/api/admissionregistration/v1alpha1.Validation"}, "k8s.io/api/admissionregistration/v1alpha1.AuditAnnotation", "k8s.io/api/admissionregistration/v1alpha1.MatchCondition", "k8s.io/api/admissionregistration/v1alpha1.MatchResources", "k8s.io/api/admissionregistration/v1alpha1.ParamKind", "k8s.io/api/admissionregistration/v1alpha1.Validation", "k8s.io/api/admissionregistration/v1alpha1.Variable"},
} }
} }
@@ -2728,7 +2753,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref common.Refer
Properties: map[string]spec.Schema{ Properties: map[string]spec.Schema{
"expression": { "expression": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", Description: "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.",
Default: "", Default: "",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
@@ -2762,6 +2787,36 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref common.Refer
} }
} }
func schema_k8sio_api_admissionregistration_v1alpha1_Variable(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Variable is the definition of a variable that is used for composition.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"expression": {
SchemaProps: spec.SchemaProps{
Description: "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name", "expression"},
},
},
}
}
func schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { func schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{ return common.OpenAPIDefinition{
Schema: spec.Schema{ Schema: spec.Schema{

View File

@@ -493,6 +493,34 @@ func (m *Validation) XXX_DiscardUnknown() {
var xxx_messageInfo_Validation proto.InternalMessageInfo var xxx_messageInfo_Validation proto.InternalMessageInfo
func (m *Variable) Reset() { *m = Variable{} }
func (*Variable) ProtoMessage() {}
func (*Variable) Descriptor() ([]byte, []int) {
return fileDescriptor_c3be8d256e3ae3cf, []int{16}
}
func (m *Variable) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Variable) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *Variable) XXX_Merge(src proto.Message) {
xxx_messageInfo_Variable.Merge(m, src)
}
func (m *Variable) XXX_Size() int {
return m.Size()
}
func (m *Variable) XXX_DiscardUnknown() {
xxx_messageInfo_Variable.DiscardUnknown(m)
}
var xxx_messageInfo_Variable proto.InternalMessageInfo
func init() { func init() {
proto.RegisterType((*AuditAnnotation)(nil), "k8s.io.api.admissionregistration.v1alpha1.AuditAnnotation") proto.RegisterType((*AuditAnnotation)(nil), "k8s.io.api.admissionregistration.v1alpha1.AuditAnnotation")
proto.RegisterType((*ExpressionWarning)(nil), "k8s.io.api.admissionregistration.v1alpha1.ExpressionWarning") proto.RegisterType((*ExpressionWarning)(nil), "k8s.io.api.admissionregistration.v1alpha1.ExpressionWarning")
@@ -510,6 +538,7 @@ func init() {
proto.RegisterType((*ValidatingAdmissionPolicySpec)(nil), "k8s.io.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicySpec") proto.RegisterType((*ValidatingAdmissionPolicySpec)(nil), "k8s.io.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicySpec")
proto.RegisterType((*ValidatingAdmissionPolicyStatus)(nil), "k8s.io.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyStatus") proto.RegisterType((*ValidatingAdmissionPolicyStatus)(nil), "k8s.io.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyStatus")
proto.RegisterType((*Validation)(nil), "k8s.io.api.admissionregistration.v1alpha1.Validation") proto.RegisterType((*Validation)(nil), "k8s.io.api.admissionregistration.v1alpha1.Validation")
proto.RegisterType((*Variable)(nil), "k8s.io.api.admissionregistration.v1alpha1.Variable")
} }
func init() { func init() {
@@ -517,95 +546,99 @@ func init() {
} }
var fileDescriptor_c3be8d256e3ae3cf = []byte{ var fileDescriptor_c3be8d256e3ae3cf = []byte{
// 1407 bytes of a gzipped FileDescriptorProto // 1457 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcb, 0x6f, 0x1b, 0x45, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcd, 0x6f, 0x1b, 0x45,
0x18, 0xcf, 0xc6, 0x4e, 0x9a, 0x8c, 0xf3, 0xb0, 0x87, 0x56, 0x75, 0x23, 0x6a, 0x47, 0xab, 0x0a, 0x1b, 0xcf, 0xc6, 0x6e, 0x12, 0x8f, 0xf3, 0x61, 0xcf, 0xdb, 0xaa, 0x6e, 0xf4, 0xd6, 0x8e, 0x56,
0x35, 0x12, 0xec, 0x92, 0xb4, 0x50, 0x40, 0x48, 0x28, 0xdb, 0x17, 0x7d, 0xa4, 0x89, 0xa6, 0x28, 0xd5, 0xab, 0x46, 0x7a, 0xd9, 0x25, 0x69, 0xa1, 0x80, 0x90, 0x50, 0xb6, 0x5f, 0xf4, 0x23, 0x4d,
0x91, 0x10, 0x95, 0x98, 0xec, 0x4e, 0xec, 0xa9, 0xbd, 0x0f, 0x76, 0xd6, 0xa1, 0x11, 0x48, 0x54, 0x34, 0x45, 0x89, 0x84, 0xa8, 0xc4, 0x78, 0x77, 0x62, 0x4f, 0xed, 0xfd, 0x60, 0x67, 0x6d, 0x1a,
0xe2, 0x02, 0x37, 0x0e, 0x5c, 0xf8, 0x5f, 0xb8, 0x70, 0xeb, 0xb1, 0xc7, 0x72, 0xc0, 0x22, 0xe6, 0x81, 0x44, 0x25, 0x2e, 0x70, 0xe3, 0xc0, 0x85, 0x2b, 0xff, 0x09, 0xb7, 0x1e, 0x7b, 0x2c, 0x07,
0xc2, 0x5f, 0x00, 0x52, 0x2e, 0xa0, 0x99, 0x9d, 0x7d, 0x3b, 0xc4, 0x2e, 0x81, 0x9b, 0xf7, 0x7b, 0x2c, 0x6a, 0x2e, 0xfc, 0x05, 0x20, 0xe5, 0x02, 0x9a, 0xd9, 0xd9, 0x4f, 0x3b, 0xc4, 0x2e, 0x81,
0xfc, 0x7e, 0xf3, 0x7d, 0xf3, 0x7d, 0x33, 0xdf, 0x18, 0xa0, 0xce, 0x3b, 0x4c, 0xa3, 0xae, 0xde, 0x9b, 0xf7, 0xf9, 0xf8, 0xfd, 0xe6, 0x79, 0xe6, 0x79, 0x66, 0x9e, 0x31, 0x40, 0x9d, 0xb7, 0x98,
0xe9, 0xed, 0x12, 0xdf, 0x21, 0x01, 0x61, 0xfa, 0x3e, 0x71, 0x2c, 0xd7, 0xd7, 0xa5, 0x02, 0x7b, 0x46, 0x5d, 0xbd, 0xd3, 0x6b, 0x12, 0xdf, 0x21, 0x01, 0x61, 0x7a, 0x9f, 0x38, 0x96, 0xeb, 0xeb,
0x54, 0xc7, 0x96, 0x4d, 0x19, 0xa3, 0xae, 0xe3, 0x93, 0x16, 0x65, 0x81, 0x8f, 0x03, 0xea, 0x3a, 0x52, 0x81, 0x3d, 0xaa, 0x63, 0xcb, 0xa6, 0x8c, 0x51, 0xd7, 0xf1, 0x49, 0x8b, 0xb2, 0xc0, 0xc7,
0xfa, 0xfe, 0x2a, 0xee, 0x7a, 0x6d, 0xbc, 0xaa, 0xb7, 0x88, 0x43, 0x7c, 0x1c, 0x10, 0x4b, 0xf3, 0x01, 0x75, 0x1d, 0xbd, 0xbf, 0x81, 0xbb, 0x5e, 0x1b, 0x6f, 0xe8, 0x2d, 0xe2, 0x10, 0x1f, 0x07,
0x7c, 0x37, 0x70, 0xe1, 0x4a, 0xe8, 0xaa, 0x61, 0x8f, 0x6a, 0x43, 0x5d, 0xb5, 0xc8, 0x75, 0xe9, 0xc4, 0xd2, 0x3c, 0xdf, 0x0d, 0x5c, 0xb8, 0x1e, 0xba, 0x6a, 0xd8, 0xa3, 0xda, 0x58, 0x57, 0x2d,
0x8d, 0x16, 0x0d, 0xda, 0xbd, 0x5d, 0xcd, 0x74, 0x6d, 0xbd, 0xe5, 0xb6, 0x5c, 0x5d, 0x20, 0xec, 0x72, 0x5d, 0x7d, 0xad, 0x45, 0x83, 0x76, 0xaf, 0xa9, 0x99, 0xae, 0xad, 0xb7, 0xdc, 0x96, 0xab,
0xf6, 0xf6, 0xc4, 0x97, 0xf8, 0x10, 0xbf, 0x42, 0xe4, 0xa5, 0x2b, 0x23, 0x2c, 0x2a, 0xbf, 0x9c, 0x0b, 0x84, 0x66, 0xef, 0x40, 0x7c, 0x89, 0x0f, 0xf1, 0x2b, 0x44, 0x5e, 0xbd, 0x32, 0xc1, 0xa2,
0xa5, 0xab, 0x89, 0x93, 0x8d, 0xcd, 0x36, 0x75, 0x88, 0x7f, 0xa0, 0x7b, 0x9d, 0x16, 0x17, 0x30, 0xf2, 0xcb, 0x59, 0xbd, 0x9a, 0x38, 0xd9, 0xd8, 0x6c, 0x53, 0x87, 0xf8, 0x87, 0xba, 0xd7, 0x69,
0xdd, 0x26, 0x01, 0x1e, 0xe6, 0xa5, 0x1f, 0xe7, 0xe5, 0xf7, 0x9c, 0x80, 0xda, 0xa4, 0xe0, 0xf0, 0x71, 0x01, 0xd3, 0x6d, 0x12, 0xe0, 0x71, 0x5e, 0xfa, 0x71, 0x5e, 0x7e, 0xcf, 0x09, 0xa8, 0x4d,
0xf6, 0x49, 0x0e, 0xcc, 0x6c, 0x13, 0x1b, 0xe7, 0xfd, 0x54, 0x06, 0x16, 0xd7, 0x7b, 0x16, 0x0d, 0x46, 0x1c, 0xde, 0x3c, 0xc9, 0x81, 0x99, 0x6d, 0x62, 0xe3, 0xbc, 0x9f, 0xca, 0xc0, 0xca, 0x56,
0xd6, 0x1d, 0xc7, 0x0d, 0x44, 0x10, 0xf0, 0x22, 0x28, 0x75, 0xc8, 0x41, 0x5d, 0x59, 0x56, 0x2e, 0xcf, 0xa2, 0xc1, 0x96, 0xe3, 0xb8, 0x81, 0x08, 0x02, 0x5e, 0x04, 0x85, 0x0e, 0x39, 0xac, 0x29,
0xcf, 0x1a, 0x95, 0x67, 0xfd, 0xe6, 0xc4, 0xa0, 0xdf, 0x2c, 0xdd, 0x23, 0x07, 0x88, 0xcb, 0xe1, 0x6b, 0xca, 0xe5, 0x92, 0x51, 0x7e, 0x36, 0x68, 0xcc, 0x0c, 0x07, 0x8d, 0xc2, 0x3d, 0x72, 0x88,
0x3a, 0x58, 0xdc, 0xc7, 0xdd, 0x1e, 0xb9, 0xf9, 0xc4, 0xf3, 0x89, 0x48, 0x41, 0x7d, 0x52, 0x98, 0xb8, 0x1c, 0x6e, 0x81, 0x95, 0x3e, 0xee, 0xf6, 0xc8, 0xcd, 0x27, 0x9e, 0x4f, 0x44, 0x0a, 0x6a,
0x9e, 0x97, 0xa6, 0x8b, 0xdb, 0x59, 0x35, 0xca, 0xdb, 0xab, 0x5d, 0x50, 0x4b, 0xbe, 0x76, 0xb0, 0xb3, 0xc2, 0xf4, 0xbc, 0x34, 0x5d, 0xd9, 0xcb, 0xaa, 0x51, 0xde, 0x5e, 0xed, 0x82, 0x6a, 0xf2,
0xef, 0x50, 0xa7, 0x05, 0x5f, 0x07, 0x33, 0x7b, 0x94, 0x74, 0x2d, 0x44, 0xf6, 0x24, 0x60, 0x55, 0xb5, 0x8f, 0x7d, 0x87, 0x3a, 0x2d, 0xf8, 0x7f, 0xb0, 0x70, 0x40, 0x49, 0xd7, 0x42, 0xe4, 0x40,
0x02, 0xce, 0xdc, 0x92, 0x72, 0x14, 0x5b, 0xc0, 0x15, 0x70, 0xe6, 0xf3, 0xd0, 0xb1, 0x5e, 0x12, 0x02, 0x56, 0x24, 0xe0, 0xc2, 0x2d, 0x29, 0x47, 0xb1, 0x05, 0x5c, 0x07, 0xf3, 0x9f, 0x86, 0x8e,
0xc6, 0x8b, 0xd2, 0xf8, 0x8c, 0xc4, 0x43, 0x91, 0x5e, 0xdd, 0x03, 0x0b, 0x1b, 0x38, 0x30, 0xdb, 0xb5, 0x82, 0x30, 0x5e, 0x91, 0xc6, 0xf3, 0x12, 0x0f, 0x45, 0x7a, 0xf5, 0x00, 0x2c, 0x6f, 0xe3,
0xd7, 0x5d, 0xc7, 0xa2, 0x22, 0xc2, 0x65, 0x50, 0x76, 0xb0, 0x4d, 0x64, 0x88, 0x73, 0xd2, 0xb3, 0xc0, 0x6c, 0x5f, 0x77, 0x1d, 0x8b, 0x8a, 0x08, 0xd7, 0x40, 0xd1, 0xc1, 0x36, 0x91, 0x21, 0x2e,
0xfc, 0x00, 0xdb, 0x04, 0x09, 0x0d, 0x5c, 0x03, 0x80, 0xe4, 0xe3, 0x83, 0xd2, 0x0e, 0xa4, 0x42, 0x4a, 0xcf, 0xe2, 0x03, 0x6c, 0x13, 0x24, 0x34, 0x70, 0x13, 0x00, 0x92, 0x8f, 0x0f, 0x4a, 0x3b,
0x4b, 0x59, 0xa9, 0x3f, 0x97, 0x25, 0x11, 0x22, 0xcc, 0xed, 0xf9, 0x26, 0x61, 0xf0, 0x09, 0xa8, 0x90, 0x0a, 0x2d, 0x65, 0xa5, 0xfe, 0x58, 0x94, 0x44, 0x88, 0x30, 0xb7, 0xe7, 0x9b, 0x84, 0xc1,
0x71, 0x38, 0xe6, 0x61, 0x93, 0x3c, 0x24, 0x5d, 0x62, 0x06, 0xae, 0x2f, 0x58, 0x2b, 0x6b, 0x57, 0x27, 0xa0, 0xca, 0xe1, 0x98, 0x87, 0x4d, 0xf2, 0x90, 0x74, 0x89, 0x19, 0xb8, 0xbe, 0x60, 0x2d,
0xb4, 0xa4, 0x4e, 0xe3, 0x1d, 0xd3, 0xbc, 0x4e, 0x8b, 0x0b, 0x98, 0xc6, 0x0b, 0x43, 0xdb, 0x5f, 0x6f, 0x5e, 0xd1, 0x92, 0x3a, 0x8d, 0x77, 0x4c, 0xf3, 0x3a, 0x2d, 0x2e, 0x60, 0x1a, 0x2f, 0x0c,
0xd5, 0xee, 0xe3, 0x5d, 0xd2, 0x8d, 0x5c, 0x8d, 0x73, 0x83, 0x7e, 0xb3, 0xf6, 0x20, 0x8f, 0x88, 0xad, 0xbf, 0xa1, 0xdd, 0xc7, 0x4d, 0xd2, 0x8d, 0x5c, 0x8d, 0x73, 0xc3, 0x41, 0xa3, 0xfa, 0x20,
0x8a, 0x24, 0xd0, 0x05, 0x0b, 0xee, 0xee, 0x63, 0x62, 0x06, 0x31, 0xed, 0xe4, 0xcb, 0xd3, 0xc2, 0x8f, 0x88, 0x46, 0x49, 0xa0, 0x0b, 0x96, 0xdd, 0xe6, 0x63, 0x62, 0x06, 0x31, 0xed, 0xec, 0xab,
0x41, 0xbf, 0xb9, 0xb0, 0x99, 0x81, 0x43, 0x39, 0x78, 0xf8, 0x15, 0x98, 0xf7, 0x65, 0xdc, 0xa8, 0xd3, 0xc2, 0xe1, 0xa0, 0xb1, 0xbc, 0x93, 0x81, 0x43, 0x39, 0x78, 0xf8, 0x05, 0x58, 0xf2, 0x65,
0xd7, 0x25, 0xac, 0x5e, 0x5a, 0x2e, 0x5d, 0xae, 0xac, 0x19, 0xda, 0xc8, 0xed, 0xa8, 0xf1, 0xc0, 0xdc, 0xa8, 0xd7, 0x25, 0xac, 0x56, 0x58, 0x2b, 0x5c, 0x2e, 0x6f, 0x1a, 0xda, 0xc4, 0xed, 0xa8,
0x2c, 0xee, 0xbc, 0x43, 0x83, 0xf6, 0xa6, 0x47, 0x42, 0x3d, 0x33, 0xce, 0xc9, 0xc4, 0xcf, 0xa3, 0xf1, 0xc0, 0x2c, 0xee, 0xbc, 0x4f, 0x83, 0xf6, 0x8e, 0x47, 0x42, 0x3d, 0x33, 0xce, 0xc9, 0xc4,
0x34, 0x01, 0xca, 0xf2, 0xc1, 0xef, 0x15, 0x70, 0x96, 0x3c, 0x31, 0xbb, 0x3d, 0x8b, 0x64, 0xec, 0x2f, 0xa1, 0x34, 0x01, 0xca, 0xf2, 0xc1, 0x6f, 0x15, 0x70, 0x96, 0x3c, 0x31, 0xbb, 0x3d, 0x8b,
0xea, 0xe5, 0x53, 0x5b, 0xc8, 0xab, 0x72, 0x21, 0x67, 0x6f, 0x0e, 0xe1, 0x41, 0x43, 0xd9, 0xe1, 0x64, 0xec, 0x6a, 0xc5, 0x53, 0x5b, 0xc8, 0x7f, 0xe5, 0x42, 0xce, 0xde, 0x1c, 0xc3, 0x83, 0xc6,
0x0d, 0x50, 0xb1, 0x79, 0x51, 0x6c, 0xb9, 0x5d, 0x6a, 0x1e, 0xd4, 0xcf, 0x88, 0x52, 0x52, 0x07, 0xb2, 0xc3, 0x1b, 0xa0, 0x6c, 0xf3, 0xa2, 0xd8, 0x75, 0xbb, 0xd4, 0x3c, 0xac, 0xcd, 0x8b, 0x52,
0xfd, 0x66, 0x65, 0x23, 0x11, 0x1f, 0xf5, 0x9b, 0x8b, 0xa9, 0xcf, 0x8f, 0x0e, 0x3c, 0x82, 0xd2, 0x52, 0x87, 0x83, 0x46, 0x79, 0x3b, 0x11, 0x1f, 0x0d, 0x1a, 0x2b, 0xa9, 0xcf, 0x0f, 0x0e, 0x3d,
0x6e, 0xea, 0x0b, 0x05, 0x9c, 0x3f, 0x66, 0x55, 0xf0, 0x5a, 0x92, 0x79, 0x51, 0x1a, 0x75, 0x65, 0x82, 0xd2, 0x6e, 0xea, 0x0b, 0x05, 0x9c, 0x3f, 0x66, 0x55, 0xf0, 0x5a, 0x92, 0x79, 0x51, 0x1a,
0xb9, 0x74, 0x79, 0xd6, 0xa8, 0xa5, 0x33, 0x26, 0x14, 0x28, 0x6b, 0x07, 0xbf, 0x56, 0x00, 0xf4, 0x35, 0x65, 0xad, 0x70, 0xb9, 0x64, 0x54, 0xd3, 0x19, 0x13, 0x0a, 0x94, 0xb5, 0x83, 0x5f, 0x2a,
0x0b, 0x78, 0xb2, 0x50, 0xae, 0x8d, 0x92, 0x2f, 0x6d, 0x48, 0x92, 0x96, 0x64, 0x92, 0x60, 0x51, 0x00, 0xfa, 0x23, 0x78, 0xb2, 0x50, 0xae, 0x4d, 0x92, 0x2f, 0x6d, 0x4c, 0x92, 0x56, 0x65, 0x92,
0x87, 0x86, 0xd0, 0xa9, 0x18, 0xcc, 0x6e, 0x61, 0x1f, 0xdb, 0xf7, 0xa8, 0x63, 0xf1, 0xbe, 0xc3, 0xe0, 0xa8, 0x0e, 0x8d, 0xa1, 0x53, 0x31, 0x28, 0xed, 0x62, 0x1f, 0xdb, 0xf7, 0xa8, 0x63, 0xf1,
0x1e, 0xdd, 0x26, 0xbe, 0xe8, 0x3b, 0x25, 0xdb, 0x77, 0xeb, 0x5b, 0x77, 0xa4, 0x06, 0xa5, 0xac, 0xbe, 0xc3, 0x1e, 0xdd, 0x23, 0xbe, 0xe8, 0x3b, 0x25, 0xdb, 0x77, 0x5b, 0xbb, 0x77, 0xa4, 0x06,
0x78, 0x37, 0x77, 0xa8, 0x63, 0xc9, 0x2e, 0x8d, 0xbb, 0x99, 0xe3, 0x21, 0xa1, 0x51, 0x1f, 0x81, 0xa5, 0xac, 0x78, 0x37, 0x77, 0xa8, 0x63, 0xc9, 0x2e, 0x8d, 0xbb, 0x99, 0xe3, 0x21, 0xa1, 0x51,
0x19, 0x41, 0xc1, 0x0f, 0x8e, 0x93, 0x7b, 0x5f, 0x07, 0xb3, 0x71, 0x3f, 0x49, 0xd0, 0x9a, 0x34, 0x1f, 0x81, 0x05, 0x41, 0xc1, 0x0f, 0x8e, 0x93, 0x7b, 0x5f, 0x07, 0xa5, 0xb8, 0x9f, 0x24, 0x68,
0x9b, 0x8d, 0x7b, 0x0f, 0x25, 0x36, 0xea, 0x0f, 0x0a, 0x98, 0xe3, 0x5b, 0x76, 0xbd, 0x4d, 0xcc, 0x55, 0x9a, 0x95, 0xe2, 0xde, 0x43, 0x89, 0x8d, 0xfa, 0x9d, 0x02, 0x16, 0xf9, 0x96, 0x5d, 0x6f,
0x0e, 0x3f, 0xca, 0xbe, 0x51, 0x00, 0x24, 0xf9, 0x03, 0x2e, 0xdc, 0x97, 0xca, 0xda, 0xfb, 0x63, 0x13, 0xb3, 0xc3, 0x8f, 0xb2, 0xaf, 0x14, 0x00, 0x49, 0xfe, 0x80, 0x0b, 0xf7, 0xa5, 0xbc, 0xf9,
0x14, 0x62, 0xe1, 0x94, 0x4c, 0xb2, 0x5b, 0x50, 0x31, 0x34, 0x84, 0x53, 0xfd, 0x65, 0x12, 0x5c, 0xee, 0x14, 0x85, 0x38, 0x72, 0x4a, 0x26, 0xd9, 0x1d, 0x51, 0x31, 0x34, 0x86, 0x53, 0xfd, 0x69,
0xd8, 0xc6, 0x5d, 0x6a, 0xe1, 0x80, 0x3a, 0xad, 0xf5, 0x88, 0x2e, 0x2c, 0x2b, 0xf8, 0x29, 0x98, 0x16, 0x5c, 0xd8, 0xc3, 0x5d, 0x6a, 0xe1, 0x80, 0x3a, 0xad, 0xad, 0x88, 0x2e, 0x2c, 0x2b, 0xf8,
0xe1, 0x1d, 0x6f, 0xe1, 0x00, 0xcb, 0x63, 0xe9, 0xcd, 0xd1, 0xce, 0x87, 0xf0, 0x30, 0xd8, 0x20, 0x31, 0x58, 0xe0, 0x1d, 0x6f, 0xe1, 0x00, 0xcb, 0x63, 0xe9, 0xf5, 0xc9, 0xce, 0x87, 0xf0, 0x30,
0x01, 0x4e, 0xb6, 0x27, 0x91, 0xa1, 0x18, 0x15, 0x3e, 0x06, 0x65, 0xe6, 0x11, 0x53, 0x16, 0xd5, 0xd8, 0x26, 0x01, 0x4e, 0xb6, 0x27, 0x91, 0xa1, 0x18, 0x15, 0x3e, 0x06, 0x45, 0xe6, 0x11, 0x53,
0x87, 0x63, 0xc4, 0x7e, 0xec, 0xaa, 0x1f, 0x7a, 0xc4, 0x4c, 0x36, 0x8e, 0x7f, 0x21, 0xc1, 0x01, 0x16, 0xd5, 0xfb, 0x53, 0xc4, 0x7e, 0xec, 0xaa, 0x1f, 0x7a, 0xc4, 0x4c, 0x36, 0x8e, 0x7f, 0x21,
0x7d, 0x30, 0xcd, 0x02, 0x1c, 0xf4, 0x98, 0xb8, 0x12, 0x2a, 0x6b, 0x77, 0x4f, 0x85, 0x4d, 0x20, 0xc1, 0x01, 0x7d, 0x30, 0xc7, 0x02, 0x1c, 0xf4, 0x98, 0xb8, 0x12, 0xca, 0x9b, 0x77, 0x4f, 0x85,
0x1a, 0x0b, 0x92, 0x6f, 0x3a, 0xfc, 0x46, 0x92, 0x49, 0xfd, 0x53, 0x01, 0xcb, 0xc7, 0xfa, 0x1a, 0x4d, 0x20, 0x1a, 0xcb, 0x92, 0x6f, 0x2e, 0xfc, 0x46, 0x92, 0x49, 0xfd, 0x5d, 0x01, 0x6b, 0xc7,
0xd4, 0xb1, 0x78, 0x3d, 0xfc, 0xf7, 0x69, 0xfe, 0x2c, 0x93, 0xe6, 0xcd, 0xd3, 0x08, 0x5c, 0x2e, 0xfa, 0x1a, 0xd4, 0xb1, 0x78, 0x3d, 0xfc, 0xf3, 0x69, 0xfe, 0x24, 0x93, 0xe6, 0x9d, 0xd3, 0x08,
0xfe, 0xb8, 0x6c, 0xab, 0x7f, 0x28, 0xe0, 0xd2, 0x49, 0xce, 0xf7, 0x29, 0x0b, 0xe0, 0x27, 0x85, 0x5c, 0x2e, 0xfe, 0xb8, 0x6c, 0xab, 0xbf, 0x29, 0xe0, 0xd2, 0x49, 0xce, 0xf7, 0x29, 0x0b, 0xe0,
0xe8, 0xb5, 0x11, 0x2f, 0x21, 0xca, 0xc2, 0xd8, 0xe3, 0x41, 0x20, 0x92, 0xa4, 0x22, 0xf7, 0xc0, 0x47, 0x23, 0xd1, 0x6b, 0x13, 0x5e, 0x42, 0x94, 0x85, 0xb1, 0xc7, 0x83, 0x40, 0x24, 0x49, 0x45,
0x14, 0x0d, 0x88, 0xcd, 0x8f, 0x2d, 0xde, 0x5d, 0xf7, 0x4e, 0x31, 0x74, 0x63, 0x5e, 0xf2, 0x4e, 0xee, 0x81, 0x33, 0x34, 0x20, 0x36, 0x3f, 0xb6, 0x78, 0x77, 0xdd, 0x3b, 0xc5, 0xd0, 0x8d, 0x25,
0xdd, 0xe1, 0x0c, 0x28, 0x24, 0x52, 0xbf, 0x2d, 0x9d, 0x1c, 0x38, 0xcf, 0x13, 0x3f, 0xcc, 0x3c, 0xc9, 0x7b, 0xe6, 0x0e, 0x67, 0x40, 0x21, 0x91, 0xfa, 0x75, 0xe1, 0xe4, 0xc0, 0x79, 0x9e, 0xf8,
0x21, 0x7c, 0x90, 0x1c, 0x38, 0xf1, 0x36, 0x6e, 0xc5, 0x1a, 0x94, 0xb2, 0x82, 0x8f, 0xc0, 0x8c, 0x61, 0xe6, 0x09, 0xe1, 0x83, 0xe4, 0xc0, 0x89, 0xb7, 0x71, 0x37, 0xd6, 0xa0, 0x94, 0x15, 0x7c,
0x27, 0x8f, 0xaa, 0x21, 0x37, 0xf6, 0x49, 0x11, 0x45, 0xa7, 0x9c, 0x31, 0xc7, 0xb3, 0x15, 0x7d, 0x04, 0x16, 0x3c, 0x79, 0x54, 0x8d, 0xb9, 0xb1, 0x4f, 0x8a, 0x28, 0x3a, 0xe5, 0x8c, 0x45, 0x9e,
0xa1, 0x18, 0x12, 0xf6, 0xc0, 0x82, 0x9d, 0x19, 0x51, 0x64, 0xab, 0xbc, 0x3b, 0x06, 0x49, 0x76, 0xad, 0xe8, 0x0b, 0xc5, 0x90, 0xb0, 0x07, 0x96, 0xed, 0xcc, 0x88, 0x22, 0x5b, 0xe5, 0xed, 0x29,
0xc6, 0x09, 0x87, 0x83, 0xac, 0x0c, 0xe5, 0x48, 0xe0, 0x0e, 0xa8, 0xed, 0xcb, 0x8c, 0xb9, 0xce, 0x48, 0xb2, 0x33, 0x4e, 0x38, 0x1c, 0x64, 0x65, 0x28, 0x47, 0x02, 0xf7, 0x41, 0xb5, 0x2f, 0x33,
0xba, 0x19, 0xde, 0x33, 0x65, 0x71, 0x4d, 0xad, 0xf0, 0x91, 0x66, 0x3b, 0xaf, 0x3c, 0xea, 0x37, 0xe6, 0x3a, 0x5b, 0x66, 0x78, 0xcf, 0x14, 0xc5, 0x35, 0xb5, 0xce, 0x47, 0x9a, 0xbd, 0xbc, 0xf2,
0xab, 0x79, 0x21, 0x2a, 0x62, 0xa8, 0xbf, 0x2b, 0xe0, 0xe2, 0xb1, 0x7b, 0xf1, 0x3f, 0x54, 0x1f, 0x68, 0xd0, 0xa8, 0xe4, 0x85, 0x68, 0x14, 0x43, 0xfd, 0x55, 0x01, 0x17, 0x8f, 0xdd, 0x8b, 0x7f,
0xcd, 0x56, 0xdf, 0x8d, 0x53, 0xa9, 0xbe, 0xe1, 0x65, 0xf7, 0xe3, 0xd4, 0x3f, 0x84, 0x2a, 0xea, 0xa1, 0xfa, 0x68, 0xb6, 0xfa, 0x6e, 0x9c, 0x4a, 0xf5, 0x8d, 0x2f, 0xbb, 0xef, 0xe7, 0xfe, 0x22,
0x0d, 0x83, 0x59, 0x2f, 0xba, 0x49, 0x65, 0xac, 0x57, 0xc7, 0x2d, 0x1e, 0xee, 0x6b, 0xcc, 0xf3, 0x54, 0x51, 0x6f, 0x18, 0x94, 0xbc, 0xe8, 0x26, 0x95, 0xb1, 0x5e, 0x9d, 0xb6, 0x78, 0xb8, 0xaf,
0xab, 0x2e, 0xfe, 0x44, 0x09, 0x2a, 0xfc, 0x02, 0x54, 0x6d, 0x39, 0x4b, 0x73, 0x00, 0xea, 0x04, 0xb1, 0xc4, 0xaf, 0xba, 0xf8, 0x13, 0x25, 0xa8, 0xf0, 0x33, 0x50, 0xb1, 0xe5, 0x2c, 0xcd, 0x01,
0xd1, 0xbc, 0xf0, 0x2f, 0x2a, 0xe8, 0xec, 0xa0, 0xdf, 0xac, 0x6e, 0xe4, 0x60, 0x51, 0x81, 0x08, 0xa8, 0x13, 0x44, 0xf3, 0xc2, 0xdf, 0xa8, 0xa0, 0xb3, 0xc3, 0x41, 0xa3, 0xb2, 0x9d, 0x83, 0x45,
0x76, 0x41, 0x25, 0xa9, 0x80, 0x68, 0xc0, 0x7c, 0xeb, 0x25, 0x52, 0xee, 0x3a, 0xc6, 0x2b, 0x32, 0x23, 0x44, 0xb0, 0x0b, 0xca, 0x49, 0x05, 0x44, 0x03, 0xe6, 0x1b, 0xaf, 0x90, 0x72, 0xd7, 0x31,
0xc7, 0x95, 0x44, 0xc6, 0x50, 0x1a, 0x1e, 0xde, 0x07, 0xf3, 0x7b, 0x98, 0x76, 0x7b, 0x3e, 0x91, 0xfe, 0x23, 0x73, 0x5c, 0x4e, 0x64, 0x0c, 0xa5, 0xe1, 0xe1, 0x7d, 0xb0, 0x74, 0x80, 0x69, 0xb7,
0xa3, 0x5b, 0x59, 0x34, 0xf0, 0x6b, 0x7c, 0xac, 0xba, 0x95, 0x56, 0x1c, 0xf5, 0x9b, 0xb5, 0x8c, 0xe7, 0x13, 0x39, 0xba, 0x15, 0x45, 0x03, 0xff, 0x8f, 0x8f, 0x55, 0xb7, 0xd2, 0x8a, 0xa3, 0x41,
0x40, 0x8c, 0x6f, 0x59, 0x67, 0xf8, 0x54, 0x01, 0x55, 0x9c, 0x7d, 0x68, 0xb1, 0xfa, 0x94, 0x88, 0xa3, 0x9a, 0x11, 0x88, 0xf1, 0x2d, 0xeb, 0x0c, 0x9f, 0x2a, 0xa0, 0x82, 0xb3, 0x0f, 0x2d, 0x56,
0xe0, 0xbd, 0x31, 0x22, 0xc8, 0xbd, 0xd5, 0x8c, 0xba, 0x0c, 0xa3, 0x9a, 0x53, 0x30, 0x54, 0x60, 0x3b, 0x23, 0x22, 0x78, 0x67, 0x8a, 0x08, 0x72, 0x6f, 0x35, 0xa3, 0x26, 0xc3, 0xa8, 0xe4, 0x14,
0x83, 0x5f, 0x82, 0x45, 0x3b, 0xf3, 0x0e, 0x62, 0xf5, 0x69, 0xb1, 0x80, 0xb1, 0xb7, 0x2e, 0x46, 0x0c, 0x8d, 0xb0, 0xc1, 0xcf, 0xc1, 0x8a, 0x9d, 0x79, 0x07, 0xb1, 0xda, 0x9c, 0x58, 0xc0, 0xd4,
0x48, 0xde, 0x7c, 0x59, 0x39, 0x43, 0x79, 0x2a, 0xf5, 0xa7, 0x49, 0xd0, 0x3c, 0xe1, 0x92, 0x85, 0x5b, 0x17, 0x23, 0x24, 0x6f, 0xbe, 0xac, 0x9c, 0xa1, 0x3c, 0x15, 0xb4, 0x40, 0xa9, 0x8f, 0x7d,
0x77, 0x01, 0x74, 0x77, 0x19, 0xf1, 0xf7, 0x89, 0x75, 0x3b, 0x7c, 0xa7, 0x46, 0x53, 0x60, 0x29, 0x8a, 0x9b, 0x7c, 0x24, 0x9f, 0x17, 0xbc, 0x57, 0xa6, 0xda, 0xba, 0xd0, 0x37, 0x19, 0xc5, 0x22,
0x19, 0x7c, 0x36, 0x0b, 0x16, 0x68, 0x88, 0x17, 0xb4, 0xc1, 0x5c, 0x90, 0x9a, 0xc9, 0xc6, 0x99, 0x09, 0x43, 0x09, 0xb0, 0xfa, 0xc3, 0x2c, 0x68, 0x9c, 0x70, 0x95, 0xc3, 0xbb, 0x00, 0xba, 0x4d,
0x6a, 0x65, 0xa8, 0xe9, 0x91, 0xce, 0xa8, 0x0e, 0xfa, 0xcd, 0xcc, 0x90, 0x87, 0x32, 0xf0, 0xd0, 0x46, 0xfc, 0x3e, 0xb1, 0x6e, 0x87, 0xaf, 0xe1, 0x68, 0xd6, 0x2c, 0x24, 0xe3, 0xd5, 0xce, 0x88,
0x04, 0xc0, 0x4c, 0xf2, 0x1a, 0x96, 0xa6, 0x3e, 0xda, 0x41, 0x93, 0x64, 0x33, 0xbe, 0x1c, 0x52, 0x05, 0x1a, 0xe3, 0x05, 0x6d, 0xb0, 0x18, 0xa4, 0x26, 0xbf, 0x69, 0x66, 0x67, 0x19, 0x58, 0x7a,
0x89, 0x4c, 0xc1, 0xaa, 0x7f, 0x29, 0x00, 0x24, 0xf5, 0x0a, 0x2f, 0x81, 0xd4, 0x53, 0x54, 0xde, 0x70, 0x34, 0x2a, 0xc3, 0x41, 0x23, 0x33, 0x4a, 0xa2, 0x0c, 0x3c, 0x34, 0x01, 0x30, 0x93, 0xdd,
0x2f, 0x65, 0x0e, 0x81, 0x52, 0x72, 0xfe, 0x52, 0xb6, 0x09, 0x63, 0xb8, 0x15, 0x0d, 0xb3, 0xf1, 0x0b, 0x1b, 0x40, 0x9f, 0xec, 0x38, 0x4b, 0xf6, 0x2c, 0xbe, 0x82, 0x52, 0xdb, 0x95, 0x82, 0x55,
0x4b, 0x79, 0x23, 0x14, 0xa3, 0x48, 0x0f, 0x77, 0xc0, 0xb4, 0x4f, 0x30, 0x73, 0x1d, 0xf9, 0xa6, 0xff, 0x50, 0x00, 0x48, 0xba, 0x02, 0x5e, 0x02, 0xa9, 0x07, 0xaf, 0xbc, 0xc5, 0x8a, 0x1c, 0x02,
0xfe, 0x80, 0x0f, 0x3c, 0x48, 0x48, 0x8e, 0xfa, 0xcd, 0xd5, 0x51, 0xfe, 0xc9, 0xd0, 0xe4, 0x7c, 0xa5, 0xe4, 0xfc, 0x3d, 0x6e, 0x13, 0xc6, 0x70, 0x2b, 0x1a, 0x99, 0xe3, 0xf7, 0xf8, 0x76, 0x28,
0x24, 0x9c, 0x90, 0x84, 0x83, 0xb7, 0x41, 0x4d, 0x72, 0xa4, 0x16, 0x1c, 0xf6, 0xd3, 0x05, 0xb9, 0x46, 0x91, 0x1e, 0xee, 0x83, 0x39, 0x9f, 0x60, 0xe6, 0x3a, 0xf2, 0xe5, 0xfe, 0x1e, 0x1f, 0xab,
0x9a, 0xda, 0x46, 0xde, 0x00, 0x15, 0x7d, 0x8c, 0xcd, 0x67, 0x87, 0x8d, 0x89, 0xe7, 0x87, 0x8d, 0x90, 0x90, 0x1c, 0x0d, 0x1a, 0x1b, 0x93, 0xfc, 0x5f, 0xa2, 0xc9, 0x29, 0x4c, 0x38, 0x21, 0x09,
0x89, 0x17, 0x87, 0x8d, 0x89, 0xa7, 0x83, 0x86, 0xf2, 0x6c, 0xd0, 0x50, 0x9e, 0x0f, 0x1a, 0xca, 0x07, 0x6f, 0x83, 0xaa, 0xe4, 0x48, 0x2d, 0x38, 0xec, 0xda, 0x0b, 0x72, 0x35, 0xd5, 0xed, 0xbc,
0x8b, 0x41, 0x43, 0xf9, 0x75, 0xd0, 0x50, 0xbe, 0xfb, 0xad, 0x31, 0xf1, 0xf1, 0xca, 0xc8, 0xff, 0x01, 0x1a, 0xf5, 0x51, 0xef, 0x82, 0x85, 0xa8, 0xba, 0x60, 0x0d, 0x14, 0x53, 0xd7, 0x77, 0x18,
0x1e, 0xfd, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x08, 0xaf, 0xaa, 0x52, 0x82, 0x12, 0x00, 0x00, 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,
} }
func (m *AuditAnnotation) Marshal() (dAtA []byte, err error) { func (m *AuditAnnotation) Marshal() (dAtA []byte, err error) {
@@ -1205,6 +1238,20 @@ func (m *ValidatingAdmissionPolicySpec) MarshalToSizedBuffer(dAtA []byte) (int,
_ = i _ = i
var l int var l int
_ = l _ = l
if len(m.Variables) > 0 {
for iNdEx := len(m.Variables) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Variables[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x3a
}
}
if len(m.MatchConditions) > 0 { if len(m.MatchConditions) > 0 {
for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- {
{ {
@@ -1378,6 +1425,39 @@ func (m *Validation) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil return len(dAtA) - i, nil
} }
func (m *Variable) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Variable) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Variable) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
i -= len(m.Expression)
copy(dAtA[i:], m.Expression)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression)))
i--
dAtA[i] = 0x12
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
offset -= sovGenerated(v) offset -= sovGenerated(v)
base := offset base := offset
@@ -1642,6 +1722,12 @@ func (m *ValidatingAdmissionPolicySpec) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l)) n += 1 + l + sovGenerated(uint64(l))
} }
} }
if len(m.Variables) > 0 {
for _, e := range m.Variables {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n return n
} }
@@ -1684,6 +1770,19 @@ func (m *Validation) Size() (n int) {
return n return n
} }
func (m *Variable) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
n += 1 + l + sovGenerated(uint64(l))
l = len(m.Expression)
n += 1 + l + sovGenerated(uint64(l))
return n
}
func sovGenerated(x uint64) (n int) { func sovGenerated(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7 return (math_bits.Len64(x|1) + 6) / 7
} }
@@ -1882,6 +1981,11 @@ func (this *ValidatingAdmissionPolicySpec) String() string {
repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + ","
} }
repeatedStringForMatchConditions += "}" repeatedStringForMatchConditions += "}"
repeatedStringForVariables := "[]Variable{"
for _, f := range this.Variables {
repeatedStringForVariables += strings.Replace(strings.Replace(f.String(), "Variable", "Variable", 1), `&`, ``, 1) + ","
}
repeatedStringForVariables += "}"
s := strings.Join([]string{`&ValidatingAdmissionPolicySpec{`, s := strings.Join([]string{`&ValidatingAdmissionPolicySpec{`,
`ParamKind:` + strings.Replace(this.ParamKind.String(), "ParamKind", "ParamKind", 1) + `,`, `ParamKind:` + strings.Replace(this.ParamKind.String(), "ParamKind", "ParamKind", 1) + `,`,
`MatchConstraints:` + strings.Replace(this.MatchConstraints.String(), "MatchResources", "MatchResources", 1) + `,`, `MatchConstraints:` + strings.Replace(this.MatchConstraints.String(), "MatchResources", "MatchResources", 1) + `,`,
@@ -1889,6 +1993,7 @@ func (this *ValidatingAdmissionPolicySpec) String() string {
`FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`,
`AuditAnnotations:` + repeatedStringForAuditAnnotations + `,`, `AuditAnnotations:` + repeatedStringForAuditAnnotations + `,`,
`MatchConditions:` + repeatedStringForMatchConditions + `,`, `MatchConditions:` + repeatedStringForMatchConditions + `,`,
`Variables:` + repeatedStringForVariables + `,`,
`}`, `}`,
}, "") }, "")
return s return s
@@ -1923,6 +2028,17 @@ func (this *Validation) String() string {
}, "") }, "")
return s return s
} }
func (this *Variable) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&Variable{`,
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`Expression:` + fmt.Sprintf("%v", this.Expression) + `,`,
`}`,
}, "")
return s
}
func valueToStringGenerated(v interface{}) string { func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.IsNil() { if rv.IsNil() {
@@ -3844,6 +3960,40 @@ func (m *ValidatingAdmissionPolicySpec) Unmarshal(dAtA []byte) error {
return err return err
} }
iNdEx = postIndex iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Variables", 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
}
m.Variables = append(m.Variables, Variable{})
if err := m.Variables[len(m.Variables)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:]) skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -4183,6 +4333,120 @@ func (m *Validation) Unmarshal(dAtA []byte) error {
} }
return nil return nil
} }
func (m *Variable) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Variable: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Variable: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", 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
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Expression", 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
}
m.Expression = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipGenerated(dAtA []byte) (n int, err error) { func skipGenerated(dAtA []byte) (n int, err error) {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0

View File

@@ -430,6 +430,20 @@ message ValidatingAdmissionPolicySpec {
// +listMapKey=name // +listMapKey=name
// +optional // +optional
repeated MatchCondition matchConditions = 6; repeated MatchCondition matchConditions = 6;
// Variables contain definitions of variables that can be used in composition of other expressions.
// Each variable is defined as a named CEL expression.
// The variables defined here will be available under `variables` in other expressions of the policy
// except MatchConditions because MatchConditions are evaluated before the rest of the policy.
//
// The expression of a variable can refer to other variables defined earlier in the list but not those after.
// Thus, Variables must be sorted by the order of first appearance and acyclic.
// +patchMergeKey=name
// +patchStrategy=merge
// +listType=map
// +listMapKey=name
// +optional
repeated Variable variables = 7;
} }
// ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy. // ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy.
@@ -460,6 +474,8 @@ message Validation {
// - 'oldObject' - The existing object. The value is null for CREATE requests. // - 'oldObject' - The existing object. The value is null for CREATE requests.
// - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)).
// - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind.
// - 'variables' - Map of composited variables, from its name to its lazily evaluated value.
// For example, a variable named 'foo' can be accessed as 'variables.foo'.
// - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
// - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
@@ -525,3 +541,15 @@ message Validation {
optional string messageExpression = 4; optional string messageExpression = 4;
} }
// Variable is the definition of a variable that is used for composition.
message Variable {
// Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables.
// The variable can be accessed in other expressions through `variables`
// For example, if name is "foo", the variable will be available as `variables.foo`
optional string Name = 1;
// Expression is the expression that will be evaluated as the value of the variable.
// The CEL expression has access to the same identifiers as the CEL expressions in Validation.
optional string Expression = 2;
}

View File

@@ -201,6 +201,20 @@ type ValidatingAdmissionPolicySpec struct {
// +listMapKey=name // +listMapKey=name
// +optional // +optional
MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,6,rep,name=matchConditions"` MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,6,rep,name=matchConditions"`
// Variables contain definitions of variables that can be used in composition of other expressions.
// Each variable is defined as a named CEL expression.
// The variables defined here will be available under `variables` in other expressions of the policy
// except MatchConditions because MatchConditions are evaluated before the rest of the policy.
//
// The expression of a variable can refer to other variables defined earlier in the list but not those after.
// Thus, Variables must be sorted by the order of first appearance and acyclic.
// +patchMergeKey=name
// +patchStrategy=merge
// +listType=map
// +listMapKey=name
// +optional
Variables []Variable `json:"variables" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,7,rep,name=variables"`
} }
type MatchCondition v1.MatchCondition type MatchCondition v1.MatchCondition
@@ -228,6 +242,8 @@ type Validation struct {
// - 'oldObject' - The existing object. The value is null for CREATE requests. // - 'oldObject' - The existing object. The value is null for CREATE requests.
// - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)).
// - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind.
// - 'variables' - Map of composited variables, from its name to its lazily evaluated value.
// For example, a variable named 'foo' can be accessed as 'variables.foo'.
// - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
// - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
@@ -290,6 +306,18 @@ type Validation struct {
MessageExpression string `json:"messageExpression,omitempty" protobuf:"bytes,4,opt,name=messageExpression"` MessageExpression string `json:"messageExpression,omitempty" protobuf:"bytes,4,opt,name=messageExpression"`
} }
// Variable is the definition of a variable that is used for composition.
type Variable struct {
// Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables.
// The variable can be accessed in other expressions through `variables`
// For example, if name is "foo", the variable will be available as `variables.foo`
Name string `json:"name" protobuf:"bytes,1,opt,name=Name"`
// Expression is the expression that will be evaluated as the value of the variable.
// The CEL expression has access to the same identifiers as the CEL expressions in Validation.
Expression string `json:"expression" protobuf:"bytes,2,opt,name=Expression"`
}
// AuditAnnotation describes how to produce an audit annotation for an API request. // AuditAnnotation describes how to produce an audit annotation for an API request.
type AuditAnnotation struct { type AuditAnnotation struct {
// key specifies the audit annotation key. The audit annotation keys of // key specifies the audit annotation key. The audit annotation keys of

View File

@@ -159,6 +159,7 @@ var map_ValidatingAdmissionPolicySpec = map[string]string{
"failurePolicy": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if spec.paramKind refers to a non-existent Kind. A binding is invalid if spec.paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nWhen failurePolicy is set to Fail, ValidatingAdmissionPolicyBinding validationActions define how failures are enforced.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", "failurePolicy": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if spec.paramKind refers to a non-existent Kind. A binding is invalid if spec.paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nWhen failurePolicy is set to Fail, ValidatingAdmissionPolicyBinding validationActions define how failures are enforced.\n\nAllowed values are Ignore or Fail. Defaults to Fail.",
"auditAnnotations": "auditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request. validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is required.", "auditAnnotations": "auditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request. validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is required.",
"matchConditions": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped",
"variables": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.",
} }
func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string {
@@ -178,7 +179,7 @@ func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string {
var map_Validation = map[string]string{ var map_Validation = map[string]string{
"": "Validation specifies the CEL expression which is used to apply the validation.", "": "Validation specifies the CEL expression which is used to apply the validation.",
"expression": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", "expression": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.",
"message": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", "message": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".",
"reason": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", "reason": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.",
"messageExpression": "messageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a validation, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the `expression` except for 'authorizer' and 'authorizer.requestResource'. Example: \"object.x must be less than max (\"+string(params.max)+\")\"", "messageExpression": "messageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a validation, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the `expression` except for 'authorizer' and 'authorizer.requestResource'. Example: \"object.x must be less than max (\"+string(params.max)+\")\"",
@@ -188,4 +189,14 @@ func (Validation) SwaggerDoc() map[string]string {
return map_Validation return map_Validation
} }
var map_Variable = map[string]string{
"": "Variable is the definition of a variable that is used for composition.",
"name": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`",
"expression": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.",
}
func (Variable) SwaggerDoc() map[string]string {
return map_Variable
}
// AUTO-GENERATED FUNCTIONS END HERE // AUTO-GENERATED FUNCTIONS END HERE

View File

@@ -381,6 +381,11 @@ func (in *ValidatingAdmissionPolicySpec) DeepCopyInto(out *ValidatingAdmissionPo
*out = make([]MatchCondition, len(*in)) *out = make([]MatchCondition, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Variables != nil {
in, out := &in.Variables, &out.Variables
*out = make([]Variable, len(*in))
copy(*out, *in)
}
return return
} }
@@ -442,3 +447,19 @@ func (in *Validation) DeepCopy() *Validation {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Variable) DeepCopyInto(out *Variable) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Variable.
func (in *Variable) DeepCopy() *Variable {
if in == nil {
return nil
}
out := new(Variable)
in.DeepCopyInto(out)
return out
}

View File

@@ -139,6 +139,12 @@
"name": "nameValue", "name": "nameValue",
"expression": "expressionValue" "expression": "expressionValue"
} }
],
"variables": [
{
"name": "nameValue",
"expression": "expressionValue"
}
] ]
}, },
"status": { "status": {

View File

@@ -90,6 +90,9 @@ spec:
message: messageValue message: messageValue
messageExpression: messageExpressionValue messageExpression: messageExpressionValue
reason: reasonValue reason: reasonValue
variables:
- expression: expressionValue
name: nameValue
status: status:
conditions: conditions:
- lastTransitionTime: "2004-01-01T01:01:01Z" - lastTransitionTime: "2004-01-01T01:01:01Z"

View File

@@ -126,7 +126,8 @@
"reason": "reasonValue" "reason": "reasonValue"
} }
], ],
"failurePolicy": "failurePolicyValue" "failurePolicy": "failurePolicyValue",
"variables": null
}, },
"status": {} "status": {}
} }

View File

@@ -83,4 +83,5 @@ spec:
- expression: expressionValue - expression: expressionValue
message: messageValue message: messageValue
reason: reasonValue reason: reasonValue
variables: null
status: {} status: {}

View File

@@ -0,0 +1,166 @@
{
"kind": "ValidatingAdmissionPolicy",
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
"metadata": {
"name": "nameValue",
"generateName": "generateNameValue",
"namespace": "namespaceValue",
"selfLink": "selfLinkValue",
"uid": "uidValue",
"resourceVersion": "resourceVersionValue",
"generation": 7,
"creationTimestamp": "2008-01-01T01:01:01Z",
"deletionTimestamp": "2009-01-01T01:01:01Z",
"deletionGracePeriodSeconds": 10,
"labels": {
"labelsKey": "labelsValue"
},
"annotations": {
"annotationsKey": "annotationsValue"
},
"ownerReferences": [
{
"apiVersion": "apiVersionValue",
"kind": "kindValue",
"name": "nameValue",
"uid": "uidValue",
"controller": true,
"blockOwnerDeletion": true
}
],
"finalizers": [
"finalizersValue"
],
"managedFields": [
{
"manager": "managerValue",
"operation": "operationValue",
"apiVersion": "apiVersionValue",
"time": "2004-01-01T01:01:01Z",
"fieldsType": "fieldsTypeValue",
"fieldsV1": {},
"subresource": "subresourceValue"
}
]
},
"spec": {
"paramKind": {
"apiVersion": "apiVersionValue",
"kind": "kindValue"
},
"matchConstraints": {
"namespaceSelector": {
"matchLabels": {
"matchLabelsKey": "matchLabelsValue"
},
"matchExpressions": [
{
"key": "keyValue",
"operator": "operatorValue",
"values": [
"valuesValue"
]
}
]
},
"objectSelector": {
"matchLabels": {
"matchLabelsKey": "matchLabelsValue"
},
"matchExpressions": [
{
"key": "keyValue",
"operator": "operatorValue",
"values": [
"valuesValue"
]
}
]
},
"resourceRules": [
{
"resourceNames": [
"resourceNamesValue"
],
"operations": [
"operationsValue"
],
"apiGroups": [
"apiGroupsValue"
],
"apiVersions": [
"apiVersionsValue"
],
"resources": [
"resourcesValue"
],
"scope": "scopeValue"
}
],
"excludeResourceRules": [
{
"resourceNames": [
"resourceNamesValue"
],
"operations": [
"operationsValue"
],
"apiGroups": [
"apiGroupsValue"
],
"apiVersions": [
"apiVersionsValue"
],
"resources": [
"resourcesValue"
],
"scope": "scopeValue"
}
],
"matchPolicy": "matchPolicyValue"
},
"validations": [
{
"expression": "expressionValue",
"message": "messageValue",
"reason": "reasonValue",
"messageExpression": "messageExpressionValue"
}
],
"failurePolicy": "failurePolicyValue",
"auditAnnotations": [
{
"key": "keyValue",
"valueExpression": "valueExpressionValue"
}
],
"matchConditions": [
{
"name": "nameValue",
"expression": "expressionValue"
}
],
"variables": null
},
"status": {
"observedGeneration": 1,
"typeChecking": {
"expressionWarnings": [
{
"fieldRef": "fieldRefValue",
"warning": "warningValue"
}
]
},
"conditions": [
{
"type": "typeValue",
"status": "statusValue",
"observedGeneration": 3,
"lastTransitionTime": "2004-01-01T01:01:01Z",
"reason": "reasonValue",
"message": "messageValue"
}
]
}
}

View File

@@ -0,0 +1,106 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
annotations:
annotationsKey: annotationsValue
creationTimestamp: "2008-01-01T01:01:01Z"
deletionGracePeriodSeconds: 10
deletionTimestamp: "2009-01-01T01:01:01Z"
finalizers:
- finalizersValue
generateName: generateNameValue
generation: 7
labels:
labelsKey: labelsValue
managedFields:
- apiVersion: apiVersionValue
fieldsType: fieldsTypeValue
fieldsV1: {}
manager: managerValue
operation: operationValue
subresource: subresourceValue
time: "2004-01-01T01:01:01Z"
name: nameValue
namespace: namespaceValue
ownerReferences:
- apiVersion: apiVersionValue
blockOwnerDeletion: true
controller: true
kind: kindValue
name: nameValue
uid: uidValue
resourceVersion: resourceVersionValue
selfLink: selfLinkValue
uid: uidValue
spec:
auditAnnotations:
- key: keyValue
valueExpression: valueExpressionValue
failurePolicy: failurePolicyValue
matchConditions:
- expression: expressionValue
name: nameValue
matchConstraints:
excludeResourceRules:
- apiGroups:
- apiGroupsValue
apiVersions:
- apiVersionsValue
operations:
- operationsValue
resourceNames:
- resourceNamesValue
resources:
- resourcesValue
scope: scopeValue
matchPolicy: matchPolicyValue
namespaceSelector:
matchExpressions:
- key: keyValue
operator: operatorValue
values:
- valuesValue
matchLabels:
matchLabelsKey: matchLabelsValue
objectSelector:
matchExpressions:
- key: keyValue
operator: operatorValue
values:
- valuesValue
matchLabels:
matchLabelsKey: matchLabelsValue
resourceRules:
- apiGroups:
- apiGroupsValue
apiVersions:
- apiVersionsValue
operations:
- operationsValue
resourceNames:
- resourceNamesValue
resources:
- resourcesValue
scope: scopeValue
paramKind:
apiVersion: apiVersionValue
kind: kindValue
validations:
- expression: expressionValue
message: messageValue
messageExpression: messageExpressionValue
reason: reasonValue
variables: null
status:
conditions:
- lastTransitionTime: "2004-01-01T01:01:01Z"
message: messageValue
observedGeneration: 3
reason: reasonValue
status: statusValue
type: typeValue
observedGeneration: 1
typeChecking:
expressionWarnings:
- fieldRef: fieldRefValue
warning: warningValue

View File

@@ -35,6 +35,7 @@ const (
RequestVarName = "request" RequestVarName = "request"
AuthorizerVarName = "authorizer" AuthorizerVarName = "authorizer"
RequestResourceAuthorizerVarName = "authorizer.requestResource" RequestResourceAuthorizerVarName = "authorizer.requestResource"
VariableVarName = "variables"
) )
// BuildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that // BuildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
@@ -132,7 +133,7 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op
found := false found := false
returnTypes := expressionAccessor.ReturnTypes() returnTypes := expressionAccessor.ReturnTypes()
for _, returnType := range returnTypes { for _, returnType := range returnTypes {
if ast.OutputType() == returnType { if ast.OutputType() == returnType || cel.AnyType == returnType {
found = true found = true
break break
} }

View File

@@ -0,0 +1,197 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cel
import (
"context"
"math"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
v1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/apiserver/pkg/admission"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/cel/environment"
"k8s.io/apiserver/pkg/cel/lazy"
)
const VariablesTypeName = "kubernetes.variables"
type CompositedCompiler struct {
Compiler
FilterCompiler
CompositionEnv *CompositionEnv
}
type CompositedFilter struct {
Filter
compositionEnv *CompositionEnv
}
func NewCompositedCompiler(envSet *environment.EnvSet) (*CompositedCompiler, error) {
compositionContext, err := NewCompositionEnv(VariablesTypeName, envSet)
if err != nil {
return nil, err
}
compiler := NewCompiler(compositionContext.EnvSet)
filterCompiler := NewFilterCompiler(compositionContext.EnvSet)
return &CompositedCompiler{
Compiler: compiler,
FilterCompiler: filterCompiler,
CompositionEnv: compositionContext,
}, nil
}
func (c *CompositedCompiler) CompileAndStoreVariables(variables []NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) {
for _, v := range variables {
_ = c.CompileAndStoreVariable(v, options, mode)
}
}
func (c *CompositedCompiler) CompileAndStoreVariable(variable NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult {
c.CompositionEnv.AddField(variable.GetName())
result := c.Compiler.CompileCELExpression(variable, options, mode)
c.CompositionEnv.CompiledVariables[variable.GetName()] = result
return result
}
func (c *CompositedCompiler) Compile(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) Filter {
filter := c.FilterCompiler.Compile(expressions, optionalDecls, envType)
return &CompositedFilter{
Filter: filter,
compositionEnv: c.CompositionEnv,
}
}
type CompositionEnv struct {
*environment.EnvSet
MapType *apiservercel.DeclType
CompiledVariables map[string]CompilationResult
}
func (c *CompositionEnv) AddField(name string) {
c.MapType.Fields[name] = apiservercel.NewDeclField(name, apiservercel.DynType, true, nil, nil)
}
func NewCompositionEnv(typeName string, baseEnvSet *environment.EnvSet) (*CompositionEnv, error) {
declType := apiservercel.NewObjectType(typeName, map[string]*apiservercel.DeclField{})
envSet, err := baseEnvSet.Extend(environment.VersionedOptions{
// set to 1.0 because composition is one of the fundamental components
IntroducedVersion: version.MajorMinor(1, 0),
EnvOptions: []cel.EnvOption{
cel.Variable("variables", declType.CelType()),
},
DeclTypes: []*apiservercel.DeclType{
declType,
},
})
if err != nil {
return nil, err
}
return &CompositionEnv{
MapType: declType,
EnvSet: envSet,
CompiledVariables: map[string]CompilationResult{},
}, nil
}
func (c *CompositionEnv) CreateContext(parent context.Context) CompositionContext {
return &compositionContext{
Context: parent,
compositionEnv: c,
}
}
type CompositionContext interface {
context.Context
Variables(activation any) ref.Val
GetAndResetCost() int64
}
type compositionContext struct {
context.Context
compositionEnv *CompositionEnv
accumulatedCost int64
}
func (c *compositionContext) Variables(activation any) ref.Val {
lazyMap := lazy.NewMapValue(c.compositionEnv.MapType)
for name, result := range c.compositionEnv.CompiledVariables {
accessor := &variableAccessor{
name: name,
result: result,
activation: activation,
context: c,
}
lazyMap.Append(name, accessor.Callback)
}
return lazyMap
}
func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
ctx = f.compositionEnv.CreateContext(ctx)
return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, runtimeCELCostBudget)
}
func (c *compositionContext) reportCost(cost int64) {
c.accumulatedCost += cost
}
func (c *compositionContext) GetAndResetCost() int64 {
cost := c.accumulatedCost
c.accumulatedCost = 0
return cost
}
type variableAccessor struct {
name string
result CompilationResult
activation any
context *compositionContext
}
func (a *variableAccessor) Callback(_ *lazy.MapValue) ref.Val {
if a.result.Error != nil {
return types.NewErr("composited variable %q fails to compile: %v", a.name, a.result.Error)
}
v, details, err := a.result.Program.Eval(a.activation)
if details == nil {
return types.NewErr("unable to get evaluation details of variable %q", a.name)
}
costPtr := details.ActualCost()
if costPtr == nil {
return types.NewErr("unable to calculate cost of variable %q", a.name)
}
cost := int64(*costPtr)
if *costPtr > math.MaxInt64 {
cost = math.MaxInt64
}
a.context.reportCost(cost)
if err != nil {
return types.NewErr("composited variable %q fails to evaluate: %v", a.name, err)
}
return v
}

View File

@@ -0,0 +1,172 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cel
import (
"context"
"strings"
"testing"
"github.com/google/cel-go/cel"
"k8s.io/apiserver/pkg/admission"
celconfig "k8s.io/apiserver/pkg/apis/cel"
"k8s.io/apiserver/pkg/cel/environment"
)
type testVariable struct {
name string
expression string
}
func (t *testVariable) GetExpression() string {
return t.expression
}
func (t *testVariable) ReturnTypes() []*cel.Type {
return []*cel.Type{cel.AnyType}
}
func (t *testVariable) GetName() string {
return t.name
}
func TestCompositedPolicies(t *testing.T) {
cases := []struct {
name string
variables []NamedExpressionAccessor
expression string
attributes admission.Attributes
expectedResult any
expectErr bool
expectedErrorMessage string
runtimeCostBudget int64
}{
{
name: "simple",
variables: []NamedExpressionAccessor{
&testVariable{
name: "name",
expression: "object.metadata.name",
},
},
attributes: endpointCreateAttributes(),
expression: "variables.name == 'endpoints1'",
expectedResult: true,
},
{
name: "delayed compile error",
variables: []NamedExpressionAccessor{
&testVariable{
name: "name",
expression: "1 == '1'", // won't compile
},
},
attributes: endpointCreateAttributes(),
expression: "variables.name == 'endpoints1'",
expectErr: true,
expectedErrorMessage: `composited variable "name" fails to compile:`,
},
{
name: "delayed eval error",
variables: []NamedExpressionAccessor{
&testVariable{
name: "name",
expression: "object.spec.subsets[114514].addresses.size()", // array index out of bound
},
},
attributes: endpointCreateAttributes(),
expression: "variables.name == 'endpoints1'",
expectErr: true,
expectedErrorMessage: `composited variable "name" fails to evaluate:`,
},
{
name: "out of budget during lazy evaluation",
variables: []NamedExpressionAccessor{
&testVariable{
name: "name",
expression: "object.metadata.name", // cost = 3
},
},
attributes: endpointCreateAttributes(),
expression: "variables.name == 'endpoints1'", // cost = 3
expectedResult: true,
runtimeCostBudget: 4, // enough for main variable but not for entire expression
expectErr: true,
expectedErrorMessage: "running out of cost budget",
},
{
name: "lazy evaluation, budget counts only once",
variables: []NamedExpressionAccessor{
&testVariable{
name: "name",
expression: "object.metadata.name", // cost = 3
},
},
attributes: endpointCreateAttributes(),
expression: "variables.name == 'endpoints1' && variables.name == 'endpoints1' ", // cost = 7
expectedResult: true,
runtimeCostBudget: 10, // enough for one lazy evaluation but not two, should pass
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
compiler, err := NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
if err != nil {
t.Fatal(err)
}
compiler.CompileAndStoreVariables(tc.variables, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, environment.NewExpressions)
validations := []ExpressionAccessor{&condition{Expression: tc.expression}}
f := compiler.Compile(validations, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, environment.NewExpressions)
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
if err != nil {
t.Fatal(err)
}
optionalVars := OptionalVariableBindings{}
costBudget := tc.runtimeCostBudget
if costBudget == 0 {
costBudget = celconfig.RuntimeCELCostBudget
}
result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, costBudget)
if !tc.expectErr && err != nil {
t.Fatalf("failed evaluation: %v", err)
}
if !tc.expectErr && len(result) == 0 {
t.Fatal("unexpected empty result")
}
if err == nil {
err = result[0].Error
}
if tc.expectErr {
if err == nil {
t.Fatal("unexpected no error")
}
if !strings.Contains(err.Error(), tc.expectedErrorMessage) {
t.Errorf("expected error to contain %q but got %s", tc.expectedErrorMessage, err.Error())
}
return
}
if err != nil {
t.Fatalf("failed validation: %v", result[0].Error)
}
if tc.expectedResult != result[0].EvalResult.Value() {
t.Errorf("wrong result: expected %v but got %v", tc.expectedResult, result)
}
})
}
}

View File

@@ -46,7 +46,7 @@ func NewFilterCompiler(env *environment.EnvSet) FilterCompiler {
} }
type evaluationActivation struct { type evaluationActivation struct {
object, oldObject, params, request, authorizer, requestResourceAuthorizer interface{} object, oldObject, params, request, authorizer, requestResourceAuthorizer, variables interface{}
} }
// ResolveName returns a value from the activation by qualified name, or false if the name // ResolveName returns a value from the activation by qualified name, or false if the name
@@ -65,6 +65,8 @@ func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
return a.authorizer, a.authorizer != nil return a.authorizer, a.authorizer != nil
case RequestResourceAuthorizerVarName: case RequestResourceAuthorizerVarName:
return a.requestResourceAuthorizer, a.requestResourceAuthorizer != nil return a.requestResourceAuthorizer, a.requestResourceAuthorizer != nil
case VariableVarName: // variables always present
return a.variables, true
default: default:
return nil, false return nil, false
} }
@@ -163,6 +165,14 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione
requestResourceAuthorizer: requestResourceAuthorizerVal, requestResourceAuthorizer: requestResourceAuthorizerVal,
} }
// composition is an optional feature that only applies for ValidatingAdmissionPolicy.
// check if the context allows composition
var compositionCtx CompositionContext
var ok bool
if compositionCtx, ok = ctx.(CompositionContext); ok {
va.variables = compositionCtx.Variables(va)
}
remainingBudget := runtimeCELCostBudget remainingBudget := runtimeCELCostBudget
for i, compilationResult := range f.compilationResults { for i, compilationResult := range f.compilationResults {
var evaluation = &evaluations[i] var evaluation = &evaluations[i]
@@ -186,6 +196,17 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione
} }
t1 := time.Now() t1 := time.Now()
evalResult, evalDetails, err := compilationResult.Program.ContextEval(ctx, va) evalResult, evalDetails, err := compilationResult.Program.ContextEval(ctx, va)
// budget may be spent due to lazy evaluation of composited variables
if compositionCtx != nil {
compositionCost := compositionCtx.GetAndResetCost()
if compositionCost > remainingBudget {
return nil, -1, &cel.Error{
Type: cel.ErrorTypeInvalid,
Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"),
}
}
remainingBudget -= compositionCost
}
elapsed := time.Since(t1) elapsed := time.Since(t1)
evaluation.Elapsed = elapsed evaluation.Elapsed = elapsed
if evalDetails == nil { if evalDetails == nil {

View File

@@ -35,6 +35,13 @@ type ExpressionAccessor interface {
ReturnTypes() []*cel.Type ReturnTypes() []*cel.Type
} }
// NamedExpressionAccessor extends NamedExpressionAccessor with a name.
type NamedExpressionAccessor interface {
ExpressionAccessor
GetName() string // follows the naming convention of ExpressionAccessor
}
// EvaluationResult contains the minimal required fields and metadata of a cel evaluation // EvaluationResult contains the minimal required fields and metadata of a cel evaluation
type EvaluationResult struct { type EvaluationResult struct {
EvalResult ref.Val EvalResult ref.Val

View File

@@ -36,12 +36,10 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
celmetrics "k8s.io/apiserver/pkg/admission/cel" celmetrics "k8s.io/apiserver/pkg/admission/cel"
"k8s.io/apiserver/pkg/admission/plugin/cel"
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic" "k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching" "k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching"
celconfig "k8s.io/apiserver/pkg/apis/cel" celconfig "k8s.io/apiserver/pkg/apis/cel"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/cel/environment"
"k8s.io/apiserver/pkg/warning" "k8s.io/apiserver/pkg/warning"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
@@ -135,7 +133,7 @@ func NewAdmissionController(
restMapper, restMapper,
client, client,
dynamicClient, dynamicClient,
cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())), nil,
NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)), NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)),
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy]( generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies().Informer()), informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies().Informer()),

View File

@@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
celmetrics "k8s.io/apiserver/pkg/admission/cel" celmetrics "k8s.io/apiserver/pkg/admission/cel"
"k8s.io/apiserver/pkg/admission/plugin/cel" "k8s.io/apiserver/pkg/admission/plugin/cel"
@@ -53,6 +54,7 @@ type policyController struct {
// Provided to the policy's Compile function as an injected dependency to // Provided to the policy's Compile function as an injected dependency to
// assist with compiling its expressions to CEL // assist with compiling its expressions to CEL
// pass nil to create filter compiler in demand
filterCompiler cel.FilterCompiler filterCompiler cel.FilterCompiler
matcher Matcher matcher Matcher
@@ -463,18 +465,29 @@ func (c *policyController) latestPolicyData() []policyData {
failurePolicy := convertv1alpha1FailurePolicyTypeTov1FailurePolicyType(definitionInfo.lastReconciledValue.Spec.FailurePolicy) failurePolicy := convertv1alpha1FailurePolicyTypeTov1FailurePolicyType(definitionInfo.lastReconciledValue.Spec.FailurePolicy)
var matcher matchconditions.Matcher = nil var matcher matchconditions.Matcher = nil
matchConditions := definitionInfo.lastReconciledValue.Spec.MatchConditions matchConditions := definitionInfo.lastReconciledValue.Spec.MatchConditions
filterCompiler := c.filterCompiler
if filterCompiler == nil {
compositedCompiler, err := cel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
if err == nil {
filterCompiler = compositedCompiler
compositedCompiler.CompileAndStoreVariables(convertV1alpha1Variables(definitionInfo.lastReconciledValue.Spec.Variables), optionalVars, environment.StoredExpressions)
} else {
utilruntime.HandleError(err)
}
}
if len(matchConditions) > 0 { if len(matchConditions) > 0 {
matchExpressionAccessors := make([]cel.ExpressionAccessor, len(matchConditions)) matchExpressionAccessors := make([]cel.ExpressionAccessor, len(matchConditions))
for i := range matchConditions { for i := range matchConditions {
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i]) matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
} }
matcher = matchconditions.NewMatcher(c.filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "validatingadmissionpolicy", definitionInfo.lastReconciledValue.Name) matcher = matchconditions.NewMatcher(filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "validatingadmissionpolicy", definitionInfo.lastReconciledValue.Name)
} }
bindingInfo.validator = c.newValidator( bindingInfo.validator = c.newValidator(
c.filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, environment.StoredExpressions), filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, environment.StoredExpressions),
matcher, matcher,
c.filterCompiler.Compile(convertv1alpha1AuditAnnotations(definitionInfo.lastReconciledValue.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions), filterCompiler.Compile(convertv1alpha1AuditAnnotations(definitionInfo.lastReconciledValue.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
c.filterCompiler.Compile(convertV1Alpha1MessageExpressions(definitionInfo.lastReconciledValue.Spec.Validations), expressionOptionalVars, environment.StoredExpressions), filterCompiler.Compile(convertV1Alpha1MessageExpressions(definitionInfo.lastReconciledValue.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
failurePolicy, failurePolicy,
) )
} }
@@ -551,6 +564,14 @@ func convertv1alpha1AuditAnnotations(inputValidations []v1alpha1.AuditAnnotation
return celExpressionAccessor return celExpressionAccessor
} }
func convertV1alpha1Variables(variables []v1alpha1.Variable) []cel.NamedExpressionAccessor {
namedExpressions := make([]cel.NamedExpressionAccessor, len(variables))
for i, variable := range variables {
namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression}
}
return namedExpressions
}
func getNamespaceName(namespace, name string) namespacedName { func getNamespaceName(namespace, name string) namespacedName {
return namespacedName{ return namespacedName{
namespace: namespace, namespace: namespace,

View File

@@ -61,6 +61,24 @@ func (v *AuditAnnotationCondition) ReturnTypes() []*celgo.Type {
return []*celgo.Type{celgo.StringType, celgo.NullType} return []*celgo.Type{celgo.StringType, celgo.NullType}
} }
// Variable is a named expression for composition.
type Variable struct {
Name string
Expression string
}
func (v *Variable) GetExpression() string {
return v.Expression
}
func (v *Variable) ReturnTypes() []*celgo.Type {
return []*celgo.Type{celgo.AnyType, celgo.DynType}
}
func (v *Variable) GetName() string {
return v.Name
}
// Matcher is used for matching ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding to attributes // Matcher is used for matching ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding to attributes
type Matcher interface { type Matcher interface {
admission.InitializationValidator admission.InitializationValidator

View File

@@ -0,0 +1,191 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lazy
import (
"fmt"
"reflect"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"k8s.io/apiserver/pkg/cel"
)
type GetFieldFunc func(*MapValue) ref.Val
var _ ref.Val = (*MapValue)(nil)
var _ traits.Mapper = (*MapValue)(nil)
// MapValue is a map that lazily evaluate its value when a field is first accessed.
// The map value is not designed to be thread-safe.
type MapValue struct {
typeValue *types.TypeValue
// values are previously evaluated values obtained from callbacks
values map[string]ref.Val
// callbacks are a map of field name to the function that returns the field Val
callbacks map[string]GetFieldFunc
// knownValues are registered names, used for iteration
knownValues []string
}
func NewMapValue(objectType ref.Type) *MapValue {
return &MapValue{
typeValue: types.NewTypeValue(objectType.TypeName(), traits.IndexerType|traits.FieldTesterType|traits.IterableType),
values: map[string]ref.Val{},
callbacks: map[string]GetFieldFunc{},
}
}
// Append adds the given field with its name and callback.
func (m *MapValue) Append(name string, callback GetFieldFunc) {
m.knownValues = append(m.knownValues, name)
m.callbacks[name] = callback
}
// Contains checks if the key is known to the map
func (m *MapValue) Contains(key ref.Val) ref.Val {
v, found := m.Find(key)
if v != nil && types.IsUnknownOrError(v) {
return v
}
return types.Bool(found)
}
// Iterator returns an iterator to traverse the map.
func (m *MapValue) Iterator() traits.Iterator {
return &iterator{parent: m, index: 0}
}
// Size returns the number of currently known fields
func (m *MapValue) Size() ref.Val {
return types.Int(len(m.callbacks))
}
// ConvertToNative returns an error because it is disallowed
func (m *MapValue) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, fmt.Errorf("disallowed conversion from %q to %q", m.typeValue.TypeName(), typeDesc.Name())
}
// ConvertToType converts the map to the given type.
// Only its own type and "Type" type are allowed.
func (m *MapValue) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case m.typeValue:
return m
case types.TypeType:
return m.typeValue
}
return types.NewErr("disallowed conversion from %q to %q", m.typeValue.TypeName(), typeVal.TypeName())
}
// Equal returns true if the other object is the same pointer-wise.
func (m *MapValue) Equal(other ref.Val) ref.Val {
otherMap, ok := other.(*MapValue)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(m == otherMap)
}
// Type returns its registered type.
func (m *MapValue) Type() ref.Type {
return m.typeValue
}
// Value is not allowed.
func (m *MapValue) Value() any {
return types.NoSuchOverloadErr()
}
// resolveField resolves the field. Calls the callback if the value is not yet stored.
func (m *MapValue) resolveField(name string) ref.Val {
v, seen := m.values[name]
if seen {
return v
}
f := m.callbacks[name]
v = f(m)
m.values[name] = v
return v
}
func (m *MapValue) Find(key ref.Val) (ref.Val, bool) {
n, ok := key.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(n), true
}
name, ok := cel.Unescape(n.Value().(string))
if !ok {
return nil, false
}
if _, exists := m.callbacks[name]; !exists {
return nil, false
}
return m.resolveField(name), true
}
func (m *MapValue) Get(key ref.Val) ref.Val {
v, found := m.Find(key)
if found {
return v
}
return types.ValOrErr(key, "no such key: %v", key)
}
type iterator struct {
parent *MapValue
index int
}
func (i *iterator) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, fmt.Errorf("disallowed conversion to %q", typeDesc.Name())
}
func (i *iterator) ConvertToType(typeValue ref.Type) ref.Val {
return types.NewErr("disallowed conversion o %q", typeValue.TypeName())
}
func (i *iterator) Equal(other ref.Val) ref.Val {
otherIterator, ok := other.(*iterator)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(otherIterator == i)
}
func (i *iterator) Type() ref.Type {
return types.IteratorType
}
func (i *iterator) Value() any {
return nil
}
func (i *iterator) HasNext() ref.Val {
return types.Bool(i.index < len(i.parent.knownValues))
}
func (i *iterator) Next() ref.Val {
ret := i.parent.Get(types.String(i.parent.knownValues[i.index]))
i.index++
return ret
}
var _ traits.Iterator = (*iterator)(nil)

View File

@@ -0,0 +1,150 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lazy
import (
"fmt"
"testing"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
"k8s.io/apimachinery/pkg/util/version"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/cel/environment"
)
func TestLazyMapType(t *testing.T) {
env, variablesType, err := buildTestEnv()
if err != nil {
t.Fatal(err)
}
variablesMap := NewMapValue(variablesType)
activation := &testActivation{variables: variablesMap}
// add foo as a string
variablesType.Fields["foo"] = apiservercel.NewDeclField("foo", apiservercel.StringType, true, nil, nil)
variablesMap.Append("foo", func(_ *MapValue) ref.Val {
return types.String("foo-string")
})
exp := "variables.foo == 'foo-string'"
v, err := compileAndRun(env, activation, exp)
if err != nil {
t.Fatalf("%q: %v", exp, err)
}
if !v.Value().(bool) {
t.Errorf("expected equal but got non-equal")
}
evalCounter := 0
// add dict as a map constructed from an expression
variablesType.Fields["dict"] = apiservercel.NewDeclField("dict", apiservercel.DynType, true, nil, nil)
variablesMap.Append("dict", func(_ *MapValue) ref.Val {
evalCounter++
v, err := compileAndRun(env, activation, `{"a": "a"}`)
if err != nil {
return types.NewErr(err.Error())
}
return v
})
// iterate the map with .all
exp = `variables.all(n, n != "")`
v, err = compileAndRun(env, activation, exp)
if err != nil {
t.Fatalf("%q: %v", exp, err)
}
if v.Value().(bool) != true {
t.Errorf("%q: wrong result: %v", exp, v.Value())
}
// add unused as a string
variablesType.Fields["unused"] = apiservercel.NewDeclField("unused", apiservercel.StringType, true, nil, nil)
variablesMap.Append("unused", func(_ *MapValue) ref.Val {
t.Fatalf("unused variable must not be evaluated")
return nil
})
exp = "variables.dict.a + ' ' + variables.dict.a + ' ' + variables.foo"
v, err = compileAndRun(env, activation, exp)
if err != nil {
t.Fatalf("%q: %v", exp, err)
}
if v.Value().(string) != "a a foo-string" {
t.Errorf("%q: wrong result: %v", exp, v.Value())
}
if evalCounter != 1 {
t.Errorf("expected eval %d times but got %d", 1, evalCounter)
}
}
type testActivation struct {
variables *MapValue
}
func compileAndRun(env *cel.Env, activation *testActivation, exp string) (ref.Val, error) {
ast, issues := env.Compile(exp)
if issues != nil {
return nil, fmt.Errorf("fail to compile: %v", issues)
}
prog, err := env.Program(ast)
if err != nil {
return nil, fmt.Errorf("cannot create program: %w", err)
}
v, _, err := prog.Eval(activation)
if err != nil {
return nil, fmt.Errorf("cannot eval program: %w", err)
}
return v, nil
}
func buildTestEnv() (*cel.Env, *apiservercel.DeclType, error) {
variablesType := apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, 0)
variablesType.Fields = make(map[string]*apiservercel.DeclField)
envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()).Extend(
environment.VersionedOptions{
IntroducedVersion: version.MajorMinor(1, 28),
EnvOptions: []cel.EnvOption{
cel.Variable("variables", variablesType.CelType()),
},
DeclTypes: []*apiservercel.DeclType{
variablesType,
},
})
if err != nil {
return nil, nil, err
}
// TODO: change to NewExpressions after 1.28
env, err := envSet.Env(environment.StoredExpressions)
return env, variablesType, err
}
func (a *testActivation) ResolveName(name string) (any, bool) {
switch name {
case "variables":
return a.variables, true
default:
return nil, false
}
}
func (a *testActivation) Parent() interpreter.Activation {
return nil
}

View File

@@ -31,6 +31,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct {
FailurePolicy *admissionregistrationv1alpha1.FailurePolicyType `json:"failurePolicy,omitempty"` FailurePolicy *admissionregistrationv1alpha1.FailurePolicyType `json:"failurePolicy,omitempty"`
AuditAnnotations []AuditAnnotationApplyConfiguration `json:"auditAnnotations,omitempty"` AuditAnnotations []AuditAnnotationApplyConfiguration `json:"auditAnnotations,omitempty"`
MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"` MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"`
Variables []VariableApplyConfiguration `json:"variables,omitempty"`
} }
// ValidatingAdmissionPolicySpecApplyConfiguration constructs an declarative configuration of the ValidatingAdmissionPolicySpec type for use with // ValidatingAdmissionPolicySpecApplyConfiguration constructs an declarative configuration of the ValidatingAdmissionPolicySpec type for use with
@@ -101,3 +102,16 @@ func (b *ValidatingAdmissionPolicySpecApplyConfiguration) WithMatchConditions(va
} }
return b return b
} }
// WithVariables adds the given value to the Variables field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Variables field.
func (b *ValidatingAdmissionPolicySpecApplyConfiguration) WithVariables(values ...*VariableApplyConfiguration) *ValidatingAdmissionPolicySpecApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithVariables")
}
b.Variables = append(b.Variables, *values[i])
}
return b
}

View File

@@ -0,0 +1,48 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1alpha1
// VariableApplyConfiguration represents an declarative configuration of the Variable type for use
// with apply.
type VariableApplyConfiguration struct {
Name *string `json:"name,omitempty"`
Expression *string `json:"expression,omitempty"`
}
// VariableApplyConfiguration constructs an declarative configuration of the Variable type for use with
// apply.
func Variable() *VariableApplyConfiguration {
return &VariableApplyConfiguration{}
}
// WithName sets the Name 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 Name field is set to the value of the last call.
func (b *VariableApplyConfiguration) WithName(value string) *VariableApplyConfiguration {
b.Name = &value
return b
}
// WithExpression sets the Expression 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 Expression field is set to the value of the last call.
func (b *VariableApplyConfiguration) WithExpression(value string) *VariableApplyConfiguration {
b.Expression = &value
return b
}

View File

@@ -464,6 +464,14 @@ var schemaYAML = typed.YAMLObject(`types:
elementType: elementType:
namedType: io.k8s.api.admissionregistration.v1alpha1.Validation namedType: io.k8s.api.admissionregistration.v1alpha1.Validation
elementRelationship: atomic elementRelationship: atomic
- name: variables
type:
list:
elementType:
namedType: io.k8s.api.admissionregistration.v1alpha1.Variable
elementRelationship: associative
keys:
- name
- name: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyStatus - name: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyStatus
map: map:
fields: fields:
@@ -497,6 +505,17 @@ var schemaYAML = typed.YAMLObject(`types:
- name: reason - name: reason
type: type:
scalar: string scalar: string
- name: io.k8s.api.admissionregistration.v1alpha1.Variable
map:
fields:
- name: expression
type:
scalar: string
default: ""
- name: name
type:
scalar: string
default: ""
- name: io.k8s.api.admissionregistration.v1beta1.MatchCondition - name: io.k8s.api.admissionregistration.v1beta1.MatchCondition
map: map:
fields: fields:

View File

@@ -171,6 +171,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicyStatusApplyConfiguration{} return &admissionregistrationv1alpha1.ValidatingAdmissionPolicyStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("Validation"): case v1alpha1.SchemeGroupVersion.WithKind("Validation"):
return &admissionregistrationv1alpha1.ValidationApplyConfiguration{} return &admissionregistrationv1alpha1.ValidationApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("Variable"):
return &admissionregistrationv1alpha1.VariableApplyConfiguration{}
// Group=admissionregistration.k8s.io, Version=v1beta1 // Group=admissionregistration.k8s.io, Version=v1beta1
case v1beta1.SchemeGroupVersion.WithKind("MatchCondition"): case v1beta1.SchemeGroupVersion.WithKind("MatchCondition"):

View File

@@ -188,6 +188,66 @@ var _ = SIGDescribe("ValidatingAdmissionPolicy [Privileged:ClusterAdmin][Alpha][
gomega.Expect(warning.Warning).To(gomega.ContainSubstring("found no matching overload for '_+_' applied to '(string, int)'")) gomega.Expect(warning.Warning).To(gomega.ContainSubstring("found no matching overload for '_+_' applied to '(string, int)'"))
}) })
}) })
ginkgo.It("should allow expressions to refer variables.", func(ctx context.Context) {
ginkgo.By("creating a policy with variables", func() {
policy := newValidatingAdmissionPolicyBuilder(f.UniqueName+".policy.example.com").
MatchUniqueNamespace(f.UniqueName).
StartResourceRule().
MatchResource([]string{"apps"}, []string{"v1"}, []string{"deployments"}).
EndResourceRule().
WithVariable(admissionregistrationv1alpha1.Variable{
Name: "replicas",
Expression: "object.spec.replicas",
}).
WithVariable(admissionregistrationv1alpha1.Variable{
Name: "replicasReminder", // a bit artificial but good for testing purpose
Expression: "variables.replicas % 2",
}).
WithValidation(admissionregistrationv1alpha1.Validation{
Expression: "variables.replicas > 1 && variables.replicasReminder == 1",
}).
Build()
policy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, policy, metav1.CreateOptions{})
framework.ExpectNoError(err, "create policy")
ginkgo.DeferCleanup(func(ctx context.Context, name string) error {
return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Delete(ctx, name, metav1.DeleteOptions{})
}, policy.Name)
binding := createBinding(f.UniqueName+".binding.example.com", f.UniqueName, policy.Name)
binding, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(ctx, binding, metav1.CreateOptions{})
framework.ExpectNoError(err, "create policy binding")
ginkgo.DeferCleanup(func(ctx context.Context, name string) error {
return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Delete(ctx, name, metav1.DeleteOptions{})
}, binding.Name)
})
ginkgo.By("waiting until the marker is denied", func() {
deployment := basicDeployment("marker-deployment", 1)
err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
_, err = client.AppsV1().Deployments(f.Namespace.Name).Create(ctx, deployment, metav1.CreateOptions{})
defer client.AppsV1().Deployments(f.Namespace.Name).Delete(ctx, deployment.Name, metav1.DeleteOptions{})
if err != nil {
if apierrors.IsInvalid(err) {
return true, nil
}
return false, err
}
return false, nil
})
framework.ExpectNoError(err, "wait for marker")
})
ginkgo.By("testing a replicated Deployment to be allowed", func() {
deployment := basicDeployment("replicated", 3)
deployment, err := client.AppsV1().Deployments(f.Namespace.Name).Create(ctx, deployment, metav1.CreateOptions{})
defer client.AppsV1().Deployments(f.Namespace.Name).Delete(ctx, deployment.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err, "create replicated Deployment")
})
ginkgo.By("testing a non-replicated ReplicaSet not to be denied", func() {
replicaSet := basicReplicaSet("non-replicated", 1)
replicaSet, err := client.AppsV1().ReplicaSets(f.Namespace.Name).Create(ctx, replicaSet, metav1.CreateOptions{})
defer client.AppsV1().ReplicaSets(f.Namespace.Name).Delete(ctx, replicaSet.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err, "create non-replicated ReplicaSet")
})
})
}) })
func createBinding(bindingName string, uniqueLabel string, policyName string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding { func createBinding(bindingName string, uniqueLabel string, policyName string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding {
@@ -332,6 +392,11 @@ func (b *validatingAdmissionPolicyBuilder) WithValidation(validation admissionre
return b return b
} }
func (b *validatingAdmissionPolicyBuilder) WithVariable(variable admissionregistrationv1alpha1.Variable) *validatingAdmissionPolicyBuilder {
b.policy.Spec.Variables = append(b.policy.Spec.Variables, variable)
return b
}
func (b *validatingAdmissionPolicyBuilder) Build() *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { func (b *validatingAdmissionPolicyBuilder) Build() *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
return b.policy return b.policy
} }

1
vendor/modules.txt vendored
View File

@@ -1488,6 +1488,7 @@ k8s.io/apiserver/pkg/authorization/union
k8s.io/apiserver/pkg/cel k8s.io/apiserver/pkg/cel
k8s.io/apiserver/pkg/cel/common k8s.io/apiserver/pkg/cel/common
k8s.io/apiserver/pkg/cel/environment k8s.io/apiserver/pkg/cel/environment
k8s.io/apiserver/pkg/cel/lazy
k8s.io/apiserver/pkg/cel/library k8s.io/apiserver/pkg/cel/library
k8s.io/apiserver/pkg/cel/metrics k8s.io/apiserver/pkg/cel/metrics
k8s.io/apiserver/pkg/cel/openapi k8s.io/apiserver/pkg/cel/openapi