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