mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 12:32:03 +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.",
|
||||
"properties": {
|
||||
"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"
|
||||
},
|
||||
"message": {
|
||||
|
@ -552,7 +552,7 @@
|
||||
"properties": {
|
||||
"expression": {
|
||||
"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"
|
||||
},
|
||||
"message": {
|
||||
|
@ -281,6 +281,7 @@ type Validation struct {
|
||||
//'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.
|
||||
// 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.
|
||||
|
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{
|
||||
"expression": {
|
||||
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: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
|
@ -474,6 +474,7 @@ message Validation {
|
||||
// - '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.
|
||||
// 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.
|
||||
|
@ -242,6 +242,7 @@ type Validation struct {
|
||||
// - '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.
|
||||
// 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.
|
||||
|
@ -179,7 +179,7 @@ func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string {
|
||||
|
||||
var map_Validation = map[string]string{
|
||||
"": "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}\".",
|
||||
"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)+\")\"",
|
||||
|
@ -33,6 +33,7 @@ const (
|
||||
OldObjectVarName = "oldObject"
|
||||
ParamsVarName = "params"
|
||||
RequestVarName = "request"
|
||||
NamespaceVarName = "namespaceObject"
|
||||
AuthorizerVarName = "authorizer"
|
||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||
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.
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
@ -168,6 +219,7 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op
|
||||
|
||||
func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||
requestType := BuildRequestType()
|
||||
namespaceType := BuildNamespaceType()
|
||||
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 _, hasAuthorizer := range []bool{false, true} {
|
||||
@ -183,6 +235,7 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||
envOpts = append(envOpts,
|
||||
cel.Variable(ObjectVarName, cel.DynType),
|
||||
cel.Variable(OldObjectVarName, cel.DynType),
|
||||
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
||||
cel.Variable(RequestVarName, requestType.CelType()))
|
||||
|
||||
extended, err := baseEnv.Extend(
|
||||
@ -192,6 +245,7 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: envOpts,
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
namespaceType,
|
||||
requestType,
|
||||
},
|
||||
},
|
||||
|
@ -49,6 +49,11 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
||||
expressions: []string{"object.foo < params.x"},
|
||||
hasParams: true,
|
||||
},
|
||||
{
|
||||
name: "namespaceObject",
|
||||
expressions: []string{"namespaceObject.metadata.name.startsWith('test')"},
|
||||
hasParams: true,
|
||||
},
|
||||
{
|
||||
name: "without 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,
|
||||
},
|
||||
{
|
||||
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
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
@ -149,9 +150,9 @@ func (c *compositionContext) Variables(activation any) ref.Val {
|
||||
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)
|
||||
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) {
|
||||
|
@ -141,7 +141,7 @@ func TestCompositedPolicies(t *testing.T) {
|
||||
if costBudget == 0 {
|
||||
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 {
|
||||
t.Fatalf("failed evaluation: %v", err)
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -46,7 +47,7 @@ func NewFilterCompiler(env *environment.EnvSet) FilterCompiler {
|
||||
}
|
||||
|
||||
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
|
||||
@ -61,6 +62,8 @@ func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
||||
return a.params, true // params may be null
|
||||
case RequestVarName:
|
||||
return a.request, true
|
||||
case NamespaceVarName:
|
||||
return a.namespace, true
|
||||
case AuthorizerVarName:
|
||||
return a.authorizer, a.authorizer != nil
|
||||
case RequestResourceAuthorizerVarName:
|
||||
@ -126,7 +129,7 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||
// 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.
|
||||
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
|
||||
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
||||
var err error
|
||||
@ -156,11 +159,16 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
namespaceVal, err := objectToResolveVal(namespace)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
va := &evaluationActivation{
|
||||
object: objectVal,
|
||||
oldObject: oldObjectVal,
|
||||
params: paramsVal,
|
||||
request: requestVal.Object,
|
||||
namespace: namespaceVal,
|
||||
authorizer: authorizerVal,
|
||||
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
|
||||
func (e *filter) 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
|
||||
cases := []struct {
|
||||
name string
|
||||
@ -159,6 +181,7 @@ func TestFilter(t *testing.T) {
|
||||
hasParamKind bool
|
||||
authorizer authorizer.Authorizer
|
||||
testPerCallLimit uint64
|
||||
namespaceObject *corev1.Namespace
|
||||
}{
|
||||
{
|
||||
name: "valid syntax for object",
|
||||
@ -674,6 +697,64 @@ func TestFilter(t *testing.T) {
|
||||
params: crdParams,
|
||||
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 {
|
||||
@ -706,7 +787,7 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||
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 {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@ -852,7 +933,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||
}
|
||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||
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 {
|
||||
t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil")
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
@ -87,7 +88,7 @@ type Filter interface {
|
||||
// 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.
|
||||
// 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() []error
|
||||
|
@ -253,7 +253,7 @@ type fakeFilter struct {
|
||||
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
|
||||
}
|
||||
|
||||
@ -265,10 +265,10 @@ var _ Validator = &fakeValidator{}
|
||||
|
||||
type fakeValidator struct {
|
||||
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
|
||||
var key string
|
||||
if len(definition.Spec.Validations) > 0 {
|
||||
@ -285,8 +285,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmiss
|
||||
validatorMap[key] = f
|
||||
}
|
||||
|
||||
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return f.ValidateFunc(ctx, versionedAttr, versionedParams, runtimeCELCostBudget, authz)
|
||||
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, namespace, runtimeCELCostBudget, authz)
|
||||
}
|
||||
|
||||
var _ Matcher = &fakeMatcher{}
|
||||
@ -295,6 +295,10 @@ func (f *fakeMatcher) ValidateInitialization() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeMatcher) GetNamespace(name string) (*v1.Namespace, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type fakeMatcher struct {
|
||||
DefaultMatch 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{
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
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)
|
||||
return ValidateResult{
|
||||
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)
|
||||
return ValidateResult{
|
||||
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)
|
||||
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -319,6 +320,23 @@ func (c *celAdmissionController) Validate(
|
||||
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 {
|
||||
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
||||
@ -330,7 +348,7 @@ func (c *celAdmissionController) Validate(
|
||||
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 {
|
||||
switch decision.Action {
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@ -90,6 +91,10 @@ type Matcher interface {
|
||||
// BindingMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
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.
|
||||
@ -104,5 +109,5 @@ type ValidateResult struct {
|
||||
type Validator interface {
|
||||
// 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.
|
||||
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 (
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"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)
|
||||
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"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -44,6 +45,10 @@ type Matcher struct {
|
||||
objectMatcher *object.Matcher
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return m.namespaceMatcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
// NewMatcher initialize the matcher with dependencies requires
|
||||
func NewMatcher(
|
||||
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) {
|
||||
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
||||
requestType := plugincel.BuildRequestType()
|
||||
namespaceType := plugincel.BuildNamespaceType()
|
||||
|
||||
var varOpts []cel.EnvOption
|
||||
var declTypes []*apiservercel.DeclType
|
||||
|
||||
// namespace, hand-crafted type
|
||||
declTypes = append(declTypes, namespaceType)
|
||||
varOpts = append(varOpts, createVariableOpts(namespaceType, plugincel.NamespaceVarName)...)
|
||||
|
||||
// request, hand-crafted type
|
||||
declTypes = append(declTypes, requestType)
|
||||
varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"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
|
||||
// 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
|
||||
if v.failPolicy == nil {
|
||||
f = v1.Fail
|
||||
@ -101,7 +103,9 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
||||
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
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 {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
@ -114,7 +118,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
}
|
||||
}
|
||||
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 {
|
||||
var decision = &decisions[i]
|
||||
// TODO: move this to generics
|
||||
@ -191,7 +195,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
}
|
||||
|
||||
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 {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@ -47,7 +48,7 @@ type fakeCelFilter struct {
|
||||
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.
|
||||
return nil, -1, &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInvalid,
|
||||
@ -892,7 +893,7 @@ func TestValidate(t *testing.T) {
|
||||
if tc.costBudget != 0 {
|
||||
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))
|
||||
|
||||
@ -944,7 +945,7 @@ func TestContextCanceled(t *testing.T) {
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
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") {
|
||||
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{
|
||||
VersionedParams: versionedParams,
|
||||
Authorizer: authz,
|
||||
}, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||
}, nil, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||
|
||||
if err != nil {
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||
|
@ -22,6 +22,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
@ -40,7 +42,7 @@ type fakeCelFilter struct {
|
||||
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 {
|
||||
return nil, 0, errors.New("test error")
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -42,6 +44,10 @@ type Matcher struct {
|
||||
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.
|
||||
func (m *Matcher) Validate() error {
|
||||
var errs []error
|
||||
|
@ -72,6 +72,10 @@ var _ = SIGDescribe("ValidatingAdmissionPolicy [Privileged:ClusterAdmin][Alpha][
|
||||
Expression: "object.spec.replicas > 1",
|
||||
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()
|
||||
policy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, policy, metav1.CreateOptions{})
|
||||
framework.ExpectNoError(err, "create policy")
|
||||
|
@ -264,6 +264,21 @@ func Test_ValidateNamespace_NoParams(t *testing.T) {
|
||||
},
|
||||
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 {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user