@@ -350,7 +350,31 @@ can be completed in a single Kubernetes release cycle.
350
350
Before getting into all the individual fields and capabilities, let's look at the
351
351
general "shape" of the API.
352
352
353
- This enhancement introduces a new ` ValidatingAdmissionPolicy ` kind.
353
+ This API separates policy _ definition_ from policy _ configuration_ by splitting
354
+ responsibilities across resources. The resources involved are:
355
+
356
+ - Policy definitions (ValidatingAdmissionPolicy)
357
+ - Policy bindings (PolicyBinding)
358
+ - Policy param resources (custom resources)
359
+
360
+ ![ Relatinships between policy resources] ( erd.png )
361
+
362
+ This allows for a N: N relationship between policy definitions and the configuration of those policies. This separation has already been
363
+ demonstrated successfully by multiple policy frameworks (see the survey further down in this KEP). It has a few key properties:
364
+
365
+ - Reduces total amount of resource data needed to manage policies:
366
+ - Params can be shared across multiple policies instead of copied. E.g.
367
+ multiple policies can be enforce different aspects of a "no external
368
+ connections", for example, but can all share the configuration.
369
+ - Policies can be configured in different ways for different use cases without
370
+ having to copy the policy.
371
+ - Rollouts and canary-ing can be managed largely via bindings without having
372
+ to copy policies or params.
373
+ - Ownership of resources aligns well with typical separation of roles for policy
374
+ management.
375
+ - Existing policy frameworks can leverage this design far more easily because it
376
+ aligns with how separation of concerns is expressed in most major policy
377
+ frameworks.
354
378
355
379
Each ` ValidatingAdmissionPolicy ` resource defines a admission control policy.
356
380
The resource contains the CEL expressions to validate the admission policy and
@@ -379,8 +403,7 @@ spec:
379
403
validations :
380
404
- name : max-replicas
381
405
expression : " object.spec.replicas <= params.maxReplicas"
382
- message : " object.spec.replicas must be no greater than {1}"
383
- messageArgs : ["params.maxReplicas"]
406
+ messageExpression : " 'object.spec.replicas must be no greater than ' + string(params.maxReplicas)"
384
407
# ...other rule related fields here...
385
408
```
386
409
@@ -407,19 +430,6 @@ responsible for providing the `ReplicaLimit` parameter CRD.
407
430
To configure an admission policy for use in a cluster, a binding and parameter
408
431
resource are created. For example:
409
432
410
- <<[ UNRESOLVED jpbetz ] >>
411
- Clean up required:
412
-
413
- Use ` ClusterPolicyBinding ` so we can add ` PolicyBinding ` (namespace scoped) in
414
- the future?
415
-
416
- Use a verb for secondary authz check that policy binding editor has policy
417
- parameter edit roles? (tallclair suggested this, it has nice properties).
418
-
419
- kubectl support for this feature could show information about a policy and how
420
- it is applied? could be really useful pre-GA to help users
421
- <<[ /UNRESOLVED] >>
422
-
423
433
``` yaml
424
434
# Policy binding
425
435
apiVersion : admissionregistration.k8s.io/v1
@@ -499,15 +509,6 @@ With this binding, the test and global policy bindings overlap. Resources
499
509
admitted to test environment would then be checked against both policy
500
510
configurations.
501
511
502
- To summarize, this "API Shape" separates admission policy _definition_ from
503
- _configuration_. This has a couple advantages :
504
-
505
- - Access to, and delegation of, policy configuration is more manageable. In
506
- particular, Kubernetes RBAC works well with this design.
507
- - Without the separation, the next most obvious API shape would be to encode
508
- everything into a single resource, which could easily become very large and
509
- run into resource size limits.
510
-
511
512
# #### Policy Definitions
512
513
513
514
Policy definitions are responsible for :
@@ -607,7 +608,8 @@ needed.
607
608
See "Alternatives considered" section for rejected alternatives. This design was
608
609
selected because :
609
610
610
- - Matching criteria is fully defined and validated in a builtin type.
611
+ - The param CRD schema is owned entirely by the policy author.
612
+ - Matching criteria is fully defined and validated in the builtin `PolicyBinding` type.
611
613
- Type checking is straight forward.
612
614
- Policy parameterization is separated from the policy binding, allowing for
613
615
well abstracted parameterization types to be used by applied in different ways
@@ -637,6 +639,14 @@ Details:
637
639
- To address this, bindings resource will have extra auth check to verify that
638
640
anyone modifying the binding is also permitted to modify the parameters
639
641
resource.
642
+ - We should consider using a verb for secondary authz check that policy
643
+ binding editor has policy parameter edit roles? (tallclair suggested this,
644
+ it has nice properties).
645
+
646
+ API details :
647
+
648
+ - Name this `ClusterPolicyBinding` so we can add `PolicyBinding` (namespace
649
+ scoped) in the future?
640
650
641
651
# #### Match Criteria
642
652
@@ -658,6 +668,14 @@ For CEL expressions, the primary benefits of match criteria are:
658
668
- Match criteria is available on policy bindings and allows the binding author
659
669
to further constrain what resources the particular binding applies to.
660
670
671
+ We did consider not having any "YAML matching" for this feature and instead pushing all matching into CEL. The main deciders for me were :
672
+
673
+ - Kubernetes already has resource matching as a well established concept
674
+ - Match criteria can be built indexed/accelerated/built into decision trees
675
+ - Match criteria can evaluate only to true/false. There is no 'error' case to consider
676
+ - Match criteria can be used to guide static typing. If the match is for
677
+ v1.Deployment, we know ahead of runtime what type the object variable is
678
+
661
679
Matching is performed in quite a few systems across Kubernetes :
662
680
663
681
| Match type | Usages in existing matchers | Support? |
@@ -814,10 +832,6 @@ xref:
814
832
815
833
# ### Reporting violations to Clients
816
834
817
- <<[UNRESOLVED jpbetz, TristonianJones ]>>
818
- Use CEL expressions for messages instead of for message args?
819
- <<[/UNRESOLVED]>>
820
-
821
835
This section focuses on how information is reported back to clients in
822
836
when validations fail.
823
837
@@ -832,12 +846,12 @@ High level proposal:
832
846
833
847
- Each policy may set a `denyReason` and/or `denyCode`
834
848
- Each validation may define a message :
835
- - ` message` - may contain `{1}` format args that are supplied by
836
- `messageArgs : [ <cel expression>, <cel expression>, ...]`
837
- - If `message` is absent, `expression` and `name` will be included in the
838
- failure message
839
- - If any of the arg CEL expressions fail : ` expression` and `name` will be
840
- included in the failure message plus the arg evaluation failure
849
+ - ` message` - plain string message
850
+ - `messageExpression : " <cel expression>" ` (mutually exclusive with ` message`)
851
+ - If `message` and `messageExpression` are absent, `expression` and `name`
852
+ will be included in the failure message
853
+ - If `messageExpression` results in an error : ` expression` and `name` will be
854
+ included in the failure message plus the arg evaluation failure
841
855
- allow/deny, warnings, audit annotations will be collectively referred to as
842
856
`enforcement`, which will have the following options :
843
857
- ` Deny` ,
@@ -866,15 +880,13 @@ spec:
866
880
validations:
867
881
- expression: "self.name.startsWith('xyz-')"
868
882
name: name-prefix
869
- message: "{1} must start with 'xyz-'"
870
- messageArgs: ["self.name"]
883
+ messageExpression: "self.name + ' must start with \' xyz-\' '"
871
884
- expression: "self.name.contains('bad')"
872
885
name: bad-name
873
886
message: "name contains 'bad' which is discouraged due to ..."
874
887
- expression: "self.name.contains('suspicious')"
875
888
name: suspicious-name
876
- message: "{1} contains 'suspicious'"
877
- messageArgs: ["self.name"]
889
+ messageExpression: "self.name + ' contains \' suspicious\' '"
878
890
` ` `
879
891
880
892
corresponding policy configuration :
@@ -993,24 +1005,25 @@ apiVersion: admissionregistration.k8s.io/v1
993
1005
kind: ValidatingAdmissionPolicy
994
1006
...
995
1007
spec:
996
- matchResources : ...
1008
+ matchConstraints : ...
997
1009
validations:
998
1010
- expression: "object.spec.replicas < 100"
999
- enforcement: [Deny]
1011
+ singletonBinding:
1012
+ matchResources: ...
1013
+ enforcement: [Deny]
1000
1014
` ` `
1001
1015
1002
1016
Note that :
1003
1017
1004
- - ` spec.paramSource` is absent
1005
- - validations do not reference `params` (since there are none)
1006
- - ` matchResources` is used instead of `matchConstraints`. This also disables
1007
- policy binding support.
1008
- - ` enforcement` is set. This disabbles policy binding support and must
1009
- be used in conjunction with `matchConstraints`
1018
+ - ` spec.paramSource` must be absent and validations may not reference `params`
1019
+ - If `spec.singletonBinding` is present policy binding support is disabled.
1010
1020
1011
- <<[UNRESOLVED jpbetz, ?? ]>>
1012
- Is there a better way to indicate a "singleton" policy than `matchResources` + `enforcement`? Neither seem like sufficiently obvious indicators of intent.
1013
- <<[/UNRESOLVED]>>
1021
+ Safety features :
1022
+
1023
+ - This field may only be set when the policy is created. it may not be set on
1024
+ existing policies.
1025
+ - Any bindings assigned to a singleton policy are considered "misconfigured" and
1026
+ apply the `FailurePolicy`.
1014
1027
1015
1028
Reporting/debugging/analysis implications :
1016
1029
@@ -1031,9 +1044,9 @@ We will put limits on:
1031
1044
1032
1045
# ## Phase 2
1033
1046
1034
- All these capabilities are required before Beta, but will not be implemented
1035
- in the first alpha release of this enhancement due to the size and complexity
1036
- of this enhancement.
1047
+ All these capabilities are required before Beta, but will not be implemented in
1048
+ the first alpha release of this enhancement due to the size and complexity of
1049
+ this enhancement.
1037
1050
1038
1051
# ### Namespace scoped policy binding
1039
1052
@@ -1220,6 +1233,7 @@ Plan:
1220
1233
To consider :
1221
1234
1222
1235
- labelSelector evaluation functions or other match evaluator functions ([original comment thread](https://github.com/kubernetes/enhancements/pull/3492#discussion_r981747317))
1236
+ - ` string.format(string, list(dyn))` to make `messageExpression` more convenient.
1223
1237
1224
1238
# ### Metrics
1225
1239
@@ -1522,11 +1536,25 @@ xref: https://kyverno.io/docs/writing-policies/autogen/
1522
1536
1523
1537
# ### Use Case: Rollout of a new validation expression to an existing policy
1524
1538
1525
- TODO
1539
+ 1. Policy definition A exists in cluster, policy bindings X1..Xn exist
1540
+ 1. "temporary" policy definition B is created with the new validation, it has
1541
+ the same settings as policy definition A otherwise (e.g. it uses the same
1542
+ param CR)
1543
+ 1. Policy bindings X1..Xn are replicated as Y1..Yn but modified to use policy
1544
+ definition B and `enforcement : [Audit, Warn]`
1545
+ 1. Cluster administrators observe violations (via metrics, audit logs or logged warnings)
1546
+ 1. Cluster administrator determines new validation is safe
1547
+ 1. Policy definition A is updated to include the new validation
1548
+ 1. Policy definition B and policy bindings Y1..Yn are deleted
1526
1549
1527
1550
# ### Use Case: Canary-ing a policy
1528
1551
1529
- TODO
1552
+ 1. New policy definition is created
1553
+ 1. Any needed param CRs are created
1554
+ 1. policy bindings are created and set to `enforcement : [Audit, Warn]`
1555
+ 1. Cluster administrators observe violations (via metrics, audit logs or logged warnings)
1556
+ 1. Cluster administrator determines new policy is safe
1557
+ 1. policy bindings are set to `enforcement : [Deny, Audit, Warn]`
1530
1558
1531
1559
# ## Potential Applications
1532
1560
@@ -2260,6 +2288,10 @@ Why should this KEP _not_ be implemented?
2260
2288
2261
2289
- cel-policy-template [`range`](https://github.com/google/cel-policy-templates-go/blob/master/test/testdata/map_ranges/template.yaml) or equivalent.
2262
2290
- Default validations?
2291
+ - Short circuiting of validation (right now all are always evaluated)?
2292
+ - CEL based matching support?
2293
+ - kubectl support for this feature could show information about a policy and how
2294
+ it is applied? could be really useful pre-GA to help users
2263
2295
2264
2296
# # Alternatives
2265
2297
@@ -2759,8 +2791,7 @@ For example, to validate all containers:
2759
2791
validations:
2760
2792
- scope: "spec.containers[*]"
2761
2793
expression: "scope.name.startsWith('xyz-')"
2762
- message: "{1} does not start with 'xyz'"
2763
- messageArgs: ["scope.name"]
2794
+ messageExpression: "scope.name + 'does not start with \' xyz\' '"
2764
2795
` ` `
2765
2796
2766
2797
To make it possible to access the path information in the scope, we can offer a
@@ -2774,8 +2805,7 @@ spec.x[xKey].y[yIndex].field
2774
2805
validations:
2775
2806
- scope: "x[xKey].y[yIndex].field"
2776
2807
expression: "scope.startsWith('xyz-')"
2777
- message: "{1}, {2}: some problem"
2778
- messageArgs: ["scopePath.xKey", "scopePath.yIndex"]
2808
+ messageExpression: "scopePath.xKey + ', ' + scopePath.yIndex + ': some problem'"
2779
2809
` ` `
2780
2810
2781
2811
Prior art :
@@ -2796,27 +2826,26 @@ Note: We considered extending to a list of scopes, e.g.:
2796
2826
validations:
2797
2827
- scopes: ["spec.containers[*]", "initContainers[*]", "spec.ephemeralContainers[*]"]
2798
2828
expression: "scope.name.startsWith('xyz-')"
2799
- message: "{1} does not start with 'xyz'"
2800
- messageArgs: ["scope.name"]
2829
+ messageExpression: "scope.name + ' does not start with \' xyz\' '"
2801
2830
` ` `
2802
2831
2803
2832
But feedback was this is signficantly more difficult to understand.
2804
2833
2805
2834
# ## Message formatting alternatives
2806
2835
2807
- Alternative : offer a CEL expression
2836
+ Alternative : CEL args
2808
2837
2809
2838
` ` ` yaml
2810
2839
- expression: "..."
2811
- messageExpression: "string(object.int1) + ' is less than ' + string(object.int2)"
2840
+ message: "{1} is less than {2}"
2841
+ messageArgs: ["spec.value", "spec.max"]
2812
2842
` ` `
2813
2843
2814
2844
Cons :
2815
2845
2816
- - CEL requires explicit casts to string
2817
- - Plain string message support is a bit messy. Options :
2818
- - Use CEL always : ` "'this is a simple message'"`
2819
- - Offer both plain string (`message`) and CEL (`messageExpression`)
2846
+ - How all types are converted to string becomes the responsibility of this API.
2847
+ Hard to please everyone and may end up needing to reimplementing `fmt.Sprintf`.
2848
+ In which case this is probably best handled from within CEL.
2820
2849
2821
2850
Alternative : Inline CEL expressions
2822
2851
@@ -2848,7 +2877,7 @@ Alternative: CEL expressions, separate args from format string
2848
2877
` ` ` yaml
2849
2878
- expression: "..."
2850
2879
message: "{1} is less than {2}"
2851
- messageArgs: ["object.int1 ", "object.int2"]
2880
+ messageArgs: ["", "object.int2"]
2852
2881
` ` `
2853
2882
2854
2883
Note "%s is less than %s" is also viable, but CEL can always preformat and emit
0 commit comments