diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 1e0f154a642..1fd5cb71994 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -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": { diff --git a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json index eee337179da..63ef396ee54 100644 --- a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json +++ b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json @@ -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": { diff --git a/pkg/apis/admissionregistration/types.go b/pkg/apis/admissionregistration/types.go index c80511352dc..f557de975fc 100644 --- a/pkg/apis/admissionregistration/types.go +++ b/pkg/apis/admissionregistration/types.go @@ -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. diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 4be987c1966..f56a9a2ae61 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -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: "", diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto index ff5d2014f35..6142e00e9c4 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto @@ -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. diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go index bdb81416453..3b0745cc6d5 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go @@ -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. diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go index c6b121e87ee..1d2b08d39ba 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go @@ -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)+\")\"", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go index 8128b95041f..25ee108ea95 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go @@ -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, }, }, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile_test.go index 9b9cd73a8f6..5caf82858a4 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile_test.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go index afdf4d36752..38b80a304aa 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go @@ -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) { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go index 08ec6c5225c..2ae483e69e2 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go @@ -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) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter.go index bb586cbf973..9cce78e460a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter.go @@ -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{} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter_test.go index a3cc88ebd20..011d0606a70 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/filter_test.go @@ -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") } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go index 5bc708cf529..c9f4e63369f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go index 6425813c1f2..8c382be9919 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go @@ -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) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go index 4df61ed1ca3..f0ca1743809 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go @@ -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 { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go index 750295d3602..28c5a0dad50 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go @@ -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 } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matcher.go index a659a99f14c..5ed3d17b142 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matcher.go @@ -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) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching/matching.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching/matching.go index c4f7e64af2d..a97adb17143 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching/matching.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching/matching.go @@ -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, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/typechecking.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/typechecking.go index 21c454cebd6..8857933c3d8 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/typechecking.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/typechecking.go @@ -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)...) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go index 65854764f84..b21e6495b4a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go @@ -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{ diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go index 55f3ef8627f..ab9c12c7275 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go @@ -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) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher.go index 62aeadaf44f..e9c3fb8f71c 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher.go @@ -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())) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher_test.go index 76ed88c3ea5..20d9aebfb7d 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher_test.go @@ -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") } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/matcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/matcher.go index 459e3f5df6b..6427bc67484 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/matcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/matcher.go @@ -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 diff --git a/test/e2e/apimachinery/validatingadmissionpolicy.go b/test/e2e/apimachinery/validatingadmissionpolicy.go index 46bbe9ff103..fe85f8d7912 100644 --- a/test/e2e/apimachinery/validatingadmissionpolicy.go +++ b/test/e2e/apimachinery/validatingadmissionpolicy.go @@ -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") diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index 9e5dfdd6084..c0a43f08ece 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -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) {