k8s.io/kubernetes@v1.29.3/test/integration/apiserver/cel/validatingadmissionpolicy_test.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cel 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "net/http" 24 "net/http/httptest" 25 "os" 26 "strconv" 27 "strings" 28 "sync" 29 "testing" 30 "text/template" 31 "time" 32 33 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 34 apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 35 "k8s.io/apiextensions-apiserver/test/integration/fixtures" 36 auditinternal "k8s.io/apiserver/pkg/apis/audit" 37 auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" 38 genericfeatures "k8s.io/apiserver/pkg/features" 39 utilfeature "k8s.io/apiserver/pkg/util/feature" 40 "k8s.io/client-go/rest" 41 featuregatetesting "k8s.io/component-base/featuregate/testing" 42 43 "k8s.io/kubernetes/cmd/kube-apiserver/app/options" 44 apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 45 authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" 46 "k8s.io/kubernetes/test/integration/authutil" 47 "k8s.io/kubernetes/test/integration/etcd" 48 "k8s.io/kubernetes/test/integration/framework" 49 "k8s.io/kubernetes/test/utils" 50 51 apierrors "k8s.io/apimachinery/pkg/api/errors" 52 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 53 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 54 "k8s.io/apimachinery/pkg/runtime/schema" 55 "k8s.io/apimachinery/pkg/types" 56 "k8s.io/apimachinery/pkg/util/sets" 57 "k8s.io/apimachinery/pkg/util/wait" 58 "k8s.io/client-go/dynamic" 59 clientset "k8s.io/client-go/kubernetes" 60 61 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 62 admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" 63 authorizationv1 "k8s.io/api/authorization/v1" 64 v1 "k8s.io/api/core/v1" 65 rbacv1 "k8s.io/api/rbac/v1" 66 ) 67 68 // Test_ValidateNamespace_NoParams tests a ValidatingAdmissionPolicy that validates creation of a Namespace with no params. 69 func Test_ValidateNamespace_NoParams(t *testing.T) { 70 forbiddenReason := metav1.StatusReasonForbidden 71 72 testcases := []struct { 73 name string 74 policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy 75 policyBinding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding 76 namespace *v1.Namespace 77 err string 78 failureReason metav1.StatusReason 79 }{ 80 { 81 name: "namespace name contains suffix enforced by validating admission policy, using object metadata fields", 82 policy: withValidations([]admissionregistrationv1beta1.Validation{ 83 { 84 Expression: "object.metadata.name.endsWith('k8s')", 85 }, 86 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 87 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 88 namespace: &v1.Namespace{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Name: "test-k8s", 91 }, 92 }, 93 err: "", 94 }, 95 { 96 name: "namespace name does NOT contain suffix enforced by validating admission policyusing, object metadata fields", 97 policy: withValidations([]admissionregistrationv1beta1.Validation{ 98 { 99 Expression: "object.metadata.name.endsWith('k8s')", 100 }, 101 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 102 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 103 namespace: &v1.Namespace{ 104 ObjectMeta: metav1.ObjectMeta{ 105 Name: "test-foobar", 106 }, 107 }, 108 err: "namespaces \"test-foobar\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: object.metadata.name.endsWith('k8s')", 109 failureReason: metav1.StatusReasonInvalid, 110 }, 111 { 112 name: "namespace name does NOT contain suffix enforced by validating admission policy using object metadata fields, AND validating expression returns StatusReasonForbidden", 113 policy: withValidations([]admissionregistrationv1beta1.Validation{ 114 { 115 Expression: "object.metadata.name.endsWith('k8s')", 116 Reason: &forbiddenReason, 117 }, 118 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 119 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 120 namespace: &v1.Namespace{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "forbidden-test-foobar", 123 }, 124 }, 125 err: "namespaces \"forbidden-test-foobar\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: object.metadata.name.endsWith('k8s')", 126 failureReason: metav1.StatusReasonForbidden, 127 }, 128 { 129 name: "namespace name contains suffix enforced by validating admission policy, using request field", 130 policy: withValidations([]admissionregistrationv1beta1.Validation{ 131 { 132 Expression: "request.name.endsWith('k8s')", 133 }, 134 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 135 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 136 namespace: &v1.Namespace{ 137 ObjectMeta: metav1.ObjectMeta{ 138 Name: "test-k8s", 139 }, 140 }, 141 err: "", 142 }, 143 { 144 name: "namespace name does NOT contains suffix enforced by validating admission policy, using request field", 145 policy: withValidations([]admissionregistrationv1beta1.Validation{ 146 { 147 Expression: "request.name.endsWith('k8s')", 148 }, 149 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 150 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 151 namespace: &v1.Namespace{ 152 ObjectMeta: metav1.ObjectMeta{ 153 Name: "test-k8s", 154 }, 155 }, 156 err: "", 157 }, 158 { 159 name: "runtime error when validating namespace, but failurePolicy=Ignore", 160 policy: withValidations([]admissionregistrationv1beta1.Validation{ 161 { 162 Expression: "object.nonExistentProperty == 'someval'", 163 }, 164 }, withFailurePolicy(admissionregistrationv1beta1.Ignore, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 165 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 166 namespace: &v1.Namespace{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: "test-k8s", 169 }, 170 }, 171 err: "", 172 }, 173 { 174 name: "runtime error when validating namespace, but failurePolicy=Fail", 175 policy: withValidations([]admissionregistrationv1beta1.Validation{ 176 { 177 Expression: "object.nonExistentProperty == 'someval'", 178 }, 179 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))), 180 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 181 namespace: &v1.Namespace{ 182 ObjectMeta: metav1.ObjectMeta{ 183 Name: "test-k8s", 184 }, 185 }, 186 err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: expression 'object.nonExistentProperty == 'someval'' resulted in error: no such key: nonExistentProperty", 187 failureReason: metav1.StatusReasonInvalid, 188 }, 189 { 190 name: "runtime error due to unguarded params", 191 policy: withValidations([]admissionregistrationv1beta1.Validation{ 192 { 193 Expression: "object.metadata.name.startsWith(params.metadata.name)", 194 }, 195 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 196 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 197 namespace: &v1.Namespace{ 198 ObjectMeta: metav1.ObjectMeta{ 199 Name: "test-k8s", 200 }, 201 }, 202 err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: expression 'object.metadata.name.startsWith(params.metadata.name)' resulted in error: no such key: metadata", 203 failureReason: metav1.StatusReasonInvalid, 204 }, 205 { 206 name: "with check against unguarded params using has()", 207 policy: withValidations([]admissionregistrationv1beta1.Validation{ 208 { 209 Expression: "has(params.metadata) && has(params.metadata.name) && object.metadata.name.endsWith(params.metadata.name)", 210 }, 211 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 212 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 213 namespace: &v1.Namespace{ 214 ObjectMeta: metav1.ObjectMeta{ 215 Name: "test-k8s", 216 }, 217 }, 218 err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: has(params.metadata) && has(params.metadata.name) && object.metadata.name.endsWith(params.metadata.name)", 219 failureReason: metav1.StatusReasonInvalid, 220 }, 221 { 222 name: "with check against null params", 223 policy: withValidations([]admissionregistrationv1beta1.Validation{ 224 { 225 Expression: "(params != null && object.metadata.name.endsWith(params.metadata.name))", 226 }, 227 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 228 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 229 namespace: &v1.Namespace{ 230 ObjectMeta: metav1.ObjectMeta{ 231 Name: "test-k8s", 232 }, 233 }, 234 err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: (params != null && object.metadata.name.endsWith(params.metadata.name))", 235 failureReason: metav1.StatusReasonInvalid, 236 }, 237 { 238 name: "with check against unguarded params using has() and default check", 239 policy: withValidations([]admissionregistrationv1beta1.Validation{ 240 { 241 Expression: "(has(params.metadata) && has(params.metadata.name) && object.metadata.name.startsWith(params.metadata.name)) || object.metadata.name.endsWith('k8s')", 242 }, 243 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 244 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 245 namespace: &v1.Namespace{ 246 ObjectMeta: metav1.ObjectMeta{ 247 Name: "test-k8s", 248 }, 249 }, 250 err: "", 251 }, 252 { 253 name: "with check against null params and default check", 254 policy: withValidations([]admissionregistrationv1beta1.Validation{ 255 { 256 Expression: "(params != null && object.metadata.name.startsWith(params.metadata.name)) || object.metadata.name.endsWith('k8s')", 257 }, 258 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 259 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 260 namespace: &v1.Namespace{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Name: "test-k8s", 263 }, 264 }, 265 err: "", 266 }, 267 { 268 name: "with check against namespaceObject", 269 policy: withValidations([]admissionregistrationv1beta1.Validation{ 270 { 271 Expression: "namespaceObject == null", // because namespace itself is cluster-scoped. 272 }, 273 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 274 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), 275 namespace: &v1.Namespace{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: "test-k8s", 278 }, 279 }, 280 err: "", 281 }, 282 } 283 for _, testcase := range testcases { 284 t.Run(testcase.name, func(t *testing.T) { 285 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 286 287 server, err := apiservertesting.StartTestServer(t, nil, []string{ 288 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 289 }, framework.SharedEtcd()) 290 if err != nil { 291 t.Fatal(err) 292 } 293 defer server.TearDownFn() 294 295 config := server.ClientConfig 296 297 client, err := clientset.NewForConfig(config) 298 if err != nil { 299 t.Fatal(err) 300 } 301 policy := withWaitReadyConstraintAndExpression(testcase.policy) 302 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 303 t.Fatal(err) 304 } 305 if err := createAndWaitReady(t, client, testcase.policyBinding, nil); err != nil { 306 t.Fatal(err) 307 } 308 309 _, err = client.CoreV1().Namespaces().Create(context.TODO(), testcase.namespace, metav1.CreateOptions{}) 310 311 checkExpectedError(t, err, testcase.err) 312 checkFailureReason(t, err, testcase.failureReason) 313 }) 314 } 315 } 316 func Test_ValidateAnnotationsAndWarnings(t *testing.T) { 317 testcases := []struct { 318 name string 319 policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy 320 policyBinding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding 321 object *v1.ConfigMap 322 err string 323 failureReason metav1.StatusReason 324 auditAnnotations map[string]string 325 warnings sets.Set[string] 326 }{ 327 { 328 name: "with audit annotations", 329 policy: withAuditAnnotations([]admissionregistrationv1beta1.AuditAnnotation{ 330 { 331 Key: "example-key", 332 ValueExpression: "'object name: ' + object.metadata.name", 333 }, 334 { 335 Key: "exclude-key", 336 ValueExpression: "null", 337 }, 338 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withConfigMapMatch(makePolicy("validate-audit-annotations"))))), 339 policyBinding: makeBinding("validate-audit-annotations-binding", "validate-audit-annotations", ""), 340 object: &v1.ConfigMap{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Name: "test1-k8s", 343 }, 344 }, 345 err: "", 346 auditAnnotations: map[string]string{ 347 "validate-audit-annotations/example-key": `object name: test1-k8s`, 348 }, 349 }, 350 { 351 name: "with audit annotations with invalid expression", 352 policy: withAuditAnnotations([]admissionregistrationv1beta1.AuditAnnotation{ 353 { 354 Key: "example-key", 355 ValueExpression: "string(params.metadata.name)", // runtime error, params is null 356 }, 357 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withConfigMapMatch(makePolicy("validate-audit-annotations-invalid"))))), 358 policyBinding: makeBinding("validate-audit-annotations-invalid-binding", "validate-audit-annotations-invalid", ""), 359 object: &v1.ConfigMap{ 360 ObjectMeta: metav1.ObjectMeta{ 361 Name: "test2-k8s", 362 }, 363 }, 364 err: "configmaps \"test2-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-audit-annotations-invalid' with binding 'validate-audit-annotations-invalid-binding' denied request: expression 'string(params.metadata.name)' resulted in error: no such key: metadata", 365 failureReason: metav1.StatusReasonInvalid, 366 }, 367 { 368 name: "with audit annotations with invalid expression and ignore failure policy", 369 policy: withAuditAnnotations([]admissionregistrationv1beta1.AuditAnnotation{ 370 { 371 Key: "example-key", 372 ValueExpression: "string(params.metadata.name)", // runtime error, params is null 373 }, 374 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Ignore, withConfigMapMatch(makePolicy("validate-audit-annotations-invalid-ignore"))))), 375 policyBinding: makeBinding("validate-audit-annotations-invalid-ignore-binding", "validate-audit-annotations-invalid-ignore", ""), 376 object: &v1.ConfigMap{ 377 ObjectMeta: metav1.ObjectMeta{ 378 Name: "test3-k8s", 379 }, 380 }, 381 err: "", 382 }, 383 { 384 name: "with warn validationActions", 385 policy: withValidations([]admissionregistrationv1beta1.Validation{ 386 { 387 Expression: "object.metadata.name.endsWith('k8s')", 388 }, 389 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withConfigMapMatch(makePolicy("validate-actions-warn"))))), 390 policyBinding: withValidationActions([]admissionregistrationv1beta1.ValidationAction{admissionregistrationv1beta1.Warn}, makeBinding("validate-actions-warn-binding", "validate-actions-warn", "")), 391 object: &v1.ConfigMap{ 392 ObjectMeta: metav1.ObjectMeta{ 393 Name: "test4-nope", 394 }, 395 }, 396 warnings: sets.New("Validation failed for ValidatingAdmissionPolicy 'validate-actions-warn' with binding 'validate-actions-warn-binding': failed expression: object.metadata.name.endsWith('k8s')"), 397 }, 398 { 399 name: "with audit validationActions", 400 policy: withValidations([]admissionregistrationv1beta1.Validation{ 401 { 402 Expression: "object.metadata.name.endsWith('k8s')", 403 }, 404 }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1beta1.Fail, withConfigMapMatch(makePolicy("validate-actions-audit"))))), 405 policyBinding: withValidationActions([]admissionregistrationv1beta1.ValidationAction{admissionregistrationv1beta1.Deny, admissionregistrationv1beta1.Audit}, makeBinding("validate-actions-audit-binding", "validate-actions-audit", "")), 406 object: &v1.ConfigMap{ 407 ObjectMeta: metav1.ObjectMeta{ 408 Name: "test5-nope", 409 }, 410 }, 411 err: "configmaps \"test5-nope\" is forbidden: ValidatingAdmissionPolicy 'validate-actions-audit' with binding 'validate-actions-audit-binding' denied request: failed expression: object.metadata.name.endsWith('k8s')", 412 failureReason: metav1.StatusReasonInvalid, 413 auditAnnotations: map[string]string{ 414 "validation.policy.admission.k8s.io/validation_failure": `[{"message":"failed expression: object.metadata.name.endsWith('k8s')","policy":"validate-actions-audit","binding":"validate-actions-audit-binding","expressionIndex":1,"validationActions":["Deny","Audit"]}]`, 415 }, 416 }, 417 } 418 419 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 420 421 // prepare audit policy file 422 policyFile, err := os.CreateTemp("", "audit-policy.yaml") 423 if err != nil { 424 t.Fatalf("Failed to create audit policy file: %v", err) 425 } 426 defer os.Remove(policyFile.Name()) 427 if _, err := policyFile.Write([]byte(auditPolicy)); err != nil { 428 t.Fatalf("Failed to write audit policy file: %v", err) 429 } 430 if err := policyFile.Close(); err != nil { 431 t.Fatalf("Failed to close audit policy file: %v", err) 432 } 433 434 // prepare audit log file 435 logFile, err := os.CreateTemp("", "audit.log") 436 if err != nil { 437 t.Fatalf("Failed to create audit log file: %v", err) 438 } 439 defer os.Remove(logFile.Name()) 440 441 server, err := apiservertesting.StartTestServer(t, nil, []string{ 442 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 443 "--audit-policy-file", policyFile.Name(), 444 "--audit-log-version", "audit.k8s.io/v1", 445 "--audit-log-mode", "blocking", 446 "--audit-log-path", logFile.Name(), 447 }, framework.SharedEtcd()) 448 if err != nil { 449 t.Fatal(err) 450 } 451 defer server.TearDownFn() 452 453 config := server.ClientConfig 454 455 warnHandler := newWarningHandler() 456 config.WarningHandler = warnHandler 457 config.Impersonate.UserName = testReinvocationClientUsername 458 client, err := clientset.NewForConfig(config) 459 if err != nil { 460 t.Fatal(err) 461 } 462 for i, testcase := range testcases { 463 t.Run(testcase.name, func(t *testing.T) { 464 testCaseID := strconv.Itoa(i) 465 ns := "auditannotations-" + testCaseID 466 _, err = client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, metav1.CreateOptions{}) 467 if err != nil { 468 t.Fatal(err) 469 } 470 471 policy := withWaitReadyConstraintAndExpression(testcase.policy) 472 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 473 t.Fatal(err) 474 } 475 476 if err := createAndWaitReadyNamespacedWithWarnHandler(t, client, withMatchNamespace(testcase.policyBinding, ns), nil, ns, warnHandler); err != nil { 477 t.Fatal(err) 478 } 479 warnHandler.reset() 480 testcase.object.Namespace = ns 481 _, err = client.CoreV1().ConfigMaps(ns).Create(context.TODO(), testcase.object, metav1.CreateOptions{}) 482 483 code := int32(201) 484 if testcase.err != "" { 485 code = 422 486 } 487 488 auditAnnotationFilter := func(key, val string) bool { 489 _, ok := testcase.auditAnnotations[key] 490 return ok 491 } 492 493 checkExpectedError(t, err, testcase.err) 494 checkFailureReason(t, err, testcase.failureReason) 495 checkExpectedWarnings(t, warnHandler, testcase.warnings) 496 checkAuditEvents(t, logFile, expectedAuditEvents(testcase.auditAnnotations, ns, code), auditAnnotationFilter) 497 }) 498 } 499 } 500 501 // Test_ValidateNamespace_WithConfigMapParams tests a ValidatingAdmissionPolicy that validates creation of a Namespace, 502 // using ConfigMap as a param reference. 503 func Test_ValidateNamespace_WithConfigMapParams(t *testing.T) { 504 testcases := []struct { 505 name string 506 policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy 507 policyBinding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding 508 configMap *v1.ConfigMap 509 namespace *v1.Namespace 510 err string 511 failureReason metav1.StatusReason 512 }{ 513 { 514 name: "namespace name contains suffix enforced by validating admission policy", 515 policy: withValidations([]admissionregistrationv1beta1.Validation{ 516 { 517 Expression: "object.metadata.name.endsWith(params.data.namespaceSuffix)", 518 }, 519 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withParams(configParamKind(), withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 520 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", "validate-namespace-suffix-param"), 521 configMap: makeConfigParams("validate-namespace-suffix-param", map[string]string{ 522 "namespaceSuffix": "k8s", 523 }), 524 namespace: &v1.Namespace{ 525 ObjectMeta: metav1.ObjectMeta{ 526 Name: "test-k8s", 527 }, 528 }, 529 err: "", 530 }, 531 { 532 name: "namespace name does NOT contain suffix enforced by validating admission policy", 533 policy: withValidations([]admissionregistrationv1beta1.Validation{ 534 { 535 Expression: "object.metadata.name.endsWith(params.data.namespaceSuffix)", 536 }, 537 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withParams(configParamKind(), withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), 538 policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", "validate-namespace-suffix-param"), 539 configMap: makeConfigParams("validate-namespace-suffix-param", map[string]string{ 540 "namespaceSuffix": "k8s", 541 }), 542 namespace: &v1.Namespace{ 543 ObjectMeta: metav1.ObjectMeta{ 544 Name: "test-foo", 545 }, 546 }, 547 err: "namespaces \"test-foo\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: object.metadata.name.endsWith(params.data.namespaceSuffix)", 548 failureReason: metav1.StatusReasonInvalid, 549 }, 550 } 551 552 for _, testcase := range testcases { 553 t.Run(testcase.name, func(t *testing.T) { 554 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 555 server, err := apiservertesting.StartTestServer(t, nil, []string{ 556 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 557 }, framework.SharedEtcd()) 558 if err != nil { 559 t.Fatal(err) 560 } 561 defer server.TearDownFn() 562 563 config := server.ClientConfig 564 565 client, err := clientset.NewForConfig(config) 566 if err != nil { 567 t.Fatal(err) 568 } 569 570 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), testcase.configMap, metav1.CreateOptions{}); err != nil { 571 t.Fatal(err) 572 } 573 574 policy := withWaitReadyConstraintAndExpression(testcase.policy) 575 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 576 t.Fatal(err) 577 } 578 if err := createAndWaitReady(t, client, testcase.policyBinding, nil); err != nil { 579 t.Fatal(err) 580 } 581 582 _, err = client.CoreV1().Namespaces().Create(context.TODO(), testcase.namespace, metav1.CreateOptions{}) 583 584 checkExpectedError(t, err, testcase.err) 585 checkFailureReason(t, err, testcase.failureReason) 586 }) 587 } 588 } 589 590 func TestMultiplePolicyBindings(t *testing.T) { 591 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 592 server, err := apiservertesting.StartTestServer(t, nil, nil, framework.SharedEtcd()) 593 if err != nil { 594 t.Fatal(err) 595 } 596 defer server.TearDownFn() 597 598 config := server.ClientConfig 599 600 client, err := clientset.NewForConfig(config) 601 if err != nil { 602 t.Fatal(err) 603 } 604 605 paramKind := &admissionregistrationv1beta1.ParamKind{ 606 APIVersion: "v1", 607 Kind: "ConfigMap", 608 } 609 policy := withPolicyExistsLabels([]string{"paramIdent"}, withParams(paramKind, withPolicyMatch("secrets", withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("test-policy"))))) 610 policy.Spec.Validations = []admissionregistrationv1beta1.Validation{ 611 { 612 Expression: "params.data.autofail != 'true' && (params.data.conditional == 'false' || object.metadata.name.startsWith(params.data.check))", 613 }, 614 } 615 policy = withWaitReadyConstraintAndExpression(policy) 616 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 617 t.Fatal(err) 618 } 619 620 autoFailParams := makeConfigParams("autofail-params", map[string]string{ 621 "autofail": "true", 622 }) 623 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), autoFailParams, metav1.CreateOptions{}); err != nil { 624 t.Fatal(err) 625 } 626 autofailBinding := withBindingExistsLabels([]string{"autofail-binding-label"}, policy, makeBinding("autofail-binding", "test-policy", "autofail-params")) 627 if err := createAndWaitReady(t, client, autofailBinding, map[string]string{"paramIdent": "true", "autofail-binding-label": "true"}); err != nil { 628 t.Fatal(err) 629 } 630 631 autoPassParams := makeConfigParams("autopass-params", map[string]string{ 632 "autofail": "false", 633 "conditional": "false", 634 }) 635 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), autoPassParams, metav1.CreateOptions{}); err != nil { 636 t.Fatal(err) 637 } 638 autopassBinding := withBindingExistsLabels([]string{"autopass-binding-label"}, policy, makeBinding("autopass-binding", "test-policy", "autopass-params")) 639 if err := createAndWaitReady(t, client, autopassBinding, map[string]string{"paramIdent": "true", "autopass-binding-label": "true"}); err != nil { 640 t.Fatal(err) 641 } 642 643 condpassParams := makeConfigParams("condpass-params", map[string]string{ 644 "autofail": "false", 645 "conditional": "true", 646 "check": "prefix-", 647 }) 648 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), condpassParams, metav1.CreateOptions{}); err != nil { 649 t.Fatal(err) 650 } 651 condpassBinding := withBindingExistsLabels([]string{"condpass-binding-label"}, policy, makeBinding("condpass-binding", "test-policy", "condpass-params")) 652 if err := createAndWaitReady(t, client, condpassBinding, map[string]string{"paramIdent": "true", "condpass-binding-label": "true"}); err != nil { 653 t.Fatal(err) 654 } 655 656 autofailingSecret := &v1.Secret{ 657 ObjectMeta: metav1.ObjectMeta{ 658 Name: "autofailing-secret", 659 Labels: map[string]string{ 660 "paramIdent": "someVal", 661 "autofail-binding-label": "true", 662 }, 663 }, 664 } 665 _, err = client.CoreV1().Secrets("default").Create(context.TODO(), autofailingSecret, metav1.CreateOptions{}) 666 if err == nil { 667 t.Fatal("expected secret creation to fail due to autofail-binding") 668 } 669 checkForFailedRule(t, err) 670 checkFailureReason(t, err, metav1.StatusReasonInvalid) 671 672 autopassingSecret := &v1.Secret{ 673 ObjectMeta: metav1.ObjectMeta{ 674 Name: "autopassing-secret", 675 Labels: map[string]string{ 676 "paramIdent": "someVal", 677 "autopass-binding-label": "true", 678 }, 679 }, 680 } 681 if _, err := client.CoreV1().Secrets("default").Create(context.TODO(), autopassingSecret, metav1.CreateOptions{}); err != nil { 682 t.Fatalf("expected secret creation to succeed, got: %s", err) 683 } 684 685 condpassingSecret := &v1.Secret{ 686 ObjectMeta: metav1.ObjectMeta{ 687 Name: "prefix-condpassing-secret", 688 Labels: map[string]string{ 689 "paramIdent": "someVal", 690 "condpass-binding-label": "true", 691 }, 692 }, 693 } 694 if _, err := client.CoreV1().Secrets("default").Create(context.TODO(), condpassingSecret, metav1.CreateOptions{}); err != nil { 695 t.Fatalf("expected secret creation to succeed, got: %s", err) 696 } 697 698 condfailingSecret := &v1.Secret{ 699 ObjectMeta: metav1.ObjectMeta{ 700 Name: "condfailing-secret", 701 Labels: map[string]string{ 702 "paramIdent": "someVal", 703 "condpass-binding-label": "true", 704 }, 705 }, 706 } 707 _, err = client.CoreV1().Secrets("default").Create(context.TODO(), condfailingSecret, metav1.CreateOptions{}) 708 if err == nil { 709 t.Fatal("expected secret creation to fail due to autofail-binding") 710 } 711 checkForFailedRule(t, err) 712 checkFailureReason(t, err, metav1.StatusReasonInvalid) 713 } 714 715 // Test_PolicyExemption tests that ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding resources 716 // are exempt from policy rules. 717 func Test_PolicyExemption(t *testing.T) { 718 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 719 server, err := apiservertesting.StartTestServer(t, nil, []string{ 720 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 721 }, framework.SharedEtcd()) 722 if err != nil { 723 t.Fatal(err) 724 } 725 defer server.TearDownFn() 726 727 config := server.ClientConfig 728 729 client, err := clientset.NewForConfig(config) 730 if err != nil { 731 t.Fatal(err) 732 } 733 734 policy := makePolicy("test-policy") 735 policy.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{ 736 ResourceRules: []admissionregistrationv1beta1.NamedRuleWithOperations{ 737 { 738 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 739 Operations: []admissionregistrationv1.OperationType{ 740 "*", 741 }, 742 Rule: admissionregistrationv1.Rule{ 743 APIGroups: []string{ 744 "*", 745 }, 746 APIVersions: []string{ 747 "*", 748 }, 749 Resources: []string{ 750 "*", 751 }, 752 }, 753 }, 754 }, 755 }, 756 } 757 758 policy.Spec.Validations = []admissionregistrationv1beta1.Validation{{ 759 Expression: "false", 760 Message: "marker denied; policy is ready", 761 }} 762 763 policy, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 764 if err != nil { 765 t.Fatal(err) 766 } 767 768 policyBinding := makeBinding("test-policy-binding", "test-policy", "") 769 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 770 t.Fatal(err) 771 } 772 773 // validate that operations to ValidatingAdmissionPolicy are exempt from an existing policy that catches all resources 774 policy, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Get(context.TODO(), policy.Name, metav1.GetOptions{}) 775 if err != nil { 776 t.Fatal(err) 777 } 778 ignoreFailurePolicy := admissionregistrationv1beta1.Ignore 779 policy.Spec.FailurePolicy = &ignoreFailurePolicy 780 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Update(context.TODO(), policy, metav1.UpdateOptions{}) 781 if err != nil { 782 t.Error(err) 783 } 784 785 policyBinding, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Get(context.TODO(), policyBinding.Name, metav1.GetOptions{}) 786 if err != nil { 787 t.Fatal(err) 788 } 789 790 // validate that operations to ValidatingAdmissionPolicyBindings are exempt from an existing policy that catches all resources 791 policyBindingCopy := policyBinding.DeepCopy() 792 policyBindingCopy.Spec.PolicyName = "different-binding" 793 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Update(context.TODO(), policyBindingCopy, metav1.UpdateOptions{}) 794 if err != nil { 795 t.Error(err) 796 } 797 } 798 799 // Test_ValidatingAdmissionPolicy_UpdateParamKind validates the behavior of ValidatingAdmissionPolicy when 800 // only the ParamKind is updated. This test creates a policy where namespaces must have a prefix that matches 801 // the ParamKind set in the policy. Switching the ParamKind should result in only namespaces with prefixes matching 802 // the new ParamKind to be allowed. For example, when Paramkind is v1/ConfigMap, only namespaces prefixed with "configmap" 803 // is allowed and when ParamKind is updated to v1/Secret, only namespaces prefixed with "secret" is allowed, etc. 804 func Test_ValidatingAdmissionPolicy_UpdateParamKind(t *testing.T) { 805 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 806 server, err := apiservertesting.StartTestServer(t, nil, []string{ 807 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 808 }, framework.SharedEtcd()) 809 if err != nil { 810 t.Fatal(err) 811 } 812 defer server.TearDownFn() 813 814 config := server.ClientConfig 815 816 client, err := clientset.NewForConfig(config) 817 if err != nil { 818 t.Fatal(err) 819 } 820 821 allowedPrefixesParamsConfigMap := &v1.ConfigMap{ 822 ObjectMeta: metav1.ObjectMeta{ 823 Name: "allowed-prefixes", 824 }, 825 } 826 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), allowedPrefixesParamsConfigMap, metav1.CreateOptions{}); err != nil { 827 t.Fatal(err) 828 } 829 830 allowedPrefixesParamSecret := &v1.Secret{ 831 ObjectMeta: metav1.ObjectMeta{ 832 Name: "allowed-prefixes", 833 }, 834 } 835 if _, err := client.CoreV1().Secrets("default").Create(context.TODO(), allowedPrefixesParamSecret, metav1.CreateOptions{}); err != nil { 836 t.Fatal(err) 837 } 838 839 paramKind := &admissionregistrationv1beta1.ParamKind{ 840 APIVersion: "v1", 841 Kind: "ConfigMap", 842 } 843 844 policy := withValidations([]admissionregistrationv1beta1.Validation{ 845 { 846 Expression: "object.metadata.name.startsWith(params.kind.lowerAscii())", 847 Message: "wrong paramKind", 848 }, 849 }, withParams(paramKind, withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 850 policy = withWaitReadyConstraintAndExpression(policy) 851 policy, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 852 if err != nil { 853 t.Fatal(err) 854 } 855 856 allowedPrefixesBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "allowed-prefixes") 857 if err := createAndWaitReady(t, client, allowedPrefixesBinding, nil); err != nil { 858 t.Fatal(err) 859 } 860 861 // validate that namespaces starting with "configmap-" are allowed 862 // and namespaces starting with "secret-" are disallowed 863 allowedNamespace := &v1.Namespace{ 864 ObjectMeta: metav1.ObjectMeta{ 865 GenerateName: "configmap-", 866 }, 867 } 868 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 869 if err != nil { 870 t.Error(err) 871 } 872 873 disallowedNamespace := &v1.Namespace{ 874 ObjectMeta: metav1.ObjectMeta{ 875 GenerateName: "secret-", 876 }, 877 } 878 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 879 if err == nil { 880 t.Error("unexpected nil error") 881 } 882 if !strings.Contains(err.Error(), "wrong paramKind") { 883 t.Errorf("unexpected error message: %v", err) 884 } 885 checkFailureReason(t, err, metav1.StatusReasonInvalid) 886 887 // update the policy ParamKind to reference a Secret 888 paramKind = &admissionregistrationv1beta1.ParamKind{ 889 APIVersion: "v1", 890 Kind: "Secret", 891 } 892 policy, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Get(context.TODO(), policy.Name, metav1.GetOptions{}) 893 if err != nil { 894 t.Error(err) 895 } 896 policy.Spec.ParamKind = paramKind 897 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Update(context.TODO(), policy, metav1.UpdateOptions{}) 898 if err != nil { 899 t.Error(err) 900 } 901 902 // validate that namespaces starting with "secret-" are allowed 903 // and namespaces starting with "configmap-" are disallowed 904 // wait loop is required here since ConfigMaps were previousy allowed and we need to wait for the new policy 905 // to be enforced 906 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 907 disallowedNamespace = &v1.Namespace{ 908 ObjectMeta: metav1.ObjectMeta{ 909 GenerateName: "configmap-", 910 }, 911 } 912 913 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 914 if err == nil { 915 return false, nil 916 } 917 918 if strings.Contains(err.Error(), "not yet synced to use for admission") { 919 return false, nil 920 } 921 922 if !strings.Contains(err.Error(), "wrong paramKind") { 923 return false, err 924 } 925 926 return true, nil 927 }); waitErr != nil { 928 t.Errorf("timed out waiting: %v", err) 929 } 930 931 allowedNamespace = &v1.Namespace{ 932 ObjectMeta: metav1.ObjectMeta{ 933 GenerateName: "secret-", 934 }, 935 } 936 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 937 if err != nil { 938 t.Error(err) 939 } 940 } 941 942 // Test_ValidatingAdmissionPolicy_UpdateParamRef validates the behavior of ValidatingAdmissionPolicy when 943 // only the ParamRef in the binding is updated. This test creates a policy where namespaces must have a prefix that matches 944 // the ParamRef set in the policy binding. The paramRef in the binding is then updated to a different object. 945 func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) { 946 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 947 server, err := apiservertesting.StartTestServer(t, nil, []string{ 948 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 949 }, framework.SharedEtcd()) 950 if err != nil { 951 t.Fatal(err) 952 } 953 defer server.TearDownFn() 954 955 config := server.ClientConfig 956 957 client, err := clientset.NewForConfig(config) 958 if err != nil { 959 t.Fatal(err) 960 } 961 962 allowedPrefixesParamsConfigMap1 := &v1.ConfigMap{ 963 ObjectMeta: metav1.ObjectMeta{ 964 Name: "test-1", 965 Namespace: "default", 966 }, 967 } 968 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), allowedPrefixesParamsConfigMap1, metav1.CreateOptions{}); err != nil { 969 t.Fatal(err) 970 } 971 972 allowedPrefixesParamsConfigMap2 := &v1.ConfigMap{ 973 ObjectMeta: metav1.ObjectMeta{ 974 Name: "test-2", 975 Namespace: "default", 976 }, 977 } 978 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), allowedPrefixesParamsConfigMap2, metav1.CreateOptions{}); err != nil { 979 t.Fatal(err) 980 } 981 982 policy := withValidations([]admissionregistrationv1beta1.Validation{ 983 { 984 Expression: "object.metadata.name.startsWith(params.metadata.name)", 985 Message: "wrong paramRef", 986 }, 987 }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 988 policy = withWaitReadyConstraintAndExpression(policy) 989 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 990 if err != nil { 991 t.Fatal(err) 992 } 993 994 // validate that namespaces starting with "test-1" are allowed 995 // and namespaces starting with "test-2-" are disallowed 996 allowedPrefixesBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "test-1") 997 if err := createAndWaitReady(t, client, allowedPrefixesBinding, nil); err != nil { 998 t.Fatal(err) 999 } 1000 1001 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1002 disallowedNamespace := &v1.Namespace{ 1003 ObjectMeta: metav1.ObjectMeta{ 1004 GenerateName: "test-2-", 1005 }, 1006 } 1007 1008 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1009 if err == nil { 1010 return false, nil 1011 } 1012 1013 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1014 return false, nil 1015 } 1016 1017 if !strings.Contains(err.Error(), "wrong paramRef") { 1018 return false, err 1019 } 1020 1021 return true, nil 1022 }); waitErr != nil { 1023 t.Errorf("timed out waiting: %v", err) 1024 } 1025 1026 allowedNamespace := &v1.Namespace{ 1027 ObjectMeta: metav1.ObjectMeta{ 1028 GenerateName: "test-1-", 1029 }, 1030 } 1031 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 1032 if err != nil { 1033 t.Error(err) 1034 } 1035 1036 // Update the paramRef in the policy binding to use the test-2 ConfigMap 1037 policyBinding, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Get(context.TODO(), allowedPrefixesBinding.Name, metav1.GetOptions{}) 1038 if err != nil { 1039 t.Fatal(err) 1040 } 1041 1042 denyAction := admissionregistrationv1beta1.DenyAction 1043 policyBindingCopy := policyBinding.DeepCopy() 1044 policyBindingCopy.Spec.ParamRef = &admissionregistrationv1beta1.ParamRef{ 1045 Name: "test-2", 1046 Namespace: "default", 1047 ParameterNotFoundAction: &denyAction, 1048 } 1049 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Update(context.TODO(), policyBindingCopy, metav1.UpdateOptions{}) 1050 if err != nil { 1051 t.Error(err) 1052 } 1053 1054 // validate that namespaces starting with "test-2" are allowed 1055 // and namespaces starting with "test-1" are disallowed 1056 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1057 disallowedNamespace := &v1.Namespace{ 1058 ObjectMeta: metav1.ObjectMeta{ 1059 GenerateName: "test-1-", 1060 }, 1061 } 1062 1063 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1064 if err == nil { 1065 return false, nil 1066 } 1067 1068 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1069 return false, nil 1070 } 1071 1072 if !strings.Contains(err.Error(), "wrong paramRef") { 1073 return false, err 1074 } 1075 1076 return true, nil 1077 }); waitErr != nil { 1078 t.Errorf("timed out waiting: %v", err) 1079 } 1080 1081 allowedNamespace = &v1.Namespace{ 1082 ObjectMeta: metav1.ObjectMeta{ 1083 GenerateName: "test-2-", 1084 }, 1085 } 1086 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 1087 if err != nil { 1088 t.Error(err) 1089 } 1090 } 1091 1092 // Test_ValidatingAdmissionPolicy_UpdateParamResource validates behavior of a policy after updates to the param resource. 1093 func Test_ValidatingAdmissionPolicy_UpdateParamResource(t *testing.T) { 1094 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1095 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1096 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1097 }, framework.SharedEtcd()) 1098 if err != nil { 1099 t.Fatal(err) 1100 } 1101 defer server.TearDownFn() 1102 1103 config := server.ClientConfig 1104 1105 client, err := clientset.NewForConfig(config) 1106 if err != nil { 1107 t.Fatal(err) 1108 } 1109 1110 paramConfigMap := &v1.ConfigMap{ 1111 ObjectMeta: metav1.ObjectMeta{ 1112 Name: "allowed-prefix", 1113 Namespace: "default", 1114 }, 1115 Data: map[string]string{ 1116 "prefix": "test-1", 1117 }, 1118 } 1119 paramConfigMap, err = client.CoreV1().ConfigMaps(paramConfigMap.Namespace).Create(context.TODO(), paramConfigMap, metav1.CreateOptions{}) 1120 if err != nil { 1121 t.Fatal(err) 1122 } 1123 1124 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1125 { 1126 Expression: "object.metadata.name.startsWith(params.data['prefix'])", 1127 Message: "wrong prefix", 1128 }, 1129 }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 1130 policy = withWaitReadyConstraintAndExpression(policy) 1131 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1132 if err != nil { 1133 t.Fatal(err) 1134 } 1135 1136 // validate that namespaces starting with "test-1" are allowed 1137 // and namespaces starting with "test-2-" are disallowed 1138 allowedPrefixesBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "allowed-prefix") 1139 if err := createAndWaitReady(t, client, allowedPrefixesBinding, nil); err != nil { 1140 t.Fatal(err) 1141 } 1142 1143 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1144 disallowedNamespace := &v1.Namespace{ 1145 ObjectMeta: metav1.ObjectMeta{ 1146 GenerateName: "test-2-", 1147 }, 1148 } 1149 1150 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1151 if err == nil { 1152 return false, nil 1153 } 1154 1155 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1156 return false, nil 1157 } 1158 1159 if !strings.Contains(err.Error(), "wrong prefix") { 1160 return false, err 1161 } 1162 1163 return true, nil 1164 }); waitErr != nil { 1165 t.Errorf("timed out waiting: %v", err) 1166 } 1167 1168 allowedNamespace := &v1.Namespace{ 1169 ObjectMeta: metav1.ObjectMeta{ 1170 GenerateName: "test-1-", 1171 }, 1172 } 1173 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 1174 if err != nil { 1175 t.Error(err) 1176 } 1177 1178 // Update the param resource to use "test-2" as the new allwoed prefix 1179 paramConfigMapCopy := paramConfigMap.DeepCopy() 1180 paramConfigMapCopy.Data = map[string]string{ 1181 "prefix": "test-2", 1182 } 1183 _, err = client.CoreV1().ConfigMaps(paramConfigMapCopy.Namespace).Update(context.TODO(), paramConfigMapCopy, metav1.UpdateOptions{}) 1184 if err != nil { 1185 t.Fatal(err) 1186 } 1187 1188 // validate that namespaces starting with "test-2" are allowed 1189 // and namespaces starting with "test-1" are disallowed 1190 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1191 disallowedNamespace := &v1.Namespace{ 1192 ObjectMeta: metav1.ObjectMeta{ 1193 GenerateName: "test-1-", 1194 }, 1195 } 1196 1197 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1198 if err == nil { 1199 return false, nil 1200 } 1201 1202 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1203 return false, nil 1204 } 1205 1206 if !strings.Contains(err.Error(), "wrong prefix") { 1207 return false, err 1208 } 1209 1210 return true, nil 1211 }); waitErr != nil { 1212 t.Errorf("timed out waiting: %v", err) 1213 } 1214 1215 allowedNamespace = &v1.Namespace{ 1216 ObjectMeta: metav1.ObjectMeta{ 1217 GenerateName: "test-2-", 1218 }, 1219 } 1220 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 1221 if err != nil { 1222 t.Error(err) 1223 } 1224 } 1225 1226 func Test_ValidatingAdmissionPolicy_MatchByObjectSelector(t *testing.T) { 1227 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1228 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1229 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1230 }, framework.SharedEtcd()) 1231 if err != nil { 1232 t.Fatal(err) 1233 } 1234 defer server.TearDownFn() 1235 1236 config := server.ClientConfig 1237 1238 client, err := clientset.NewForConfig(config) 1239 if err != nil { 1240 t.Fatal(err) 1241 } 1242 1243 labelSelector := &metav1.LabelSelector{ 1244 MatchLabels: map[string]string{ 1245 "foo": "bar", 1246 }, 1247 } 1248 1249 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1250 { 1251 Expression: "false", 1252 Message: "matched by object selector!", 1253 }, 1254 }, withConfigMapMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("match-by-object-selector")))) 1255 policy = withObjectSelector(labelSelector, policy) 1256 policy = withWaitReadyConstraintAndExpression(policy) 1257 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1258 if err != nil { 1259 t.Fatal(err) 1260 } 1261 1262 policyBinding := makeBinding("match-by-object-selector-binding", "match-by-object-selector", "") 1263 if err := createAndWaitReady(t, client, policyBinding, map[string]string{"foo": "bar"}); err != nil { 1264 t.Fatal(err) 1265 } 1266 1267 matchedConfigMap := &v1.ConfigMap{ 1268 ObjectMeta: metav1.ObjectMeta{ 1269 Name: "denied", 1270 Namespace: "default", 1271 Labels: map[string]string{ 1272 "foo": "bar", 1273 }, 1274 }, 1275 } 1276 1277 _, err = client.CoreV1().ConfigMaps(matchedConfigMap.Namespace).Create(context.TODO(), matchedConfigMap, metav1.CreateOptions{}) 1278 if !strings.Contains(err.Error(), "matched by object selector!") { 1279 t.Errorf("unexpected error: %v", err) 1280 } 1281 1282 allowedConfigMap := &v1.ConfigMap{ 1283 ObjectMeta: metav1.ObjectMeta{ 1284 Name: "allowed", 1285 Namespace: "default", 1286 }, 1287 } 1288 1289 if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { 1290 t.Errorf("unexpected error: %v", err) 1291 } 1292 } 1293 1294 func Test_ValidatingAdmissionPolicy_MatchByNamespaceSelector(t *testing.T) { 1295 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1296 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1297 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1298 }, framework.SharedEtcd()) 1299 if err != nil { 1300 t.Fatal(err) 1301 } 1302 defer server.TearDownFn() 1303 1304 config := server.ClientConfig 1305 1306 client, err := clientset.NewForConfig(config) 1307 if err != nil { 1308 t.Fatal(err) 1309 } 1310 1311 // only configmaps in default will be allowed. 1312 labelSelector := &metav1.LabelSelector{ 1313 MatchExpressions: []metav1.LabelSelectorRequirement{ 1314 { 1315 Key: "kubernetes.io/metadata.name", 1316 Operator: "NotIn", 1317 Values: []string{"default"}, 1318 }, 1319 }, 1320 } 1321 1322 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1323 { 1324 Expression: "false", 1325 Message: "matched by namespace selector!", 1326 }, 1327 }, withConfigMapMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("match-by-namespace-selector")))) 1328 policy = withNamespaceSelector(labelSelector, policy) 1329 policy = withWaitReadyConstraintAndExpression(policy) 1330 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1331 if err != nil { 1332 t.Fatal(err) 1333 } 1334 1335 policyBinding := makeBinding("match-by-namespace-selector-binding", "match-by-namespace-selector", "") 1336 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), policyBinding, metav1.CreateOptions{}) 1337 if err != nil { 1338 t.Fatal(err) 1339 } 1340 1341 namespace := &v1.Namespace{ 1342 ObjectMeta: metav1.ObjectMeta{ 1343 Name: "not-default", 1344 }, 1345 } 1346 if _, err := client.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{}); err != nil { 1347 t.Fatal(err) 1348 } 1349 1350 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1351 matchedConfigMap := &v1.ConfigMap{ 1352 ObjectMeta: metav1.ObjectMeta{ 1353 GenerateName: "denied-", 1354 Namespace: "not-default", 1355 }, 1356 } 1357 1358 _, err := client.CoreV1().ConfigMaps(matchedConfigMap.Namespace).Create(context.TODO(), matchedConfigMap, metav1.CreateOptions{}) 1359 // policy not enforced yet, try again 1360 if err == nil { 1361 return false, nil 1362 } 1363 1364 if !strings.Contains(err.Error(), "matched by namespace selector!") { 1365 return false, err 1366 } 1367 1368 return true, nil 1369 1370 }); waitErr != nil { 1371 t.Errorf("timed out waiting: %v", waitErr) 1372 } 1373 1374 allowedConfigMap := &v1.ConfigMap{ 1375 ObjectMeta: metav1.ObjectMeta{ 1376 Name: "allowed", 1377 Namespace: "default", 1378 }, 1379 } 1380 1381 if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { 1382 t.Errorf("unexpected error: %v", err) 1383 } 1384 } 1385 1386 func Test_ValidatingAdmissionPolicy_MatchByResourceNames(t *testing.T) { 1387 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1388 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1389 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1390 }, framework.SharedEtcd()) 1391 if err != nil { 1392 t.Fatal(err) 1393 } 1394 defer server.TearDownFn() 1395 1396 config := server.ClientConfig 1397 1398 client, err := clientset.NewForConfig(config) 1399 if err != nil { 1400 t.Fatal(err) 1401 } 1402 1403 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1404 { 1405 Expression: "false", 1406 Message: "matched by resource names!", 1407 }, 1408 }, withConfigMapMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("match-by-resource-names")))) 1409 policy.Spec.MatchConstraints.ResourceRules[0].ResourceNames = []string{"matched-by-resource-name"} 1410 policy = withWaitReadyConstraintAndExpression(policy) 1411 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1412 if err != nil { 1413 t.Fatal(err) 1414 } 1415 1416 policyBinding := makeBinding("match-by-resource-names-binding", "match-by-resource-names", "") 1417 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 1418 t.Fatal(err) 1419 } 1420 1421 matchedConfigMap := &v1.ConfigMap{ 1422 ObjectMeta: metav1.ObjectMeta{ 1423 Name: "matched-by-resource-name", 1424 Namespace: "default", 1425 }, 1426 } 1427 1428 _, err = client.CoreV1().ConfigMaps(matchedConfigMap.Namespace).Create(context.TODO(), matchedConfigMap, metav1.CreateOptions{}) 1429 if !strings.Contains(err.Error(), "matched by resource names!") { 1430 t.Errorf("unexpected error: %v", err) 1431 } 1432 1433 allowedConfigMap := &v1.ConfigMap{ 1434 ObjectMeta: metav1.ObjectMeta{ 1435 Name: "not-matched-by-resource-name", 1436 Namespace: "default", 1437 }, 1438 } 1439 1440 if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { 1441 t.Errorf("unexpected error: %v", err) 1442 } 1443 } 1444 1445 func Test_ValidatingAdmissionPolicy_MatchWithExcludeResources(t *testing.T) { 1446 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1447 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1448 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1449 }, framework.SharedEtcd()) 1450 if err != nil { 1451 t.Fatal(err) 1452 } 1453 defer server.TearDownFn() 1454 1455 config := server.ClientConfig 1456 1457 client, err := clientset.NewForConfig(config) 1458 if err != nil { 1459 t.Fatal(err) 1460 } 1461 1462 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1463 { 1464 Expression: "false", 1465 Message: "not matched by exclude resources!", 1466 }, 1467 }, withPolicyMatch("*", withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("match-by-resource-names")))) 1468 1469 policy = withExcludePolicyMatch("configmaps", policy) 1470 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1471 if err != nil { 1472 t.Fatal(err) 1473 } 1474 1475 policyBinding := makeBinding("match-by-resource-names-binding", "match-by-resource-names", "") 1476 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), policyBinding, metav1.CreateOptions{}) 1477 if err != nil { 1478 t.Fatal(err) 1479 } 1480 1481 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1482 secret := &v1.Secret{ 1483 ObjectMeta: metav1.ObjectMeta{ 1484 GenerateName: "not-matched-by-exclude-resources", 1485 Namespace: "default", 1486 }, 1487 } 1488 1489 _, err := client.CoreV1().Secrets(secret.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) 1490 // policy not enforced yet, try again 1491 if err == nil { 1492 return false, nil 1493 } 1494 1495 if !strings.Contains(err.Error(), "not matched by exclude resources!") { 1496 return false, err 1497 } 1498 1499 return true, nil 1500 1501 }); waitErr != nil { 1502 t.Errorf("timed out waiting: %v", waitErr) 1503 } 1504 1505 allowedConfigMap := &v1.ConfigMap{ 1506 ObjectMeta: metav1.ObjectMeta{ 1507 Name: "matched-by-exclude-resources", 1508 Namespace: "default", 1509 }, 1510 } 1511 1512 if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { 1513 t.Errorf("unexpected error: %v", err) 1514 } 1515 } 1516 1517 func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyEquivalent(t *testing.T) { 1518 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1519 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1520 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1521 }, framework.SharedEtcd()) 1522 etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, versionedCustomResourceDefinition()) 1523 if err != nil { 1524 t.Fatal(err) 1525 } 1526 defer server.TearDownFn() 1527 1528 config := server.ClientConfig 1529 1530 client, err := clientset.NewForConfig(config) 1531 if err != nil { 1532 t.Fatal(err) 1533 } 1534 1535 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1536 { 1537 Expression: "false", 1538 Message: "matched by equivalent match policy!", 1539 }, 1540 }, withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("match-by-match-policy-equivalent"))) 1541 policy.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{ 1542 ResourceRules: []admissionregistrationv1beta1.NamedRuleWithOperations{ 1543 { 1544 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 1545 Operations: []admissionregistrationv1.OperationType{ 1546 "*", 1547 }, 1548 Rule: admissionregistrationv1.Rule{ 1549 APIGroups: []string{ 1550 "awesome.bears.com", 1551 }, 1552 APIVersions: []string{ 1553 "v1", 1554 }, 1555 Resources: []string{ 1556 "pandas", 1557 }, 1558 }, 1559 }, 1560 }, 1561 }, 1562 } 1563 policy = withWaitReadyConstraintAndExpression(policy) 1564 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 1565 t.Fatal(err) 1566 } 1567 1568 policyBinding := makeBinding("match-by-match-policy-equivalent-binding", "match-by-match-policy-equivalent", "") 1569 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 1570 t.Fatal(err) 1571 } 1572 1573 v1Resource := &unstructured.Unstructured{ 1574 Object: map[string]interface{}{ 1575 "apiVersion": "awesome.bears.com" + "/" + "v1", 1576 "kind": "Panda", 1577 "metadata": map[string]interface{}{ 1578 "name": "v1-bears", 1579 }, 1580 }, 1581 } 1582 1583 v2Resource := &unstructured.Unstructured{ 1584 Object: map[string]interface{}{ 1585 "apiVersion": "awesome.bears.com" + "/" + "v2", 1586 "kind": "Panda", 1587 "metadata": map[string]interface{}{ 1588 "name": "v2-bears", 1589 }, 1590 }, 1591 } 1592 1593 dynamicClient, err := dynamic.NewForConfig(config) 1594 if err != nil { 1595 t.Fatal(err) 1596 } 1597 1598 _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v1", Resource: "pandas"}).Create(context.TODO(), v1Resource, metav1.CreateOptions{}) 1599 if !strings.Contains(err.Error(), "matched by equivalent match policy!") { 1600 t.Errorf("v1 panadas did not match against policy, err: %v", err) 1601 } 1602 1603 _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v2", Resource: "pandas"}).Create(context.TODO(), v2Resource, metav1.CreateOptions{}) 1604 if !strings.Contains(err.Error(), "matched by equivalent match policy!") { 1605 t.Errorf("v2 panadas did not match against policy, err: %v", err) 1606 } 1607 } 1608 1609 func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyExact(t *testing.T) { 1610 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1611 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1612 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1613 }, framework.SharedEtcd()) 1614 etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, versionedCustomResourceDefinition()) 1615 if err != nil { 1616 t.Fatal(err) 1617 } 1618 defer server.TearDownFn() 1619 1620 config := server.ClientConfig 1621 1622 client, err := clientset.NewForConfig(config) 1623 if err != nil { 1624 t.Fatal(err) 1625 } 1626 1627 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1628 { 1629 Expression: "false", 1630 Message: "matched by exact match policy!", 1631 }, 1632 }, withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("match-by-match-policy-exact"))) 1633 matchPolicyExact := admissionregistrationv1beta1.Exact 1634 policy.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{ 1635 MatchPolicy: &matchPolicyExact, 1636 ResourceRules: []admissionregistrationv1beta1.NamedRuleWithOperations{ 1637 { 1638 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 1639 Operations: []admissionregistrationv1.OperationType{ 1640 "*", 1641 }, 1642 Rule: admissionregistrationv1.Rule{ 1643 APIGroups: []string{ 1644 "awesome.bears.com", 1645 }, 1646 APIVersions: []string{ 1647 "v1", 1648 }, 1649 Resources: []string{ 1650 "pandas", 1651 }, 1652 }, 1653 }, 1654 }, 1655 }, 1656 } 1657 policy = withWaitReadyConstraintAndExpression(policy) 1658 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 1659 t.Fatal(err) 1660 } 1661 1662 policyBinding := makeBinding("match-by-match-policy-exact-binding", "match-by-match-policy-exact", "") 1663 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 1664 t.Fatal(err) 1665 } 1666 1667 v1Resource := &unstructured.Unstructured{ 1668 Object: map[string]interface{}{ 1669 "apiVersion": "awesome.bears.com" + "/" + "v1", 1670 "kind": "Panda", 1671 "metadata": map[string]interface{}{ 1672 "name": "v1-bears", 1673 }, 1674 }, 1675 } 1676 1677 v2Resource := &unstructured.Unstructured{ 1678 Object: map[string]interface{}{ 1679 "apiVersion": "awesome.bears.com" + "/" + "v2", 1680 "kind": "Panda", 1681 "metadata": map[string]interface{}{ 1682 "name": "v2-bears", 1683 }, 1684 }, 1685 } 1686 1687 dynamicClient, err := dynamic.NewForConfig(config) 1688 if err != nil { 1689 t.Fatal(err) 1690 } 1691 1692 _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v1", Resource: "pandas"}).Create(context.TODO(), v1Resource, metav1.CreateOptions{}) 1693 if !strings.Contains(err.Error(), "matched by exact match policy!") { 1694 t.Errorf("v1 panadas did not match against policy, err: %v", err) 1695 } 1696 1697 // v2 panadas is allowed since policy specificed match policy Exact and only matched against v1 1698 _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v2", Resource: "pandas"}).Create(context.TODO(), v2Resource, metav1.CreateOptions{}) 1699 if err != nil { 1700 t.Error(err) 1701 } 1702 } 1703 1704 // Test_ValidatingAdmissionPolicy_PolicyDeletedThenRecreated validates that deleting a ValidatingAdmissionPolicy 1705 // removes the policy from the apiserver admission chain and recreating it re-enables it. 1706 func Test_ValidatingAdmissionPolicy_PolicyDeletedThenRecreated(t *testing.T) { 1707 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1708 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1709 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1710 }, framework.SharedEtcd()) 1711 if err != nil { 1712 t.Fatal(err) 1713 } 1714 defer server.TearDownFn() 1715 1716 config := server.ClientConfig 1717 1718 client, err := clientset.NewForConfig(config) 1719 if err != nil { 1720 t.Fatal(err) 1721 } 1722 1723 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1724 { 1725 Expression: "object.metadata.name.startsWith('test')", 1726 Message: "wrong prefix", 1727 }, 1728 }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 1729 policy = withWaitReadyConstraintAndExpression(policy) 1730 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1731 if err != nil { 1732 t.Fatal(err) 1733 } 1734 1735 // validate that namespaces starting with "test" are allowed 1736 policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "") 1737 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 1738 t.Fatal(err) 1739 } 1740 1741 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1742 disallowedNamespace := &v1.Namespace{ 1743 ObjectMeta: metav1.ObjectMeta{ 1744 GenerateName: "not-test-", 1745 }, 1746 } 1747 1748 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1749 if err == nil { 1750 return false, nil 1751 } 1752 1753 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1754 return false, nil 1755 } 1756 1757 if !strings.Contains(err.Error(), "wrong prefix") { 1758 return false, err 1759 } 1760 1761 return true, nil 1762 }); waitErr != nil { 1763 t.Errorf("timed out waiting: %v", err) 1764 } 1765 1766 // delete the binding object and validate that policy is not enforced 1767 if err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Delete(context.TODO(), "allowed-prefixes", metav1.DeleteOptions{}); err != nil { 1768 t.Fatal(err) 1769 } 1770 1771 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1772 allowedNamespace := &v1.Namespace{ 1773 ObjectMeta: metav1.ObjectMeta{ 1774 GenerateName: "not-test-", 1775 }, 1776 } 1777 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 1778 if err == nil { 1779 return true, nil 1780 } 1781 1782 // old policy is still enforced, try again 1783 if strings.Contains(err.Error(), "wrong prefix") { 1784 return false, nil 1785 } 1786 1787 return false, err 1788 }); waitErr != nil { 1789 t.Errorf("timed out waiting: %v", err) 1790 } 1791 1792 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1793 if err != nil { 1794 t.Fatal(err) 1795 } 1796 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1797 disallowedNamespace := &v1.Namespace{ 1798 ObjectMeta: metav1.ObjectMeta{ 1799 GenerateName: "not-test-", 1800 }, 1801 } 1802 1803 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1804 if err == nil { 1805 return false, nil 1806 } 1807 1808 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1809 return false, nil 1810 } 1811 1812 if !strings.Contains(err.Error(), "wrong prefix") { 1813 return false, err 1814 } 1815 1816 return true, nil 1817 }); waitErr != nil { 1818 t.Errorf("timed out waiting: %v", err) 1819 } 1820 } 1821 1822 // Test_ValidatingAdmissionPolicy_BindingDeletedThenRecreated validates that deleting a ValidatingAdmissionPolicyBinding 1823 // removes the policy from the apiserver admission chain and recreating it re-enables it. 1824 func Test_ValidatingAdmissionPolicy_BindingDeletedThenRecreated(t *testing.T) { 1825 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1826 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1827 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1828 }, framework.SharedEtcd()) 1829 if err != nil { 1830 t.Fatal(err) 1831 } 1832 defer server.TearDownFn() 1833 1834 config := server.ClientConfig 1835 1836 client, err := clientset.NewForConfig(config) 1837 if err != nil { 1838 t.Fatal(err) 1839 } 1840 1841 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1842 { 1843 Expression: "object.metadata.name.startsWith('test')", 1844 Message: "wrong prefix", 1845 }, 1846 }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 1847 policy = withWaitReadyConstraintAndExpression(policy) 1848 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1849 if err != nil { 1850 t.Fatal(err) 1851 } 1852 1853 // validate that namespaces starting with "test" are allowed 1854 policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "") 1855 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 1856 t.Fatal(err) 1857 } 1858 1859 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1860 disallowedNamespace := &v1.Namespace{ 1861 ObjectMeta: metav1.ObjectMeta{ 1862 GenerateName: "not-test-", 1863 }, 1864 } 1865 1866 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1867 if err == nil { 1868 return false, nil 1869 } 1870 1871 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1872 return false, nil 1873 } 1874 1875 if !strings.Contains(err.Error(), "wrong prefix") { 1876 return false, err 1877 } 1878 1879 return true, nil 1880 }); waitErr != nil { 1881 t.Errorf("timed out waiting: %v", err) 1882 } 1883 1884 // delete the binding object and validate that policy is not enforced 1885 if err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Delete(context.TODO(), "allowed-prefixes-binding", metav1.DeleteOptions{}); err != nil { 1886 t.Fatal(err) 1887 } 1888 1889 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1890 allowedNamespace := &v1.Namespace{ 1891 ObjectMeta: metav1.ObjectMeta{ 1892 GenerateName: "not-test-", 1893 }, 1894 } 1895 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 1896 if err == nil { 1897 return true, nil 1898 } 1899 1900 // old policy is still enforced, try again 1901 if strings.Contains(err.Error(), "wrong prefix") { 1902 return false, nil 1903 } 1904 1905 return false, err 1906 }); waitErr != nil { 1907 t.Errorf("timed out waiting: %v", err) 1908 } 1909 1910 // recreate the policy binding and test that policy is enforced again 1911 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), policyBinding, metav1.CreateOptions{}) 1912 if err != nil { 1913 t.Fatal(err) 1914 } 1915 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1916 disallowedNamespace := &v1.Namespace{ 1917 ObjectMeta: metav1.ObjectMeta{ 1918 GenerateName: "not-test-", 1919 }, 1920 } 1921 1922 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1923 if err == nil { 1924 return false, nil 1925 } 1926 1927 if strings.Contains(err.Error(), "not yet synced to use for admission") { 1928 return false, nil 1929 } 1930 1931 if !strings.Contains(err.Error(), "wrong prefix") { 1932 return false, err 1933 } 1934 1935 return true, nil 1936 }); waitErr != nil { 1937 t.Errorf("timed out waiting: %v", err) 1938 } 1939 } 1940 1941 // Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated validates that deleting a param resource referenced 1942 // by a binding renders the policy as invalid. Recreating the param resource re-enables the policy. 1943 func Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated(t *testing.T) { 1944 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 1945 server, err := apiservertesting.StartTestServer(t, nil, []string{ 1946 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 1947 }, framework.SharedEtcd()) 1948 if err != nil { 1949 t.Fatal(err) 1950 } 1951 defer server.TearDownFn() 1952 1953 config := server.ClientConfig 1954 1955 client, err := clientset.NewForConfig(config) 1956 if err != nil { 1957 t.Fatal(err) 1958 } 1959 1960 param := &v1.ConfigMap{ 1961 ObjectMeta: metav1.ObjectMeta{ 1962 Name: "test", 1963 Namespace: "default", 1964 }, 1965 } 1966 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), param, metav1.CreateOptions{}); err != nil { 1967 t.Fatal(err) 1968 } 1969 1970 policy := withValidations([]admissionregistrationv1beta1.Validation{ 1971 { 1972 Expression: "object.metadata.name.startsWith(params.metadata.name)", 1973 Message: "wrong prefix", 1974 }, 1975 }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 1976 policy = withWaitReadyConstraintAndExpression(policy) 1977 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 1978 if err != nil { 1979 t.Fatal(err) 1980 } 1981 1982 // validate that namespaces starting with "test" are allowed 1983 policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "test") 1984 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 1985 t.Fatal(err) 1986 } 1987 1988 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 1989 disallowedNamespace := &v1.Namespace{ 1990 ObjectMeta: metav1.ObjectMeta{ 1991 GenerateName: "not-test-", 1992 }, 1993 } 1994 1995 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 1996 if err == nil { 1997 return false, nil 1998 } 1999 2000 if strings.Contains(err.Error(), "not yet synced to use for admission") { 2001 return false, nil 2002 } 2003 2004 if !strings.Contains(err.Error(), "wrong prefix") { 2005 return false, err 2006 } 2007 2008 return true, nil 2009 }); waitErr != nil { 2010 t.Errorf("timed out waiting: %v", err) 2011 } 2012 2013 // delete param object and validate that policy is invalid 2014 if err := client.CoreV1().ConfigMaps("default").Delete(context.TODO(), "test", metav1.DeleteOptions{}); err != nil { 2015 t.Fatal(err) 2016 } 2017 2018 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 2019 allowedNamespace := &v1.Namespace{ 2020 ObjectMeta: metav1.ObjectMeta{ 2021 GenerateName: "not-test-", 2022 }, 2023 } 2024 2025 _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) 2026 // old policy is still enforced, try again 2027 if strings.Contains(err.Error(), "wrong prefix") { 2028 return false, nil 2029 } 2030 2031 if !strings.Contains(err.Error(), "failed to configure binding: no params found for policy binding with `Deny` parameterNotFoundAction") { 2032 return false, err 2033 } 2034 2035 return true, nil 2036 }); waitErr != nil { 2037 t.Errorf("timed out waiting: %v", err) 2038 } 2039 2040 // recreate the param resource and validate namespace is disallowed again 2041 if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), param, metav1.CreateOptions{}); err != nil { 2042 t.Fatal(err) 2043 } 2044 2045 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 2046 disallowedNamespace := &v1.Namespace{ 2047 ObjectMeta: metav1.ObjectMeta{ 2048 GenerateName: "not-test-", 2049 }, 2050 } 2051 2052 _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) 2053 // cache not synced with new object yet, try again 2054 if strings.Contains(err.Error(), "failed to configure binding: no params found for policy binding with `Deny` parameterNotFoundAction") { 2055 return false, nil 2056 } 2057 2058 if !strings.Contains(err.Error(), "wrong prefix") { 2059 return false, err 2060 } 2061 2062 return true, nil 2063 }); waitErr != nil { 2064 t.Errorf("timed out waiting: %v", err) 2065 } 2066 } 2067 2068 // TestCRDParams tests that a CustomResource can be used as a param resource for a ValidatingAdmissionPolicy. 2069 func TestCRDParams(t *testing.T) { 2070 testcases := []struct { 2071 name string 2072 resource *unstructured.Unstructured 2073 policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy 2074 policyBinding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding 2075 namespace *v1.Namespace 2076 err string 2077 failureReason metav1.StatusReason 2078 }{ 2079 { 2080 name: "a rule that uses data from a CRD param resource does NOT pass", 2081 resource: &unstructured.Unstructured{Object: map[string]interface{}{ 2082 "apiVersion": "awesome.bears.com/v1", 2083 "kind": "Panda", 2084 "metadata": map[string]interface{}{ 2085 "name": "config-obj", 2086 }, 2087 "spec": map[string]interface{}{ 2088 "nameCheck": "crd-test-k8s", 2089 }, 2090 }}, 2091 policy: withValidations([]admissionregistrationv1beta1.Validation{ 2092 { 2093 Expression: "params.spec.nameCheck == object.metadata.name", 2094 }, 2095 }, withNamespaceMatch(withParams(withCRDParamKind("Panda", "awesome.bears.com", "v1"), withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("test-policy"))))), 2096 policyBinding: makeBinding("crd-policy-binding", "test-policy", "config-obj"), 2097 namespace: &v1.Namespace{ 2098 ObjectMeta: metav1.ObjectMeta{ 2099 Name: "incorrect-name", 2100 }, 2101 }, 2102 err: `namespaces "incorrect-name" is forbidden: ValidatingAdmissionPolicy 'test-policy' with binding 'crd-policy-binding' denied request: failed expression: params.spec.nameCheck == object.metadata.name`, 2103 failureReason: metav1.StatusReasonInvalid, 2104 }, 2105 { 2106 name: "a rule that uses data from a CRD param resource that does pass", 2107 resource: &unstructured.Unstructured{Object: map[string]interface{}{ 2108 "apiVersion": "awesome.bears.com/v1", 2109 "kind": "Panda", 2110 "metadata": map[string]interface{}{ 2111 "name": "config-obj", 2112 }, 2113 "spec": map[string]interface{}{ 2114 "nameCheck": "crd-test-k8s", 2115 }, 2116 }}, 2117 policy: withValidations([]admissionregistrationv1beta1.Validation{ 2118 { 2119 Expression: "params.spec.nameCheck == object.metadata.name", 2120 }, 2121 }, withNamespaceMatch(withParams(withCRDParamKind("Panda", "awesome.bears.com", "v1"), withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("test-policy"))))), 2122 policyBinding: makeBinding("crd-policy-binding", "test-policy", "config-obj"), 2123 namespace: &v1.Namespace{ 2124 ObjectMeta: metav1.ObjectMeta{ 2125 Name: "crd-test-k8s", 2126 }, 2127 }, 2128 err: ``, 2129 }, 2130 } 2131 2132 for _, testcase := range testcases { 2133 t.Run(testcase.name, func(t *testing.T) { 2134 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 2135 server, err := apiservertesting.StartTestServer(t, nil, []string{ 2136 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 2137 }, framework.SharedEtcd()) 2138 if err != nil { 2139 t.Fatal(err) 2140 } 2141 defer server.TearDownFn() 2142 2143 config := server.ClientConfig 2144 2145 client, err := clientset.NewForConfig(config) 2146 if err != nil { 2147 t.Fatal(err) 2148 } 2149 2150 crd := versionedCustomResourceDefinition() 2151 etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, crd) 2152 dynamicClient, err := dynamic.NewForConfig(config) 2153 if err != nil { 2154 t.Fatal(err) 2155 } 2156 gvr := schema.GroupVersionResource{ 2157 Group: crd.Spec.Group, 2158 Version: crd.Spec.Versions[0].Name, 2159 Resource: crd.Spec.Names.Plural, 2160 } 2161 crClient := dynamicClient.Resource(gvr) 2162 _, err = crClient.Create(context.TODO(), testcase.resource, metav1.CreateOptions{}) 2163 if err != nil { 2164 t.Fatalf("error creating %s: %s", gvr, err) 2165 } 2166 2167 policy := withWaitReadyConstraintAndExpression(testcase.policy) 2168 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 2169 t.Fatal(err) 2170 } 2171 // remove default namespace since the CRD is cluster-scoped 2172 testcase.policyBinding.Spec.ParamRef.Namespace = "" 2173 if err := createAndWaitReady(t, client, testcase.policyBinding, nil); err != nil { 2174 t.Fatal(err) 2175 } 2176 2177 _, err = client.CoreV1().Namespaces().Create(context.TODO(), testcase.namespace, metav1.CreateOptions{}) 2178 2179 checkExpectedError(t, err, testcase.err) 2180 checkFailureReason(t, err, testcase.failureReason) 2181 }) 2182 } 2183 } 2184 2185 func TestBindingRemoval(t *testing.T) { 2186 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 2187 server, err := apiservertesting.StartTestServer(t, nil, []string{ 2188 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 2189 }, framework.SharedEtcd()) 2190 if err != nil { 2191 t.Fatal(err) 2192 } 2193 defer server.TearDownFn() 2194 2195 config := server.ClientConfig 2196 2197 client, err := clientset.NewForConfig(config) 2198 if err != nil { 2199 t.Fatal(err) 2200 } 2201 2202 policy := withValidations([]admissionregistrationv1beta1.Validation{ 2203 { 2204 Expression: "false", 2205 Message: "policy still in effect", 2206 }, 2207 }, withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("test-policy")))) 2208 policy = withWaitReadyConstraintAndExpression(policy) 2209 if _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 2210 t.Fatal(err) 2211 } 2212 2213 binding := makeBinding("test-binding", "test-policy", "test-params") 2214 if err := createAndWaitReady(t, client, binding, nil); err != nil { 2215 t.Fatal(err) 2216 } 2217 // check that the policy is active 2218 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 2219 namespace := &v1.Namespace{ 2220 ObjectMeta: metav1.ObjectMeta{ 2221 GenerateName: "check-namespace", 2222 }, 2223 } 2224 _, err = client.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{}) 2225 if err != nil { 2226 if strings.Contains(err.Error(), "policy still in effect") { 2227 return true, nil 2228 } else { 2229 // unexpected error while attempting namespace creation 2230 return true, err 2231 } 2232 } 2233 return false, nil 2234 }); waitErr != nil { 2235 t.Errorf("timed out waiting: %v", waitErr) 2236 } 2237 if err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Delete(context.TODO(), "test-binding", metav1.DeleteOptions{}); err != nil { 2238 t.Fatal(err) 2239 } 2240 2241 // wait for binding to be deleted 2242 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 2243 2244 _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Get(context.TODO(), "test-binding", metav1.GetOptions{}) 2245 if err != nil { 2246 if apierrors.IsNotFound(err) { 2247 return true, nil 2248 } else { 2249 return true, err 2250 } 2251 } 2252 2253 return false, nil 2254 }); waitErr != nil { 2255 t.Errorf("timed out waiting: %v", waitErr) 2256 } 2257 2258 // policy should be considered in an invalid state and namespace creation should be allowed 2259 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { 2260 namespace := &v1.Namespace{ 2261 ObjectMeta: metav1.ObjectMeta{ 2262 GenerateName: "test-namespace", 2263 }, 2264 } 2265 _, err = client.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{}) 2266 if err != nil { 2267 t.Logf("namespace creation failed: %s", err) 2268 return false, nil 2269 } 2270 2271 return true, nil 2272 }); waitErr != nil { 2273 t.Errorf("expected namespace creation to succeed but timed out waiting: %v", waitErr) 2274 } 2275 } 2276 2277 // Test_ValidateSecondaryAuthorization tests a ValidatingAdmissionPolicy that performs secondary authorization checks 2278 // for both users and service accounts. 2279 func Test_ValidateSecondaryAuthorization(t *testing.T) { 2280 testcases := []struct { 2281 name string 2282 rbac *rbacv1.PolicyRule 2283 expression string 2284 allowed bool 2285 extraAccountFn func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset 2286 extraAccountRbac *rbacv1.PolicyRule 2287 }{ 2288 { 2289 name: "principal is allowed to create a specific deployment", 2290 rbac: &rbacv1.PolicyRule{ 2291 Verbs: []string{"create"}, 2292 APIGroups: []string{"apps"}, 2293 Resources: []string{"deployments/status"}, 2294 ResourceNames: []string{"charmander"}, 2295 }, 2296 expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('default').namespace('default').name('charmander').check('create').allowed()", 2297 allowed: true, 2298 }, 2299 { 2300 name: "principal is not allowed to create a specific deployment", 2301 expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('default').name('charmander').check('create').allowed()", 2302 allowed: false, 2303 }, 2304 { 2305 name: "principal is authorized for custom verb on current resource", 2306 rbac: &rbacv1.PolicyRule{ 2307 Verbs: []string{"anthropomorphize"}, 2308 APIGroups: []string{""}, 2309 Resources: []string{"namespaces"}, 2310 }, 2311 expression: "authorizer.requestResource.check('anthropomorphize').allowed()", 2312 allowed: true, 2313 }, 2314 { 2315 name: "principal is not authorized for custom verb on current resource", 2316 expression: "authorizer.requestResource.check('anthropomorphize').allowed()", 2317 allowed: false, 2318 }, 2319 { 2320 name: "serviceaccount is authorized for custom verb on current resource", 2321 extraAccountFn: serviceAccountClient("default", "extra-acct"), 2322 extraAccountRbac: &rbacv1.PolicyRule{ 2323 Verbs: []string{"anthropomorphize"}, 2324 APIGroups: []string{""}, 2325 Resources: []string{"pods"}, 2326 }, 2327 expression: "authorizer.serviceAccount('default', 'extra-acct').group('').resource('pods').check('anthropomorphize').allowed()", 2328 allowed: true, 2329 }, 2330 } 2331 2332 for _, testcase := range testcases { 2333 t.Run(testcase.name, func(t *testing.T) { 2334 clients := map[string]func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset{ 2335 "user": secondaryAuthorizationUserClient, 2336 "serviceaccount": secondaryAuthorizationServiceAccountClient, 2337 } 2338 2339 for clientName, clientFn := range clients { 2340 t.Run(clientName, func(t *testing.T) { 2341 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 2342 server, err := apiservertesting.StartTestServer(t, nil, []string{ 2343 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 2344 "--authorization-mode=RBAC", 2345 "--anonymous-auth", 2346 }, framework.SharedEtcd()) 2347 if err != nil { 2348 t.Fatal(err) 2349 } 2350 defer server.TearDownFn() 2351 2352 // For test set up such as creating policies, bindings and RBAC rules. 2353 adminClient := clientset.NewForConfigOrDie(server.ClientConfig) 2354 2355 // Principal is always allowed to create and update namespaces so that the admission requests to test 2356 // authorization expressions can be sent by the principal. 2357 rules := []rbacv1.PolicyRule{{ 2358 Verbs: []string{"create", "update"}, 2359 APIGroups: []string{""}, 2360 Resources: []string{"namespaces"}, 2361 }} 2362 if testcase.rbac != nil { 2363 rules = append(rules, *testcase.rbac) 2364 } 2365 2366 client := clientFn(t, adminClient, server.ClientConfig, rules) 2367 2368 if testcase.extraAccountFn != nil { 2369 var extraRules []rbacv1.PolicyRule 2370 if testcase.extraAccountRbac != nil { 2371 extraRules = append(rules, *testcase.extraAccountRbac) 2372 } 2373 testcase.extraAccountFn(t, adminClient, server.ClientConfig, extraRules) 2374 } 2375 2376 policy := withWaitReadyConstraintAndExpression(withValidations([]admissionregistrationv1beta1.Validation{ 2377 { 2378 Expression: testcase.expression, 2379 }, 2380 }, withFailurePolicy(admissionregistrationv1beta1.Fail, withNamespaceMatch(makePolicy("validate-authz"))))) 2381 if _, err := adminClient.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { 2382 t.Fatal(err) 2383 } 2384 if err := createAndWaitReady(t, adminClient, makeBinding("validate-authz-binding", "validate-authz", ""), nil); err != nil { 2385 t.Fatal(err) 2386 } 2387 2388 ns := &v1.Namespace{ 2389 ObjectMeta: metav1.ObjectMeta{ 2390 Name: "test-authz", 2391 }, 2392 } 2393 _, err = client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) 2394 2395 var expected metav1.StatusReason = "" 2396 if !testcase.allowed { 2397 expected = metav1.StatusReasonInvalid 2398 } 2399 checkFailureReason(t, err, expected) 2400 }) 2401 } 2402 }) 2403 } 2404 } 2405 2406 func TestCRDsOnStartup(t *testing.T) { 2407 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 2408 2409 testContext, testCancel := context.WithCancel(context.Background()) 2410 defer testCancel() 2411 2412 // Start server and create CRD, and validatingadmission policy and binding 2413 etcdConfig := framework.SharedEtcd() 2414 server := apiservertesting.StartTestServerOrDie(t, nil, []string{ 2415 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 2416 "--authorization-mode=RBAC", 2417 "--anonymous-auth", 2418 }, etcdConfig) 2419 client := clientset.NewForConfigOrDie(server.ClientConfig) 2420 dynamicClient := dynamic.NewForConfigOrDie(server.ClientConfig) 2421 apiextclient := apiextensionsclientset.NewForConfigOrDie(server.ClientConfig) 2422 myCRD := &apiextensionsv1.CustomResourceDefinition{ 2423 ObjectMeta: metav1.ObjectMeta{ 2424 Name: "foos.cr.bar.com", 2425 }, 2426 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2427 Group: "cr.bar.com", 2428 Scope: apiextensionsv1.NamespaceScoped, 2429 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2430 Plural: "foos", 2431 Kind: "Foo", 2432 }, 2433 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2434 { 2435 Name: "v1", 2436 Served: true, 2437 Storage: true, 2438 Schema: fixtures.AllowAllSchema(), 2439 }, 2440 }, 2441 }, 2442 } 2443 2444 // Create a bunch of fake CRDs to make the initial startup sync take a long time 2445 for i := 0; i < 100; i++ { 2446 crd := myCRD.DeepCopy() 2447 crd.Name = fmt.Sprintf("foos%d.cr.bar.com", i) 2448 crd.Spec.Names.Plural = fmt.Sprintf("foos%d", i) 2449 crd.Spec.Names.Kind = fmt.Sprintf("Foo%d", i) 2450 2451 if _, err := apiextclient.ApiextensionsV1().CustomResourceDefinitions().Create(context.Background(), crd, metav1.CreateOptions{}); err != nil { 2452 t.Fatal(err) 2453 } 2454 } 2455 2456 etcd.CreateTestCRDs(t, apiextclient, false, myCRD) 2457 crdGVK := schema.GroupVersionKind{ 2458 Group: "cr.bar.com", 2459 Version: "v1", 2460 Kind: "Foo", 2461 } 2462 crdGVR := crdGVK.GroupVersion().WithResource("foos") 2463 2464 param := &unstructured.Unstructured{ 2465 Object: map[string]interface{}{ 2466 "metadata": map[string]interface{}{ 2467 "name": "test", 2468 "namespace": "default", 2469 }, 2470 "foo": "bar", 2471 }, 2472 } 2473 param.GetObjectKind().SetGroupVersionKind(crdGVK) 2474 2475 if _, err := dynamicClient.Resource(crdGVR).Namespace("default").Create(context.TODO(), param, metav1.CreateOptions{}); err != nil { 2476 t.Fatal(err) 2477 } 2478 2479 policy := withValidations([]admissionregistrationv1beta1.Validation{ 2480 { 2481 Expression: "object.metadata.name.startsWith(params.metadata.name)", 2482 Message: "wrong prefix", 2483 }, 2484 }, withParams(withCRDParamKind(crdGVK.Kind, crdGVK.Group, crdGVK.Version), withNamespaceMatch(withFailurePolicy(admissionregistrationv1beta1.Fail, makePolicy("allowed-prefixes"))))) 2485 policy = withWaitReadyConstraintAndExpression(policy) 2486 _, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) 2487 if err != nil { 2488 t.Fatal(err) 2489 } 2490 2491 // validate that namespaces starting with "test" are allowed 2492 policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "test") 2493 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { 2494 t.Fatal(err) 2495 } 2496 2497 doCheck := func(client clientset.Interface) { 2498 if waitErr := wait.PollUntilContextTimeout(testContext, time.Millisecond*100, 3*time.Minute, true, func(ctx context.Context) (bool, error) { 2499 disallowedNamespace := &v1.Namespace{ 2500 ObjectMeta: metav1.ObjectMeta{ 2501 GenerateName: "not-test-", 2502 }, 2503 } 2504 2505 _, err = client.CoreV1().Namespaces().Create(testContext, disallowedNamespace, metav1.CreateOptions{}) 2506 if err == nil { 2507 return false, nil 2508 } 2509 2510 if strings.Contains(err.Error(), "not yet synced to use for admission") { 2511 return false, nil 2512 } 2513 2514 if strings.Contains(err.Error(), "failed to find resource referenced by paramKind") { 2515 return false, nil 2516 } 2517 2518 if !strings.Contains(err.Error(), "wrong prefix") { 2519 return false, err 2520 } 2521 2522 return true, nil 2523 }); waitErr != nil { 2524 t.Errorf("timed out waiting: %v", err) 2525 } 2526 } 2527 2528 // Show that the policy & binding are correctly working before restarting 2529 // to use the paramKind and deliver an error 2530 doCheck(client) 2531 server.TearDownFn() 2532 2533 // Start the server. 2534 server = apiservertesting.StartTestServerOrDie( 2535 t, 2536 &apiservertesting.TestServerInstanceOptions{}, 2537 []string{ 2538 "--enable-admission-plugins", "ValidatingAdmissionPolicy", 2539 "--authorization-mode=RBAC", 2540 "--anonymous-auth", 2541 }, 2542 etcdConfig) 2543 defer server.TearDownFn() 2544 2545 // Now that the server is restarted, show again that the policy & binding are correctly working 2546 client = clientset.NewForConfigOrDie(server.ClientConfig) 2547 2548 doCheck(client) 2549 2550 } 2551 2552 type clientFn func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset 2553 2554 func secondaryAuthorizationUserClient(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset { 2555 clientConfig = rest.CopyConfig(clientConfig) 2556 clientConfig.Impersonate = rest.ImpersonationConfig{ 2557 UserName: "alice", 2558 UID: "1234", 2559 } 2560 client := clientset.NewForConfigOrDie(clientConfig) 2561 2562 for _, rule := range rules { 2563 authutil.GrantUserAuthorization(t, context.TODO(), adminClient, "alice", rule) 2564 } 2565 return client 2566 } 2567 2568 func secondaryAuthorizationServiceAccountClient(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset { 2569 return serviceAccountClient("default", "test-service-acct")(t, adminClient, clientConfig, rules) 2570 } 2571 2572 func serviceAccountClient(namespace, name string) clientFn { 2573 return func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset { 2574 clientConfig = rest.CopyConfig(clientConfig) 2575 sa, err := adminClient.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: name}}, metav1.CreateOptions{}) 2576 if err != nil { 2577 t.Fatal(err) 2578 } 2579 uid := sa.UID 2580 2581 clientConfig.Impersonate = rest.ImpersonationConfig{ 2582 UserName: "system:serviceaccount:" + namespace + ":" + name, 2583 UID: string(uid), 2584 } 2585 client := clientset.NewForConfigOrDie(clientConfig) 2586 2587 for _, rule := range rules { 2588 authutil.GrantServiceAccountAuthorization(t, context.TODO(), adminClient, name, namespace, rule) 2589 } 2590 return client 2591 } 2592 } 2593 2594 func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2595 policy = policy.DeepCopy() 2596 policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1beta1.NamedRuleWithOperations{ 2597 ResourceNames: []string{"test-marker"}, 2598 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 2599 Operations: []admissionregistrationv1.OperationType{ 2600 "UPDATE", 2601 }, 2602 Rule: admissionregistrationv1.Rule{ 2603 APIGroups: []string{ 2604 "", 2605 }, 2606 APIVersions: []string{ 2607 "v1", 2608 }, 2609 Resources: []string{ 2610 "endpoints", 2611 }, 2612 }, 2613 }, 2614 }) 2615 policy.Spec.Validations = append([]admissionregistrationv1beta1.Validation{{ 2616 Expression: "object.metadata.name != 'test-marker'", 2617 Message: "marker denied; policy is ready", 2618 }}, policy.Spec.Validations...) 2619 return policy 2620 } 2621 2622 func createAndWaitReady(t *testing.T, client clientset.Interface, binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string) error { 2623 return createAndWaitReadyNamespaced(t, client, binding, matchLabels, "default") 2624 } 2625 2626 func createAndWaitReadyNamespaced(t *testing.T, client clientset.Interface, binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string) error { 2627 return createAndWaitReadyNamespacedWithWarnHandler(t, client, binding, matchLabels, ns, newWarningHandler()) 2628 } 2629 2630 func createAndWaitReadyNamespacedWithWarnHandler(t *testing.T, client clientset.Interface, binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string, handler *warningHandler) error { 2631 marker := &v1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "test-marker", Namespace: ns, Labels: matchLabels}} 2632 defer func() { 2633 err := client.CoreV1().Endpoints(ns).Delete(context.TODO(), marker.Name, metav1.DeleteOptions{}) 2634 if err != nil { 2635 t.Logf("error deleting marker: %v", err) 2636 } 2637 }() 2638 marker, err := client.CoreV1().Endpoints(ns).Create(context.TODO(), marker, metav1.CreateOptions{}) 2639 if err != nil { 2640 return err 2641 } 2642 2643 _, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), binding, metav1.CreateOptions{}) 2644 if err != nil { 2645 return err 2646 } 2647 2648 if waitErr := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) { 2649 handler.reset() 2650 _, err := client.CoreV1().Endpoints(ns).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{}) 2651 if handler.hasObservedMarker() { 2652 return true, nil 2653 } 2654 if err != nil && strings.Contains(err.Error(), "marker denied; policy is ready") { 2655 return true, nil 2656 } else if err != nil && strings.Contains(err.Error(), "not yet synced to use for admission") { 2657 t.Logf("waiting for policy to be ready. Marker: %v. Admission not synced yet: %v", marker, err) 2658 return false, nil 2659 } else { 2660 t.Logf("waiting for policy to be ready. Marker: %v, Last marker patch response: %v", marker, err) 2661 return false, err 2662 } 2663 }); waitErr != nil { 2664 return waitErr 2665 } 2666 t.Logf("Marker ready: %v", marker) 2667 handler.reset() 2668 return nil 2669 } 2670 2671 func withMatchNamespace(binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding, ns string) *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding { 2672 binding.Spec.MatchResources = &admissionregistrationv1beta1.MatchResources{ 2673 NamespaceSelector: &metav1.LabelSelector{ 2674 MatchExpressions: []metav1.LabelSelectorRequirement{ 2675 { 2676 Key: "kubernetes.io/metadata.name", 2677 Operator: metav1.LabelSelectorOpIn, 2678 Values: []string{ns}, 2679 }, 2680 }, 2681 }, 2682 } 2683 return binding 2684 } 2685 2686 func makePolicy(name string) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2687 return &admissionregistrationv1beta1.ValidatingAdmissionPolicy{ 2688 ObjectMeta: metav1.ObjectMeta{Name: name}, 2689 } 2690 } 2691 2692 func withParams(params *admissionregistrationv1beta1.ParamKind, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2693 policy.Spec.ParamKind = params 2694 return policy 2695 } 2696 2697 func configParamKind() *admissionregistrationv1beta1.ParamKind { 2698 return &admissionregistrationv1beta1.ParamKind{ 2699 APIVersion: "v1", 2700 Kind: "ConfigMap", 2701 } 2702 } 2703 2704 func withFailurePolicy(failure admissionregistrationv1beta1.FailurePolicyType, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2705 policy.Spec.FailurePolicy = &failure 2706 return policy 2707 } 2708 2709 func withNamespaceMatch(policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2710 return withPolicyMatch("namespaces", policy) 2711 } 2712 2713 func withConfigMapMatch(policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2714 return withPolicyMatch("configmaps", policy) 2715 } 2716 2717 func withObjectSelector(labelSelector *metav1.LabelSelector, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2718 policy.Spec.MatchConstraints.ObjectSelector = labelSelector 2719 return policy 2720 } 2721 2722 func withNamespaceSelector(labelSelector *metav1.LabelSelector, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2723 policy.Spec.MatchConstraints.NamespaceSelector = labelSelector 2724 return policy 2725 } 2726 2727 func withPolicyMatch(resource string, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2728 policy.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{ 2729 ResourceRules: []admissionregistrationv1beta1.NamedRuleWithOperations{ 2730 { 2731 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 2732 Operations: []admissionregistrationv1.OperationType{ 2733 "*", 2734 }, 2735 Rule: admissionregistrationv1.Rule{ 2736 APIGroups: []string{ 2737 "", 2738 }, 2739 APIVersions: []string{ 2740 "*", 2741 }, 2742 Resources: []string{ 2743 resource, 2744 }, 2745 }, 2746 }, 2747 }, 2748 }, 2749 } 2750 return policy 2751 } 2752 2753 func withExcludePolicyMatch(resource string, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2754 policy.Spec.MatchConstraints.ExcludeResourceRules = []admissionregistrationv1beta1.NamedRuleWithOperations{ 2755 { 2756 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 2757 Operations: []admissionregistrationv1.OperationType{ 2758 "*", 2759 }, 2760 Rule: admissionregistrationv1.Rule{ 2761 APIGroups: []string{ 2762 "", 2763 }, 2764 APIVersions: []string{ 2765 "*", 2766 }, 2767 Resources: []string{ 2768 resource, 2769 }, 2770 }, 2771 }, 2772 }, 2773 } 2774 return policy 2775 } 2776 2777 func withPolicyExistsLabels(labels []string, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2778 if policy.Spec.MatchConstraints == nil { 2779 policy.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{} 2780 } 2781 matchExprs := buildExistsSelector(labels) 2782 policy.Spec.MatchConstraints.ObjectSelector = &metav1.LabelSelector{ 2783 MatchExpressions: matchExprs, 2784 } 2785 return policy 2786 } 2787 2788 func withValidations(validations []admissionregistrationv1beta1.Validation, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2789 policy.Spec.Validations = validations 2790 return policy 2791 } 2792 2793 func withAuditAnnotations(auditAnnotations []admissionregistrationv1beta1.AuditAnnotation, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy) *admissionregistrationv1beta1.ValidatingAdmissionPolicy { 2794 policy.Spec.AuditAnnotations = auditAnnotations 2795 return policy 2796 } 2797 2798 func makeBinding(name, policyName, paramName string) *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding { 2799 var paramRef *admissionregistrationv1beta1.ParamRef 2800 if paramName != "" { 2801 denyAction := admissionregistrationv1beta1.DenyAction 2802 paramRef = &admissionregistrationv1beta1.ParamRef{ 2803 Name: paramName, 2804 Namespace: "default", 2805 ParameterNotFoundAction: &denyAction, 2806 } 2807 } 2808 return &admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding{ 2809 ObjectMeta: metav1.ObjectMeta{Name: name}, 2810 Spec: admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec{ 2811 PolicyName: policyName, 2812 ParamRef: paramRef, 2813 ValidationActions: []admissionregistrationv1beta1.ValidationAction{admissionregistrationv1beta1.Deny}, 2814 }, 2815 } 2816 } 2817 2818 func withValidationActions(validationActions []admissionregistrationv1beta1.ValidationAction, binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding { 2819 binding.Spec.ValidationActions = validationActions 2820 return binding 2821 } 2822 2823 func withBindingExistsLabels(labels []string, policy *admissionregistrationv1beta1.ValidatingAdmissionPolicy, binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding { 2824 if policy != nil { 2825 // shallow copy 2826 constraintsCopy := *policy.Spec.MatchConstraints 2827 binding.Spec.MatchResources = &constraintsCopy 2828 } 2829 matchExprs := buildExistsSelector(labels) 2830 binding.Spec.MatchResources.ObjectSelector = &metav1.LabelSelector{ 2831 MatchExpressions: matchExprs, 2832 } 2833 return binding 2834 } 2835 2836 func buildExistsSelector(labels []string) []metav1.LabelSelectorRequirement { 2837 matchExprs := make([]metav1.LabelSelectorRequirement, len(labels)) 2838 for i := 0; i < len(labels); i++ { 2839 matchExprs[i].Key = labels[i] 2840 matchExprs[i].Operator = metav1.LabelSelectorOpExists 2841 } 2842 return matchExprs 2843 } 2844 2845 func makeConfigParams(name string, data map[string]string) *v1.ConfigMap { 2846 return &v1.ConfigMap{ 2847 ObjectMeta: metav1.ObjectMeta{Name: name}, 2848 Data: data, 2849 } 2850 } 2851 2852 func checkForFailedRule(t *testing.T, err error) { 2853 if !strings.Contains(err.Error(), "failed expression") { 2854 t.Fatalf("unexpected error (expected to find \"failed expression\"): %s", err) 2855 } 2856 if strings.Contains(err.Error(), "evaluation error") { 2857 t.Fatalf("CEL rule evaluation failed: %s", err) 2858 } 2859 } 2860 2861 func checkFailureReason(t *testing.T, err error, expectedReason metav1.StatusReason) { 2862 if err == nil && expectedReason == "" { 2863 // no reason was given, no error was passed - early exit 2864 return 2865 } 2866 switch e := err.(type) { 2867 case apierrors.APIStatus: 2868 reason := e.Status().Reason 2869 if reason != expectedReason { 2870 t.Logf("actual error reason: %v", reason) 2871 t.Logf("expected failure reason: %v", expectedReason) 2872 t.Error("Unexpected error reason") 2873 } 2874 default: 2875 t.Errorf("Unexpected error: %v", err) 2876 } 2877 } 2878 2879 func checkExpectedWarnings(t *testing.T, recordedWarnings *warningHandler, expectedWarnings sets.Set[string]) { 2880 if !recordedWarnings.equals(expectedWarnings) { 2881 t.Errorf("Expected warnings '%v' but got '%v", expectedWarnings, recordedWarnings) 2882 } 2883 } 2884 2885 func checkAuditEvents(t *testing.T, logFile *os.File, auditEvents []utils.AuditEvent, filter utils.AuditAnnotationsFilter) { 2886 stream, err := os.OpenFile(logFile.Name(), os.O_RDWR, 0600) 2887 if err != nil { 2888 t.Errorf("unexpected error: %v", err) 2889 } 2890 defer stream.Close() 2891 2892 if auditEvents != nil { 2893 missing, err := utils.CheckAuditLinesFiltered(stream, auditEvents, auditv1.SchemeGroupVersion, filter) 2894 if err != nil { 2895 t.Errorf("unexpected error checking audit lines: %v", err) 2896 } 2897 if len(missing.MissingEvents) > 0 { 2898 t.Errorf("failed to get expected events -- missing: %s", missing) 2899 } 2900 } 2901 if err := stream.Truncate(0); err != nil { 2902 t.Errorf("unexpected error truncate file: %v", err) 2903 } 2904 if _, err := stream.Seek(0, 0); err != nil { 2905 t.Errorf("unexpected error reset offset: %v", err) 2906 } 2907 } 2908 2909 func withCRDParamKind(kind, crdGroup, crdVersion string) *admissionregistrationv1beta1.ParamKind { 2910 return &admissionregistrationv1beta1.ParamKind{ 2911 APIVersion: crdGroup + "/" + crdVersion, 2912 Kind: kind, 2913 } 2914 } 2915 2916 func checkExpectedError(t *testing.T, err error, expectedErr string) { 2917 if err == nil && expectedErr == "" { 2918 return 2919 } 2920 if err == nil && expectedErr != "" { 2921 t.Logf("actual error: %v", err) 2922 t.Logf("expected error: %v", expectedErr) 2923 t.Fatal("got nil error but expected an error") 2924 } 2925 2926 if err != nil && expectedErr == "" { 2927 t.Logf("actual error: %v", err) 2928 t.Logf("expected error: %v", expectedErr) 2929 t.Fatal("got error but expected none") 2930 } 2931 2932 if err.Error() != expectedErr { 2933 t.Logf("actual validation error: %v", err) 2934 t.Logf("expected validation error: %v", expectedErr) 2935 t.Error("unexpected validation error") 2936 } 2937 } 2938 2939 // Copied from etcd.GetCustomResourceDefinitionData 2940 func versionedCustomResourceDefinition() *apiextensionsv1.CustomResourceDefinition { 2941 return &apiextensionsv1.CustomResourceDefinition{ 2942 ObjectMeta: metav1.ObjectMeta{ 2943 Name: "pandas.awesome.bears.com", 2944 }, 2945 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2946 Group: "awesome.bears.com", 2947 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2948 { 2949 Name: "v1", 2950 Served: true, 2951 Storage: true, 2952 Schema: fixtures.AllowAllSchema(), 2953 Subresources: &apiextensionsv1.CustomResourceSubresources{ 2954 Status: &apiextensionsv1.CustomResourceSubresourceStatus{}, 2955 Scale: &apiextensionsv1.CustomResourceSubresourceScale{ 2956 SpecReplicasPath: ".spec.replicas", 2957 StatusReplicasPath: ".status.replicas", 2958 LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(), 2959 }, 2960 }, 2961 }, 2962 { 2963 Name: "v2", 2964 Served: true, 2965 Storage: false, 2966 Schema: fixtures.AllowAllSchema(), 2967 Subresources: &apiextensionsv1.CustomResourceSubresources{ 2968 Status: &apiextensionsv1.CustomResourceSubresourceStatus{}, 2969 Scale: &apiextensionsv1.CustomResourceSubresourceScale{ 2970 SpecReplicasPath: ".spec.replicas", 2971 StatusReplicasPath: ".status.replicas", 2972 LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(), 2973 }, 2974 }, 2975 }, 2976 }, 2977 Scope: apiextensionsv1.ClusterScoped, 2978 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2979 Plural: "pandas", 2980 Kind: "Panda", 2981 }, 2982 }, 2983 } 2984 } 2985 2986 type warningHandler struct { 2987 lock sync.Mutex 2988 warnings sets.Set[string] 2989 observedMarker bool 2990 } 2991 2992 func newWarningHandler() *warningHandler { 2993 return &warningHandler{warnings: sets.New[string]()} 2994 } 2995 2996 func (w *warningHandler) reset() { 2997 w.lock.Lock() 2998 defer w.lock.Unlock() 2999 w.warnings = sets.New[string]() 3000 w.observedMarker = false 3001 } 3002 3003 func (w *warningHandler) equals(s sets.Set[string]) bool { 3004 w.lock.Lock() 3005 defer w.lock.Unlock() 3006 return w.warnings.Equal(s) 3007 } 3008 3009 func (w *warningHandler) hasObservedMarker() bool { 3010 w.lock.Lock() 3011 defer w.lock.Unlock() 3012 return w.observedMarker 3013 } 3014 3015 func (w *warningHandler) HandleWarningHeader(code int, _ string, message string) { 3016 if strings.HasSuffix(message, "marker denied; policy is ready") { 3017 func() { 3018 w.lock.Lock() 3019 defer w.lock.Unlock() 3020 w.observedMarker = true 3021 }() 3022 } 3023 if code != 299 || len(message) == 0 { 3024 return 3025 } 3026 w.lock.Lock() 3027 defer w.lock.Unlock() 3028 w.warnings.Insert(message) 3029 } 3030 3031 func expectedAuditEvents(auditAnnotations map[string]string, ns string, code int32) []utils.AuditEvent { 3032 return []utils.AuditEvent{ 3033 { 3034 Level: auditinternal.LevelRequest, 3035 Stage: auditinternal.StageResponseComplete, 3036 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", ns), 3037 Verb: "create", 3038 Code: code, 3039 User: "system:apiserver", 3040 ImpersonatedUser: testReinvocationClientUsername, 3041 ImpersonatedGroups: "system:authenticated", 3042 Resource: "configmaps", 3043 Namespace: ns, 3044 AuthorizeDecision: "allow", 3045 RequestObject: true, 3046 ResponseObject: false, 3047 CustomAuditAnnotations: auditAnnotations, 3048 }, 3049 } 3050 } 3051 3052 const ( 3053 testReinvocationClientUsername = "webhook-reinvocation-integration-client" 3054 auditPolicy = ` 3055 apiVersion: audit.k8s.io/v1 3056 kind: Policy 3057 rules: 3058 - level: Request 3059 resources: 3060 - group: "" # core 3061 resources: ["configmaps"] 3062 ` 3063 ) 3064 3065 func TestAuthorizationDecisionCaching(t *testing.T) { 3066 for _, tc := range []struct { 3067 name string 3068 validations []admissionregistrationv1beta1.Validation 3069 }{ 3070 { 3071 name: "hit", 3072 validations: []admissionregistrationv1beta1.Validation{ 3073 { 3074 Expression: "authorizer.requestResource.check('test').reason() == authorizer.requestResource.check('test').reason()", 3075 }, 3076 }, 3077 }, 3078 { 3079 name: "miss", 3080 validations: []admissionregistrationv1beta1.Validation{ 3081 { 3082 Expression: "authorizer.requestResource.subresource('a').check('test').reason() == '1'", 3083 }, 3084 { 3085 Expression: "authorizer.requestResource.subresource('b').check('test').reason() == '2'", 3086 }, 3087 { 3088 Expression: "authorizer.requestResource.subresource('c').check('test').reason() == '3'", 3089 }, 3090 }, 3091 }, 3092 } { 3093 t.Run(tc.name, func(t *testing.T) { 3094 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() 3095 3096 ctx, cancel := context.WithCancel(context.TODO()) 3097 defer cancel() 3098 3099 var nChecks int 3100 webhook := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 3101 var review authorizationv1.SubjectAccessReview 3102 if err := json.NewDecoder(r.Body).Decode(&review); err != nil { 3103 http.Error(w, err.Error(), http.StatusBadRequest) 3104 } 3105 3106 review.Status.Allowed = true 3107 if review.Spec.ResourceAttributes.Verb == "test" { 3108 nChecks++ 3109 review.Status.Reason = fmt.Sprintf("%d", nChecks) 3110 } 3111 3112 w.Header().Set("Content-Type", "application/json") 3113 if err := json.NewEncoder(w).Encode(review); err != nil { 3114 http.Error(w, err.Error(), http.StatusInternalServerError) 3115 } 3116 })) 3117 defer webhook.Close() 3118 3119 kcfd, err := os.CreateTemp("", "kubeconfig-") 3120 if err != nil { 3121 t.Fatal(err) 3122 } 3123 func() { 3124 defer kcfd.Close() 3125 tmpl, err := template.New("kubeconfig").Parse(` 3126 apiVersion: v1 3127 kind: Config 3128 clusters: 3129 - name: test-authz-service 3130 cluster: 3131 server: {{ .Server }} 3132 users: 3133 - name: test-api-server 3134 current-context: webhook 3135 contexts: 3136 - context: 3137 cluster: test-authz-service 3138 user: test-api-server 3139 name: webhook 3140 `) 3141 if err != nil { 3142 t.Fatal(err) 3143 } 3144 err = tmpl.Execute(kcfd, struct { 3145 Server string 3146 }{ 3147 Server: webhook.URL, 3148 }) 3149 if err != nil { 3150 t.Fatal(err) 3151 } 3152 }() 3153 3154 client, config, teardown := framework.StartTestServer(ctx, t, framework.TestServerSetup{ 3155 ModifyServerRunOptions: func(options *options.ServerRunOptions) { 3156 options.Admission.GenericAdmission.EnablePlugins = append(options.Admission.GenericAdmission.EnablePlugins, "ValidatingAdmissionPolicy") 3157 options.APIEnablement.RuntimeConfig.Set("api/all=true") 3158 3159 options.Authorization.Modes = []string{authzmodes.ModeWebhook} 3160 options.Authorization.WebhookConfigFile = kcfd.Name() 3161 options.Authorization.WebhookVersion = "v1" 3162 // Bypass webhook cache to observe the policy plugin's cache behavior. 3163 options.Authorization.WebhookCacheAuthorizedTTL = 0 3164 options.Authorization.WebhookCacheUnauthorizedTTL = 0 3165 }, 3166 }) 3167 defer teardown() 3168 3169 policy := &admissionregistrationv1beta1.ValidatingAdmissionPolicy{ 3170 ObjectMeta: metav1.ObjectMeta{ 3171 Name: "test-authorization-decision-caching-policy", 3172 }, 3173 Spec: admissionregistrationv1beta1.ValidatingAdmissionPolicySpec{ 3174 MatchConstraints: &admissionregistrationv1beta1.MatchResources{ 3175 ResourceRules: []admissionregistrationv1beta1.NamedRuleWithOperations{ 3176 { 3177 ResourceNames: []string{"test-authorization-decision-caching-namespace"}, 3178 RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{ 3179 Operations: []admissionregistrationv1.OperationType{ 3180 admissionregistrationv1.Create, 3181 }, 3182 Rule: admissionregistrationv1.Rule{ 3183 APIGroups: []string{""}, 3184 APIVersions: []string{"v1"}, 3185 Resources: []string{"namespaces"}, 3186 }, 3187 }, 3188 }, 3189 }, 3190 }, 3191 Validations: tc.validations, 3192 }, 3193 } 3194 3195 policy, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(ctx, withWaitReadyConstraintAndExpression(policy), metav1.CreateOptions{}) 3196 if err != nil { 3197 t.Fatal(err) 3198 } 3199 3200 if err := createAndWaitReady(t, client, makeBinding(policy.Name+"-binding", policy.Name, ""), nil); err != nil { 3201 t.Fatal(err) 3202 } 3203 3204 config = rest.CopyConfig(config) 3205 config.Impersonate = rest.ImpersonationConfig{ 3206 UserName: "alice", 3207 UID: "1234", 3208 } 3209 client, err = clientset.NewForConfig(config) 3210 if err != nil { 3211 t.Fatal(err) 3212 } 3213 3214 if _, err := client.CoreV1().Namespaces().Create( 3215 ctx, 3216 &v1.Namespace{ 3217 ObjectMeta: metav1.ObjectMeta{ 3218 Name: "test-authorization-decision-caching-namespace", 3219 }, 3220 }, 3221 metav1.CreateOptions{}, 3222 ); err != nil { 3223 t.Fatal(err) 3224 } 3225 }) 3226 } 3227 }