diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go index 5c5779b..aab347d 100644 --- a/internal/evaluator/evaluator.go +++ b/internal/evaluator/evaluator.go @@ -20,7 +20,9 @@ import ( admissionv1 "k8s.io/api/admission/v1" admissionregv1 "k8s.io/api/admissionregistration/v1" admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" plugin "k8s.io/apiserver/pkg/admission/plugin/cel" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -113,6 +115,7 @@ type TestCase interface { // EvaluateTest evaluates a policy against a test case and returns whether it passed. func (e *Evaluator) EvaluateTest( mutatingPolicy *admissionv1beta1.MutatingAdmissionPolicy, + mutatingBinding *admissionv1beta1.MutatingAdmissionPolicyBinding, validatingPolicy *admissionregv1.ValidatingAdmissionPolicy, validatingBinding *admissionregv1.ValidatingAdmissionPolicyBinding, testCase TestCase, @@ -135,7 +138,7 @@ func (e *Evaluator) EvaluateTest( } // Evaluate policy - evalResult, err := e.evaluatePolicy(mutatingPolicy, validatingPolicy, validatingBinding, testCase) + evalResult, err := e.evaluatePolicy(mutatingPolicy, mutatingBinding, validatingPolicy, validatingBinding, testCase) if err != nil { return &TestResult{ Passed: false, @@ -236,6 +239,7 @@ func getDiff(expected, actual string) string { // evaluatePolicy evaluates the appropriate policy (mutating or validating) and returns the result. func (e *Evaluator) evaluatePolicy( mutatingPolicy *admissionv1beta1.MutatingAdmissionPolicy, + mutatingBinding *admissionv1beta1.MutatingAdmissionPolicyBinding, validatingPolicy *admissionregv1.ValidatingAdmissionPolicy, validatingBinding *admissionregv1.ValidatingAdmissionPolicyBinding, testCase TestCase, @@ -250,6 +254,7 @@ func (e *Evaluator) evaluatePolicy( case mutatingPolicy != nil: return e.EvaluateMutating( mutatingPolicy, + mutatingBinding, testCase.GetRequest(), testCase.GetObject(), testCase.GetOldObject(), @@ -566,6 +571,7 @@ type TestOutcome struct { // EvaluateMutating evaluates a MutatingAdmissionPolicy against an admission request. func (e *Evaluator) EvaluateMutating( policy *admissionv1beta1.MutatingAdmissionPolicy, + binding *admissionv1beta1.MutatingAdmissionPolicyBinding, request *admissionv1.AdmissionRequest, object *unstructured.Unstructured, oldObject *unstructured.Unstructured, @@ -574,6 +580,14 @@ func (e *Evaluator) EvaluateMutating( authorizer authorizer.Authorizer, userInfo user.Info, ) (*EvaluationResult, error) { + // Evaluate binding's namespaceSelector if present + if matched, err := e.matchesNamespaceSelectorV1Beta1(binding, namespaceObj); err != nil { + return nil, fmt.Errorf("evaluate namespace selector: %w", err) + } else if !matched { + // Namespace selector doesn't match, policy doesn't apply + return &EvaluationResult{Allowed: true}, nil + } + requestMap, err := convertAdmissionRequest(request) if err != nil { return nil, fmt.Errorf("convert admission request: %w", err) @@ -693,7 +707,7 @@ func (e *Evaluator) applyMutations( } // EvaluateValidating evaluates a ValidatingAdmissionPolicy against an admission request. -func (e *Evaluator) EvaluateValidating( +func (e *Evaluator) EvaluateValidating( //nolint:cyclop // Complexity is inherent in evaluating all aspects of a validating policy policy *admissionregv1.ValidatingAdmissionPolicy, binding *admissionregv1.ValidatingAdmissionPolicyBinding, request *admissionv1.AdmissionRequest, @@ -704,6 +718,14 @@ func (e *Evaluator) EvaluateValidating( authorizer authorizer.Authorizer, userInfo user.Info, ) (*EvaluationResult, error) { + // Evaluate binding's namespaceSelector if present + if matched, err := e.matchesNamespaceSelector(binding, namespaceObj); err != nil { + return nil, fmt.Errorf("evaluate namespace selector: %w", err) + } else if !matched { + // Namespace selector doesn't match, policy doesn't apply + return &EvaluationResult{Allowed: true}, nil + } + // Convert admission request requestMap, err := convertAdmissionRequest(request) if err != nil { @@ -754,6 +776,60 @@ func (e *Evaluator) EvaluateValidating( }, nil } +// matchesNamespaceSelectorByLabelSelector checks if the namespace object's labels match the given label selector. +// Returns true if the selector is nil, empty, or matches the namespace labels. +func matchesNamespaceSelectorByLabelSelector( + labelSelector *metav1.LabelSelector, + namespaceObj *unstructured.Unstructured, +) (bool, error) { + if labelSelector == nil { + return true, nil + } + + // Convert LabelSelector to labels.Selector + selector, err := metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + return false, fmt.Errorf("parse namespace selector: %w", err) + } + + // Empty selector matches everything + if selector.Empty() { + return true, nil + } + + // No namespace object provided - can't evaluate selector + if namespaceObj == nil { + return true, nil + } + + // Check if namespace labels match the selector + return selector.Matches(labels.Set(namespaceObj.GetLabels())), nil +} + +// matchesNamespaceSelector checks if the namespace object's labels match the binding's namespace selector. +// Returns true if the selector matches (policy should be evaluated), false otherwise. +func (e *Evaluator) matchesNamespaceSelector( + binding *admissionregv1.ValidatingAdmissionPolicyBinding, + namespaceObj *unstructured.Unstructured, +) (bool, error) { + if binding == nil || binding.Spec.MatchResources == nil { + return true, nil + } + return matchesNamespaceSelectorByLabelSelector(binding.Spec.MatchResources.NamespaceSelector, namespaceObj) +} + +// matchesNamespaceSelectorV1Beta1 checks if the namespace object's labels match the binding's namespace selector. +// Returns true if the selector matches (policy should be evaluated), false otherwise. +func (e *Evaluator) matchesNamespaceSelectorV1Beta1( + binding *admissionv1beta1.MutatingAdmissionPolicyBinding, + namespaceObj *unstructured.Unstructured, +) (bool, error) { + if binding == nil || binding.Spec.MatchResources == nil { + return true, nil + } + return matchesNamespaceSelectorByLabelSelector(binding.Spec.MatchResources.NamespaceSelector, namespaceObj) +} + // evaluateMatchConditions evaluates all match conditions and returns true if all match. func (e *Evaluator) evaluateMatchConditions(conditions []admissionregv1.MatchCondition, vars map[string]any) (bool, error) { for _, condition := range conditions { diff --git a/internal/evaluator/evaluator_authorizer_test.go b/internal/evaluator/evaluator_authorizer_test.go index 4a78d2d..e0e44d2 100644 --- a/internal/evaluator/evaluator_authorizer_test.go +++ b/internal/evaluator/evaluator_authorizer_test.go @@ -105,7 +105,7 @@ func runMutatingTest(t *testing.T, policy *admissionv1beta1.MutatingAdmissionPol userInfo := MockUserInfo(username, groups) - result, err := evaluator.EvaluateMutating(policy, request, object, nil, nil, nil, auth, userInfo) + result, err := evaluator.EvaluateMutating(policy, nil, request, object, nil, nil, nil, auth, userInfo) if err != nil { t.Fatalf("EvaluateMutating() error = %v", err) } diff --git a/internal/evaluator/evaluator_params_test.go b/internal/evaluator/evaluator_params_test.go index c3c66d5..d0aa935 100644 --- a/internal/evaluator/evaluator_params_test.go +++ b/internal/evaluator/evaluator_params_test.go @@ -330,7 +330,7 @@ func TestEvaluateMutating_WithParams(t *testing.T) { Operation: admissionv1.Create, } - result, err := evaluator.EvaluateMutating(tc.policy, request, tc.object, nil, tc.params, nil, nil, nil) + result, err := evaluator.EvaluateMutating(tc.policy, nil, request, tc.object, nil, tc.params, nil, nil, nil) if err != nil { t.Fatalf("EvaluateMutating() error = %v", err) } diff --git a/internal/evaluator/evaluator_test.go b/internal/evaluator/evaluator_test.go index 25d7d32..e43ccfc 100644 --- a/internal/evaluator/evaluator_test.go +++ b/internal/evaluator/evaluator_test.go @@ -696,7 +696,7 @@ func TestEvaluateMutating(t *testing.T) { Operation: admissionv1.Create, } - result, err := evaluator.EvaluateMutating(tc.policy, request, tc.object, tc.oldObject, nil, nil, nil, nil) + result, err := evaluator.EvaluateMutating(tc.policy, nil, request, tc.object, tc.oldObject, nil, nil, nil, nil) if tc.expectedError { if err == nil { @@ -1558,6 +1558,7 @@ func TestEvaluator_EvaluateTest(t *testing.T) { tests := []struct { name string mutatingPolicy *admissionv1beta1.MutatingAdmissionPolicy + mutatingBinding *admissionv1beta1.MutatingAdmissionPolicyBinding validatingPolicy *admissionregv1.ValidatingAdmissionPolicy validatingBinding *admissionregv1.ValidatingAdmissionPolicyBinding testCase MockTestCase @@ -1827,7 +1828,7 @@ func TestEvaluator_EvaluateTest(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - result := evaluator.EvaluateTest(tc.mutatingPolicy, tc.validatingPolicy, tc.validatingBinding, tc.testCase) + result := evaluator.EvaluateTest(tc.mutatingPolicy, tc.mutatingBinding, tc.validatingPolicy, tc.validatingBinding, tc.testCase) if result.Passed != tc.wantPassed { t.Errorf("EvaluateTest() Passed = %v, want %v. Message: %s", result.Passed, tc.wantPassed, result.Message) diff --git a/main.go b/main.go index c82e581..0d39039 100644 --- a/main.go +++ b/main.go @@ -137,7 +137,7 @@ func runSuite(eval *evaluator.Evaluator, rep *reporter.Reporter, suite *loader.T for _, test := range suite.Tests { suiteRep.StartTest(test.Name) - mutatingPolicy, validatingPolicy, validatingBinding := findPolicies(suite, test.PolicyName) + mutatingPolicy, mutatingBinding, validatingPolicy, validatingBinding := findPolicies(suite, test.PolicyName) if mutatingPolicy == nil && validatingPolicy == nil { suiteRep.ReportFail(test.Name, fmt.Sprintf("policy %q not found", test.PolicyName)) @@ -146,7 +146,7 @@ func runSuite(eval *evaluator.Evaluator, rep *reporter.Reporter, suite *loader.T } // Evaluate test - result := eval.EvaluateTest(mutatingPolicy, validatingPolicy, validatingBinding, test) + result := eval.EvaluateTest(mutatingPolicy, mutatingBinding, validatingPolicy, validatingBinding, test) suiteRep.ReportResult(test.Name, result) } @@ -154,16 +154,25 @@ func runSuite(eval *evaluator.Evaluator, rep *reporter.Reporter, suite *loader.T return nil } -func findPolicies(suite *loader.TestSuite, policyName string) (*admissionv1beta1.MutatingAdmissionPolicy, *admissionregv1.ValidatingAdmissionPolicy, *admissionregv1.ValidatingAdmissionPolicyBinding) { - var mutatingPolicy *admissionv1beta1.MutatingAdmissionPolicy - - var validatingPolicy *admissionregv1.ValidatingAdmissionPolicy - - var validatingBinding *admissionregv1.ValidatingAdmissionPolicyBinding +func findPolicies(suite *loader.TestSuite, policyName string) (*admissionv1beta1.MutatingAdmissionPolicy, *admissionv1beta1.MutatingAdmissionPolicyBinding, *admissionregv1.ValidatingAdmissionPolicy, *admissionregv1.ValidatingAdmissionPolicyBinding) { + var ( + mutatingPolicy *admissionv1beta1.MutatingAdmissionPolicy + mutatingBinding *admissionv1beta1.MutatingAdmissionPolicyBinding + validatingPolicy *admissionregv1.ValidatingAdmissionPolicy + validatingBinding *admissionregv1.ValidatingAdmissionPolicyBinding + ) for _, policy := range suite.MutatingPolicies { if policy.Name == policyName { mutatingPolicy = policy + // Find matching binding + for _, binding := range suite.MutatingBindings { + if binding.Spec.PolicyName == policy.Name { + mutatingBinding = binding + + break + } + } break } @@ -187,7 +196,7 @@ func findPolicies(suite *loader.TestSuite, policyName string) (*admissionv1beta1 } } - return mutatingPolicy, validatingPolicy, validatingBinding + return mutatingPolicy, mutatingBinding, validatingPolicy, validatingBinding } func getVersion() string { diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/binding.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/binding.yaml new file mode 100644 index 0000000..0a0a1f7 --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/binding.yaml @@ -0,0 +1,13 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: namespace-selector-binding-mutating-test-binding +spec: + policyName: namespace-selector-binding-mutating-test + matchResources: + namespaceSelector: + matchExpressions: + - key: environment + operator: In + values: ["prod"] + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/policy.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/policy.yaml new file mode 100644 index 0000000..7abd493 --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/policy.yaml @@ -0,0 +1,21 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicy +metadata: + name: namespace-selector-binding-mutating-test +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["configmaps"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: | + Object{ + metadata: Object.metadata{ + labels: {"mutated": "true"} + } + } + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.dev-namespace.allow.object.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.dev-namespace.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.dev-namespace.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.dev-namespace.allow.request.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.dev-namespace.allow.request.yaml new file mode 100644 index 0000000..215b53b --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.dev-namespace.allow.request.yaml @@ -0,0 +1,9 @@ +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: dev-namespace + labels: + environment: dev + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.no-label.allow.object.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.no-label.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.no-label.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.no-label.allow.request.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.no-label.allow.request.yaml new file mode 100644 index 0000000..4969620 --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.no-label.allow.request.yaml @@ -0,0 +1,8 @@ +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: unlabeled-namespace + labels: {} + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.expected.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.expected.yaml new file mode 100644 index 0000000..16f0c16 --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.expected.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config + labels: + mutated: "true" +data: + key: value + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.object.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.request.yaml b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.request.yaml new file mode 100644 index 0000000..fddda26 --- /dev/null +++ b/test-policies-pass/mutating/namespace-selector-binding-mutating/tests/namespace-selector-binding-mutating-test.prod-namespace.mutate.request.yaml @@ -0,0 +1,9 @@ +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: prod-namespace + labels: + environment: prod + diff --git a/test-policies-pass/validating/namespace-selector-binding/binding.yaml b/test-policies-pass/validating/namespace-selector-binding/binding.yaml new file mode 100644 index 0000000..a739e46 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/binding.yaml @@ -0,0 +1,14 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: namespace-selector-binding-test-binding +spec: + policyName: namespace-selector-binding-test + validationActions: [Deny] + matchResources: + namespaceSelector: + matchExpressions: + - key: environment + operator: In + values: ["prod"] + diff --git a/test-policies-pass/validating/namespace-selector-binding/policy.yaml b/test-policies-pass/validating/namespace-selector-binding/policy.yaml new file mode 100644 index 0000000..bc476ed --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/policy.yaml @@ -0,0 +1,17 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: namespace-selector-binding-test +spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["configmaps"] + validations: + - expression: "false" + message: "This policy always denies - used to test namespace selector filtering" + reason: Forbidden + diff --git a/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.dev-namespace.allow.object.yaml b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.dev-namespace.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.dev-namespace.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.dev-namespace.allow.request.yaml b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.dev-namespace.allow.request.yaml new file mode 100644 index 0000000..215b53b --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.dev-namespace.allow.request.yaml @@ -0,0 +1,9 @@ +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: dev-namespace + labels: + environment: dev + diff --git a/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.no-label-namespace.allow.object.yaml b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.no-label-namespace.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.no-label-namespace.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.no-label-namespace.allow.request.yaml b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.no-label-namespace.allow.request.yaml new file mode 100644 index 0000000..4969620 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.no-label-namespace.allow.request.yaml @@ -0,0 +1,8 @@ +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: unlabeled-namespace + labels: {} + diff --git a/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.prod-namespace.deny.object.yaml b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.prod-namespace.deny.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.prod-namespace.deny.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.prod-namespace.deny.request.yaml b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.prod-namespace.deny.request.yaml new file mode 100644 index 0000000..fddda26 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-binding/tests/namespace-selector-binding-test.prod-namespace.deny.request.yaml @@ -0,0 +1,9 @@ +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: prod-namespace + labels: + environment: prod + diff --git a/test-policies-pass/validating/namespace-selector-doesnotexist/binding.yaml b/test-policies-pass/validating/namespace-selector-doesnotexist/binding.yaml new file mode 100644 index 0000000..f62277a --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-doesnotexist/binding.yaml @@ -0,0 +1,14 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: namespace-selector-doesnotexist-test-binding +spec: + policyName: namespace-selector-doesnotexist-test + validationActions: [Deny] + matchResources: + namespaceSelector: + matchExpressions: + # DoesNotExist: policy applies only if 'skip-validation' label does NOT exist + - key: skip-validation + operator: DoesNotExist + diff --git a/test-policies-pass/validating/namespace-selector-doesnotexist/policy.yaml b/test-policies-pass/validating/namespace-selector-doesnotexist/policy.yaml new file mode 100644 index 0000000..ed6e289 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-doesnotexist/policy.yaml @@ -0,0 +1,17 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: namespace-selector-doesnotexist-test +spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["configmaps"] + validations: + - expression: "false" + message: "This policy always denies - used to test DoesNotExist operator" + reason: Forbidden + diff --git a/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.has-skip-label.allow.object.yaml b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.has-skip-label.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.has-skip-label.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.has-skip-label.allow.request.yaml b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.has-skip-label.allow.request.yaml new file mode 100644 index 0000000..f3b82d4 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.has-skip-label.allow.request.yaml @@ -0,0 +1,11 @@ +# Has skip-validation label (DoesNotExist - doesn't match) -> policy doesn't apply -> allow +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: skipped-namespace + labels: + environment: prod + skip-validation: "true" + diff --git a/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.no-skip-label.deny.object.yaml b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.no-skip-label.deny.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.no-skip-label.deny.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.no-skip-label.deny.request.yaml b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.no-skip-label.deny.request.yaml new file mode 100644 index 0000000..62192d6 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-doesnotexist/tests/namespace-selector-doesnotexist-test.no-skip-label.deny.request.yaml @@ -0,0 +1,10 @@ +# No skip-validation label (DoesNotExist - matches) -> policy applies -> deny +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: normal-namespace + labels: + environment: prod + diff --git a/test-policies-pass/validating/namespace-selector-operators/binding.yaml b/test-policies-pass/validating/namespace-selector-operators/binding.yaml new file mode 100644 index 0000000..ff7e38b --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/binding.yaml @@ -0,0 +1,18 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: namespace-selector-operators-test-binding +spec: + policyName: namespace-selector-operators-test + validationActions: [Deny] + matchResources: + namespaceSelector: + matchExpressions: + # NotIn: policy applies to namespaces NOT in staging + - key: environment + operator: NotIn + values: ["staging"] + # Exists: policy applies only if 'team' label exists + - key: team + operator: Exists + diff --git a/test-policies-pass/validating/namespace-selector-operators/policy.yaml b/test-policies-pass/validating/namespace-selector-operators/policy.yaml new file mode 100644 index 0000000..1937545 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/policy.yaml @@ -0,0 +1,17 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: namespace-selector-operators-test +spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["configmaps"] + validations: + - expression: "false" + message: "This policy always denies - used to test namespace selector operators" + reason: Forbidden + diff --git a/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.exists-fail.allow.object.yaml b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.exists-fail.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.exists-fail.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.exists-fail.allow.request.yaml b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.exists-fail.allow.request.yaml new file mode 100644 index 0000000..de2984a --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.exists-fail.allow.request.yaml @@ -0,0 +1,10 @@ +# environment=prod (NotIn staging - matches) + no team label (Exists - doesn't match) -> policy doesn't apply -> allow +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: prod-no-team + labels: + environment: prod + diff --git a/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-exists-match.deny.object.yaml b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-exists-match.deny.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-exists-match.deny.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-exists-match.deny.request.yaml b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-exists-match.deny.request.yaml new file mode 100644 index 0000000..399063d --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-exists-match.deny.request.yaml @@ -0,0 +1,11 @@ +# environment=prod (NotIn staging - matches) + team=platform (Exists - matches) -> policy applies -> deny +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: prod-with-team + labels: + environment: prod + team: platform + diff --git a/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-fail.allow.object.yaml b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-fail.allow.object.yaml new file mode 100644 index 0000000..03ff25c --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-fail.allow.object.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + key: value + diff --git a/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-fail.allow.request.yaml b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-fail.allow.request.yaml new file mode 100644 index 0000000..54789f4 --- /dev/null +++ b/test-policies-pass/validating/namespace-selector-operators/tests/namespace-selector-operators-test.notin-fail.allow.request.yaml @@ -0,0 +1,11 @@ +# environment=staging (NotIn staging - doesn't match) -> policy doesn't apply -> allow +operation: CREATE +namespaceObject: + apiVersion: v1 + kind: Namespace + metadata: + name: staging-namespace + labels: + environment: staging + team: platform + diff --git a/testdata/all_policies.golden b/testdata/all_policies.golden index 88b8700..04a6654 100644 --- a/testdata/all_policies.golden +++ b/testdata/all_policies.golden @@ -1,5 +1,6 @@ ok add-default-labels 0.000s ok mutating-with-binding 0.000s +ok namespace-selector-binding-mutating 0.000s ok sidecar-injection 0.000s ok block-pod-exec 0.000s ok block-privileged-containers 0.000s @@ -9,6 +10,9 @@ ok conditional-policy 0.000s ok delete-protection 0.000s ok deprecated-api-warn 0.000s ok namespace-based-validation 0.000s +ok namespace-selector-binding 0.000s +ok namespace-selector-doesnotexist 0.000s +ok namespace-selector-operators 0.000s ok prevent-owner-change 0.000s ok replica-limit 0.000s ok replica-limit-with-params 0.000s diff --git a/testdata/json_output.golden b/testdata/json_output.golden index 8e6857d..a366094 100644 --- a/testdata/json_output.golden +++ b/testdata/json_output.golden @@ -10,6 +10,14 @@ {"time":"2000-01-01T00:00:00Z","action":"run","package":"mutating-with-binding","test":"no-params.allowed.yaml"} {"time":"2000-01-01T00:00:00Z","action":"pass","package":"mutating-with-binding","test":"no-params.allowed.yaml","elapsed":0} {"time":"2000-01-01T00:00:00Z","action":"pass","package":"mutating-with-binding","elapsed":0} +{"time":"2000-01-01T00:00:00Z","action":"run","package":"namespace-selector-binding-mutating"} +{"time":"2000-01-01T00:00:00Z","action":"run","package":"namespace-selector-binding-mutating","test":"namespace-selector-binding-mutating-test.dev-namespace.allow.yaml"} +{"time":"2000-01-01T00:00:00Z","action":"pass","package":"namespace-selector-binding-mutating","test":"namespace-selector-binding-mutating-test.dev-namespace.allow.yaml","elapsed":0} +{"time":"2000-01-01T00:00:00Z","action":"run","package":"namespace-selector-binding-mutating","test":"namespace-selector-binding-mutating-test.no-label.allow.yaml"} +{"time":"2000-01-01T00:00:00Z","action":"pass","package":"namespace-selector-binding-mutating","test":"namespace-selector-binding-mutating-test.no-label.allow.yaml","elapsed":0} +{"time":"2000-01-01T00:00:00Z","action":"run","package":"namespace-selector-binding-mutating","test":"namespace-selector-binding-mutating-test.prod-namespace.mutate.yaml"} +{"time":"2000-01-01T00:00:00Z","action":"pass","package":"namespace-selector-binding-mutating","test":"namespace-selector-binding-mutating-test.prod-namespace.mutate.yaml","elapsed":0} +{"time":"2000-01-01T00:00:00Z","action":"pass","package":"namespace-selector-binding-mutating","elapsed":0} {"time":"2000-01-01T00:00:00Z","action":"run","package":"sidecar-injection"} {"time":"2000-01-01T00:00:00Z","action":"run","package":"sidecar-injection","test":"sidecar-injection.adding-istio-sidecar.yaml"} {"time":"2000-01-01T00:00:00Z","action":"pass","package":"sidecar-injection","test":"sidecar-injection.adding-istio-sidecar.yaml","elapsed":0} diff --git a/testdata/recursion_dot.golden b/testdata/recursion_dot.golden index 097c0cc..102ab18 100644 --- a/testdata/recursion_dot.golden +++ b/testdata/recursion_dot.golden @@ -90,6 +90,7 @@ FAIL prevent-owner-change 0.000s FAIL track-privileged-audit 0.000s ok add-default-labels 0.000s ok mutating-with-binding 0.000s +ok namespace-selector-binding-mutating 0.000s ok sidecar-injection 0.000s ok block-pod-exec 0.000s ok block-privileged-containers 0.000s @@ -99,6 +100,9 @@ ok conditional-policy 0.000s ok delete-protection 0.000s ok deprecated-api-warn 0.000s ok namespace-based-validation 0.000s +ok namespace-selector-binding 0.000s +ok namespace-selector-doesnotexist 0.000s +ok namespace-selector-operators 0.000s ok prevent-owner-change 0.000s ok replica-limit 0.000s ok replica-limit-with-params 0.000s diff --git a/testdata/specific_dir_mutating.golden b/testdata/specific_dir_mutating.golden index ca812d4..fe9f6c6 100644 --- a/testdata/specific_dir_mutating.golden +++ b/testdata/specific_dir_mutating.golden @@ -1,3 +1,4 @@ ok add-default-labels 0.000s ok mutating-with-binding 0.000s +ok namespace-selector-binding-mutating 0.000s ok sidecar-injection 0.000s