mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-13 13:55:41 +00:00
ValidatingAdmissionPolicy: support namespace access (#118267)
* Support namespace access from cel expression in validatingadmissionpolicy. * Whitelist the exposed fields in namespace object and add test * better handling of cluster-scoped resources. * [API REVIEW] namespaceObject in Expression doc. * compatibility with composition. * generated: ./hack/update-codegen.sh && ./hack/update-openapi-spec.sh * workaround namespace of namespace is unexpectedly set. * basic test coverage for namespaceObject. --------- Co-authored-by: Jiahui Feng <jhf@google.com>
This commit is contained in:
parent
d5a653fd87
commit
13172cba5c
2
api/openapi-spec/swagger.json
generated
2
api/openapi-spec/swagger.json
generated
@ -817,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. - '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.",
|
"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. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - '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": {
|
||||||
|
@ -552,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. - '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.",
|
"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. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - '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": {
|
||||||
|
@ -281,6 +281,7 @@ 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.
|
||||||
|
//'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources.
|
||||||
//'variables' - Map of composited variables, from its name to its lazily evaluated value.
|
//'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'
|
// 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.
|
||||||
|
2
pkg/generated/openapi/zz_generated.openapi.go
generated
2
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -2757,7 +2757,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. - '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.",
|
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. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - '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: "",
|
||||||
|
@ -474,6 +474,7 @@ 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.
|
||||||
|
// - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources.
|
||||||
// - 'variables' - Map of composited variables, from its name to its lazily evaluated value.
|
// - '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'.
|
// 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.
|
||||||
|
@ -242,6 +242,7 @@ 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.
|
||||||
|
// - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources.
|
||||||
// - 'variables' - Map of composited variables, from its name to its lazily evaluated value.
|
// - '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'.
|
// 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.
|
||||||
|
@ -179,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. - '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.",
|
"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. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - '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)+\")\"",
|
||||||
|
@ -33,6 +33,7 @@ const (
|
|||||||
OldObjectVarName = "oldObject"
|
OldObjectVarName = "oldObject"
|
||||||
ParamsVarName = "params"
|
ParamsVarName = "params"
|
||||||
RequestVarName = "request"
|
RequestVarName = "request"
|
||||||
|
NamespaceVarName = "namespaceObject"
|
||||||
AuthorizerVarName = "authorizer"
|
AuthorizerVarName = "authorizer"
|
||||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||||
VariableVarName = "variables"
|
VariableVarName = "variables"
|
||||||
@ -85,6 +86,56 @@ func BuildRequestType() *apiservercel.DeclType {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildNamespaceType generates a DeclType for Namespace.
|
||||||
|
// Certain nested fields in Namespace (e.g. managedFields, ownerReferences etc.) are omitted in the generated DeclType
|
||||||
|
// by design.
|
||||||
|
func BuildNamespaceType() *apiservercel.DeclType {
|
||||||
|
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||||
|
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||||
|
}
|
||||||
|
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||||
|
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||||
|
for _, f := range fields {
|
||||||
|
result[f.Name] = f
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
specType := apiservercel.NewObjectType("kubernetes.NamespaceSpec", fields(
|
||||||
|
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||||
|
))
|
||||||
|
conditionType := apiservercel.NewObjectType("kubernetes.NamespaceCondition", fields(
|
||||||
|
field("status", apiservercel.StringType, true),
|
||||||
|
field("type", apiservercel.StringType, true),
|
||||||
|
field("lastTransitionTime", apiservercel.TimestampType, true),
|
||||||
|
field("message", apiservercel.StringType, true),
|
||||||
|
field("reason", apiservercel.StringType, true),
|
||||||
|
))
|
||||||
|
statusType := apiservercel.NewObjectType("kubernetes.NamespaceStatus", fields(
|
||||||
|
field("conditions", apiservercel.NewListType(conditionType, -1), true),
|
||||||
|
field("phase", apiservercel.StringType, true),
|
||||||
|
))
|
||||||
|
metadataType := apiservercel.NewObjectType("kubernetes.NamespaceMetadata", fields(
|
||||||
|
field("name", apiservercel.StringType, true),
|
||||||
|
field("generateName", apiservercel.StringType, true),
|
||||||
|
field("namespace", apiservercel.StringType, true),
|
||||||
|
field("labels", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||||
|
field("annotations", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||||
|
field("UID", apiservercel.StringType, true),
|
||||||
|
field("creationTimestamp", apiservercel.TimestampType, true),
|
||||||
|
field("deletionGracePeriodSeconds", apiservercel.IntType, true),
|
||||||
|
field("deletionTimestamp", apiservercel.TimestampType, true),
|
||||||
|
field("generation", apiservercel.IntType, true),
|
||||||
|
field("resourceVersion", apiservercel.StringType, true),
|
||||||
|
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||||
|
))
|
||||||
|
return apiservercel.NewObjectType("kubernetes.Namespace", fields(
|
||||||
|
field("metadata", metadataType, true),
|
||||||
|
field("spec", specType, true),
|
||||||
|
field("status", statusType, true),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// CompilationResult represents a compiled validations expression.
|
// CompilationResult represents a compiled validations expression.
|
||||||
type CompilationResult struct {
|
type CompilationResult struct {
|
||||||
Program cel.Program
|
Program cel.Program
|
||||||
@ -168,6 +219,7 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op
|
|||||||
|
|
||||||
func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||||
requestType := BuildRequestType()
|
requestType := BuildRequestType()
|
||||||
|
namespaceType := BuildNamespaceType()
|
||||||
envs := make(variableDeclEnvs, 4) // since the number of variable combinations is small, pre-build a environment for each
|
envs := make(variableDeclEnvs, 4) // since the number of variable combinations is small, pre-build a environment for each
|
||||||
for _, hasParams := range []bool{false, true} {
|
for _, hasParams := range []bool{false, true} {
|
||||||
for _, hasAuthorizer := range []bool{false, true} {
|
for _, hasAuthorizer := range []bool{false, true} {
|
||||||
@ -183,6 +235,7 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
|||||||
envOpts = append(envOpts,
|
envOpts = append(envOpts,
|
||||||
cel.Variable(ObjectVarName, cel.DynType),
|
cel.Variable(ObjectVarName, cel.DynType),
|
||||||
cel.Variable(OldObjectVarName, cel.DynType),
|
cel.Variable(OldObjectVarName, cel.DynType),
|
||||||
|
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
||||||
cel.Variable(RequestVarName, requestType.CelType()))
|
cel.Variable(RequestVarName, requestType.CelType()))
|
||||||
|
|
||||||
extended, err := baseEnv.Extend(
|
extended, err := baseEnv.Extend(
|
||||||
@ -192,6 +245,7 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
|||||||
IntroducedVersion: version.MajorMinor(1, 0),
|
IntroducedVersion: version.MajorMinor(1, 0),
|
||||||
EnvOptions: envOpts,
|
EnvOptions: envOpts,
|
||||||
DeclTypes: []*apiservercel.DeclType{
|
DeclTypes: []*apiservercel.DeclType{
|
||||||
|
namespaceType,
|
||||||
requestType,
|
requestType,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,11 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
|||||||
expressions: []string{"object.foo < params.x"},
|
expressions: []string{"object.foo < params.x"},
|
||||||
hasParams: true,
|
hasParams: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "namespaceObject",
|
||||||
|
expressions: []string{"namespaceObject.metadata.name.startsWith('test')"},
|
||||||
|
hasParams: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "without params",
|
name: "without params",
|
||||||
errorExpressions: map[string]string{"object.foo < params.x": "undeclared reference to 'params'"},
|
errorExpressions: map[string]string{"object.foo < params.x": "undeclared reference to 'params'"},
|
||||||
@ -135,6 +140,41 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
|||||||
},
|
},
|
||||||
envType: environment.NewExpressions,
|
envType: environment.NewExpressions,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "valid namespaceObject",
|
||||||
|
expressions: []string{
|
||||||
|
"namespaceObject.metadata != null",
|
||||||
|
"namespaceObject.metadata.name == 'test'",
|
||||||
|
"namespaceObject.metadata.generateName == 'test'",
|
||||||
|
"namespaceObject.metadata.namespace == 'testns'",
|
||||||
|
"'test' in namespaceObject.metadata.labels",
|
||||||
|
"'test' in namespaceObject.metadata.annotations",
|
||||||
|
"namespaceObject.metadata.UID == '12345'",
|
||||||
|
"type(namespaceObject.metadata.creationTimestamp) == google.protobuf.Timestamp",
|
||||||
|
"type(namespaceObject.metadata.deletionTimestamp) == google.protobuf.Timestamp",
|
||||||
|
"namespaceObject.metadata.deletionGracePeriodSeconds == 5",
|
||||||
|
"namespaceObject.metadata.generation == 2",
|
||||||
|
"namespaceObject.metadata.resourceVersion == 'v1'",
|
||||||
|
"namespaceObject.metadata.finalizers[0] == 'testEnv'",
|
||||||
|
"namespaceObject.spec.finalizers[0] == 'testEnv'",
|
||||||
|
"namespaceObject.status.phase == 'Active'",
|
||||||
|
"namespaceObject.status.conditions[0].status == 'True'",
|
||||||
|
"namespaceObject.status.conditions[0].type == 'NamespaceDeletionDiscoveryFailure'",
|
||||||
|
"type(namespaceObject.status.conditions[0].lastTransitionTime) == google.protobuf.Timestamp",
|
||||||
|
"namespaceObject.status.conditions[0].message == 'Unknow'",
|
||||||
|
"namespaceObject.status.conditions[0].reason == 'Invalid'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid namespaceObject",
|
||||||
|
errorExpressions: map[string]string{
|
||||||
|
"namespaceObject.foo1 == 'nope'": "undefined field 'foo1'",
|
||||||
|
"namespaceObject.metadata.foo2 == 'nope'": "undefined field 'foo2'",
|
||||||
|
"namespaceObject.spec.foo3 == 'nope'": "undefined field 'foo3'",
|
||||||
|
"namespaceObject.status.foo4 == 'nope'": "undefined field 'foo4'",
|
||||||
|
"namespaceObject.status.conditions[0].foo5 == 'nope'": "undefined field 'foo5'",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the test library, which includes the test() function in the storage environment during test
|
// Include the test library, which includes the test() function in the storage environment during test
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
|
||||||
v1 "k8s.io/api/admission/v1"
|
v1 "k8s.io/api/admission/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
@ -149,9 +150,9 @@ func (c *compositionContext) Variables(activation any) ref.Val {
|
|||||||
return lazyMap
|
return lazyMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||||
ctx = f.compositionEnv.CreateContext(ctx)
|
ctx = f.compositionEnv.CreateContext(ctx)
|
||||||
return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, runtimeCELCostBudget)
|
return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compositionContext) reportCost(cost int64) {
|
func (c *compositionContext) reportCost(cost int64) {
|
||||||
|
@ -141,7 +141,7 @@ func TestCompositedPolicies(t *testing.T) {
|
|||||||
if costBudget == 0 {
|
if costBudget == 0 {
|
||||||
costBudget = celconfig.RuntimeCELCostBudget
|
costBudget = celconfig.RuntimeCELCostBudget
|
||||||
}
|
}
|
||||||
result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, costBudget)
|
result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, nil, costBudget)
|
||||||
if !tc.expectErr && err != nil {
|
if !tc.expectErr && err != nil {
|
||||||
t.Fatalf("failed evaluation: %v", err)
|
t.Fatalf("failed evaluation: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -46,7 +47,7 @@ func NewFilterCompiler(env *environment.EnvSet) FilterCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type evaluationActivation struct {
|
type evaluationActivation struct {
|
||||||
object, oldObject, params, request, authorizer, requestResourceAuthorizer, variables interface{}
|
object, oldObject, params, request, namespace, 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
|
||||||
@ -61,6 +62,8 @@ func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
|||||||
return a.params, true // params may be null
|
return a.params, true // params may be null
|
||||||
case RequestVarName:
|
case RequestVarName:
|
||||||
return a.request, true
|
return a.request, true
|
||||||
|
case NamespaceVarName:
|
||||||
|
return a.namespace, true
|
||||||
case AuthorizerVarName:
|
case AuthorizerVarName:
|
||||||
return a.authorizer, a.authorizer != nil
|
return a.authorizer, a.authorizer != nil
|
||||||
case RequestResourceAuthorizerVarName:
|
case RequestResourceAuthorizerVarName:
|
||||||
@ -126,7 +129,7 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
|||||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||||
// errors per evaluation are returned on the Evaluation object
|
// errors per evaluation are returned on the Evaluation object
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||||
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||||
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
||||||
var err error
|
var err error
|
||||||
@ -156,11 +159,16 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
|
namespaceVal, err := objectToResolveVal(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
va := &evaluationActivation{
|
va := &evaluationActivation{
|
||||||
object: objectVal,
|
object: objectVal,
|
||||||
oldObject: oldObjectVal,
|
oldObject: oldObjectVal,
|
||||||
params: paramsVal,
|
params: paramsVal,
|
||||||
request: requestVal.Object,
|
request: requestVal.Object,
|
||||||
|
namespace: namespaceVal,
|
||||||
authorizer: authorizerVal,
|
authorizer: authorizerVal,
|
||||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||||
}
|
}
|
||||||
@ -307,6 +315,33 @@ func CreateAdmissionRequest(attr admission.Attributes) *admissionv1.AdmissionReq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNamespaceObject creates a Namespace object that is suitable for the CEL evaluation.
|
||||||
|
// If the namespace is nil, CreateNamespaceObject returns nil
|
||||||
|
func CreateNamespaceObject(namespace *v1.Namespace) *v1.Namespace {
|
||||||
|
if namespace == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.Namespace{
|
||||||
|
Status: namespace.Status,
|
||||||
|
Spec: namespace.Spec,
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: namespace.Name,
|
||||||
|
GenerateName: namespace.GenerateName,
|
||||||
|
Namespace: namespace.Namespace,
|
||||||
|
UID: namespace.UID,
|
||||||
|
ResourceVersion: namespace.ResourceVersion,
|
||||||
|
Generation: namespace.Generation,
|
||||||
|
CreationTimestamp: namespace.CreationTimestamp,
|
||||||
|
DeletionTimestamp: namespace.DeletionTimestamp,
|
||||||
|
DeletionGracePeriodSeconds: namespace.DeletionGracePeriodSeconds,
|
||||||
|
Labels: namespace.Labels,
|
||||||
|
Annotations: namespace.Annotations,
|
||||||
|
Finalizers: namespace.Finalizers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CompilationErrors returns a list of all the errors from the compilation of the evaluator
|
// CompilationErrors returns a list of all the errors from the compilation of the evaluator
|
||||||
func (e *filter) CompilationErrors() []error {
|
func (e *filter) CompilationErrors() []error {
|
||||||
compilationErrors := []error{}
|
compilationErrors := []error{}
|
||||||
|
@ -149,6 +149,28 @@ func TestFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsObject := &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"env": "test",
|
||||||
|
"foo": "demo",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"annotation1": "testAnnotation1",
|
||||||
|
},
|
||||||
|
Finalizers: []string{"f1"},
|
||||||
|
},
|
||||||
|
Spec: corev1.NamespaceSpec{
|
||||||
|
Finalizers: []corev1.FinalizerName{
|
||||||
|
corev1.FinalizerKubernetes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: corev1.NamespaceStatus{
|
||||||
|
Phase: corev1.NamespaceActive,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var nilUnstructured *unstructured.Unstructured
|
var nilUnstructured *unstructured.Unstructured
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
@ -159,6 +181,7 @@ func TestFilter(t *testing.T) {
|
|||||||
hasParamKind bool
|
hasParamKind bool
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
testPerCallLimit uint64
|
testPerCallLimit uint64
|
||||||
|
namespaceObject *corev1.Namespace
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid syntax for object",
|
name: "valid syntax for object",
|
||||||
@ -674,6 +697,64 @@ func TestFilter(t *testing.T) {
|
|||||||
params: crdParams,
|
params: crdParams,
|
||||||
testPerCallLimit: 1,
|
testPerCallLimit: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test namespaceObject",
|
||||||
|
validations: []ExpressionAccessor{
|
||||||
|
&condition{
|
||||||
|
Expression: "namespaceObject.metadata.name == 'test'",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "'env' in namespaceObject.metadata.labels && namespaceObject.metadata.labels.env == 'test'",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "('fake' in namespaceObject.metadata.labels) && namespaceObject.metadata.labels.fake == 'test'",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "namespaceObject.spec.finalizers[0] == 'kubernetes'",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "namespaceObject.status.phase == 'Active'",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "size(namespaceObject.metadata.managedFields) == 1",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "size(namespaceObject.metadata.ownerReferences) == 1",
|
||||||
|
},
|
||||||
|
&condition{
|
||||||
|
Expression: "'env' in namespaceObject.metadata.annotations",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attributes: newValidAttribute(&podObject, false),
|
||||||
|
results: []EvaluationResult{
|
||||||
|
{
|
||||||
|
EvalResult: celtypes.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EvalResult: celtypes.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EvalResult: celtypes.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EvalResult: celtypes.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EvalResult: celtypes.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Error: errors.New("undefined field 'managedFields'"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Error: errors.New("undefined field 'ownerReferences'"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EvalResult: celtypes.False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasParamKind: false,
|
||||||
|
namespaceObject: nsObject,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@ -706,7 +787,7 @@ func TestFilter(t *testing.T) {
|
|||||||
|
|
||||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, celconfig.RuntimeCELCostBudget)
|
evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, CreateNamespaceObject(tc.namespaceObject), celconfig.RuntimeCELCostBudget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -852,7 +933,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, tc.testRuntimeCELCostBudget)
|
evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, nil, tc.testRuntimeCELCostBudget)
|
||||||
if tc.exceedBudget && err == nil {
|
if tc.exceedBudget && err == nil {
|
||||||
t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil")
|
t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil")
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
|
||||||
v1 "k8s.io/api/admission/v1"
|
v1 "k8s.io/api/admission/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
@ -87,7 +88,7 @@ type Filter interface {
|
|||||||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
// If cost budget is calculated, the filter should return the remaining budget.
|
// If cost budget is calculated, the filter should return the remaining budget.
|
||||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
||||||
|
|
||||||
// CompilationErrors returns a list of errors from the compilation of the evaluator
|
// CompilationErrors returns a list of errors from the compilation of the evaluator
|
||||||
CompilationErrors() []error
|
CompilationErrors() []error
|
||||||
|
@ -253,7 +253,7 @@ type fakeFilter struct {
|
|||||||
keyId string
|
keyId string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs cel.OptionalVariableBindings, runtimeCELCostBudget int64) ([]cel.EvaluationResult, int64, error) {
|
func (f *fakeFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs cel.OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]cel.EvaluationResult, int64, error) {
|
||||||
return []cel.EvaluationResult{}, 0, nil
|
return []cel.EvaluationResult{}, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,10 +265,10 @@ var _ Validator = &fakeValidator{}
|
|||||||
|
|
||||||
type fakeValidator struct {
|
type fakeValidator struct {
|
||||||
validationFilter, auditAnnotationFilter, messageFilter *fakeFilter
|
validationFilter, auditAnnotationFilter, messageFilter *fakeFilter
|
||||||
ValidateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
ValidateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult) {
|
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult) {
|
||||||
//Key must be something that we can decipher from the inputs to Validate so using message which will be on the validationCondition object of evalResult
|
//Key must be something that we can decipher from the inputs to Validate so using message which will be on the validationCondition object of evalResult
|
||||||
var key string
|
var key string
|
||||||
if len(definition.Spec.Validations) > 0 {
|
if len(definition.Spec.Validations) > 0 {
|
||||||
@ -285,8 +285,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmiss
|
|||||||
validatorMap[key] = f
|
validatorMap[key] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return f.ValidateFunc(ctx, versionedAttr, versionedParams, runtimeCELCostBudget, authz)
|
return f.ValidateFunc(ctx, versionedAttr, versionedParams, namespace, runtimeCELCostBudget, authz)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Matcher = &fakeMatcher{}
|
var _ Matcher = &fakeMatcher{}
|
||||||
@ -295,6 +295,10 @@ func (f *fakeMatcher) ValidateInitialization() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeMatcher) GetNamespace(name string) (*v1.Namespace, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
type fakeMatcher struct {
|
type fakeMatcher struct {
|
||||||
DefaultMatch bool
|
DefaultMatch bool
|
||||||
DefinitionMatchFuncs map[namespacedName]func(*v1alpha1.ValidatingAdmissionPolicy, admission.Attributes) bool
|
DefinitionMatchFuncs map[namespacedName]func(*v1alpha1.ValidatingAdmissionPolicy, admission.Attributes) bool
|
||||||
@ -770,7 +774,7 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -840,7 +844,7 @@ func TestDefinitionDoesntMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -953,7 +957,7 @@ func TestReconfigureBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1063,7 +1067,7 @@ func TestRemoveDefinition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1132,7 +1136,7 @@ func TestRemoveBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1242,7 +1246,7 @@ func TestInvalidParamSourceInstanceName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1310,7 +1314,7 @@ func TestEmptyParamSource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1412,7 +1416,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
evaluations1.Add(1)
|
evaluations1.Add(1)
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
@ -1431,7 +1435,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
evaluations2.Add(1)
|
evaluations2.Add(1)
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
@ -1541,7 +1545,7 @@ func TestNativeTypeParam(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
evaluations.Add(1)
|
evaluations.Add(1)
|
||||||
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
@ -1623,7 +1627,7 @@ func TestAuditValidationAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1694,7 +1698,7 @@ func TestWarnValidationAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1753,7 +1757,7 @@ func TestAllValidationActions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1824,7 +1828,7 @@ func TestAuditAnnotations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
o, err := meta.Accessor(versionedParams)
|
o, err := meta.Accessor(versionedParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -319,6 +320,23 @@ func (c *celAdmissionController) Validate(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var namespace *v1.Namespace
|
||||||
|
namespaceName := a.GetNamespace()
|
||||||
|
|
||||||
|
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||||
|
// unset it if the incoming object is a namespace
|
||||||
|
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||||
|
namespaceName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is cluster scoped, namespaceName will be empty
|
||||||
|
// Otherwise, get the Namespace resource.
|
||||||
|
if namespaceName != "" {
|
||||||
|
namespace, err = c.policyController.matcher.GetNamespace(namespaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if versionedAttr == nil {
|
if versionedAttr == nil {
|
||||||
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
||||||
@ -330,7 +348,7 @@ func (c *celAdmissionController) Validate(
|
|||||||
versionedAttr = va
|
versionedAttr = va
|
||||||
}
|
}
|
||||||
|
|
||||||
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, celconfig.RuntimeCELCostBudget, authz)
|
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, namespace, celconfig.RuntimeCELCostBudget, authz)
|
||||||
|
|
||||||
for i, decision := range validationResult.Decisions {
|
for i, decision := range validationResult.Decisions {
|
||||||
switch decision.Action {
|
switch decision.Action {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
celgo "github.com/google/cel-go/cel"
|
celgo "github.com/google/cel-go/cel"
|
||||||
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -90,6 +91,10 @@ type Matcher interface {
|
|||||||
// BindingMatches says whether this policy definition matches the provided admission
|
// BindingMatches says whether this policy definition matches the provided admission
|
||||||
// resource request
|
// resource request
|
||||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error)
|
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error)
|
||||||
|
|
||||||
|
// GetNamespace retrieves the Namespace resource by the given name. The name may be empty, in which case
|
||||||
|
// GetNamespace must return nil, nil
|
||||||
|
GetNamespace(name string) (*corev1.Namespace, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateResult defines the result of a Validator.Validate operation.
|
// ValidateResult defines the result of a Validator.Validate operation.
|
||||||
@ -104,5 +109,5 @@ type ValidateResult struct {
|
|||||||
type Validator interface {
|
type Validator interface {
|
||||||
// Validate is used to take cel evaluations and convert into decisions
|
// Validate is used to take cel evaluations and convert into decisions
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package validatingadmissionpolicy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -76,3 +77,7 @@ func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInter
|
|||||||
isMatch, _, err := c.Matcher.Matches(a, o, &criteria)
|
isMatch, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||||
return isMatch, err
|
return isMatch, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||||
|
return c.Matcher.GetNamespace(name)
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
v1 "k8s.io/api/admissionregistration/v1"
|
v1 "k8s.io/api/admissionregistration/v1"
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
@ -44,6 +45,10 @@ type Matcher struct {
|
|||||||
objectMatcher *object.Matcher
|
objectMatcher *object.Matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||||
|
return m.namespaceMatcher.GetNamespace(name)
|
||||||
|
}
|
||||||
|
|
||||||
// NewMatcher initialize the matcher with dependencies requires
|
// NewMatcher initialize the matcher with dependencies requires
|
||||||
func NewMatcher(
|
func NewMatcher(
|
||||||
namespaceLister listersv1.NamespaceLister,
|
namespaceLister listersv1.NamespaceLister,
|
||||||
|
@ -347,10 +347,15 @@ func sortGVKList(list []schema.GroupVersionKind) []schema.GroupVersionKind {
|
|||||||
func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env, error) {
|
func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env, error) {
|
||||||
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
||||||
requestType := plugincel.BuildRequestType()
|
requestType := plugincel.BuildRequestType()
|
||||||
|
namespaceType := plugincel.BuildNamespaceType()
|
||||||
|
|
||||||
var varOpts []cel.EnvOption
|
var varOpts []cel.EnvOption
|
||||||
var declTypes []*apiservercel.DeclType
|
var declTypes []*apiservercel.DeclType
|
||||||
|
|
||||||
|
// namespace, hand-crafted type
|
||||||
|
declTypes = append(declTypes, namespaceType)
|
||||||
|
varOpts = append(varOpts, createVariableOpts(namespaceType, plugincel.NamespaceVarName)...)
|
||||||
|
|
||||||
// request, hand-crafted type
|
// request, hand-crafted type
|
||||||
declTypes = append(declTypes, requestType)
|
declTypes = append(declTypes, requestType)
|
||||||
varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
|
varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
celtypes "github.com/google/cel-go/common/types"
|
celtypes "github.com/google/cel-go/common/types"
|
||||||
|
|
||||||
v1 "k8s.io/api/admissionregistration/v1"
|
v1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
@ -70,7 +71,8 @@ func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnota
|
|||||||
|
|
||||||
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
|
||||||
|
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
var f v1.FailurePolicyType
|
var f v1.FailurePolicyType
|
||||||
if v.failPolicy == nil {
|
if v.failPolicy == nil {
|
||||||
f = v1.Fail
|
f = v1.Fail
|
||||||
@ -101,7 +103,9 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
||||||
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||||
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
|
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
|
||||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, runtimeCELCostBudget)
|
// Decide which fields are exposed
|
||||||
|
ns := cel.CreateNamespaceObject(namespace)
|
||||||
|
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, ns, runtimeCELCostBudget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
@ -114,7 +118,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
decisions := make([]PolicyDecision, len(evalResults))
|
decisions := make([]PolicyDecision, len(evalResults))
|
||||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, remainingBudget)
|
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, ns, remainingBudget)
|
||||||
for i, evalResult := range evalResults {
|
for i, evalResult := range evalResults {
|
||||||
var decision = &decisions[i]
|
var decision = &decisions[i]
|
||||||
// TODO: move this to generics
|
// TODO: move this to generics
|
||||||
@ -191,7 +195,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||||||
}
|
}
|
||||||
|
|
||||||
options := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
options := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||||
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), options, runtimeCELCostBudget)
|
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), options, namespace, runtimeCELCostBudget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
v1 "k8s.io/api/admissionregistration/v1"
|
v1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -47,7 +48,7 @@ type fakeCelFilter struct {
|
|||||||
throwError bool
|
throwError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeCelFilter) ForInput(_ context.Context, _ *admission.VersionedAttributes, _ *admissionv1.AdmissionRequest, _ cel.OptionalVariableBindings, costBudget int64) ([]cel.EvaluationResult, int64, error) {
|
func (f *fakeCelFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, optionalVars cel.OptionalVariableBindings, namespace *corev1.Namespace, costBudget int64) ([]cel.EvaluationResult, int64, error) {
|
||||||
if costBudget <= 0 { // this filter will cost 1, so cost = 0 means fail.
|
if costBudget <= 0 { // this filter will cost 1, so cost = 0 means fail.
|
||||||
return nil, -1, &apiservercel.Error{
|
return nil, -1, &apiservercel.Error{
|
||||||
Type: apiservercel.ErrorTypeInvalid,
|
Type: apiservercel.ErrorTypeInvalid,
|
||||||
@ -892,7 +893,7 @@ func TestValidate(t *testing.T) {
|
|||||||
if tc.costBudget != 0 {
|
if tc.costBudget != 0 {
|
||||||
budget = tc.costBudget
|
budget = tc.costBudget
|
||||||
}
|
}
|
||||||
validateResult := v.Validate(ctx, fakeVersionedAttr, nil, budget, nil)
|
validateResult := v.Validate(ctx, fakeVersionedAttr, nil, nil, budget, nil)
|
||||||
|
|
||||||
require.Equal(t, len(validateResult.Decisions), len(tc.policyDecision))
|
require.Equal(t, len(validateResult.Decisions), len(tc.policyDecision))
|
||||||
|
|
||||||
@ -944,7 +945,7 @@ func TestContextCanceled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
cancel()
|
cancel()
|
||||||
validationResult := v.Validate(ctx, fakeVersionedAttr, nil, celconfig.RuntimeCELCostBudget, nil)
|
validationResult := v.Validate(ctx, fakeVersionedAttr, nil, nil, celconfig.RuntimeCELCostBudget, nil)
|
||||||
if len(validationResult.Decisions) != 1 || !strings.Contains(validationResult.Decisions[0].Message, "operation interrupted") {
|
if len(validationResult.Decisions) != 1 || !strings.Contains(validationResult.Decisions[0].Message, "operation interrupted") {
|
||||||
t.Errorf("Expected 'operation interrupted' but got %v", validationResult.Decisions)
|
t.Errorf("Expected 'operation interrupted' but got %v", validationResult.Decisions)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedA
|
|||||||
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
|
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
|
||||||
VersionedParams: versionedParams,
|
VersionedParams: versionedParams,
|
||||||
Authorizer: authz,
|
Authorizer: authz,
|
||||||
}, celconfig.RuntimeCELCostBudgetMatchConditions)
|
}, nil, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
api "k8s.io/api/core/v1"
|
||||||
|
|
||||||
v1 "k8s.io/api/admissionregistration/v1"
|
v1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
|
||||||
celtypes "github.com/google/cel-go/common/types"
|
celtypes "github.com/google/cel-go/common/types"
|
||||||
@ -40,7 +42,7 @@ type fakeCelFilter struct {
|
|||||||
throwError bool
|
throwError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeCelFilter) ForInput(context.Context, *admission.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, int64) ([]cel.EvaluationResult, int64, error) {
|
func (f *fakeCelFilter) ForInput(context.Context, *admission.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, *api.Namespace, int64) ([]cel.EvaluationResult, int64, error) {
|
||||||
if f.throwError {
|
if f.throwError {
|
||||||
return nil, 0, errors.New("test error")
|
return nil, 0, errors.New("test error")
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -42,6 +44,10 @@ type Matcher struct {
|
|||||||
Client clientset.Interface
|
Client clientset.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Matcher) GetNamespace(name string) (*v1.Namespace, error) {
|
||||||
|
return m.NamespaceLister.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate checks if the Matcher has a NamespaceLister and Client.
|
// Validate checks if the Matcher has a NamespaceLister and Client.
|
||||||
func (m *Matcher) Validate() error {
|
func (m *Matcher) Validate() error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
@ -72,6 +72,10 @@ var _ = SIGDescribe("ValidatingAdmissionPolicy [Privileged:ClusterAdmin][Alpha][
|
|||||||
Expression: "object.spec.replicas > 1",
|
Expression: "object.spec.replicas > 1",
|
||||||
MessageExpression: "'wants replicas > 1, got ' + object.spec.replicas",
|
MessageExpression: "'wants replicas > 1, got ' + object.spec.replicas",
|
||||||
}).
|
}).
|
||||||
|
WithValidation(admissionregistrationv1alpha1.Validation{
|
||||||
|
Expression: "namespaceObject.metadata.name == '" + f.UniqueName + "'",
|
||||||
|
Message: "Internal error! Other namespace should not be allowed.",
|
||||||
|
}).
|
||||||
Build()
|
Build()
|
||||||
policy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, policy, metav1.CreateOptions{})
|
policy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, policy, metav1.CreateOptions{})
|
||||||
framework.ExpectNoError(err, "create policy")
|
framework.ExpectNoError(err, "create policy")
|
||||||
|
@ -264,6 +264,21 @@ func Test_ValidateNamespace_NoParams(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: "",
|
err: "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with check against namespaceObject",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "namespaceObject == null", // because namespace itself is cluster-scoped.
|
||||||
|
},
|
||||||
|
}, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user