Validating Admission Policy

    This page provides an overview of Validating Admission Policy.

    Validating admission policies offer a declarative, in-process alternative to validating admission webhooks.

    Validating admission policies use the Common Expression Language (CEL) to declare the validation rules of a policy. Validation admission policies are highly configurable, enabling policy authors to define policies that can be parameterized and scoped to resources as needed by cluster administrators.

    A policy is generally made up of three resources:

    • The ValidatingAdmissionPolicy describes the abstract logic of a policy (think: “this policy makes sure a particular label is set to a particular value”).

    • A ValidatingAdmissionPolicyBinding links the above resources together and provides scoping. If you only want to require an owner label to be set for Pods, the binding is where you would specify this restriction.

    • A parameter resource provides information to a ValidatingAdmissionPolicy to make it a concrete statement (think “the owner label must be set to something that ends in .company.com“). A native type such as ConfigMap or a CRD defines the schema of a parameter resource. ValidatingAdmissionPolicy objects specify what Kind they are expecting for their parameter resource.

    At least a ValidatingAdmissionPolicy and a corresponding ValidatingAdmissionPolicyBinding must be defined for a policy to have an effect.

    If a ValidatingAdmissionPolicy does not need to be configured via parameters, simply leave spec.paramKind in ValidatingAdmissionPolicy unset.

    • Ensure the ValidatingAdmissionPolicy is enabled.
    • Ensure that the admissionregistration.k8s.io/v1alpha1 API is enabled.

    Validating Admission Policy is part of the cluster control-plane. You should write and deploy them with great caution. The following describes how to quickly experiment with Validating Admission Policy.

    The following is an example of a ValidatingAdmissionPolicy.

    spec.validations contains CEL expressions which use the Common Expression Language (CEL) to validate the request. If an expression evaluates to false, the validation check is enforced according to the spec.failurePolicy field.

    To configure a validating admission policy for use in a cluster, a binding is required. The following is an example of a ValidatingAdmissionPolicyBinding.:

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicyBinding
    3. metadata:
    4. name: "demo-binding-test.example.com"
    5. spec:
    6. policyName: "demo-policy.example.com"
    7. validationActions: [Deny]
    8. matchResources:
    9. namespaceSelector:
    10. matchLabels:
    11. environment: test

    When trying to create a deployment with replicas set not satisfying the validation expression, an error will return containing message:

    1. ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

    The above provides a simple example of using ValidatingAdmissionPolicy without a parameter configured.

    Validation actions

    Each ValidatingAdmissionPolicyBinding must specify one or more validationActions to declare how validations of a policy are enforced.

    The supported validationActions are:

    • Deny: Validation failure results in a denied request.
    • Warn: Validation failure is reported to the request client as a warning.
    • Audit: Validation failure is included in the audit event for the API request.

    For example, to both warn clients about a validation failure and to audit the validation failures, use:

    1. validationActions: [Warn, Audit]

    Deny and Warn may not be used together since this combination needlessly duplicates the validation failure both in the API response body and the HTTP warning headers.

    A validation that evaluates to false is always enforced according to these actions. Failures defined by the failurePolicy are enforced according to these actions only if the failurePolicy is set to Fail (or unset), otherwise the failures are ignored.

    See for more details about the validation failure audit annotation.

    Parameter resources

    Parameter resources allow a policy configuration to be separate from its definition. A policy can define paramKind, which outlines GVK of the parameter resource, and then a policy binding ties a policy by name (via policyName) to a particular parameter resource via paramRef.

    If parameter configuration is needed, the following is an example of a ValidatingAdmissionPolicy with parameter configuration.

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. metadata:
    4. name: "replicalimit-policy.example.com"
    5. spec:
    6. failurePolicy: Fail
    7. paramKind:
    8. apiVersion: rules.example.com/v1
    9. kind: ReplicaLimit
    10. matchConstraints:
    11. resourceRules:
    12. - apiGroups: ["apps"]
    13. apiVersions: ["v1"]
    14. operations: ["CREATE", "UPDATE"]
    15. resources: ["deployments"]
    16. validations:
    17. - expression: "object.spec.replicas <= params.maxReplicas"
    18. reason: Invalid

    The spec.validations fields contain CEL expressions. If an expression evaluates to false, the validation check is enforced according to the spec.failurePolicy field.

    The validating admission policy author is responsible for providing the ReplicaLimit parameter CRD.

    To configure an validating admission policy for use in a cluster, a binding and parameter resource are created. The following is an example of a ValidatingAdmissionPolicyBinding.

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicyBinding
    3. metadata:
    4. name: "replicalimit-binding-test.example.com"
    5. spec:
    6. policyName: "replicalimit-policy.example.com"
    7. validationActions: [Deny]
    8. paramRef:
    9. name: "replica-limit-test.example.com"
    10. matchResources:
    11. namespaceSelector:
    12. matchLabels:
    13. environment: test

    The parameter resource could be as following:

    1. apiVersion: rules.example.com/v1
    2. kind: ReplicaLimit
    3. metadata:
    4. name: "replica-limit-test.example.com"
    5. maxReplicas: 3

    This policy parameter resource limits deployments to a max of 3 replicas in all namespaces in the test environment. An admission policy may have multiple bindings. To bind all other environments environment to have a maxReplicas limit of 100, create another ValidatingAdmissionPolicyBinding:

    And have a parameter resource like:

    1. apiVersion: rules.example.com/v1
    2. metadata:
    3. name: "replica-limit-clusterwide.example.com"
    4. maxReplicas: 100

    Bindings can have overlapping match criteria. The policy is evaluated for each matching binding. In the above example, the “nontest” policy binding could instead have been defined as a global policy:

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicyBinding
    3. metadata:
    4. name: "replicalimit-binding-global"
    5. spec:
    6. policyName: "replicalimit-policy.example.com"
    7. validationActions: [Deny]
    8. params: "replica-limit-clusterwide.example.com"
    9. matchResources:
    10. namespaceSelector:
    11. matchExpressions:
    12. - key: environment
    13. operator: Exists

    The params object representing a parameter resource will not be set if a parameter resource has not been bound, so for policies requiring a parameter resource, it can be useful to add a check to ensure one has been bound.

    For the use cases require parameter configuration, we recommend to add a param check in spec.validations[0].expression:

    1. message: "params missing but required to bind to this policy"

    It can be convenient to be able to have optional parameters as part of a parameter resource, and only validate them if present. CEL provides has(), which checks if the key passed to it exists. CEL also implements Boolean short-circuiting. If the first half of a logical OR evaluates to true, it won’t evaluate the other half (since the result of the entire OR will be true regardless).

    Combining the two, we can provide a way to validate optional parameters:

    !has(params.optionalNumber) || (params.optionalNumber >= 5 && params.optionalNumber <= 10)

    Here, we first check that the optional parameter is present with !has(params.optionalNumber).

    • If optionalNumber hasn’t been defined, then the expression short-circuits since !has(params.optionalNumber) will evaluate to true.
    • If optionalNumber has been defined, then the latter half of the CEL expression will be evaluated, and optionalNumber will be checked to ensure that it contains a value between 5 and 10 inclusive.

    Authorization Check

    We introduced the authorization check for parameter resources. User is expected to have read access to the resources referenced by paramKind in ValidatingAdmissionPolicy and paramRef in ValidatingAdmissionPolicyBinding.

    Note that if a resource in paramKind fails resolving via the restmapper, read access to all resources of groups is required.

    Failure Policy

    failurePolicy defines how mis-configurations and CEL expressions evaluating to error from the admission policy are handled. Allowed values are Ignore or Fail.

    • Ignore means that an error calling the ValidatingAdmissionPolicy is ignored and the API request is allowed to continue.
    • Fail means that an error calling the ValidatingAdmissionPolicy causes the admission to fail and the API request to be rejected.

    Note that the failurePolicy is defined inside ValidatingAdmissionPolicy:

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. spec:
    4. ...
    5. failurePolicy: Ignore # The default is "Fail"
    6. validations:
    7. - expression: "object.spec.xyz == params.x"

    spec.validations[i].expression represents the expression which will be evaluated by CEL. To learn more, see the CEL language specification CEL expressions have access to the contents of the Admission request/response, organized into CEL variables as well as some other useful variables:

    • ‘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 .
    • ‘params’ - Parameter resource referred to by the policy binding being evaluated. The value is null if ParamKind is unset.
    • authorizer - A CEL Authorizer. May be used to perform authorization checks for the principal (authenticated user) of the request. See Authz in the Kubernetes CEL library documentation for more details.
    • authorizer.requestResource - A shortcut for an authorization check configured with the request resource (group, resource, (subresource), namespace, name).

    The apiVersion, kind, metadata.name and metadata.generateName are always accessible from the root of the object. No other metadata properties are accessible.

    Only 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:

    Note: A CEL reserved keyword only needs to be escaped if the token is an exact match for the reserved keyword. For example, int in the word “sprint” would not be escaped.

    Examples on escaping:

    Equality 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: - ‘set’: X + Y performs a union where the array positions of all elements in X are preserved and non-intersecting elements in Y are appended, retaining their partial order. - ‘map’: X + Y performs a merge where the array positions of all keys in X are preserved but the values are overwritten by values in Y when the key sets of X and Y intersect. Elements in Y with non-intersecting keys are appended, retaining their partial order.

    Validation expression examples

    spec.validation[i].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.

    Matching requests: matchConditions

    You can define match conditions for a ValidatingAdmissionPolicy if you need fine-grained request filtering. These conditions are useful if you find that match rules, objectSelectors and namespaceSelectors still doesn’t provide the filtering you want. Match conditions are . All match conditions must evaluate to true for the resource to be evaluated.

    Here is an example illustrating a few different uses for match conditions:

    access/validating-admission-policy-match-conditions.yaml

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. metadata:
    4. name: "demo-policy.example.com"
    5. spec:
    6. failurePolicy: Fail
    7. matchConstraints:
    8. resourceRules:
    9. - apiGroups: ["*"]
    10. apiVersions: ["*"]
    11. operations: ["CREATE", "UPDATE"]
    12. resources: ["*"]
    13. matchConditions:
    14. - name: 'exclude-leases' # Each match condition must have a unique name
    15. expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # Match non-lease resources.
    16. - name: 'exclude-kubelet-requests'
    17. expression: '!("system:nodes" in request.userInfo.groups)' # Match requests made by non-node users.
    18. - name: 'rbac' # Skip RBAC requests.
    19. expression: 'request.resource.group != "rbac.authorization.k8s.io"'
    20. validations:
    21. - expression: "!object.metadata.name.contains('demo') || object.metadata.namespace == 'demo'"

    Match conditions have access to the same CEL variables as validation expressions.

    In the event of an error evaluating a match condition the policy is not evaluated. Whether to reject the request is determined as follows:

    1. If any match condition evaluated to (regardless of other errors), the API server skips the policy.
    2. Otherwise:
    • for , reject the request (without evaluating the policy).
    • for failurePolicy: Ignore, proceed with the request but skip the policy.

    auditAnnotations may be used to include audit annotations in the audit event of the API request.

    For example, here is an admission policy with an audit annotation:

    access/validating-admission-policy-audit-annotation.yaml Validating Admission Policy - 图2

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. metadata:
    4. name: "demo-policy.example.com"
    5. spec:
    6. failurePolicy: Fail
    7. matchConstraints:
    8. resourceRules:
    9. - apiGroups: ["apps"]
    10. apiVersions: ["v1"]
    11. operations: ["CREATE", "UPDATE"]
    12. resources: ["deployments"]
    13. validations:
    14. valueExpression: "object.spec.replicas > 50 ? 'Deployment spec.replicas set to ' + string(object.spec.replicas) : null"

    When an API request is validated with this admission policy, the resulting audit event will look like:

    In this example the annotation will only be included if the spec.replicas of the Deployment is more than 50, otherwise the CEL expression evalutes to null and the annotation will not be included.

    Note that audit annotation keys are prefixed by the name of the ValidatingAdmissionWebhook and a /. If another admission controller, such as an admission webhook, uses the exact same audit annotation key, the value of the first admission controller to include the audit annotation will be included in the audit event and all other values will be ignored.

    Message expression

    To return a more friendly message when the policy rejects a request, we can use a CEL expression to composite a message with spec.validations[i].messageExpression. Similar to the validation expression, a message expression has access to object, oldObject, request, and params. Unlike validations, message expression must evaluate to a string.

    For example, to better inform the user of the reason of denial when the policy refers to a parameter, we can have the following validation:

    Validating Admission Policy - 图4

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. metadata:
    4. name: "deploy-replica-policy.example.com"
    5. spec:
    6. paramKind:
    7. group: rules.example.com
    8. kind: ReplicaLimit
    9. version: v1
    10. matchConstraints:
    11. resourceRules:
    12. - apiGroups: ["apps"]
    13. apiVersions: ["v1"]
    14. operations: ["CREATE", "UPDATE"]
    15. resources: ["deployments"]
    16. validations:
    17. - expression: "object.spec.replicas <= params.maxReplicas"
    18. messageExpression: "'object.spec.replicas must be no greater than ' + string(params.maxReplicas)"
    19. reason: Invalid

    After creating a params object that limits the replicas to 3 and setting up the binding, when we try to create a deployment with 5 replicas, we will receive the following message.

    1. $ kubectl create deploy --image=nginx nginx --replicas=5
    2. error: failed to create deployment: deployments.apps "nginx" is forbidden: ValidatingAdmissionPolicy 'deploy-replica-policy.example.com' with binding 'demo-binding-test.example.com' denied request: object.spec.replicas must be no greater than 3

    This is more informative than a static message of “too many replicas”.

    The message expression takes precedence over the static message defined in spec.validations[i].message if both are defined. However, if the message expression fails to evaluate, the static message will be used instead. Additionally, if the message expression evaluates to a multi-line string, the evaluation result will be discarded and the static message will be used if present. Note that static message is validated against multi-line strings.

    When a policy definition is created or updated, the validation process parses the expressions it contains and reports any syntax errors, rejecting the definition if any errors are found. Afterward, the referred variables are checked for type errors, including missing fields and type confusion, against the matched types of spec.matchConstraints. The result of type checking can be retrieved from status.typeChecking. The presence of status.typeChecking indicates the completion of type checking, and an empty status.typeChecking means that no errors were detected.

    For example, given the following policy definition:

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. metadata:
    4. name: "deploy-replica-policy.example.com"
    5. spec:
    6. matchConstraints:
    7. resourceRules:
    8. - apiGroups: ["apps"]
    9. apiVersions: ["v1"]
    10. operations: ["CREATE", "UPDATE"]
    11. resources: ["deployments"]
    12. validations:
    13. - expression: "object.replicas > 1" # should be "object.spec.replicas > 1"
    14. message: "must be replicated"
    15. reason: Invalid

    The status will yield the following information:

    1. status:
    2. typeChecking:
    3. expressionWarnings:
    4. - fieldRef: spec.validations[0].expression
    5. warning: |-
    6. apps/v1, Kind=Deployment: ERROR: <input>:1:7: undefined field 'replicas'
    7. | object.replicas > 1
    8. | ......^

    If multiple resources are matched in spec.matchConstraints, all of matched resources will be checked against. For example, the following policy definition

    1. apiVersion: admissionregistration.k8s.io/v1alpha1
    2. kind: ValidatingAdmissionPolicy
    3. metadata:
    4. name: "replica-policy.example.com"
    5. spec:
    6. matchConstraints:
    7. resourceRules:
    8. - apiGroups: ["apps"]
    9. apiVersions: ["v1"]
    10. operations: ["CREATE", "UPDATE"]
    11. resources: ["deployments","replicasets"]
    12. validations:
    13. - expression: "object.replicas > 1" # should be "object.spec.replicas > 1"
    14. message: "must be replicated"
    15. reason: Invalid
    1. status:
    2. typeChecking:
    3. expressionWarnings:
    4. - fieldRef: spec.validations[0].expression
    5. warning: |-
    6. apps/v1, Kind=Deployment: ERROR: <input>:1:7: undefined field 'replicas'
    7. | object.replicas > 1
    8. | ......^
    9. apps/v1, Kind=ReplicaSet: ERROR: <input>:1:7: undefined field 'replicas'
    10. | object.replicas > 1
    11. | ......^

    Type Checking has the following limitation:

    • No wildcard matching. If spec.matchConstraints.resourceRules contains "*" in any of apiGroups, apiVersions or , the types that "*" matches will not be checked.
    • The number of matched types is limited to 10. This is to prevent a policy that manually specifying too many types. to consume excessive computing resources. In the order of ascending group, version, and then resource, 11th combination and beyond are ignored.
    • Type Checking does not apply to CRDs, including matched CRD types and reference of paramKind. The support for CRDs will come in future release.