k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/apiserver/admissionwebhook/match_conditions_test.go (about)

     1  /*
     2  Copyright 2023 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 admissionwebhook
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"encoding/json"
    24  	"io"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"testing"
    31  	"time"
    32  
    33  	admissionv1 "k8s.io/api/admission/v1"
    34  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    35  	corev1 "k8s.io/api/core/v1"
    36  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	"k8s.io/apimachinery/pkg/util/wait"
    40  	genericfeatures "k8s.io/apiserver/pkg/features"
    41  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    42  	clientset "k8s.io/client-go/kubernetes"
    43  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    44  	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    45  	"k8s.io/kubernetes/test/integration/framework"
    46  )
    47  
    48  type admissionRecorder struct {
    49  	mu       sync.Mutex
    50  	upCh     chan struct{}
    51  	upOnce   sync.Once
    52  	requests []*admissionv1.AdmissionRequest
    53  }
    54  
    55  func (r *admissionRecorder) Record(req *admissionv1.AdmissionRequest) {
    56  	r.mu.Lock()
    57  	defer r.mu.Unlock()
    58  	r.requests = append(r.requests, req)
    59  }
    60  
    61  func (r *admissionRecorder) MarkerReceived() {
    62  	r.mu.Lock()
    63  	defer r.mu.Unlock()
    64  	r.upOnce.Do(func() {
    65  		close(r.upCh)
    66  	})
    67  }
    68  
    69  func (r *admissionRecorder) Reset() chan struct{} {
    70  	r.mu.Lock()
    71  	defer r.mu.Unlock()
    72  	r.requests = []*admissionv1.AdmissionRequest{}
    73  	r.upCh = make(chan struct{})
    74  	r.upOnce = sync.Once{}
    75  	return r.upCh
    76  }
    77  
    78  func newMatchConditionHandler(recorder *admissionRecorder) http.Handler {
    79  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    80  		defer r.Body.Close()
    81  		data, err := io.ReadAll(r.Body)
    82  		if err != nil {
    83  			http.Error(w, err.Error(), 400)
    84  		}
    85  		review := admissionv1.AdmissionReview{}
    86  		if err := json.Unmarshal(data, &review); err != nil {
    87  			http.Error(w, err.Error(), 400)
    88  		}
    89  
    90  		review.Response = &admissionv1.AdmissionResponse{
    91  			Allowed: true,
    92  			UID:     review.Request.UID,
    93  			Result:  &metav1.Status{Message: "admitted"},
    94  		}
    95  
    96  		w.Header().Set("Content-Type", "application/json")
    97  		if err := json.NewEncoder(w).Encode(review); err != nil {
    98  			http.Error(w, err.Error(), 400)
    99  			return
   100  		}
   101  
   102  		switch r.URL.Path {
   103  		case "/marker":
   104  			recorder.MarkerReceived()
   105  			return
   106  		}
   107  
   108  		recorder.Record(review.Request)
   109  	})
   110  }
   111  
   112  // TestMatchConditions tests ValidatingWebhookConfigurations and MutatingWebhookConfigurations that validates different cases of matchCondition fields
   113  func TestMatchConditions(t *testing.T) {
   114  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.StrictCostEnforcementForWebhooks, false)
   115  	fail := admissionregistrationv1.Fail
   116  	ignore := admissionregistrationv1.Ignore
   117  
   118  	testcases := []struct {
   119  		name            string
   120  		matchConditions []admissionregistrationv1.MatchCondition
   121  		pods            []*corev1.Pod
   122  		matchedPods     []*corev1.Pod
   123  		expectErrorPod  bool
   124  		failPolicy      *admissionregistrationv1.FailurePolicyType
   125  		errMessage      string
   126  	}{
   127  		{
   128  			name: "pods in namespace kube-system is ignored",
   129  			matchConditions: []admissionregistrationv1.MatchCondition{
   130  				{
   131  					Name:       "pods-in-kube-system-exempt.kubernetes.io",
   132  					Expression: "object.metadata.namespace != 'kube-system'",
   133  				},
   134  			},
   135  			pods: []*corev1.Pod{
   136  				matchConditionsTestPod("test1", "kube-system"),
   137  				matchConditionsTestPod("test2", "default"),
   138  			},
   139  			matchedPods: []*corev1.Pod{
   140  				matchConditionsTestPod("test2", "default"),
   141  			},
   142  		},
   143  		{
   144  			name: "matchConditions are ANDed together",
   145  			matchConditions: []admissionregistrationv1.MatchCondition{
   146  				{
   147  					Name:       "pods-in-kube-system-exempt.kubernetes.io",
   148  					Expression: "object.metadata.namespace != 'kube-system'",
   149  				},
   150  				{
   151  					Name:       "pods-with-name-test1.kubernetes.io",
   152  					Expression: "object.metadata.name == 'test1'",
   153  				},
   154  			},
   155  			pods: []*corev1.Pod{
   156  				matchConditionsTestPod("test1", "kube-system"),
   157  				matchConditionsTestPod("test1", "default"),
   158  				matchConditionsTestPod("test2", "default"),
   159  			},
   160  			matchedPods: []*corev1.Pod{
   161  				matchConditionsTestPod("test1", "default"),
   162  			},
   163  		},
   164  		{
   165  			name: "mix of true, error and false should not match and not call webhook",
   166  			matchConditions: []admissionregistrationv1.MatchCondition{
   167  				{
   168  					Name:       "test1",
   169  					Expression: "object.nonExistentProperty == 'someval'",
   170  				},
   171  				{
   172  					Name:       "test2",
   173  					Expression: "true",
   174  				},
   175  				{
   176  					Name:       "test3",
   177  					Expression: "false",
   178  				},
   179  				{
   180  					Name:       "test4",
   181  					Expression: "true",
   182  				},
   183  				{
   184  					Name:       "test5",
   185  					Expression: "object.nonExistentProperty == 'someval'",
   186  				},
   187  			},
   188  			pods: []*corev1.Pod{
   189  				matchConditionsTestPod("test1", "kube-system"),
   190  				matchConditionsTestPod("test2", "default"),
   191  			},
   192  			matchedPods:    []*corev1.Pod{},
   193  			expectErrorPod: false,
   194  		},
   195  		{
   196  			name: "mix of true and error should reject request without fail policy",
   197  			matchConditions: []admissionregistrationv1.MatchCondition{
   198  				{
   199  					Name:       "test1",
   200  					Expression: "object.nonExistentProperty == 'someval'",
   201  				},
   202  				{
   203  					Name:       "test2",
   204  					Expression: "true",
   205  				},
   206  				{
   207  					Name:       "test4",
   208  					Expression: "true",
   209  				},
   210  				{
   211  					Name:       "test5",
   212  					Expression: "object.nonExistentProperty == 'someval'",
   213  				},
   214  			},
   215  			pods: []*corev1.Pod{
   216  				matchConditionsTestPod("test1", "kube-system"),
   217  				matchConditionsTestPod("test2", "default"),
   218  			},
   219  			matchedPods:    []*corev1.Pod{},
   220  			expectErrorPod: true,
   221  		},
   222  		{
   223  			name: "mix of true and error should reject request with fail policy fail",
   224  			matchConditions: []admissionregistrationv1.MatchCondition{
   225  				{
   226  					Name:       "test1",
   227  					Expression: "object.nonExistentProperty == 'someval'",
   228  				},
   229  				{
   230  					Name:       "test2",
   231  					Expression: "true",
   232  				},
   233  				{
   234  					Name:       "test4",
   235  					Expression: "true",
   236  				},
   237  				{
   238  					Name:       "test5",
   239  					Expression: "object.nonExistentProperty == 'someval'",
   240  				},
   241  			},
   242  			pods: []*corev1.Pod{
   243  				matchConditionsTestPod("test1", "kube-system"),
   244  				matchConditionsTestPod("test2", "default"),
   245  			},
   246  			matchedPods:    []*corev1.Pod{},
   247  			failPolicy:     &fail,
   248  			expectErrorPod: true,
   249  		},
   250  		{
   251  			name: "mix of true and error should match request and call webhook with fail policy ignore",
   252  			matchConditions: []admissionregistrationv1.MatchCondition{
   253  				{
   254  					Name:       "tes1",
   255  					Expression: "object.nonExistentProperty == 'someval'",
   256  				},
   257  				{
   258  					Name:       "test2",
   259  					Expression: "true",
   260  				},
   261  				{
   262  					Name:       "test4",
   263  					Expression: "true",
   264  				},
   265  				{
   266  					Name:       "test5",
   267  					Expression: "object.nonExistentProperty == 'someval'",
   268  				},
   269  			},
   270  			pods: []*corev1.Pod{
   271  				matchConditionsTestPod("test1", "kube-system"),
   272  				matchConditionsTestPod("test2", "default"),
   273  			},
   274  			matchedPods: []*corev1.Pod{},
   275  			failPolicy:  &ignore,
   276  		},
   277  		{
   278  			name: "has access to oldObject",
   279  			matchConditions: []admissionregistrationv1.MatchCondition{
   280  				{
   281  					Name:       "old-object-is-null.kubernetes.io",
   282  					Expression: "oldObject == null",
   283  				},
   284  			},
   285  			pods: []*corev1.Pod{
   286  				matchConditionsTestPod("test2", "default"),
   287  			},
   288  			matchedPods: []*corev1.Pod{
   289  				matchConditionsTestPod("test2", "default"),
   290  			},
   291  		},
   292  		{
   293  			name: "without strict cost enforcement: Authz check does not exceed per call limit",
   294  			matchConditions: []admissionregistrationv1.MatchCondition{
   295  				{
   296  					Name:       "test1",
   297  					Expression: "authorizer.group('').resource('pods').name('test1').check('create').allowed() && authorizer.group('').resource('pods').name('test1').check('create').allowed() && authorizer.group('').resource('pods').name('test1').check('create').allowed()",
   298  				},
   299  			},
   300  			pods: []*corev1.Pod{
   301  				matchConditionsTestPod("test1", "kube-system"),
   302  			},
   303  			matchedPods: []*corev1.Pod{
   304  				matchConditionsTestPod("test1", "kube-system"),
   305  			},
   306  			failPolicy:     &fail,
   307  			expectErrorPod: false,
   308  		},
   309  		{
   310  			name:            "without strict cost enforcement: Authz check does not exceed overall cost limit",
   311  			matchConditions: generateMatchConditionsWithAuthzCheck(8, "authorizer.group('').resource('pods').name('test1').check('create').allowed() && authorizer.group('').resource('pods').name('test1').check('create').allowed()"),
   312  			pods: []*corev1.Pod{
   313  				matchConditionsTestPod("test1", "kube-system"),
   314  			},
   315  			matchedPods: []*corev1.Pod{
   316  				matchConditionsTestPod("test1", "kube-system"),
   317  			},
   318  			failPolicy:     &fail,
   319  			expectErrorPod: false,
   320  		},
   321  	}
   322  
   323  	roots := x509.NewCertPool()
   324  	if !roots.AppendCertsFromPEM(localhostCert) {
   325  		t.Fatal("Failed to append Cert from PEM")
   326  	}
   327  	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
   328  	if err != nil {
   329  		t.Fatalf("Failed to build cert with error: %+v", err)
   330  	}
   331  
   332  	recorder := &admissionRecorder{requests: []*admissionv1.AdmissionRequest{}}
   333  
   334  	webhookServer := httptest.NewUnstartedServer(newMatchConditionHandler(recorder))
   335  	webhookServer.TLS = &tls.Config{
   336  		RootCAs:      roots,
   337  		Certificates: []tls.Certificate{cert},
   338  	}
   339  	webhookServer.StartTLS()
   340  	defer webhookServer.Close()
   341  
   342  	dryRunCreate := metav1.CreateOptions{
   343  		DryRun: []string{metav1.DryRunAll},
   344  	}
   345  
   346  	for _, testcase := range testcases {
   347  		t.Run(testcase.name, func(t *testing.T) {
   348  			upCh := recorder.Reset()
   349  
   350  			server, err := apiservertesting.StartTestServer(t, nil, []string{
   351  				"--disable-admission-plugins=ServiceAccount",
   352  			}, framework.SharedEtcd())
   353  			if err != nil {
   354  				t.Fatal(err)
   355  			}
   356  			defer server.TearDownFn()
   357  
   358  			config := server.ClientConfig
   359  
   360  			client, err := clientset.NewForConfig(config)
   361  			if err != nil {
   362  				t.Fatal(err)
   363  			}
   364  
   365  			// Write markers to a separate namespace to avoid cross-talk
   366  			markerNs := "marker"
   367  			_, err = client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: markerNs}}, metav1.CreateOptions{})
   368  			if err != nil {
   369  				t.Fatal(err)
   370  			}
   371  
   372  			// Create a marker object to use to check for the webhook configurations to be ready.
   373  			marker, err := client.CoreV1().Pods(markerNs).Create(context.TODO(), newMarkerPod(markerNs), metav1.CreateOptions{})
   374  			if err != nil {
   375  				t.Fatal(err)
   376  			}
   377  
   378  			endpoint := webhookServer.URL
   379  			markerEndpoint := webhookServer.URL + "/marker"
   380  			validatingwebhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
   381  				ObjectMeta: metav1.ObjectMeta{
   382  					Name: "admission.integration.test",
   383  				},
   384  				Webhooks: []admissionregistrationv1.ValidatingWebhook{
   385  					{
   386  						Name: "admission.integration.test",
   387  						Rules: []admissionregistrationv1.RuleWithOperations{{
   388  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
   389  							Rule: admissionregistrationv1.Rule{
   390  								APIGroups:   []string{""},
   391  								APIVersions: []string{"v1"},
   392  								Resources:   []string{"pods"},
   393  							},
   394  						}},
   395  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   396  							URL:      &endpoint,
   397  							CABundle: localhostCert,
   398  						},
   399  						// ignore pods in the marker namespace
   400  						NamespaceSelector: &metav1.LabelSelector{
   401  							MatchExpressions: []metav1.LabelSelectorRequirement{
   402  								{
   403  									Key:      corev1.LabelMetadataName,
   404  									Operator: metav1.LabelSelectorOpNotIn,
   405  									Values:   []string{"marker"},
   406  								},
   407  							}},
   408  						FailurePolicy:           testcase.failPolicy,
   409  						SideEffects:             &noSideEffects,
   410  						AdmissionReviewVersions: []string{"v1"},
   411  						MatchConditions:         testcase.matchConditions,
   412  					},
   413  					{
   414  						Name: "admission.integration.test.marker",
   415  						Rules: []admissionregistrationv1.RuleWithOperations{{
   416  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
   417  							Rule:       admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
   418  						}},
   419  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   420  							URL:      &markerEndpoint,
   421  							CABundle: localhostCert,
   422  						},
   423  						NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
   424  							corev1.LabelMetadataName: "marker",
   425  						}},
   426  						ObjectSelector:          &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
   427  						FailurePolicy:           testcase.failPolicy,
   428  						SideEffects:             &noSideEffects,
   429  						AdmissionReviewVersions: []string{"v1"},
   430  					},
   431  				},
   432  			}
   433  
   434  			validatingcfg, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), validatingwebhook, metav1.CreateOptions{})
   435  			if err != nil {
   436  				t.Fatal(err)
   437  			}
   438  
   439  			vhwHasBeenCleanedUp := false
   440  			defer func() {
   441  				if !vhwHasBeenCleanedUp {
   442  					err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
   443  					if err != nil {
   444  						t.Fatal(err)
   445  					}
   446  				}
   447  			}()
   448  
   449  			// wait until new webhook is called the first time
   450  			if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
   451  				_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
   452  				select {
   453  				case <-upCh:
   454  					return true, nil
   455  				default:
   456  					t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
   457  					return false, nil
   458  				}
   459  			}); err != nil {
   460  				t.Fatal(err)
   461  			}
   462  
   463  			for _, pod := range testcase.pods {
   464  				_, err := client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, dryRunCreate)
   465  				if !testcase.expectErrorPod && err != nil {
   466  					t.Fatalf("unexpected error creating test pod: %v", err)
   467  				} else if testcase.expectErrorPod && err == nil {
   468  					t.Fatal("expected error creating pods")
   469  				} else if testcase.expectErrorPod && err != nil && !strings.Contains(err.Error(), testcase.errMessage) {
   470  					t.Fatalf("expected error message includes: %v, but get %v", testcase.errMessage, err)
   471  				}
   472  			}
   473  
   474  			if len(recorder.requests) != len(testcase.matchedPods) {
   475  				t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
   476  			}
   477  
   478  			for i, request := range recorder.requests {
   479  				if request.Name != testcase.matchedPods[i].Name {
   480  					t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
   481  				}
   482  				if request.Namespace != testcase.matchedPods[i].Namespace {
   483  					t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
   484  				}
   485  			}
   486  
   487  			// Reset and rerun against mutating webhook configuration
   488  			// TODO: private helper function for validation after creating vwh or mwh
   489  			upCh = recorder.Reset()
   490  			err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
   491  			if err != nil {
   492  				t.Fatal(err)
   493  			} else {
   494  				vhwHasBeenCleanedUp = true
   495  			}
   496  
   497  			mutatingwebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
   498  				ObjectMeta: metav1.ObjectMeta{
   499  					Name: "admission.integration.test",
   500  				},
   501  				Webhooks: []admissionregistrationv1.MutatingWebhook{
   502  					{
   503  						Name: "admission.integration.test",
   504  						Rules: []admissionregistrationv1.RuleWithOperations{{
   505  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
   506  							Rule: admissionregistrationv1.Rule{
   507  								APIGroups:   []string{""},
   508  								APIVersions: []string{"v1"},
   509  								Resources:   []string{"pods"},
   510  							},
   511  						}},
   512  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   513  							URL:      &endpoint,
   514  							CABundle: localhostCert,
   515  						},
   516  						// ignore pods in the marker namespace
   517  						NamespaceSelector: &metav1.LabelSelector{
   518  							MatchExpressions: []metav1.LabelSelectorRequirement{
   519  								{
   520  									Key:      corev1.LabelMetadataName,
   521  									Operator: metav1.LabelSelectorOpNotIn,
   522  									Values:   []string{"marker"},
   523  								},
   524  							}},
   525  						FailurePolicy:           testcase.failPolicy,
   526  						SideEffects:             &noSideEffects,
   527  						AdmissionReviewVersions: []string{"v1"},
   528  						MatchConditions:         testcase.matchConditions,
   529  					},
   530  					{
   531  						Name: "admission.integration.test.marker",
   532  						Rules: []admissionregistrationv1.RuleWithOperations{{
   533  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
   534  							Rule:       admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
   535  						}},
   536  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   537  							URL:      &markerEndpoint,
   538  							CABundle: localhostCert,
   539  						},
   540  						NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
   541  							corev1.LabelMetadataName: "marker",
   542  						}},
   543  						ObjectSelector:          &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
   544  						FailurePolicy:           testcase.failPolicy,
   545  						SideEffects:             &noSideEffects,
   546  						AdmissionReviewVersions: []string{"v1"},
   547  					},
   548  				},
   549  			}
   550  
   551  			mutatingcfg, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingwebhook, metav1.CreateOptions{})
   552  			if err != nil {
   553  				t.Fatal(err)
   554  			}
   555  			defer func() {
   556  				err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(context.TODO(), mutatingcfg.GetName(), metav1.DeleteOptions{})
   557  				if err != nil {
   558  					t.Fatal(err)
   559  				}
   560  			}()
   561  
   562  			// wait until new webhook is called the first time
   563  			if err := wait.PollUntilContextTimeout(context.Background(), time.Millisecond*5, wait.ForeverTestTimeout, true, func(_ context.Context) (bool, error) {
   564  				_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
   565  				select {
   566  				case <-upCh:
   567  					return true, nil
   568  				default:
   569  					t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
   570  					return false, nil
   571  				}
   572  			}); err != nil {
   573  				t.Fatal(err)
   574  			}
   575  
   576  			for _, pod := range testcase.pods {
   577  				_, err = client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, dryRunCreate)
   578  				if testcase.expectErrorPod == false && err != nil {
   579  					t.Fatalf("unexpected error creating test pod: %v", err)
   580  				} else if testcase.expectErrorPod == true && err == nil {
   581  					t.Fatal("expected error creating pods")
   582  				}
   583  			}
   584  
   585  			if len(recorder.requests) != len(testcase.matchedPods) {
   586  				t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
   587  			}
   588  
   589  			for i, request := range recorder.requests {
   590  				if request.Name != testcase.matchedPods[i].Name {
   591  					t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
   592  				}
   593  				if request.Namespace != testcase.matchedPods[i].Namespace {
   594  					t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
   595  				}
   596  			}
   597  		})
   598  	}
   599  }
   600  
   601  func TestMatchConditionsWithStrictCostEnforcement(t *testing.T) {
   602  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.StrictCostEnforcementForWebhooks, true)
   603  
   604  	testcases := []struct {
   605  		name            string
   606  		matchConditions []admissionregistrationv1.MatchCondition
   607  		pods            []*corev1.Pod
   608  		matchedPods     []*corev1.Pod
   609  		expectErrorPod  bool
   610  		failPolicy      *admissionregistrationv1.FailurePolicyType
   611  		errMessage      string
   612  	}{
   613  		{
   614  			name: "with strict cost enforcement: exceed per call limit should reject request with fail policy fail",
   615  			matchConditions: []admissionregistrationv1.MatchCondition{
   616  				{
   617  					Name:       "test1",
   618  					Expression: "authorizer.group('').resource('pods').namespace('default').check('create').allowed() && authorizer.group('').resource('pods').namespace('default').check('create').allowed() && authorizer.group('').resource('pods').namespace('default').check('create').allowed()",
   619  				},
   620  			},
   621  			pods: []*corev1.Pod{
   622  				matchConditionsTestPod("test1", "default"),
   623  			},
   624  			matchedPods:    []*corev1.Pod{},
   625  			expectErrorPod: true,
   626  			errMessage:     "operation cancelled: actual cost limit exceeded",
   627  		},
   628  		{
   629  			name:            "with strict cost enforcement: exceed overall cost limit should reject request with fail policy fail",
   630  			matchConditions: generateMatchConditionsWithAuthzCheck(8, "authorizer.group('').resource('pods').name('test1').check('create').allowed() && authorizer.group('').resource('pods').name('test1').check('create').allowed()"),
   631  			pods: []*corev1.Pod{
   632  				matchConditionsTestPod("test1", "kube-system"),
   633  			},
   634  			matchedPods:    []*corev1.Pod{},
   635  			expectErrorPod: true,
   636  			errMessage:     "validation failed due to running out of cost budget, no further validation rules will be run",
   637  		},
   638  	}
   639  
   640  	roots := x509.NewCertPool()
   641  	if !roots.AppendCertsFromPEM(localhostCert) {
   642  		t.Fatal("Failed to append Cert from PEM")
   643  	}
   644  	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
   645  	if err != nil {
   646  		t.Fatalf("Failed to build cert with error: %+v", err)
   647  	}
   648  
   649  	recorder := &admissionRecorder{requests: []*admissionv1.AdmissionRequest{}}
   650  
   651  	webhookServer := httptest.NewUnstartedServer(newMatchConditionHandler(recorder))
   652  	webhookServer.TLS = &tls.Config{
   653  		RootCAs:      roots,
   654  		Certificates: []tls.Certificate{cert},
   655  	}
   656  	webhookServer.StartTLS()
   657  	defer webhookServer.Close()
   658  
   659  	dryRunCreate := metav1.CreateOptions{
   660  		DryRun: []string{metav1.DryRunAll},
   661  	}
   662  
   663  	for _, testcase := range testcases {
   664  		t.Run(testcase.name, func(t *testing.T) {
   665  			upCh := recorder.Reset()
   666  			server, err := apiservertesting.StartTestServer(t, nil, []string{
   667  				"--disable-admission-plugins=ServiceAccount",
   668  			}, framework.SharedEtcd())
   669  			if err != nil {
   670  				t.Fatal(err)
   671  			}
   672  			defer server.TearDownFn()
   673  
   674  			config := server.ClientConfig
   675  
   676  			client, err := clientset.NewForConfig(config)
   677  			if err != nil {
   678  				t.Fatal(err)
   679  			}
   680  
   681  			// Write markers to a separate namespace to avoid cross-talk
   682  			markerNs := "marker"
   683  			_, err = client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: markerNs}}, metav1.CreateOptions{})
   684  			if err != nil {
   685  				t.Fatal(err)
   686  			}
   687  
   688  			// Create a marker object to use to check for the webhook configurations to be ready.
   689  			marker, err := client.CoreV1().Pods(markerNs).Create(context.TODO(), newMarkerPod(markerNs), metav1.CreateOptions{})
   690  			if err != nil {
   691  				t.Fatal(err)
   692  			}
   693  
   694  			endpoint := webhookServer.URL
   695  			markerEndpoint := webhookServer.URL + "/marker"
   696  			validatingwebhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
   697  				ObjectMeta: metav1.ObjectMeta{
   698  					Name: "admission.integration.test",
   699  				},
   700  				Webhooks: []admissionregistrationv1.ValidatingWebhook{
   701  					{
   702  						Name: "admission.integration.test",
   703  						Rules: []admissionregistrationv1.RuleWithOperations{{
   704  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
   705  							Rule: admissionregistrationv1.Rule{
   706  								APIGroups:   []string{""},
   707  								APIVersions: []string{"v1"},
   708  								Resources:   []string{"pods"},
   709  							},
   710  						}},
   711  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   712  							URL:      &endpoint,
   713  							CABundle: localhostCert,
   714  						},
   715  						// ignore pods in the marker namespace
   716  						NamespaceSelector: &metav1.LabelSelector{
   717  							MatchExpressions: []metav1.LabelSelectorRequirement{
   718  								{
   719  									Key:      corev1.LabelMetadataName,
   720  									Operator: metav1.LabelSelectorOpNotIn,
   721  									Values:   []string{"marker"},
   722  								},
   723  							}},
   724  						FailurePolicy:           testcase.failPolicy,
   725  						SideEffects:             &noSideEffects,
   726  						AdmissionReviewVersions: []string{"v1"},
   727  						MatchConditions:         testcase.matchConditions,
   728  					},
   729  					{
   730  						Name: "admission.integration.test.marker",
   731  						Rules: []admissionregistrationv1.RuleWithOperations{{
   732  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
   733  							Rule:       admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
   734  						}},
   735  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   736  							URL:      &markerEndpoint,
   737  							CABundle: localhostCert,
   738  						},
   739  						NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
   740  							corev1.LabelMetadataName: "marker",
   741  						}},
   742  						ObjectSelector:          &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
   743  						FailurePolicy:           testcase.failPolicy,
   744  						SideEffects:             &noSideEffects,
   745  						AdmissionReviewVersions: []string{"v1"},
   746  					},
   747  				},
   748  			}
   749  
   750  			validatingcfg, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), validatingwebhook, metav1.CreateOptions{})
   751  			if err != nil {
   752  				t.Fatal(err)
   753  			}
   754  
   755  			vhwHasBeenCleanedUp := false
   756  			defer func() {
   757  				if !vhwHasBeenCleanedUp {
   758  					err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
   759  					if err != nil {
   760  						t.Fatal(err)
   761  					}
   762  				}
   763  			}()
   764  
   765  			// wait until new webhook is called the first time
   766  			if err := wait.PollUntilContextTimeout(context.Background(), time.Millisecond*5, wait.ForeverTestTimeout, true, func(_ context.Context) (bool, error) {
   767  				_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
   768  				select {
   769  				case <-upCh:
   770  					return true, nil
   771  				default:
   772  					t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
   773  					return false, nil
   774  				}
   775  			}); err != nil {
   776  				t.Fatal(err)
   777  			}
   778  
   779  			for _, pod := range testcase.pods {
   780  				_, err := client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, dryRunCreate)
   781  				if !testcase.expectErrorPod && err != nil {
   782  					t.Fatalf("unexpected error creating test pod: %v", err)
   783  				} else if testcase.expectErrorPod && err == nil {
   784  					t.Fatal("expected error creating pods")
   785  				} else if testcase.expectErrorPod && err != nil && !strings.Contains(err.Error(), testcase.errMessage) {
   786  					t.Fatalf("expected error message includes: %v, but get %v", testcase.errMessage, err)
   787  				}
   788  			}
   789  
   790  			if len(recorder.requests) != len(testcase.matchedPods) {
   791  				t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
   792  			}
   793  
   794  			for i, request := range recorder.requests {
   795  				if request.Name != testcase.matchedPods[i].Name {
   796  					t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
   797  				}
   798  				if request.Namespace != testcase.matchedPods[i].Namespace {
   799  					t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
   800  				}
   801  			}
   802  
   803  			// Reset and rerun against mutating webhook configuration
   804  			// TODO: private helper function for validation after creating vwh or mwh
   805  			upCh = recorder.Reset()
   806  			err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
   807  			if err != nil {
   808  				t.Fatal(err)
   809  			} else {
   810  				vhwHasBeenCleanedUp = true
   811  			}
   812  
   813  			mutatingwebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
   814  				ObjectMeta: metav1.ObjectMeta{
   815  					Name: "admission.integration.test",
   816  				},
   817  				Webhooks: []admissionregistrationv1.MutatingWebhook{
   818  					{
   819  						Name: "admission.integration.test",
   820  						Rules: []admissionregistrationv1.RuleWithOperations{{
   821  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
   822  							Rule: admissionregistrationv1.Rule{
   823  								APIGroups:   []string{""},
   824  								APIVersions: []string{"v1"},
   825  								Resources:   []string{"pods"},
   826  							},
   827  						}},
   828  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   829  							URL:      &endpoint,
   830  							CABundle: localhostCert,
   831  						},
   832  						// ignore pods in the marker namespace
   833  						NamespaceSelector: &metav1.LabelSelector{
   834  							MatchExpressions: []metav1.LabelSelectorRequirement{
   835  								{
   836  									Key:      corev1.LabelMetadataName,
   837  									Operator: metav1.LabelSelectorOpNotIn,
   838  									Values:   []string{"marker"},
   839  								},
   840  							}},
   841  						FailurePolicy:           testcase.failPolicy,
   842  						SideEffects:             &noSideEffects,
   843  						AdmissionReviewVersions: []string{"v1"},
   844  						MatchConditions:         testcase.matchConditions,
   845  					},
   846  					{
   847  						Name: "admission.integration.test.marker",
   848  						Rules: []admissionregistrationv1.RuleWithOperations{{
   849  							Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
   850  							Rule:       admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
   851  						}},
   852  						ClientConfig: admissionregistrationv1.WebhookClientConfig{
   853  							URL:      &markerEndpoint,
   854  							CABundle: localhostCert,
   855  						},
   856  						NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
   857  							corev1.LabelMetadataName: "marker",
   858  						}},
   859  						ObjectSelector:          &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
   860  						FailurePolicy:           testcase.failPolicy,
   861  						SideEffects:             &noSideEffects,
   862  						AdmissionReviewVersions: []string{"v1"},
   863  					},
   864  				},
   865  			}
   866  
   867  			mutatingcfg, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingwebhook, metav1.CreateOptions{})
   868  			if err != nil {
   869  				t.Fatal(err)
   870  			}
   871  			defer func() {
   872  				err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(context.TODO(), mutatingcfg.GetName(), metav1.DeleteOptions{})
   873  				if err != nil {
   874  					t.Fatal(err)
   875  				}
   876  			}()
   877  
   878  			// wait until new webhook is called the first time
   879  			if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
   880  				_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
   881  				select {
   882  				case <-upCh:
   883  					return true, nil
   884  				default:
   885  					t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
   886  					return false, nil
   887  				}
   888  			}); err != nil {
   889  				t.Fatal(err)
   890  			}
   891  
   892  			for _, pod := range testcase.pods {
   893  				_, err = client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, dryRunCreate)
   894  				if testcase.expectErrorPod == false && err != nil {
   895  					t.Fatalf("unexpected error creating test pod: %v", err)
   896  				} else if testcase.expectErrorPod == true && err == nil {
   897  					t.Fatal("expected error creating pods")
   898  				}
   899  			}
   900  
   901  			if len(recorder.requests) != len(testcase.matchedPods) {
   902  				t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
   903  			}
   904  
   905  			for i, request := range recorder.requests {
   906  				if request.Name != testcase.matchedPods[i].Name {
   907  					t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
   908  				}
   909  				if request.Namespace != testcase.matchedPods[i].Namespace {
   910  					t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
   911  				}
   912  			}
   913  		})
   914  	}
   915  }
   916  
   917  func TestMatchConditions_validation(t *testing.T) {
   918  
   919  	server := apiservertesting.StartTestServerOrDie(t, nil, []string{
   920  		"--disable-admission-plugins=ServiceAccount",
   921  	}, framework.SharedEtcd())
   922  	defer server.TearDownFn()
   923  
   924  	client := clientset.NewForConfigOrDie(server.ClientConfig)
   925  
   926  	testcases := []struct {
   927  		name            string
   928  		matchConditions []admissionregistrationv1.MatchCondition
   929  		expectError     bool
   930  	}{{
   931  		name: "valid match condition",
   932  		matchConditions: []admissionregistrationv1.MatchCondition{{
   933  			Name:       "true",
   934  			Expression: "true",
   935  		}},
   936  		expectError: false,
   937  	}, {
   938  		name: "multiple valid match conditions",
   939  		matchConditions: []admissionregistrationv1.MatchCondition{{
   940  			Name:       "exclude-leases",
   941  			Expression: "!(request.resource.group == 'coordination.k8s.io' && request.resource.resource == 'leases')",
   942  		}, {
   943  			Name:       "exclude-kubelet-requests",
   944  			Expression: "!('system:nodes' in request.userInfo.groups)",
   945  		}, {
   946  			Name:       "breakglass",
   947  			Expression: "!authorizer.group('admissionregistration.k8s.io').resource('validatingwebhookconfigurations').name('my-webhook.example.com').check('breakglass').allowed()",
   948  		}},
   949  		expectError: false,
   950  	}, {
   951  		name: "invalid field should error",
   952  		matchConditions: []admissionregistrationv1.MatchCondition{{
   953  			Name:       "old-object-is-null.kubernetes.io",
   954  			Expression: "imnotafield == null",
   955  		}},
   956  		expectError: true,
   957  	}, {
   958  		name: "missing expression should error",
   959  		matchConditions: []admissionregistrationv1.MatchCondition{{
   960  			Name: "old-object-is-null.kubernetes.io",
   961  		}},
   962  		expectError: true,
   963  	}, {
   964  		name: "missing name should error",
   965  		matchConditions: []admissionregistrationv1.MatchCondition{{
   966  			Expression: "oldObject == null",
   967  		}},
   968  		expectError: true,
   969  	}, {
   970  		name: "empty name should error",
   971  		matchConditions: []admissionregistrationv1.MatchCondition{{
   972  			Name:       "",
   973  			Expression: "oldObject == null",
   974  		}},
   975  		expectError: true,
   976  	}, {
   977  		name: "empty expression should error",
   978  		matchConditions: []admissionregistrationv1.MatchCondition{{
   979  			Name:       "test-empty-expression.kubernetes.io",
   980  			Expression: "",
   981  		}},
   982  		expectError: true,
   983  	}, {
   984  		name: "duplicate name should error",
   985  		matchConditions: []admissionregistrationv1.MatchCondition{{
   986  			Name:       "test1",
   987  			Expression: "oldObject == null",
   988  		}, {
   989  			Name:       "test1",
   990  			Expression: "oldObject == null",
   991  		}},
   992  		expectError: true,
   993  	}, {
   994  		name: "name must be qualified name",
   995  		matchConditions: []admissionregistrationv1.MatchCondition{{
   996  			Name:       " test1",
   997  			Expression: "oldObject == null",
   998  		}},
   999  		expectError: true,
  1000  	}, {
  1001  		name:            "less than 65 match conditions should pass",
  1002  		matchConditions: repeatedMatchConditions(64),
  1003  		expectError:     false,
  1004  	}, {
  1005  		name:            "more than 64 match conditions should error",
  1006  		matchConditions: repeatedMatchConditions(65),
  1007  		expectError:     true,
  1008  	},
  1009  	}
  1010  
  1011  	dryRunCreate := metav1.CreateOptions{
  1012  		DryRun: []string{metav1.DryRunAll},
  1013  	}
  1014  	endpoint := "https://localhost:1234/server"
  1015  	for _, testcase := range testcases {
  1016  		t.Run(testcase.name, func(t *testing.T) {
  1017  			rules := []admissionregistrationv1.RuleWithOperations{{
  1018  				Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
  1019  				Rule: admissionregistrationv1.Rule{
  1020  					APIGroups:   []string{""},
  1021  					APIVersions: []string{"v1"},
  1022  					Resources:   []string{"pods"},
  1023  				},
  1024  			}}
  1025  			clientConfig := admissionregistrationv1.WebhookClientConfig{
  1026  				URL:      &endpoint,
  1027  				CABundle: localhostCert,
  1028  			}
  1029  			versions := []string{"v1"}
  1030  			validatingwebhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
  1031  				ObjectMeta: metav1.ObjectMeta{
  1032  					Name: "admission.integration.test",
  1033  				},
  1034  				Webhooks: []admissionregistrationv1.ValidatingWebhook{
  1035  					{
  1036  						Name:                    "admission.integration.test",
  1037  						Rules:                   rules,
  1038  						ClientConfig:            clientConfig,
  1039  						SideEffects:             &noSideEffects,
  1040  						AdmissionReviewVersions: versions,
  1041  						MatchConditions:         testcase.matchConditions,
  1042  					},
  1043  				},
  1044  			}
  1045  
  1046  			_, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), validatingwebhook, dryRunCreate)
  1047  			if testcase.expectError {
  1048  				if err == nil {
  1049  					t.Fatalf("Expected error creating ValidatingWebhookConfiguration; got nil")
  1050  				} else if !apierrors.IsInvalid(err) {
  1051  					t.Errorf("Expected Invalid error creating ValidatingWebhookConfiguration; got: %v", err)
  1052  				}
  1053  			} else if !testcase.expectError && err != nil {
  1054  				t.Fatalf("Unexpected error creating ValidatingWebhookConfiguration: %v", err)
  1055  			}
  1056  
  1057  			mutatingwebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
  1058  				ObjectMeta: metav1.ObjectMeta{
  1059  					Name: "admission.integration.test",
  1060  				},
  1061  				Webhooks: []admissionregistrationv1.MutatingWebhook{
  1062  					{
  1063  						Name:                    "admission.integration.test",
  1064  						Rules:                   rules,
  1065  						ClientConfig:            clientConfig,
  1066  						SideEffects:             &noSideEffects,
  1067  						AdmissionReviewVersions: versions,
  1068  						MatchConditions:         testcase.matchConditions,
  1069  					},
  1070  				},
  1071  			}
  1072  
  1073  			_, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingwebhook, dryRunCreate)
  1074  			if testcase.expectError {
  1075  				if err == nil {
  1076  					t.Fatalf("Expected error creating MutatingWebhookConfiguration; got: nil")
  1077  				} else if !apierrors.IsInvalid(err) {
  1078  					t.Errorf("Expected Invalid error creating MutatingWebhookConfiguration; got: %v", err)
  1079  				}
  1080  			} else if !testcase.expectError && err != nil {
  1081  				t.Fatalf("Unexpected error creating MutatingWebhookConfiguration: %v", err)
  1082  			}
  1083  		})
  1084  	}
  1085  }
  1086  
  1087  func matchConditionsTestPod(name, ns string) *corev1.Pod {
  1088  	return &corev1.Pod{
  1089  		ObjectMeta: metav1.ObjectMeta{
  1090  			Name:      name,
  1091  			Namespace: ns,
  1092  		},
  1093  		Spec: corev1.PodSpec{
  1094  			Containers: []corev1.Container{
  1095  				{
  1096  					Name:  "test",
  1097  					Image: "test",
  1098  				},
  1099  			},
  1100  		},
  1101  	}
  1102  }
  1103  
  1104  func newMarkerPod(namespace string) *corev1.Pod {
  1105  	return &corev1.Pod{
  1106  		ObjectMeta: metav1.ObjectMeta{
  1107  			Namespace: namespace,
  1108  			Name:      "marker",
  1109  			Labels: map[string]string{
  1110  				"marker": "true",
  1111  			},
  1112  		},
  1113  		Spec: corev1.PodSpec{
  1114  			Containers: []corev1.Container{{
  1115  				Name:  "fake-name",
  1116  				Image: "fakeimage",
  1117  			}},
  1118  		},
  1119  	}
  1120  }
  1121  
  1122  func repeatedMatchConditions(size int) []admissionregistrationv1.MatchCondition {
  1123  	matchConditions := make([]admissionregistrationv1.MatchCondition, 0, size)
  1124  	for i := 0; i < size; i++ {
  1125  		matchConditions = append(matchConditions, admissionregistrationv1.MatchCondition{
  1126  			Name:       "repeated-" + strconv.Itoa(i),
  1127  			Expression: "true",
  1128  		})
  1129  	}
  1130  	return matchConditions
  1131  }
  1132  
  1133  // generate n matchConditions with provided expression
  1134  func generateMatchConditionsWithAuthzCheck(num int, exp string) []admissionregistrationv1.MatchCondition {
  1135  	var conditions = make([]admissionregistrationv1.MatchCondition, num)
  1136  	for i := 0; i < num; i++ {
  1137  		conditions[i].Name = "test" + strconv.Itoa(i)
  1138  		conditions[i].Expression = exp
  1139  	}
  1140  	return conditions
  1141  }