1
16
17 package audit
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "net/http"
24 "os"
25 "strings"
26 "testing"
27 "time"
28
29 "k8s.io/api/admission/v1beta1"
30 admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
31 appsv1 "k8s.io/api/apps/v1"
32 authenticationv1 "k8s.io/api/authentication/v1"
33 autoscalingv1 "k8s.io/api/autoscaling/v1"
34 apiv1 "k8s.io/api/core/v1"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37 "k8s.io/apimachinery/pkg/runtime/schema"
38 "k8s.io/apimachinery/pkg/types"
39 "k8s.io/apimachinery/pkg/util/wait"
40 "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
41 auditinternal "k8s.io/apiserver/pkg/apis/audit"
42 auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
43 clientset "k8s.io/client-go/kubernetes"
44 utiltesting "k8s.io/client-go/util/testing"
45 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
46 "k8s.io/kubernetes/test/integration/framework"
47 "k8s.io/kubernetes/test/utils"
48
49 jsonpatch "github.com/evanphx/json-patch"
50 )
51
52 const (
53 testWebhookConfigurationName = "auditmutation.integration.test"
54 testWebhookName = "auditmutation.integration.test"
55 )
56
57 var (
58 auditPolicyPattern = `
59 apiVersion: {version}
60 kind: Policy
61 rules:
62 - level: RequestResponse
63 namespaces: ["no-webhook-namespace"]
64 resources:
65 - group: "" # core
66 resources: ["configmaps"]
67 - level: Metadata
68 namespaces: ["webhook-audit-metadata"]
69 resources:
70 - group: "" # core
71 resources: ["configmaps"]
72 - level: Request
73 namespaces: ["webhook-audit-request"]
74 resources:
75 - group: "" # core
76 resources: ["configmaps"]
77 - level: RequestResponse
78 namespaces: ["webhook-audit-response"]
79 resources:
80 - group: "" # core
81 resources: ["configmaps"]
82 - level: Request
83 namespaces: ["create-audit-request"]
84 resources:
85 - group: "" # core
86 resources: ["serviceaccounts/token"]
87 - level: RequestResponse
88 namespaces: ["create-audit-response"]
89 resources:
90 - group: "" # core
91 resources: ["serviceaccounts/token"]
92 - level: Request
93 namespaces: ["update-audit-request"]
94 resources:
95 - group: "apps"
96 resources: ["deployments/scale"]
97 - level: RequestResponse
98 namespaces: ["update-audit-response"]
99 resources:
100 - group: "apps"
101 resources: ["deployments/scale"]
102
103 `
104 nonAdmissionWebhookNamespace = "no-webhook-namespace"
105 watchTestTimeout int64 = 1
106 watchOptions = metav1.ListOptions{TimeoutSeconds: &watchTestTimeout}
107 patch, _ = json.Marshal(jsonpatch.Patch{})
108 auditTestUser = "system:apiserver"
109 versions = map[string]schema.GroupVersion{
110 "audit.k8s.io/v1": auditv1.SchemeGroupVersion,
111 }
112
113 expectedEvents = []utils.AuditEvent{
114 {
115 Level: auditinternal.LevelRequestResponse,
116 Stage: auditinternal.StageResponseComplete,
117 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", nonAdmissionWebhookNamespace),
118 Verb: "create",
119 Code: 201,
120 User: auditTestUser,
121 Resource: "configmaps",
122 Namespace: nonAdmissionWebhookNamespace,
123 RequestObject: true,
124 ResponseObject: true,
125 AuthorizeDecision: "allow",
126 }, {
127 Level: auditinternal.LevelRequestResponse,
128 Stage: auditinternal.StageResponseComplete,
129 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", nonAdmissionWebhookNamespace),
130 Verb: "get",
131 Code: 200,
132 User: auditTestUser,
133 Resource: "configmaps",
134 Namespace: nonAdmissionWebhookNamespace,
135 RequestObject: false,
136 ResponseObject: true,
137 AuthorizeDecision: "allow",
138 }, {
139 Level: auditinternal.LevelRequestResponse,
140 Stage: auditinternal.StageResponseComplete,
141 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", nonAdmissionWebhookNamespace),
142 Verb: "list",
143 Code: 200,
144 User: auditTestUser,
145 Resource: "configmaps",
146 Namespace: nonAdmissionWebhookNamespace,
147 RequestObject: false,
148 ResponseObject: true,
149 AuthorizeDecision: "allow",
150 }, {
151 Level: auditinternal.LevelRequestResponse,
152 Stage: auditinternal.StageResponseStarted,
153 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps?timeout=%ds&timeoutSeconds=%d&watch=true", nonAdmissionWebhookNamespace, watchTestTimeout, watchTestTimeout),
154 Verb: "watch",
155 Code: 200,
156 User: auditTestUser,
157 Resource: "configmaps",
158 Namespace: nonAdmissionWebhookNamespace,
159 RequestObject: false,
160 ResponseObject: false,
161 AuthorizeDecision: "allow",
162 }, {
163 Level: auditinternal.LevelRequestResponse,
164 Stage: auditinternal.StageResponseComplete,
165 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps?timeout=%ds&timeoutSeconds=%d&watch=true", nonAdmissionWebhookNamespace, watchTestTimeout, watchTestTimeout),
166 Verb: "watch",
167 Code: 200,
168 User: auditTestUser,
169 Resource: "configmaps",
170 Namespace: nonAdmissionWebhookNamespace,
171 RequestObject: false,
172 ResponseObject: false,
173 AuthorizeDecision: "allow",
174 }, {
175 Level: auditinternal.LevelRequestResponse,
176 Stage: auditinternal.StageResponseComplete,
177 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", nonAdmissionWebhookNamespace),
178 Verb: "update",
179 Code: 200,
180 User: auditTestUser,
181 Resource: "configmaps",
182 Namespace: nonAdmissionWebhookNamespace,
183 RequestObject: true,
184 ResponseObject: true,
185 AuthorizeDecision: "allow",
186 }, {
187 Level: auditinternal.LevelRequestResponse,
188 Stage: auditinternal.StageResponseComplete,
189 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", nonAdmissionWebhookNamespace),
190 Verb: "patch",
191 Code: 200,
192 User: auditTestUser,
193 Resource: "configmaps",
194 Namespace: nonAdmissionWebhookNamespace,
195 RequestObject: true,
196 ResponseObject: true,
197 AuthorizeDecision: "allow",
198 }, {
199 Level: auditinternal.LevelRequestResponse,
200 Stage: auditinternal.StageResponseComplete,
201 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", nonAdmissionWebhookNamespace),
202 Verb: "delete",
203 Code: 200,
204 User: auditTestUser,
205 Resource: "configmaps",
206 Namespace: nonAdmissionWebhookNamespace,
207 RequestObject: true,
208 ResponseObject: true,
209 AuthorizeDecision: "allow",
210 },
211 }
212 )
213
214
215 func TestAudit(t *testing.T) {
216 for version := range versions {
217 runTestWithVersion(t, version)
218 }
219 }
220
221 func runTestWithVersion(t *testing.T, version string) {
222 webhookMux := http.NewServeMux()
223 webhookMux.Handle("/mutation", utils.AdmissionWebhookHandler(t, admitFunc))
224 url, closeFunc, err := utils.NewAdmissionWebhookServer(webhookMux)
225 defer closeFunc()
226 if err != nil {
227 t.Fatalf("%v", err)
228 }
229
230
231 auditPolicy := strings.Replace(auditPolicyPattern, "{version}", version, 1)
232 policyFile, err := os.CreateTemp("", "audit-policy.yaml")
233 if err != nil {
234 t.Fatalf("Failed to create audit policy file: %v", err)
235 }
236 defer os.Remove(policyFile.Name())
237 if _, err := policyFile.Write([]byte(auditPolicy)); err != nil {
238 t.Fatalf("Failed to write audit policy file: %v", err)
239 }
240 if err := policyFile.Close(); err != nil {
241 t.Fatalf("Failed to close audit policy file: %v", err)
242 }
243
244
245 logFile, err := os.CreateTemp("", "audit.log")
246 if err != nil {
247 t.Fatalf("Failed to create audit log file: %v", err)
248 }
249 defer utiltesting.CloseAndRemove(t, logFile)
250
251
252 result := kubeapiservertesting.StartTestServerOrDie(t, nil,
253 []string{
254 "--audit-policy-file", policyFile.Name(),
255 "--audit-log-version", version,
256 "--audit-log-mode", "blocking",
257 "--audit-log-path", logFile.Name()},
258 framework.SharedEtcd())
259 defer result.TearDownFn()
260
261 kubeclient, err := clientset.NewForConfig(result.ClientConfig)
262 if err != nil {
263 t.Fatalf("Unexpected error: %v", err)
264 }
265
266 if err := createMutationWebhook(kubeclient, url+"/mutation"); err != nil {
267 t.Fatal(err)
268 }
269
270 tcs := []struct {
271 auditLevel auditinternal.Level
272 enableMutatingWebhook bool
273 namespace string
274 }{
275 {
276 auditLevel: auditinternal.LevelRequestResponse,
277 enableMutatingWebhook: false,
278 namespace: nonAdmissionWebhookNamespace,
279 },
280 {
281 auditLevel: auditinternal.LevelMetadata,
282 enableMutatingWebhook: true,
283 namespace: "webhook-audit-metadata",
284 },
285 {
286 auditLevel: auditinternal.LevelRequest,
287 enableMutatingWebhook: true,
288 namespace: "webhook-audit-request",
289 },
290 {
291 auditLevel: auditinternal.LevelRequestResponse,
292 enableMutatingWebhook: true,
293 namespace: "webhook-audit-response",
294 },
295 }
296
297 crossGroupTestCases := []struct {
298 auditLevel auditinternal.Level
299 expEvents []utils.AuditEvent
300 namespace string
301 }{
302 {
303 auditLevel: auditinternal.LevelRequest,
304 namespace: "create-audit-request",
305 expEvents: []utils.AuditEvent{
306 {
307 Level: auditinternal.LevelRequest,
308 Stage: auditinternal.StageResponseComplete,
309 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/serviceaccounts/%s/token", "create-audit-request", "audit-serviceaccount"),
310 Verb: "create",
311 Code: 201,
312 User: auditTestUser,
313 Resource: "serviceaccounts",
314 Namespace: "create-audit-request",
315 RequestObject: true,
316 ResponseObject: false,
317 AuthorizeDecision: "allow",
318 },
319 },
320 },
321 {
322 auditLevel: auditinternal.LevelRequestResponse,
323 namespace: "create-audit-response",
324 expEvents: []utils.AuditEvent{
325 {
326 Level: auditinternal.LevelRequestResponse,
327 Stage: auditinternal.StageResponseComplete,
328 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/serviceaccounts/%s/token", "create-audit-response", "audit-serviceaccount"),
329 Verb: "create",
330 Code: 201,
331 User: auditTestUser,
332 Resource: "serviceaccounts",
333 Namespace: "create-audit-response",
334 RequestObject: true,
335 ResponseObject: true,
336 AuthorizeDecision: "allow",
337 },
338 },
339 },
340 {
341 auditLevel: auditinternal.LevelRequest,
342 namespace: "update-audit-request",
343 expEvents: []utils.AuditEvent{
344 {
345 Level: auditinternal.LevelRequest,
346 Stage: auditinternal.StageResponseComplete,
347 RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/%s/scale", "update-audit-request", "audit-deployment"),
348 Verb: "update",
349 Code: 200,
350 User: auditTestUser,
351 Resource: "deployments",
352 Namespace: "update-audit-request",
353 RequestObject: true,
354 ResponseObject: false,
355 AuthorizeDecision: "allow",
356 },
357 },
358 },
359 {
360 auditLevel: auditinternal.LevelRequestResponse,
361 namespace: "update-audit-response",
362 expEvents: []utils.AuditEvent{
363 {
364 Level: auditinternal.LevelRequestResponse,
365 Stage: auditinternal.StageResponseComplete,
366 RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/%s/scale", "update-audit-response", "audit-deployment"),
367 Verb: "update",
368 Code: 200,
369 User: auditTestUser,
370 Resource: "deployments",
371 Namespace: "update-audit-response",
372 RequestObject: true,
373 ResponseObject: true,
374 AuthorizeDecision: "allow",
375 },
376 },
377 },
378 }
379
380 for _, tc := range tcs {
381 t.Run(fmt.Sprintf("%s.%s.%t", version, tc.auditLevel, tc.enableMutatingWebhook), func(t *testing.T) {
382 testAudit(t, version, tc.auditLevel, tc.enableMutatingWebhook, tc.namespace, kubeclient, logFile)
383 })
384 }
385
386
387 for _, tc := range crossGroupTestCases {
388 t.Run(fmt.Sprintf("cross-group-%s.%s.%s", version, tc.auditLevel, tc.namespace), func(t *testing.T) {
389 testAuditCrossGroupSubResource(t, version, tc.expEvents, tc.namespace, kubeclient, logFile)
390 })
391 }
392 }
393
394 func testAudit(t *testing.T, version string, level auditinternal.Level, enableMutatingWebhook bool, namespace string, kubeclient clientset.Interface, logFile *os.File) {
395 var lastMissingReport string
396 createNamespace(t, kubeclient, namespace)
397
398 if err := wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
399
400 configMapOperations(t, kubeclient, namespace)
401
402
403 stream, err := os.Open(logFile.Name())
404 if err != nil {
405 return false, fmt.Errorf("unexpected error: %v", err)
406 }
407 defer stream.Close()
408 missingReport, err := utils.CheckAuditLines(stream, getExpectedEvents(level, enableMutatingWebhook, namespace), versions[version])
409 if err != nil {
410 return false, fmt.Errorf("unexpected error: %v", err)
411 }
412 if len(missingReport.MissingEvents) > 0 {
413 lastMissingReport = missingReport.String()
414 return false, nil
415 }
416 return true, nil
417 }); err != nil {
418 t.Fatalf("failed to get expected events -- missingReport: %s, error: %v", lastMissingReport, err)
419 }
420 }
421
422 func testAuditCrossGroupSubResource(t *testing.T, version string, expEvents []utils.AuditEvent, namespace string, kubeclient clientset.Interface, logFile *os.File) {
423 var (
424 lastMissingReport string
425 sa *apiv1.ServiceAccount
426 deploy *appsv1.Deployment
427 )
428
429 createNamespace(t, kubeclient, namespace)
430 switch expEvents[0].Resource {
431 case "serviceaccounts":
432 sa = createServiceAccount(t, kubeclient, namespace)
433 case "deployments":
434 deploy = createDeployment(t, kubeclient, namespace)
435 default:
436 t.Fatalf("%v resource has no cross-group sub-resources", expEvents[0].Resource)
437 }
438
439 if err := wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
440
441 if sa != nil {
442 tokenRequestOperations(t, kubeclient, sa.Namespace, sa.Name)
443 }
444 if deploy != nil {
445 scaleOperations(t, kubeclient, deploy.Namespace, deploy.Name)
446 }
447
448
449 stream, err := os.Open(logFile.Name())
450 if err != nil {
451 return false, fmt.Errorf("unexpected error: %v", err)
452 }
453 defer stream.Close()
454 missingReport, err := utils.CheckAuditLines(stream, expEvents, versions[version])
455 if err != nil {
456 return false, fmt.Errorf("unexpected error: %v", err)
457 }
458 if len(missingReport.MissingEvents) > 0 {
459 lastMissingReport = missingReport.String()
460 return false, nil
461 }
462 return true, nil
463 }); err != nil {
464 t.Fatalf("failed to get expected events -- missingReport: %s, error: %v", lastMissingReport, err)
465 }
466 }
467
468 func getExpectedEvents(level auditinternal.Level, enableMutatingWebhook bool, namespace string) []utils.AuditEvent {
469 if !enableMutatingWebhook {
470 return expectedEvents
471 }
472
473 var webhookMutationAnnotations, webhookPatchAnnotations map[string]string
474 var requestObject, responseObject bool
475 if level.GreaterOrEqual(auditinternal.LevelMetadata) {
476
477 webhookMutationAnnotations = map[string]string{}
478 webhookMutationAnnotations[mutating.MutationAuditAnnotationPrefix+"round_0_index_0"] = fmt.Sprintf(`{"configuration":"%s","webhook":"%s","mutated":%t}`, testWebhookConfigurationName, testWebhookName, true)
479 }
480 if level.GreaterOrEqual(auditinternal.LevelRequest) {
481
482 webhookPatchAnnotations = map[string]string{}
483 webhookPatchAnnotations[mutating.PatchAuditAnnotationPrefix+"round_0_index_0"] = strings.Replace(fmt.Sprintf(`{"configuration": "%s", "webhook": "%s", "patch": %s, "patchType": "JSONPatch"}`, testWebhookConfigurationName, testWebhookName, `[{"op":"add","path":"/data","value":{"test":"dummy"}}]`), " ", "", -1)
484
485 requestObject = true
486 }
487 if level.GreaterOrEqual(auditinternal.LevelRequestResponse) {
488
489 responseObject = true
490 }
491 return []utils.AuditEvent{
492 {
493
494 Level: level,
495 Stage: auditinternal.StageResponseComplete,
496 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", namespace),
497 Verb: "create",
498 Code: 201,
499 User: auditTestUser,
500 Resource: "configmaps",
501 Namespace: namespace,
502 AuthorizeDecision: "allow",
503 RequestObject: requestObject,
504 ResponseObject: responseObject,
505 AdmissionWebhookMutationAnnotations: webhookMutationAnnotations,
506 AdmissionWebhookPatchAnnotations: webhookPatchAnnotations,
507 }, {
508
509 Level: level,
510 Stage: auditinternal.StageResponseComplete,
511 RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace),
512 Verb: "update",
513 Code: 200,
514 User: auditTestUser,
515 Resource: "configmaps",
516 Namespace: namespace,
517 AuthorizeDecision: "allow",
518 RequestObject: requestObject,
519 ResponseObject: responseObject,
520 AdmissionWebhookMutationAnnotations: webhookMutationAnnotations,
521 AdmissionWebhookPatchAnnotations: webhookPatchAnnotations,
522 },
523 }
524 }
525
526
527
528
529 func configMapOperations(t *testing.T, kubeclient clientset.Interface, namespace string) {
530
531 configMap := &apiv1.ConfigMap{
532 ObjectMeta: metav1.ObjectMeta{
533 Name: "audit-configmap",
534 Namespace: namespace,
535 },
536 Data: map[string]string{
537 "map-key": "map-value",
538 },
539 }
540
541 if namespace != nonAdmissionWebhookNamespace {
542 configMap.Labels = map[string]string{
543 "admission": "true",
544 }
545 }
546
547 _, err := kubeclient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), configMap, metav1.CreateOptions{})
548 expectNoError(t, err, "failed to create audit-configmap")
549
550 _, err = kubeclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMap.Name, metav1.GetOptions{})
551 expectNoError(t, err, "failed to get audit-configmap")
552
553 configMapChan, err := kubeclient.CoreV1().ConfigMaps(namespace).Watch(context.TODO(), watchOptions)
554 expectNoError(t, err, "failed to create watch for config maps")
555 for range configMapChan.ResultChan() {
556
557
558
559 }
560
561 _, err = kubeclient.CoreV1().ConfigMaps(namespace).Update(context.TODO(), configMap, metav1.UpdateOptions{})
562 expectNoError(t, err, "failed to update audit-configmap")
563
564 _, err = kubeclient.CoreV1().ConfigMaps(namespace).Patch(context.TODO(), configMap.Name, types.JSONPatchType, patch, metav1.PatchOptions{})
565 expectNoError(t, err, "failed to patch configmap")
566
567 _, err = kubeclient.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{})
568 expectNoError(t, err, "failed to list config maps")
569
570 err = kubeclient.CoreV1().ConfigMaps(namespace).Delete(context.TODO(), configMap.Name, metav1.DeleteOptions{})
571 expectNoError(t, err, "failed to delete audit-configmap")
572 }
573
574 func tokenRequestOperations(t *testing.T, kubeClient clientset.Interface, namespace, name string) {
575 var (
576 treq = &authenticationv1.TokenRequest{
577 Spec: authenticationv1.TokenRequestSpec{
578 Audiences: []string{"api"},
579 },
580 }
581 )
582
583 _, err := kubeClient.CoreV1().ServiceAccounts(namespace).CreateToken(context.TODO(), name, treq, metav1.CreateOptions{})
584 expectNoError(t, err, "failed to create audit-tokenRequest")
585 }
586
587 func scaleOperations(t *testing.T, kubeClient clientset.Interface, namespace, name string) {
588 var (
589 scale = &autoscalingv1.Scale{
590 ObjectMeta: metav1.ObjectMeta{
591 Name: "audit-deployment",
592 Namespace: namespace,
593 },
594 Spec: autoscalingv1.ScaleSpec{
595 Replicas: 2,
596 },
597 }
598 )
599
600
601 _, err := kubeClient.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), name, scale, metav1.UpdateOptions{})
602 expectNoError(t, err, fmt.Sprintf("failed to update scale %v", scale))
603 }
604
605 func expectNoError(t *testing.T, err error, msg string) {
606 if err != nil {
607 t.Fatalf("%s: %v", msg, err)
608 }
609 }
610
611 func admitFunc(review *v1beta1.AdmissionReview) error {
612 gvk := schema.GroupVersionKind{Group: "admission.k8s.io", Version: "v1beta1", Kind: "AdmissionReview"}
613 if review.GetObjectKind().GroupVersionKind() != gvk {
614 return fmt.Errorf("invalid admission review kind: %#v", review.GetObjectKind().GroupVersionKind())
615 }
616 if len(review.Request.Object.Raw) > 0 {
617 u := &unstructured.Unstructured{Object: map[string]interface{}{}}
618 if err := json.Unmarshal(review.Request.Object.Raw, u); err != nil {
619 return fmt.Errorf("failed to deserialize object: %s with error: %v", string(review.Request.Object.Raw), err)
620 }
621 review.Request.Object.Object = u
622 }
623 if len(review.Request.OldObject.Raw) > 0 {
624 u := &unstructured.Unstructured{Object: map[string]interface{}{}}
625 if err := json.Unmarshal(review.Request.OldObject.Raw, u); err != nil {
626 return fmt.Errorf("failed to deserialize object: %s with error: %v", string(review.Request.OldObject.Raw), err)
627 }
628 review.Request.OldObject.Object = u
629 }
630
631 review.Response = &v1beta1.AdmissionResponse{
632 Allowed: true,
633 UID: review.Request.UID,
634 Result: &metav1.Status{Message: "admitted"},
635 }
636 review.Response.Patch = []byte(`[{"op":"add","path":"/data","value":{"test":"dummy"}}]`)
637 jsonPatch := v1beta1.PatchTypeJSONPatch
638 review.Response.PatchType = &jsonPatch
639 return nil
640 }
641
642 func createMutationWebhook(client clientset.Interface, endpoint string) error {
643 fail := admissionregistrationv1.Fail
644 noSideEffects := admissionregistrationv1.SideEffectClassNone
645
646 _, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), &admissionregistrationv1.MutatingWebhookConfiguration{
647 ObjectMeta: metav1.ObjectMeta{Name: testWebhookConfigurationName},
648 Webhooks: []admissionregistrationv1.MutatingWebhook{{
649 Name: testWebhookName,
650 ClientConfig: admissionregistrationv1.WebhookClientConfig{
651 URL: &endpoint,
652 CABundle: utils.LocalhostCert,
653 },
654 Rules: []admissionregistrationv1.RuleWithOperations{{
655 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
656 Rule: admissionregistrationv1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
657 }},
658 ObjectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"admission": "true"}},
659 FailurePolicy: &fail,
660 AdmissionReviewVersions: []string{"v1beta1"},
661 SideEffects: &noSideEffects,
662 }},
663 }, metav1.CreateOptions{})
664 return err
665 }
666
667 func createNamespace(t *testing.T, kubeclient clientset.Interface, namespace string) {
668 ns := &apiv1.Namespace{
669 ObjectMeta: metav1.ObjectMeta{
670 Name: namespace,
671 },
672 }
673 _, err := kubeclient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
674 expectNoError(t, err, fmt.Sprintf("failed to create namespace ns %s", namespace))
675 }
676
677 func createServiceAccount(t *testing.T, cs clientset.Interface, namespace string) *apiv1.ServiceAccount {
678 sa := &apiv1.ServiceAccount{
679 ObjectMeta: metav1.ObjectMeta{
680 Name: "audit-serviceaccount",
681 Namespace: namespace,
682 },
683 }
684 _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).Create(context.TODO(), sa, metav1.CreateOptions{})
685 expectNoError(t, err, fmt.Sprintf("failed to create serviceaccount %v", sa))
686 return sa
687 }
688
689 func createDeployment(t *testing.T, cs clientset.Interface, namespace string) *appsv1.Deployment {
690 deploy := &appsv1.Deployment{
691 ObjectMeta: metav1.ObjectMeta{
692 Name: "audit-deployment",
693 Namespace: namespace,
694 },
695 Spec: appsv1.DeploymentSpec{
696 Selector: &metav1.LabelSelector{
697 MatchLabels: map[string]string{"app": "test"},
698 },
699 Template: apiv1.PodTemplateSpec{
700 Spec: apiv1.PodSpec{
701 Containers: []apiv1.Container{
702 {
703 Name: "foo",
704 Image: "foo/bar",
705 },
706 },
707 },
708 ObjectMeta: metav1.ObjectMeta{
709 Name: "audit-deployment-scale",
710 Namespace: namespace,
711 Labels: map[string]string{"app": "test"},
712 },
713 },
714 },
715 }
716 _, err := cs.AppsV1().Deployments(deploy.Namespace).Create(context.TODO(), deploy, metav1.CreateOptions{})
717 expectNoError(t, err, fmt.Sprintf("failed to create deployment %v", deploy))
718 return deploy
719 }
720
View as plain text