k8s.io/kubernetes@v1.29.3/test/integration/controlplane/audit/audit_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package 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  // TestAudit ensures that both v1beta1 and v1 version audit api could work.
   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  	// prepare audit policy file
   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  	// prepare audit log file
   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  	// start api server
   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  	// cross-group subResources
   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  		// perform configmap operations
   400  		configMapOperations(t, kubeclient, namespace)
   401  
   402  		// check for corresponding audit logs
   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  		// perform cross-group subresources operations
   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  		// check for corresponding audit logs
   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  		// expect mutation existence annotation
   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  		// expect actual patch annotation
   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  		// expect request object in audit log
   485  		requestObject = true
   486  	}
   487  	if level.GreaterOrEqual(auditinternal.LevelRequestResponse) {
   488  		// expect response obect in audit log
   489  		responseObject = true
   490  	}
   491  	return []utils.AuditEvent{
   492  		{
   493  			// expect CREATE audit event with webhook in effect
   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  			// expect UPDATE audit event with webhook in effect
   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  // configMapOperations is a set of known operations performed on the configmap type
   527  // which correspond to the expected events.
   528  // This is shared by the dynamic test
   529  func configMapOperations(t *testing.T, kubeclient clientset.Interface, namespace string) {
   530  	// create, get, watch, update, patch, list and delete configmap.
   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  	// add admission label to config maps that are to be sent to webhook
   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  		// Block until watchOptions.TimeoutSeconds expires.
   557  		// If the test finishes before watchOptions.TimeoutSeconds expires, the watch audit
   558  		// event at stage ResponseComplete will not be generated.
   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  	// create tokenRequest
   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  	// update scale
   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  	// Attaching Mutation webhook to API server
   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  }