knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/resourcesemantics/defaulting/table_test.go (about)

     1  /*
     2  Copyright 2019 The Knative 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 defaulting
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  	"time"
    23  
    24  	kubeclient "knative.dev/pkg/client/injection/kube/client/fake"
    25  	_ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake"
    26  	pkgreconciler "knative.dev/pkg/reconciler"
    27  
    28  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	clientgotesting "k8s.io/client-go/testing"
    36  
    37  	"knative.dev/pkg/configmap"
    38  	"knative.dev/pkg/controller"
    39  	"knative.dev/pkg/ptr"
    40  	"knative.dev/pkg/system"
    41  	"knative.dev/pkg/webhook"
    42  	certresources "knative.dev/pkg/webhook/certificates/resources"
    43  	"knative.dev/pkg/webhook/resourcesemantics"
    44  
    45  	. "knative.dev/pkg/reconciler/testing"
    46  	. "knative.dev/pkg/webhook/testing"
    47  )
    48  
    49  func TestReconcile(t *testing.T) {
    50  	name, path := "foo.bar.baz", "/blah"
    51  	secretName := "webhook-secret"
    52  
    53  	secret := &corev1.Secret{
    54  		ObjectMeta: metav1.ObjectMeta{
    55  			Name:      secretName,
    56  			Namespace: system.Namespace(),
    57  		},
    58  		Data: map[string][]byte{
    59  			certresources.ServerKey:  []byte("present"),
    60  			certresources.ServerCert: []byte("present"),
    61  			certresources.CACert:     []byte("present"),
    62  		},
    63  	}
    64  	ns := &corev1.Namespace{
    65  		ObjectMeta: metav1.ObjectMeta{
    66  			Name: system.Namespace(),
    67  		},
    68  	}
    69  	nsRef := *metav1.NewControllerRef(ns, corev1.SchemeGroupVersion.WithKind("Namespace"))
    70  	nsRef.Controller = ptr.Bool(false)
    71  	expectedOwnerReferences := []metav1.OwnerReference{nsRef}
    72  
    73  	// This is the namespace selector setup
    74  	namespaceSelector := &metav1.LabelSelector{
    75  		MatchExpressions: []metav1.LabelSelectorRequirement{{
    76  			Key:      "webhooks.knative.dev/exclude",
    77  			Operator: metav1.LabelSelectorOpDoesNotExist,
    78  		}},
    79  	}
    80  
    81  	// These are the rules we expect given the context of "handlers".
    82  	expectedRules := []admissionregistrationv1.RuleWithOperations{{
    83  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
    84  		Rule: admissionregistrationv1.Rule{
    85  			APIGroups:   []string{""},
    86  			APIVersions: []string{"v1"},
    87  			Resources:   []string{"pods", "pods/status"},
    88  		},
    89  	}, {
    90  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
    91  		Rule: admissionregistrationv1.Rule{
    92  			APIGroups:   []string{"pkg.knative.dev"},
    93  			APIVersions: []string{"v1alpha1"},
    94  			Resources:   []string{"innerdefaultresources", "innerdefaultresources/status"},
    95  		},
    96  	}, {
    97  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
    98  		Rule: admissionregistrationv1.Rule{
    99  			APIGroups:   []string{"pkg.knative.dev"},
   100  			APIVersions: []string{"v1alpha1"},
   101  			Resources:   []string{"resources", "resources/status"},
   102  		},
   103  	}, {
   104  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   105  		Rule: admissionregistrationv1.Rule{
   106  			APIGroups:   []string{"pkg.knative.dev"},
   107  			APIVersions: []string{"v1beta1"},
   108  			Resources:   []string{"resourcecallbackdefaultcreates", "resourcecallbackdefaultcreates/status"},
   109  		},
   110  	}, {
   111  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   112  		Rule: admissionregistrationv1.Rule{
   113  			APIGroups:   []string{"pkg.knative.dev"},
   114  			APIVersions: []string{"v1beta1"},
   115  			Resources:   []string{"resourcecallbackdefaults", "resourcecallbackdefaults/status"},
   116  		},
   117  	}, {
   118  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   119  		Rule: admissionregistrationv1.Rule{
   120  			APIGroups:   []string{"pkg.knative.dev"},
   121  			APIVersions: []string{"v1beta1"},
   122  			Resources:   []string{"resources", "resources/status"},
   123  		},
   124  	}, {
   125  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   126  		Rule: admissionregistrationv1.Rule{
   127  			APIGroups:   []string{"pkg.knative.io"},
   128  			APIVersions: []string{"v1alpha1"},
   129  			Resources:   []string{"innerdefaultresources", "innerdefaultresources/status"},
   130  		},
   131  	}}
   132  
   133  	// The key to use, which for this singleton reconciler doesn't matter (although the
   134  	// namespace matters for namespace validation).
   135  	key := system.Namespace() + "/does not matter"
   136  
   137  	table := TableTest{{
   138  		Name:    "no secret",
   139  		Key:     key,
   140  		WantErr: true,
   141  	}, {
   142  		Name: "secret missing CA Cert",
   143  		Key:  key,
   144  		Objects: []runtime.Object{&corev1.Secret{
   145  			ObjectMeta: metav1.ObjectMeta{
   146  				Name:      secretName,
   147  				Namespace: system.Namespace(),
   148  			},
   149  			Data: map[string][]byte{
   150  				certresources.ServerKey:  []byte("present"),
   151  				certresources.ServerCert: []byte("present"),
   152  				// certresources.CACert:     []byte("missing"),
   153  			},
   154  		}},
   155  		WantErr: true,
   156  	}, {
   157  		Name:    "secret exists, but MWH does not",
   158  		Key:     key,
   159  		Objects: []runtime.Object{secret},
   160  		WantErr: true,
   161  	}, {
   162  		Name: "secret and MWH exist, missing service reference",
   163  		Key:  key,
   164  		Objects: []runtime.Object{
   165  			secret, ns,
   166  			&admissionregistrationv1.MutatingWebhookConfiguration{
   167  				ObjectMeta: metav1.ObjectMeta{
   168  					Name: name,
   169  				},
   170  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   171  					Name: name,
   172  				}},
   173  			},
   174  		},
   175  		WantErr: true,
   176  	}, {
   177  		Name: "secret and MWH exist, missing other stuff",
   178  		Key:  key,
   179  		Objects: []runtime.Object{
   180  			secret, ns,
   181  			&admissionregistrationv1.MutatingWebhookConfiguration{
   182  				ObjectMeta: metav1.ObjectMeta{
   183  					Name: name,
   184  				},
   185  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   186  					Name: name,
   187  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   188  						Service: &admissionregistrationv1.ServiceReference{
   189  							Namespace: system.Namespace(),
   190  							Name:      "webhook",
   191  						},
   192  					},
   193  				}},
   194  			},
   195  		},
   196  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   197  			Object: &admissionregistrationv1.MutatingWebhookConfiguration{
   198  				ObjectMeta: metav1.ObjectMeta{
   199  					Name:            name,
   200  					OwnerReferences: expectedOwnerReferences,
   201  				},
   202  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   203  					Name: name,
   204  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   205  						Service: &admissionregistrationv1.ServiceReference{
   206  							Namespace: system.Namespace(),
   207  							Name:      "webhook",
   208  							// Path is added.
   209  							Path: ptr.String(path),
   210  						},
   211  						// CABundle is added.
   212  						CABundle: []byte("present"),
   213  					},
   214  					// Rules are added.
   215  					Rules:              expectedRules,
   216  					NamespaceSelector:  namespaceSelector,
   217  					ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
   218  				}},
   219  			},
   220  		}},
   221  	}, {
   222  		Name: "secret and MWH exist, added fields are incorrect",
   223  		Key:  key,
   224  		Objects: []runtime.Object{
   225  			secret, ns,
   226  			&admissionregistrationv1.MutatingWebhookConfiguration{
   227  				ObjectMeta: metav1.ObjectMeta{
   228  					Name: name,
   229  				},
   230  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   231  					Name: name,
   232  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   233  						Service: &admissionregistrationv1.ServiceReference{
   234  							Namespace: system.Namespace(),
   235  							Name:      "webhook",
   236  							// Incorrect
   237  							Path: ptr.String("incorrect"),
   238  						},
   239  						// Incorrect
   240  						CABundle: []byte("incorrect"),
   241  					},
   242  					// Incorrect (really just incomplete)
   243  					Rules: []admissionregistrationv1.RuleWithOperations{{
   244  						Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   245  						Rule: admissionregistrationv1.Rule{
   246  							APIGroups:   []string{"pkg.knative.dev"},
   247  							APIVersions: []string{"v1alpha1"},
   248  							Resources:   []string{"innerdefaultresources", "innerdefaultresources/status"},
   249  						},
   250  					}},
   251  					// Incorrect
   252  					ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.NeverReinvocationPolicy),
   253  				}},
   254  			},
   255  		},
   256  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   257  			Object: &admissionregistrationv1.MutatingWebhookConfiguration{
   258  				ObjectMeta: metav1.ObjectMeta{
   259  					Name:            name,
   260  					OwnerReferences: expectedOwnerReferences,
   261  				},
   262  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   263  					Name: name,
   264  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   265  						Service: &admissionregistrationv1.ServiceReference{
   266  							Namespace: system.Namespace(),
   267  							Name:      "webhook",
   268  							// Path is fixed.
   269  							Path: ptr.String(path),
   270  						},
   271  						// CABundle is fixed.
   272  						CABundle: []byte("present"),
   273  					},
   274  					// Rules are fixed.
   275  					Rules:              expectedRules,
   276  					NamespaceSelector:  namespaceSelector,
   277  					ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
   278  				}},
   279  			},
   280  		}},
   281  	}, {
   282  		Name:    "failure updating MWH",
   283  		Key:     key,
   284  		WantErr: true,
   285  		WithReactors: []clientgotesting.ReactionFunc{
   286  			InduceFailure("update", "mutatingwebhookconfigurations"),
   287  		},
   288  		Objects: []runtime.Object{
   289  			secret, ns,
   290  			&admissionregistrationv1.MutatingWebhookConfiguration{
   291  				ObjectMeta: metav1.ObjectMeta{
   292  					Name: name,
   293  				},
   294  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   295  					Name: name,
   296  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   297  						Service: &admissionregistrationv1.ServiceReference{
   298  							Namespace: system.Namespace(),
   299  							Name:      "webhook",
   300  							// Incorrect
   301  							Path: ptr.String("incorrect"),
   302  						},
   303  						// Incorrect
   304  						CABundle: []byte("incorrect"),
   305  					},
   306  					// Incorrect (really just incomplete)
   307  					Rules: []admissionregistrationv1.RuleWithOperations{{
   308  						Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   309  						Rule: admissionregistrationv1.Rule{
   310  							APIGroups:   []string{"pkg.knative.dev"},
   311  							APIVersions: []string{"v1alpha1"},
   312  							Resources:   []string{"innerdefaultresources", "innerdefaultresources/status"},
   313  						},
   314  					}},
   315  				}},
   316  			},
   317  		},
   318  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   319  			Object: &admissionregistrationv1.MutatingWebhookConfiguration{
   320  				ObjectMeta: metav1.ObjectMeta{
   321  					Name:            name,
   322  					OwnerReferences: expectedOwnerReferences,
   323  				},
   324  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   325  					Name: name,
   326  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   327  						Service: &admissionregistrationv1.ServiceReference{
   328  							Namespace: system.Namespace(),
   329  							Name:      "webhook",
   330  							// Path is fixed.
   331  							Path: ptr.String(path),
   332  						},
   333  						// CABundle is fixed.
   334  						CABundle: []byte("present"),
   335  					},
   336  					// Rules are fixed.
   337  					Rules:              expectedRules,
   338  					NamespaceSelector:  namespaceSelector,
   339  					ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
   340  				}},
   341  			},
   342  		}},
   343  	}, {
   344  		Name: ":fire: everything is fine :fire:",
   345  		Key:  key,
   346  		Objects: []runtime.Object{
   347  			secret, ns,
   348  			&admissionregistrationv1.MutatingWebhookConfiguration{
   349  				ObjectMeta: metav1.ObjectMeta{
   350  					Name:            name,
   351  					OwnerReferences: expectedOwnerReferences,
   352  				},
   353  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   354  					Name: name,
   355  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   356  						Service: &admissionregistrationv1.ServiceReference{
   357  							Namespace: system.Namespace(),
   358  							Name:      "webhook",
   359  							// Path is fine.
   360  							Path: ptr.String(path),
   361  						},
   362  						// CABundle is fine.
   363  						CABundle: []byte("present"),
   364  					},
   365  					// Rules are fine.
   366  					Rules: expectedRules,
   367  					// A non-knative key in the namespace selector is fine.
   368  					NamespaceSelector: &metav1.LabelSelector{
   369  						MatchExpressions: []metav1.LabelSelectorRequirement{{
   370  							Key:      "webhooks.knative.dev/exclude",
   371  							Operator: metav1.LabelSelectorOpDoesNotExist,
   372  						}, {
   373  							Key:      "foo.bar/baz",
   374  							Operator: metav1.LabelSelectorOpDoesNotExist,
   375  						}},
   376  					},
   377  					ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
   378  				}},
   379  			},
   380  		},
   381  	}, {
   382  		Name: "secret and MWH exist, correcting namespaceSelector",
   383  		Key:  key,
   384  		Objects: []runtime.Object{
   385  			secret, ns,
   386  			&admissionregistrationv1.MutatingWebhookConfiguration{
   387  				ObjectMeta: metav1.ObjectMeta{
   388  					Name: name,
   389  				},
   390  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   391  					Name: name,
   392  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   393  						Service: &admissionregistrationv1.ServiceReference{
   394  							Namespace: system.Namespace(),
   395  							Name:      "webhook",
   396  							// Path is fine.
   397  							Path: ptr.String(path),
   398  						},
   399  						// CABundle is fine.
   400  						CABundle: []byte("present"),
   401  					},
   402  					// Rules are fine.
   403  					Rules: expectedRules,
   404  					// NamespaceSelector contains non-knative things.
   405  					NamespaceSelector: &metav1.LabelSelector{
   406  						MatchExpressions: []metav1.LabelSelectorRequirement{{
   407  							Key:      "foo.knative.dev/exclude",
   408  							Operator: metav1.LabelSelectorOpDoesNotExist,
   409  						}, {
   410  							Key:      "foo.bar/baz",
   411  							Operator: metav1.LabelSelectorOpDoesNotExist,
   412  						}},
   413  					},
   414  				}},
   415  			},
   416  		},
   417  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   418  			Object: &admissionregistrationv1.MutatingWebhookConfiguration{
   419  				ObjectMeta: metav1.ObjectMeta{
   420  					Name:            name,
   421  					OwnerReferences: expectedOwnerReferences,
   422  				},
   423  				Webhooks: []admissionregistrationv1.MutatingWebhook{{
   424  					Name: name,
   425  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   426  						Service: &admissionregistrationv1.ServiceReference{
   427  							Namespace: system.Namespace(),
   428  							Name:      "webhook",
   429  							Path:      ptr.String(path),
   430  						},
   431  						CABundle: []byte("present"),
   432  					},
   433  					Rules: expectedRules,
   434  					NamespaceSelector: &metav1.LabelSelector{
   435  						// The knative key is added while the non-knative key is kept.
   436  						// Old knative key is removed.
   437  						MatchExpressions: []metav1.LabelSelectorRequirement{{
   438  							Key:      "webhooks.knative.dev/exclude",
   439  							Operator: metav1.LabelSelectorOpDoesNotExist,
   440  						}, {
   441  							Key:      "foo.bar/baz",
   442  							Operator: metav1.LabelSelectorOpDoesNotExist,
   443  						}},
   444  					},
   445  					ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
   446  				}},
   447  			},
   448  		}},
   449  	}}
   450  
   451  	table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
   452  		return &reconciler{
   453  			key: types.NamespacedName{
   454  				Name: name,
   455  			},
   456  			path: path,
   457  
   458  			handlers:  handlers,
   459  			callbacks: callbacks,
   460  
   461  			client:       kubeclient.Get(ctx),
   462  			mwhlister:    listers.GetMutatingWebhookConfigurationLister(),
   463  			secretlister: listers.GetSecretLister(),
   464  
   465  			secretName: secretName,
   466  		}
   467  	}))
   468  }
   469  
   470  func TestNew(t *testing.T) {
   471  	ctx, _ := SetupFakeContext(t)
   472  	ctx = webhook.WithOptions(ctx, webhook.Options{})
   473  
   474  	c := NewAdmissionController(ctx, "foo", "/bar",
   475  		map[schema.GroupVersionKind]resourcesemantics.GenericCRD{},
   476  		func(ctx context.Context) context.Context {
   477  			return ctx
   478  		}, true /* disallow unknown field */)
   479  	if c == nil {
   480  		t.Fatal("Expected NewController to return a non-nil value")
   481  	}
   482  
   483  	if want, got := 0, c.WorkQueue().Len(); want != got {
   484  		t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want)
   485  	}
   486  
   487  	la, ok := c.Reconciler.(pkgreconciler.LeaderAware)
   488  	if !ok {
   489  		t.Fatalf("%T is not leader aware", c.Reconciler)
   490  	}
   491  
   492  	if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil {
   493  		t.Error("Promote() =", err)
   494  	}
   495  
   496  	// Queue has async moving parts so if we check at the wrong moment, this might still be 0.
   497  	if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) {
   498  		return c.WorkQueue().Len() == 1, nil
   499  	}) != nil {
   500  		t.Error("Queue length was never 1")
   501  	}
   502  }