Custom match criteria (#116350)

* Add custom match conditions for CEL admission

This PR is based off of, and dependent on the following PR:

https://github.com/kubernetes/kubernetes/pull/116261

Signed-off-by: Max Smythe <smythe@google.com>

* run `make update`

Signed-off-by: Max Smythe <smythe@google.com>

* Fix unit tests

Signed-off-by: Max Smythe <smythe@google.com>

* Fix unit tests

Signed-off-by: Max Smythe <smythe@google.com>

* Update compatibility test data

Signed-off-by: Max Smythe <smythe@google.com>

* Revert "Update compatibility test data"

This reverts commit 312ba7f9e74e0ec4a7ac1f07bf575479c608af28.

* Allow params during validation; make match conditions optional

Signed-off-by: Max Smythe <smythe@google.com>

* Add conditional ignoring of matcher CEL expression validation on update

Signed-off-by: Max Smythe <smythe@google.com>

* Run codegen

Signed-off-by: Max Smythe <smythe@google.com>

* Add more validation tests

Signed-off-by: Max Smythe <smythe@google.com>

* Short-circuit CEL matcher when no matchers specified

Signed-off-by: Max Smythe <smythe@google.com>

* Run codegen

Signed-off-by: Max Smythe <smythe@google.com>

* Address review comments

Signed-off-by: Max Smythe <smythe@google.com>

---------

Signed-off-by: Max Smythe <smythe@google.com>
This commit is contained in:
Max Smythe
2023-03-15 17:23:15 -07:00
committed by GitHub
parent 6711a81f02
commit e5fd204c33
24 changed files with 1173 additions and 120 deletions

View File

@@ -3150,6 +3150,131 @@ func TestValidateValidatingAdmissionPolicy(t *testing.T) {
},
expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>`,
},
{
name: "single match condition must have a name",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
MatchConditions: []admissionregistration.MatchCondition{
{
Expression: "true",
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 100",
},
},
},
},
expectedError: `spec.matchConditions[0].name: Required value`,
},
{
name: "match condition with parameters allowed",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
ParamKind: &admissionregistration.ParamKind{
Kind: "Foo",
APIVersion: "foobar/v1alpha1",
},
MatchConstraints: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"*"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
},
FailurePolicy: func() *admissionregistration.FailurePolicyType {
r := admissionregistration.FailurePolicyType("Fail")
return &r
}(),
MatchConditions: []admissionregistration.MatchCondition{
{
Name: "hasParams",
Expression: `params.foo == "okay"`,
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 100",
},
},
},
},
expectedError: "",
},
{
name: "match condition with parameters not allowed if no param kind",
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
MatchConstraints: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"*"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
},
FailurePolicy: func() *admissionregistration.FailurePolicyType {
r := admissionregistration.FailurePolicyType("Fail")
return &r
}(),
MatchConditions: []admissionregistration.MatchCondition{
{
Name: "hasParams",
Expression: `params.foo == "okay"`,
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 100",
},
},
},
},
expectedError: `undeclared reference to 'params'`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -3293,6 +3418,202 @@ func TestValidateValidatingAdmissionPolicyUpdate(t *testing.T) {
Spec: admissionregistration.ValidatingAdmissionPolicySpec{},
},
},
{
name: "match conditions re-checked if paramKind changes",
oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
ParamKind: &admissionregistration.ParamKind{
Kind: "Foo",
APIVersion: "foobar/v1alpha1",
},
MatchConstraints: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"*"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
},
FailurePolicy: func() *admissionregistration.FailurePolicyType {
r := admissionregistration.FailurePolicyType("Fail")
return &r
}(),
MatchConditions: []admissionregistration.MatchCondition{
{
Name: "hasParams",
Expression: `params.foo == "okay"`,
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 100",
},
},
},
},
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
MatchConstraints: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"*"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
},
FailurePolicy: func() *admissionregistration.FailurePolicyType {
r := admissionregistration.FailurePolicyType("Fail")
return &r
}(),
MatchConditions: []admissionregistration.MatchCondition{
{
Name: "hasParams",
Expression: `params.foo == "okay"`,
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 100",
},
},
},
},
expectedError: `undeclared reference to 'params'`,
},
{
name: "match conditions not re-checked if no change to paramKind or matchConditions",
oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
MatchConstraints: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"*"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
},
FailurePolicy: func() *admissionregistration.FailurePolicyType {
r := admissionregistration.FailurePolicyType("Fail")
return &r
}(),
MatchConditions: []admissionregistration.MatchCondition{
{
Name: "hasParams",
Expression: `params.foo == "okay"`,
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 100",
},
},
},
},
config: &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
MatchConstraints: &admissionregistration.MatchResources{
ResourceRules: []admissionregistration.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistration.RuleWithOperations{
Operations: []admissionregistration.OperationType{"*"},
Rule: admissionregistration.Rule{
APIGroups: []string{"a"},
APIVersions: []string{"a"},
Resources: []string{"a"},
},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
ObjectSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"a": "b"},
},
MatchPolicy: func() *admissionregistration.MatchPolicyType {
r := admissionregistration.MatchPolicyType("Exact")
return &r
}(),
},
FailurePolicy: func() *admissionregistration.FailurePolicyType {
r := admissionregistration.FailurePolicyType("Ignore")
return &r
}(),
MatchConditions: []admissionregistration.MatchCondition{
{
Name: "hasParams",
Expression: `params.foo == "okay"`,
},
},
Validations: []admissionregistration.Validation{
{
Expression: "object.x < 50",
},
},
},
},
expectedError: "",
},
// TODO: CustomAuditAnnotations: string valueExpression with {oldObject} is allowed
}
for _, test := range tests {