1
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
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",
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 defer 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)",
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)",
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 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
421
422
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
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
503
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 defer 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 defer 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
717
718 func Test_PolicyExemption(t *testing.T) {
719 defer 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
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
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
801
802
803
804
805 func Test_ValidatingAdmissionPolicy_UpdateParamKind(t *testing.T) {
806 defer 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
863
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
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
904
905
906
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
944
945
946 func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) {
947 defer 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
996
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
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
1056
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
1094 func Test_ValidatingAdmissionPolicy_UpdateParamResource(t *testing.T) {
1095 defer 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
1138
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
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
1190
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 defer 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 defer 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
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
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 defer 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 defer 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
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 defer 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 defer 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
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 defer 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
1765 if len(r.Status.UserInfo.UID) == 0 {
1766 t.Errorf("unexpected invalid user info: %v", r.Status.UserInfo)
1767 }
1768 }
1769
1770
1771
1772 func Test_ValidatingAdmissionPolicy_PolicyDeletedThenRecreated(t *testing.T) {
1773 defer 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
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
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
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
1889
1890 func Test_ValidatingAdmissionPolicy_BindingDeletedThenRecreated(t *testing.T) {
1891 defer 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
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
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
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
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
2008
2009 func Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated(t *testing.T) {
2010 defer 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
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
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
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
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
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
2135 func TestCRDParams(t *testing.T) {
2136 testcases := []struct {
2137 name string
2138 resource *unstructured.Unstructured
2139 policy *admissionregistrationv1.ValidatingAdmissionPolicy
2140 policyBinding *admissionregistrationv1.ValidatingAdmissionPolicyBinding
2141 namespace *v1.Namespace
2142 err string
2143 failureReason metav1.StatusReason
2144 }{
2145 {
2146 name: "a rule that uses data from a CRD param resource does NOT pass",
2147 resource: &unstructured.Unstructured{Object: map[string]interface{}{
2148 "apiVersion": "awesome.bears.com/v1",
2149 "kind": "Panda",
2150 "metadata": map[string]interface{}{
2151 "name": "config-obj",
2152 },
2153 "spec": map[string]interface{}{
2154 "nameCheck": "crd-test-k8s",
2155 },
2156 }},
2157 policy: withValidations([]admissionregistrationv1.Validation{
2158 {
2159 Expression: "params.spec.nameCheck == object.metadata.name",
2160 },
2161 }, withNamespaceMatch(withParams(withCRDParamKind("Panda", "awesome.bears.com", "v1"), withFailurePolicy(admissionregistrationv1.Fail, makePolicy("test-policy"))))),
2162 policyBinding: makeBinding("crd-policy-binding", "test-policy", "config-obj"),
2163 namespace: &v1.Namespace{
2164 ObjectMeta: metav1.ObjectMeta{
2165 Name: "incorrect-name",
2166 },
2167 },
2168 err: `namespaces "incorrect-name" is forbidden: ValidatingAdmissionPolicy 'test-policy' with binding 'crd-policy-binding' denied request: failed expression: params.spec.nameCheck == object.metadata.name`,
2169 failureReason: metav1.StatusReasonInvalid,
2170 },
2171 {
2172 name: "a rule that uses data from a CRD param resource that does pass",
2173 resource: &unstructured.Unstructured{Object: map[string]interface{}{
2174 "apiVersion": "awesome.bears.com/v1",
2175 "kind": "Panda",
2176 "metadata": map[string]interface{}{
2177 "name": "config-obj",
2178 },
2179 "spec": map[string]interface{}{
2180 "nameCheck": "crd-test-k8s",
2181 },
2182 }},
2183 policy: withValidations([]admissionregistrationv1.Validation{
2184 {
2185 Expression: "params.spec.nameCheck == object.metadata.name",
2186 },
2187 }, withNamespaceMatch(withParams(withCRDParamKind("Panda", "awesome.bears.com", "v1"), withFailurePolicy(admissionregistrationv1.Fail, makePolicy("test-policy"))))),
2188 policyBinding: makeBinding("crd-policy-binding", "test-policy", "config-obj"),
2189 namespace: &v1.Namespace{
2190 ObjectMeta: metav1.ObjectMeta{
2191 Name: "crd-test-k8s",
2192 },
2193 },
2194 err: ``,
2195 },
2196 }
2197
2198 for _, testcase := range testcases {
2199 t.Run(testcase.name, func(t *testing.T) {
2200 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
2201 server, err := apiservertesting.StartTestServer(t, nil, []string{
2202 "--enable-admission-plugins", "ValidatingAdmissionPolicy",
2203 }, framework.SharedEtcd())
2204 if err != nil {
2205 t.Fatal(err)
2206 }
2207 defer server.TearDownFn()
2208
2209 config := server.ClientConfig
2210
2211 client, err := clientset.NewForConfig(config)
2212 if err != nil {
2213 t.Fatal(err)
2214 }
2215
2216 crd := versionedCustomResourceDefinition()
2217 etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, crd)
2218 dynamicClient, err := dynamic.NewForConfig(config)
2219 if err != nil {
2220 t.Fatal(err)
2221 }
2222 gvr := schema.GroupVersionResource{
2223 Group: crd.Spec.Group,
2224 Version: crd.Spec.Versions[0].Name,
2225 Resource: crd.Spec.Names.Plural,
2226 }
2227 crClient := dynamicClient.Resource(gvr)
2228 _, err = crClient.Create(context.TODO(), testcase.resource, metav1.CreateOptions{})
2229 if err != nil {
2230 t.Fatalf("error creating %s: %s", gvr, err)
2231 }
2232
2233 policy := withWaitReadyConstraintAndExpression(testcase.policy)
2234 if _, err := client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil {
2235 t.Fatal(err)
2236 }
2237
2238 testcase.policyBinding.Spec.ParamRef.Namespace = ""
2239 if err := createAndWaitReady(t, client, testcase.policyBinding, nil); err != nil {
2240 t.Fatal(err)
2241 }
2242
2243 _, err = client.CoreV1().Namespaces().Create(context.TODO(), testcase.namespace, metav1.CreateOptions{})
2244
2245 checkExpectedError(t, err, testcase.err)
2246 checkFailureReason(t, err, testcase.failureReason)
2247 })
2248 }
2249 }
2250
2251 func TestBindingRemoval(t *testing.T) {
2252 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
2253 server, err := apiservertesting.StartTestServer(t, nil, []string{
2254 "--enable-admission-plugins", "ValidatingAdmissionPolicy",
2255 }, framework.SharedEtcd())
2256 if err != nil {
2257 t.Fatal(err)
2258 }
2259 defer server.TearDownFn()
2260
2261 config := server.ClientConfig
2262
2263 client, err := clientset.NewForConfig(config)
2264 if err != nil {
2265 t.Fatal(err)
2266 }
2267
2268 policy := withValidations([]admissionregistrationv1.Validation{
2269 {
2270 Expression: "false",
2271 Message: "policy still in effect",
2272 },
2273 }, withNamespaceMatch(withFailurePolicy(admissionregistrationv1.Fail, makePolicy("test-policy"))))
2274 policy = withWaitReadyConstraintAndExpression(policy)
2275 if _, err := client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil {
2276 t.Fatal(err)
2277 }
2278
2279 binding := makeBinding("test-binding", "test-policy", "test-params")
2280 if err := createAndWaitReady(t, client, binding, nil); err != nil {
2281 t.Fatal(err)
2282 }
2283
2284 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) {
2285 namespace := &v1.Namespace{
2286 ObjectMeta: metav1.ObjectMeta{
2287 GenerateName: "check-namespace",
2288 },
2289 }
2290 _, err = client.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{})
2291 if err != nil {
2292 if strings.Contains(err.Error(), "policy still in effect") {
2293 return true, nil
2294 } else {
2295
2296 return true, err
2297 }
2298 }
2299 return false, nil
2300 }); waitErr != nil {
2301 t.Errorf("timed out waiting: %v", waitErr)
2302 }
2303 if err = client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Delete(context.TODO(), "test-binding", metav1.DeleteOptions{}); err != nil {
2304 t.Fatal(err)
2305 }
2306
2307
2308 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) {
2309
2310 _, err := client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Get(context.TODO(), "test-binding", metav1.GetOptions{})
2311 if err != nil {
2312 if apierrors.IsNotFound(err) {
2313 return true, nil
2314 } else {
2315 return true, err
2316 }
2317 }
2318
2319 return false, nil
2320 }); waitErr != nil {
2321 t.Errorf("timed out waiting: %v", waitErr)
2322 }
2323
2324
2325 if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) {
2326 namespace := &v1.Namespace{
2327 ObjectMeta: metav1.ObjectMeta{
2328 GenerateName: "test-namespace",
2329 },
2330 }
2331 _, err = client.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{})
2332 if err != nil {
2333 t.Logf("namespace creation failed: %s", err)
2334 return false, nil
2335 }
2336
2337 return true, nil
2338 }); waitErr != nil {
2339 t.Errorf("expected namespace creation to succeed but timed out waiting: %v", waitErr)
2340 }
2341 }
2342
2343
2344
2345 func Test_ValidateSecondaryAuthorization(t *testing.T) {
2346 testcases := []struct {
2347 name string
2348 rbac *rbacv1.PolicyRule
2349 expression string
2350 allowed bool
2351 extraAccountFn func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset
2352 extraAccountRbac *rbacv1.PolicyRule
2353 }{
2354 {
2355 name: "principal is allowed to create a specific deployment",
2356 rbac: &rbacv1.PolicyRule{
2357 Verbs: []string{"create"},
2358 APIGroups: []string{"apps"},
2359 Resources: []string{"deployments/status"},
2360 ResourceNames: []string{"charmander"},
2361 },
2362 expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('default').namespace('default').name('charmander').check('create').allowed()",
2363 allowed: true,
2364 },
2365 {
2366 name: "principal is not allowed to create a specific deployment",
2367 expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('default').name('charmander').check('create').allowed()",
2368 allowed: false,
2369 },
2370 {
2371 name: "principal is authorized for custom verb on current resource",
2372 rbac: &rbacv1.PolicyRule{
2373 Verbs: []string{"anthropomorphize"},
2374 APIGroups: []string{""},
2375 Resources: []string{"namespaces"},
2376 },
2377 expression: "authorizer.requestResource.check('anthropomorphize').allowed()",
2378 allowed: true,
2379 },
2380 {
2381 name: "principal is not authorized for custom verb on current resource",
2382 expression: "authorizer.requestResource.check('anthropomorphize').allowed()",
2383 allowed: false,
2384 },
2385 {
2386 name: "serviceaccount is authorized for custom verb on current resource",
2387 extraAccountFn: serviceAccountClient("default", "extra-acct"),
2388 extraAccountRbac: &rbacv1.PolicyRule{
2389 Verbs: []string{"anthropomorphize"},
2390 APIGroups: []string{""},
2391 Resources: []string{"pods"},
2392 },
2393 expression: "authorizer.serviceAccount('default', 'extra-acct').group('').resource('pods').check('anthropomorphize').allowed()",
2394 allowed: true,
2395 },
2396 }
2397
2398 for _, testcase := range testcases {
2399 t.Run(testcase.name, func(t *testing.T) {
2400 clients := map[string]func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset{
2401 "user": secondaryAuthorizationUserClient,
2402 "serviceaccount": secondaryAuthorizationServiceAccountClient,
2403 }
2404
2405 for clientName, clientFn := range clients {
2406 t.Run(clientName, func(t *testing.T) {
2407 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
2408 server, err := apiservertesting.StartTestServer(t, nil, []string{
2409 "--enable-admission-plugins", "ValidatingAdmissionPolicy",
2410 "--authorization-mode=RBAC",
2411 "--anonymous-auth",
2412 }, framework.SharedEtcd())
2413 if err != nil {
2414 t.Fatal(err)
2415 }
2416 defer server.TearDownFn()
2417
2418
2419 adminClient := clientset.NewForConfigOrDie(server.ClientConfig)
2420
2421
2422
2423 rules := []rbacv1.PolicyRule{{
2424 Verbs: []string{"create", "update"},
2425 APIGroups: []string{""},
2426 Resources: []string{"namespaces"},
2427 }}
2428 if testcase.rbac != nil {
2429 rules = append(rules, *testcase.rbac)
2430 }
2431
2432 client := clientFn(t, adminClient, server.ClientConfig, rules)
2433
2434 if testcase.extraAccountFn != nil {
2435 var extraRules []rbacv1.PolicyRule
2436 if testcase.extraAccountRbac != nil {
2437 extraRules = append(rules, *testcase.extraAccountRbac)
2438 }
2439 testcase.extraAccountFn(t, adminClient, server.ClientConfig, extraRules)
2440 }
2441
2442 policy := withWaitReadyConstraintAndExpression(withValidations([]admissionregistrationv1.Validation{
2443 {
2444 Expression: testcase.expression,
2445 },
2446 }, withFailurePolicy(admissionregistrationv1.Fail, withNamespaceMatch(makePolicy("validate-authz")))))
2447 if _, err := adminClient.AdmissionregistrationV1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil {
2448 t.Fatal(err)
2449 }
2450 if err := createAndWaitReady(t, adminClient, makeBinding("validate-authz-binding", "validate-authz", ""), nil); err != nil {
2451 t.Fatal(err)
2452 }
2453
2454 ns := &v1.Namespace{
2455 ObjectMeta: metav1.ObjectMeta{
2456 Name: "test-authz",
2457 },
2458 }
2459 _, err = client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
2460
2461 var expected metav1.StatusReason = ""
2462 if !testcase.allowed {
2463 expected = metav1.StatusReasonInvalid
2464 }
2465 checkFailureReason(t, err, expected)
2466 })
2467 }
2468 })
2469 }
2470 }
2471
2472 func TestCRDsOnStartup(t *testing.T) {
2473 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
2474
2475 testContext, testCancel := context.WithCancel(context.Background())
2476 defer testCancel()
2477
2478
2479 etcdConfig := framework.SharedEtcd()
2480 server := apiservertesting.StartTestServerOrDie(t, nil, []string{
2481 "--enable-admission-plugins", "ValidatingAdmissionPolicy",
2482 "--authorization-mode=RBAC",
2483 "--anonymous-auth",
2484 }, etcdConfig)
2485 client := clientset.NewForConfigOrDie(server.ClientConfig)
2486 dynamicClient := dynamic.NewForConfigOrDie(server.ClientConfig)
2487 apiextclient := apiextensionsclientset.NewForConfigOrDie(server.ClientConfig)
2488 myCRD := &apiextensionsv1.CustomResourceDefinition{
2489 ObjectMeta: metav1.ObjectMeta{
2490 Name: "foos.cr.bar.com",
2491 },
2492 Spec: apiextensionsv1.CustomResourceDefinitionSpec{
2493 Group: "cr.bar.com",
2494 Scope: apiextensionsv1.NamespaceScoped,
2495 Names: apiextensionsv1.CustomResourceDefinitionNames{
2496 Plural: "foos",
2497 Kind: "Foo",
2498 },
2499 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
2500 {
2501 Name: "v1",
2502 Served: true,
2503 Storage: true,
2504 Schema: fixtures.AllowAllSchema(),
2505 },
2506 },
2507 },
2508 }
2509
2510
2511 for i := 0; i < 100; i++ {
2512 crd := myCRD.DeepCopy()
2513 crd.Name = fmt.Sprintf("foos%d.cr.bar.com", i)
2514 crd.Spec.Names.Plural = fmt.Sprintf("foos%d", i)
2515 crd.Spec.Names.Kind = fmt.Sprintf("Foo%d", i)
2516
2517 if _, err := apiextclient.ApiextensionsV1().CustomResourceDefinitions().Create(context.Background(), crd, metav1.CreateOptions{}); err != nil {
2518 t.Fatal(err)
2519 }
2520 }
2521
2522 etcd.CreateTestCRDs(t, apiextclient, false, myCRD)
2523 crdGVK := schema.GroupVersionKind{
2524 Group: "cr.bar.com",
2525 Version: "v1",
2526 Kind: "Foo",
2527 }
2528 crdGVR := crdGVK.GroupVersion().WithResource("foos")
2529
2530 param := &unstructured.Unstructured{
2531 Object: map[string]interface{}{
2532 "metadata": map[string]interface{}{
2533 "name": "test",
2534 "namespace": "default",
2535 },
2536 "foo": "bar",
2537 },
2538 }
2539 param.GetObjectKind().SetGroupVersionKind(crdGVK)
2540
2541 if _, err := dynamicClient.Resource(crdGVR).Namespace("default").Create(context.TODO(), param, metav1.CreateOptions{}); err != nil {
2542 t.Fatal(err)
2543 }
2544
2545 policy := withValidations([]admissionregistrationv1.Validation{
2546 {
2547 Expression: "object.metadata.name.startsWith(params.metadata.name)",
2548 Message: "wrong prefix",
2549 },
2550 }, withParams(withCRDParamKind(crdGVK.Kind, crdGVK.Group, crdGVK.Version), withNamespaceMatch(withFailurePolicy(admissionregistrationv1.Fail, makePolicy("allowed-prefixes")))))
2551 policy = withWaitReadyConstraintAndExpression(policy)
2552 _, err := client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{})
2553 if err != nil {
2554 t.Fatal(err)
2555 }
2556
2557
2558 policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "test")
2559 if err := createAndWaitReady(t, client, policyBinding, nil); err != nil {
2560 t.Fatal(err)
2561 }
2562
2563 doCheck := func(client clientset.Interface) {
2564 if waitErr := wait.PollUntilContextTimeout(testContext, time.Millisecond*100, 3*time.Minute, true, func(ctx context.Context) (bool, error) {
2565 disallowedNamespace := &v1.Namespace{
2566 ObjectMeta: metav1.ObjectMeta{
2567 GenerateName: "not-test-",
2568 },
2569 }
2570
2571 _, err = client.CoreV1().Namespaces().Create(testContext, disallowedNamespace, metav1.CreateOptions{})
2572 if err == nil {
2573 return false, nil
2574 }
2575
2576 if strings.Contains(err.Error(), "not yet synced to use for admission") {
2577 return false, nil
2578 }
2579
2580 if strings.Contains(err.Error(), "failed to find resource referenced by paramKind") {
2581 return false, nil
2582 }
2583
2584 if !strings.Contains(err.Error(), "wrong prefix") {
2585 return false, err
2586 }
2587
2588 return true, nil
2589 }); waitErr != nil {
2590 t.Errorf("timed out waiting: %v", err)
2591 }
2592 }
2593
2594
2595
2596 doCheck(client)
2597 server.TearDownFn()
2598
2599
2600 server = apiservertesting.StartTestServerOrDie(
2601 t,
2602 &apiservertesting.TestServerInstanceOptions{},
2603 []string{
2604 "--enable-admission-plugins", "ValidatingAdmissionPolicy",
2605 "--authorization-mode=RBAC",
2606 "--anonymous-auth",
2607 },
2608 etcdConfig)
2609 defer server.TearDownFn()
2610
2611
2612 client = clientset.NewForConfigOrDie(server.ClientConfig)
2613
2614 doCheck(client)
2615
2616 }
2617
2618 type clientFn func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset
2619
2620 func secondaryAuthorizationUserClient(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset {
2621 clientConfig = rest.CopyConfig(clientConfig)
2622 clientConfig.Impersonate = rest.ImpersonationConfig{
2623 UserName: "alice",
2624 UID: "1234",
2625 }
2626 client := clientset.NewForConfigOrDie(clientConfig)
2627
2628 for _, rule := range rules {
2629 authutil.GrantUserAuthorization(t, context.TODO(), adminClient, "alice", rule)
2630 }
2631 return client
2632 }
2633
2634 func secondaryAuthorizationServiceAccountClient(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset {
2635 return serviceAccountClient("default", "test-service-acct")(t, adminClient, clientConfig, rules)
2636 }
2637
2638 func serviceAccountClient(namespace, name string) clientFn {
2639 return func(t *testing.T, adminClient *clientset.Clientset, clientConfig *rest.Config, rules []rbacv1.PolicyRule) *clientset.Clientset {
2640 clientConfig = rest.CopyConfig(clientConfig)
2641 sa, err := adminClient.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: name}}, metav1.CreateOptions{})
2642 if err != nil {
2643 t.Fatal(err)
2644 }
2645 uid := sa.UID
2646
2647 clientConfig.Impersonate = rest.ImpersonationConfig{
2648 UserName: "system:serviceaccount:" + namespace + ":" + name,
2649 UID: string(uid),
2650 }
2651 client := clientset.NewForConfigOrDie(clientConfig)
2652
2653 for _, rule := range rules {
2654 authutil.GrantServiceAccountAuthorization(t, context.TODO(), adminClient, name, namespace, rule)
2655 }
2656 return client
2657 }
2658 }
2659
2660 func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2661 policy = policy.DeepCopy()
2662 policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1.NamedRuleWithOperations{
2663 ResourceNames: []string{"test-marker"},
2664 RuleWithOperations: admissionregistrationv1.RuleWithOperations{
2665 Operations: []admissionregistrationv1.OperationType{
2666 "UPDATE",
2667 },
2668 Rule: admissionregistrationv1.Rule{
2669 APIGroups: []string{
2670 "",
2671 },
2672 APIVersions: []string{
2673 "v1",
2674 },
2675 Resources: []string{
2676 "endpoints",
2677 },
2678 },
2679 },
2680 })
2681 policy.Spec.Validations = append([]admissionregistrationv1.Validation{{
2682 Expression: "object.metadata.name != 'test-marker'",
2683 Message: "marker denied; policy is ready",
2684 }}, policy.Spec.Validations...)
2685 return policy
2686 }
2687
2688 func createAndWaitReady(t *testing.T, client clientset.Interface, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string) error {
2689 return createAndWaitReadyNamespaced(t, client, binding, matchLabels, "default")
2690 }
2691
2692 func createAndWaitReadyNamespaced(t *testing.T, client clientset.Interface, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string) error {
2693 return createAndWaitReadyNamespacedWithWarnHandler(t, client, binding, matchLabels, ns, newWarningHandler())
2694 }
2695
2696 func createAndWaitReadyNamespacedWithWarnHandler(t *testing.T, client clientset.Interface, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string, handler *warningHandler) error {
2697 marker := &v1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "test-marker", Namespace: ns, Labels: matchLabels}}
2698 defer func() {
2699 err := client.CoreV1().Endpoints(ns).Delete(context.TODO(), marker.Name, metav1.DeleteOptions{})
2700 if err != nil {
2701 t.Logf("error deleting marker: %v", err)
2702 }
2703 }()
2704 marker, err := client.CoreV1().Endpoints(ns).Create(context.TODO(), marker, metav1.CreateOptions{})
2705 if err != nil {
2706 return err
2707 }
2708
2709 _, err = client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), binding, metav1.CreateOptions{})
2710 if err != nil {
2711 return err
2712 }
2713
2714 if waitErr := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
2715 handler.reset()
2716 _, err := client.CoreV1().Endpoints(ns).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
2717 if handler.hasObservedMarker() {
2718 return true, nil
2719 }
2720 if err != nil && strings.Contains(err.Error(), "marker denied; policy is ready") {
2721 return true, nil
2722 } else if err != nil && strings.Contains(err.Error(), "not yet synced to use for admission") {
2723 t.Logf("waiting for policy to be ready. Marker: %v. Admission not synced yet: %v", marker, err)
2724 return false, nil
2725 } else {
2726 t.Logf("waiting for policy to be ready. Marker: %v, Last marker patch response: %v", marker, err)
2727 return false, err
2728 }
2729 }); waitErr != nil {
2730 return waitErr
2731 }
2732 t.Logf("Marker ready: %v", marker)
2733 handler.reset()
2734 return nil
2735 }
2736
2737 func withMatchNamespace(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, ns string) *admissionregistrationv1.ValidatingAdmissionPolicyBinding {
2738 binding.Spec.MatchResources = &admissionregistrationv1.MatchResources{
2739 NamespaceSelector: &metav1.LabelSelector{
2740 MatchExpressions: []metav1.LabelSelectorRequirement{
2741 {
2742 Key: "kubernetes.io/metadata.name",
2743 Operator: metav1.LabelSelectorOpIn,
2744 Values: []string{ns},
2745 },
2746 },
2747 },
2748 }
2749 return binding
2750 }
2751
2752 func makePolicy(name string) *admissionregistrationv1.ValidatingAdmissionPolicy {
2753 return &admissionregistrationv1.ValidatingAdmissionPolicy{
2754 ObjectMeta: metav1.ObjectMeta{Name: name},
2755 }
2756 }
2757
2758 func withParams(params *admissionregistrationv1.ParamKind, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2759 policy.Spec.ParamKind = params
2760 return policy
2761 }
2762
2763 func configParamKind() *admissionregistrationv1.ParamKind {
2764 return &admissionregistrationv1.ParamKind{
2765 APIVersion: "v1",
2766 Kind: "ConfigMap",
2767 }
2768 }
2769
2770 func withFailurePolicy(failure admissionregistrationv1.FailurePolicyType, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2771 policy.Spec.FailurePolicy = &failure
2772 return policy
2773 }
2774
2775 func withNamespaceMatch(policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2776 return withPolicyMatch("namespaces", policy)
2777 }
2778
2779 func withConfigMapMatch(policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2780 return withPolicyMatch("configmaps", policy)
2781 }
2782
2783 func withObjectSelector(labelSelector *metav1.LabelSelector, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2784 policy.Spec.MatchConstraints.ObjectSelector = labelSelector
2785 return policy
2786 }
2787
2788 func withNamespaceSelector(labelSelector *metav1.LabelSelector, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2789 policy.Spec.MatchConstraints.NamespaceSelector = labelSelector
2790 return policy
2791 }
2792
2793 func withPolicyMatch(resource string, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2794 policy.Spec.MatchConstraints = &admissionregistrationv1.MatchResources{
2795 ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
2796 {
2797 RuleWithOperations: admissionregistrationv1.RuleWithOperations{
2798 Operations: []admissionregistrationv1.OperationType{
2799 "*",
2800 },
2801 Rule: admissionregistrationv1.Rule{
2802 APIGroups: []string{
2803 "",
2804 },
2805 APIVersions: []string{
2806 "*",
2807 },
2808 Resources: []string{
2809 resource,
2810 },
2811 },
2812 },
2813 },
2814 },
2815 }
2816 return policy
2817 }
2818
2819 func withExcludePolicyMatch(resource string, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2820 policy.Spec.MatchConstraints.ExcludeResourceRules = []admissionregistrationv1.NamedRuleWithOperations{
2821 {
2822 RuleWithOperations: admissionregistrationv1.RuleWithOperations{
2823 Operations: []admissionregistrationv1.OperationType{
2824 "*",
2825 },
2826 Rule: admissionregistrationv1.Rule{
2827 APIGroups: []string{
2828 "",
2829 },
2830 APIVersions: []string{
2831 "*",
2832 },
2833 Resources: []string{
2834 resource,
2835 },
2836 },
2837 },
2838 },
2839 }
2840 return policy
2841 }
2842
2843 func withPolicyExistsLabels(labels []string, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2844 if policy.Spec.MatchConstraints == nil {
2845 policy.Spec.MatchConstraints = &admissionregistrationv1.MatchResources{}
2846 }
2847 matchExprs := buildExistsSelector(labels)
2848 policy.Spec.MatchConstraints.ObjectSelector = &metav1.LabelSelector{
2849 MatchExpressions: matchExprs,
2850 }
2851 return policy
2852 }
2853
2854 func withValidations(validations []admissionregistrationv1.Validation, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2855 policy.Spec.Validations = validations
2856 return policy
2857 }
2858
2859 func withAuditAnnotations(auditAnnotations []admissionregistrationv1.AuditAnnotation, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
2860 policy.Spec.AuditAnnotations = auditAnnotations
2861 return policy
2862 }
2863
2864 func makeBinding(name, policyName, paramName string) *admissionregistrationv1.ValidatingAdmissionPolicyBinding {
2865 var paramRef *admissionregistrationv1.ParamRef
2866 if paramName != "" {
2867 denyAction := admissionregistrationv1.DenyAction
2868 paramRef = &admissionregistrationv1.ParamRef{
2869 Name: paramName,
2870 Namespace: "default",
2871 ParameterNotFoundAction: &denyAction,
2872 }
2873 }
2874 return &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
2875 ObjectMeta: metav1.ObjectMeta{Name: name},
2876 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
2877 PolicyName: policyName,
2878 ParamRef: paramRef,
2879 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny},
2880 },
2881 }
2882 }
2883
2884 func withValidationActions(validationActions []admissionregistrationv1.ValidationAction, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1.ValidatingAdmissionPolicyBinding {
2885 binding.Spec.ValidationActions = validationActions
2886 return binding
2887 }
2888
2889 func withBindingExistsLabels(labels []string, policy *admissionregistrationv1.ValidatingAdmissionPolicy, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1.ValidatingAdmissionPolicyBinding {
2890 if policy != nil {
2891
2892 constraintsCopy := *policy.Spec.MatchConstraints
2893 binding.Spec.MatchResources = &constraintsCopy
2894 }
2895 matchExprs := buildExistsSelector(labels)
2896 binding.Spec.MatchResources.ObjectSelector = &metav1.LabelSelector{
2897 MatchExpressions: matchExprs,
2898 }
2899 return binding
2900 }
2901
2902 func buildExistsSelector(labels []string) []metav1.LabelSelectorRequirement {
2903 matchExprs := make([]metav1.LabelSelectorRequirement, len(labels))
2904 for i := 0; i < len(labels); i++ {
2905 matchExprs[i].Key = labels[i]
2906 matchExprs[i].Operator = metav1.LabelSelectorOpExists
2907 }
2908 return matchExprs
2909 }
2910
2911 func makeConfigParams(name string, data map[string]string) *v1.ConfigMap {
2912 return &v1.ConfigMap{
2913 ObjectMeta: metav1.ObjectMeta{Name: name},
2914 Data: data,
2915 }
2916 }
2917
2918 func checkForFailedRule(t *testing.T, err error) {
2919 if !strings.Contains(err.Error(), "failed expression") {
2920 t.Fatalf("unexpected error (expected to find \"failed expression\"): %s", err)
2921 }
2922 if strings.Contains(err.Error(), "evaluation error") {
2923 t.Fatalf("CEL rule evaluation failed: %s", err)
2924 }
2925 }
2926
2927 func checkFailureReason(t *testing.T, err error, expectedReason metav1.StatusReason) {
2928 if err == nil && expectedReason == "" {
2929
2930 return
2931 }
2932 switch e := err.(type) {
2933 case apierrors.APIStatus:
2934 reason := e.Status().Reason
2935 if reason != expectedReason {
2936 t.Logf("actual error reason: %v", reason)
2937 t.Logf("expected failure reason: %v", expectedReason)
2938 t.Error("Unexpected error reason")
2939 }
2940 default:
2941 t.Errorf("Unexpected error: %v", err)
2942 }
2943 }
2944
2945 func checkExpectedWarnings(t *testing.T, recordedWarnings *warningHandler, expectedWarnings sets.Set[string]) {
2946 if !recordedWarnings.equals(expectedWarnings) {
2947 t.Errorf("Expected warnings '%v' but got '%v", expectedWarnings, recordedWarnings)
2948 }
2949 }
2950
2951 func checkAuditEvents(t *testing.T, logFile *os.File, auditEvents []utils.AuditEvent, filter utils.AuditAnnotationsFilter) {
2952 stream, err := os.OpenFile(logFile.Name(), os.O_RDWR, 0600)
2953 if err != nil {
2954 t.Errorf("unexpected error: %v", err)
2955 }
2956 defer stream.Close()
2957
2958 if auditEvents != nil {
2959 missing, err := utils.CheckAuditLinesFiltered(stream, auditEvents, auditv1.SchemeGroupVersion, filter)
2960 if err != nil {
2961 t.Errorf("unexpected error checking audit lines: %v", err)
2962 }
2963 if len(missing.MissingEvents) > 0 {
2964 t.Errorf("failed to get expected events -- missing: %s", missing)
2965 }
2966 }
2967 if err := stream.Truncate(0); err != nil {
2968 t.Errorf("unexpected error truncate file: %v", err)
2969 }
2970 if _, err := stream.Seek(0, 0); err != nil {
2971 t.Errorf("unexpected error reset offset: %v", err)
2972 }
2973 }
2974
2975 func withCRDParamKind(kind, crdGroup, crdVersion string) *admissionregistrationv1.ParamKind {
2976 return &admissionregistrationv1.ParamKind{
2977 APIVersion: crdGroup + "/" + crdVersion,
2978 Kind: kind,
2979 }
2980 }
2981
2982 func checkExpectedError(t *testing.T, err error, expectedErr string) {
2983 if err == nil && expectedErr == "" {
2984 return
2985 }
2986 if err == nil && expectedErr != "" {
2987 t.Logf("actual error: %v", err)
2988 t.Logf("expected error: %v", expectedErr)
2989 t.Fatal("got nil error but expected an error")
2990 }
2991
2992 if err != nil && expectedErr == "" {
2993 t.Logf("actual error: %v", err)
2994 t.Logf("expected error: %v", expectedErr)
2995 t.Fatal("got error but expected none")
2996 }
2997
2998 if err.Error() != expectedErr {
2999 t.Logf("actual validation error: %v", err)
3000 t.Logf("expected validation error: %v", expectedErr)
3001 t.Error("unexpected validation error")
3002 }
3003 }
3004
3005
3006 func versionedCustomResourceDefinition() *apiextensionsv1.CustomResourceDefinition {
3007 return &apiextensionsv1.CustomResourceDefinition{
3008 ObjectMeta: metav1.ObjectMeta{
3009 Name: "pandas.awesome.bears.com",
3010 },
3011 Spec: apiextensionsv1.CustomResourceDefinitionSpec{
3012 Group: "awesome.bears.com",
3013 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
3014 {
3015 Name: "v1",
3016 Served: true,
3017 Storage: true,
3018 Schema: fixtures.AllowAllSchema(),
3019 Subresources: &apiextensionsv1.CustomResourceSubresources{
3020 Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
3021 Scale: &apiextensionsv1.CustomResourceSubresourceScale{
3022 SpecReplicasPath: ".spec.replicas",
3023 StatusReplicasPath: ".status.replicas",
3024 LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
3025 },
3026 },
3027 },
3028 {
3029 Name: "v2",
3030 Served: true,
3031 Storage: false,
3032 Schema: fixtures.AllowAllSchema(),
3033 Subresources: &apiextensionsv1.CustomResourceSubresources{
3034 Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
3035 Scale: &apiextensionsv1.CustomResourceSubresourceScale{
3036 SpecReplicasPath: ".spec.replicas",
3037 StatusReplicasPath: ".status.replicas",
3038 LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
3039 },
3040 },
3041 },
3042 },
3043 Scope: apiextensionsv1.ClusterScoped,
3044 Names: apiextensionsv1.CustomResourceDefinitionNames{
3045 Plural: "pandas",
3046 Kind: "Panda",
3047 },
3048 },
3049 }
3050 }
3051
3052 type warningHandler struct {
3053 lock sync.Mutex
3054 warnings sets.Set[string]
3055 observedMarker bool
3056 }
3057
3058 func newWarningHandler() *warningHandler {
3059 return &warningHandler{warnings: sets.New[string]()}
3060 }
3061
3062 func (w *warningHandler) reset() {
3063 w.lock.Lock()
3064 defer w.lock.Unlock()
3065 w.warnings = sets.New[string]()
3066 w.observedMarker = false
3067 }
3068
3069 func (w *warningHandler) equals(s sets.Set[string]) bool {
3070 w.lock.Lock()
3071 defer w.lock.Unlock()
3072 return w.warnings.Equal(s)
3073 }
3074
3075 func (w *warningHandler) hasObservedMarker() bool {
3076 w.lock.Lock()
3077 defer w.lock.Unlock()
3078 return w.observedMarker
3079 }
3080
3081 func (w *warningHandler) HandleWarningHeader(code int, _ string, message string) {
3082 if strings.HasSuffix(message, "marker denied; policy is ready") {
3083 func() {
3084 w.lock.Lock()
3085 defer w.lock.Unlock()
3086 w.observedMarker = true
3087 }()
3088 }
3089 if code != 299 || len(message) == 0 {
3090 return
3091 }
3092 w.lock.Lock()
3093 defer w.lock.Unlock()
3094 w.warnings.Insert(message)
3095 }
3096
3097 func expectedAuditEvents(auditAnnotations map[string]string, ns string, code int32) []utils.AuditEvent {
3098 return []utils.AuditEvent{
3099 {
3100 Level: auditinternal.LevelRequest,
3101 Stage: auditinternal.StageResponseComplete,
3102 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", ns),
3103 Verb: "create",
3104 Code: code,
3105 User: "system:apiserver",
3106 ImpersonatedUser: testReinvocationClientUsername,
3107 ImpersonatedGroups: "system:authenticated",
3108 Resource: "configmaps",
3109 Namespace: ns,
3110 AuthorizeDecision: "allow",
3111 RequestObject: true,
3112 ResponseObject: false,
3113 CustomAuditAnnotations: auditAnnotations,
3114 },
3115 }
3116 }
3117
3118 const (
3119 testReinvocationClientUsername = "webhook-reinvocation-integration-client"
3120 auditPolicy = `
3121 apiVersion: audit.k8s.io/v1
3122 kind: Policy
3123 rules:
3124 - level: Request
3125 resources:
3126 - group: "" # core
3127 resources: ["configmaps"]
3128 `
3129 )
3130
3131 func TestAuthorizationDecisionCaching(t *testing.T) {
3132 for _, tc := range []struct {
3133 name string
3134 validations []admissionregistrationv1.Validation
3135 }{
3136 {
3137 name: "hit",
3138 validations: []admissionregistrationv1.Validation{
3139 {
3140 Expression: "authorizer.requestResource.check('test').reason() == authorizer.requestResource.check('test').reason()",
3141 },
3142 },
3143 },
3144 {
3145 name: "miss",
3146 validations: []admissionregistrationv1.Validation{
3147 {
3148 Expression: "authorizer.requestResource.subresource('a').check('test').reason() == '1'",
3149 },
3150 {
3151 Expression: "authorizer.requestResource.subresource('b').check('test').reason() == '2'",
3152 },
3153 {
3154 Expression: "authorizer.requestResource.subresource('c').check('test').reason() == '3'",
3155 },
3156 },
3157 },
3158 } {
3159 t.Run(tc.name, func(t *testing.T) {
3160 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
3161
3162 ctx, cancel := context.WithCancel(context.TODO())
3163 defer cancel()
3164
3165 var nChecks int
3166 webhook := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3167 var review authorizationv1.SubjectAccessReview
3168 if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
3169 http.Error(w, err.Error(), http.StatusBadRequest)
3170 }
3171
3172 review.Status.Allowed = true
3173 if review.Spec.ResourceAttributes.Verb == "test" {
3174 nChecks++
3175 review.Status.Reason = fmt.Sprintf("%d", nChecks)
3176 }
3177
3178 w.Header().Set("Content-Type", "application/json")
3179 if err := json.NewEncoder(w).Encode(review); err != nil {
3180 http.Error(w, err.Error(), http.StatusInternalServerError)
3181 }
3182 }))
3183 defer webhook.Close()
3184
3185 kcfd, err := os.CreateTemp("", "kubeconfig-")
3186 if err != nil {
3187 t.Fatal(err)
3188 }
3189 func() {
3190 defer kcfd.Close()
3191 tmpl, err := template.New("kubeconfig").Parse(`
3192 apiVersion: v1
3193 kind: Config
3194 clusters:
3195 - name: test-authz-service
3196 cluster:
3197 server: {{ .Server }}
3198 users:
3199 - name: test-api-server
3200 current-context: webhook
3201 contexts:
3202 - context:
3203 cluster: test-authz-service
3204 user: test-api-server
3205 name: webhook
3206 `)
3207 if err != nil {
3208 t.Fatal(err)
3209 }
3210 err = tmpl.Execute(kcfd, struct {
3211 Server string
3212 }{
3213 Server: webhook.URL,
3214 })
3215 if err != nil {
3216 t.Fatal(err)
3217 }
3218 }()
3219
3220 client, config, teardown := framework.StartTestServer(ctx, t, framework.TestServerSetup{
3221 ModifyServerRunOptions: func(options *options.ServerRunOptions) {
3222 options.Admission.GenericAdmission.EnablePlugins = append(options.Admission.GenericAdmission.EnablePlugins, "ValidatingAdmissionPolicy")
3223 options.APIEnablement.RuntimeConfig.Set("api/all=true")
3224
3225 options.Authorization.Modes = []string{authzmodes.ModeWebhook}
3226 options.Authorization.WebhookConfigFile = kcfd.Name()
3227 options.Authorization.WebhookVersion = "v1"
3228
3229 options.Authorization.WebhookCacheAuthorizedTTL = 0
3230 options.Authorization.WebhookCacheUnauthorizedTTL = 0
3231 },
3232 })
3233 defer teardown()
3234
3235 policy := &admissionregistrationv1.ValidatingAdmissionPolicy{
3236 ObjectMeta: metav1.ObjectMeta{
3237 Name: "test-authorization-decision-caching-policy",
3238 },
3239 Spec: admissionregistrationv1.ValidatingAdmissionPolicySpec{
3240 MatchConstraints: &admissionregistrationv1.MatchResources{
3241 ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
3242 {
3243 ResourceNames: []string{"test-authorization-decision-caching-namespace"},
3244 RuleWithOperations: admissionregistrationv1.RuleWithOperations{
3245 Operations: []admissionregistrationv1.OperationType{
3246 admissionregistrationv1.Create,
3247 },
3248 Rule: admissionregistrationv1.Rule{
3249 APIGroups: []string{""},
3250 APIVersions: []string{"v1"},
3251 Resources: []string{"namespaces"},
3252 },
3253 },
3254 },
3255 },
3256 },
3257 Validations: tc.validations,
3258 },
3259 }
3260
3261 policy, err = client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Create(ctx, withWaitReadyConstraintAndExpression(policy), metav1.CreateOptions{})
3262 if err != nil {
3263 t.Fatal(err)
3264 }
3265
3266 if err := createAndWaitReady(t, client, makeBinding(policy.Name+"-binding", policy.Name, ""), nil); err != nil {
3267 t.Fatal(err)
3268 }
3269
3270 config = rest.CopyConfig(config)
3271 config.Impersonate = rest.ImpersonationConfig{
3272 UserName: "alice",
3273 UID: "1234",
3274 }
3275 client, err = clientset.NewForConfig(config)
3276 if err != nil {
3277 t.Fatal(err)
3278 }
3279
3280 if _, err := client.CoreV1().Namespaces().Create(
3281 ctx,
3282 &v1.Namespace{
3283 ObjectMeta: metav1.ObjectMeta{
3284 Name: "test-authorization-decision-caching-namespace",
3285 },
3286 },
3287 metav1.CreateOptions{},
3288 ); err != nil {
3289 t.Fatal(err)
3290 }
3291 })
3292 }
3293 }
3294
View as plain text