knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/configmaps/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 configmaps
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  	"time"
    24  
    25  	kubeclient "knative.dev/pkg/client/injection/kube/client/fake"
    26  	_ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake"
    27  	pkgreconciler "knative.dev/pkg/reconciler"
    28  
    29  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	clientgotesting "k8s.io/client-go/testing"
    36  	"knative.dev/pkg/configmap"
    37  	"knative.dev/pkg/controller"
    38  	"knative.dev/pkg/ptr"
    39  	"knative.dev/pkg/system"
    40  	"knative.dev/pkg/webhook"
    41  	certresources "knative.dev/pkg/webhook/certificates/resources"
    42  
    43  	. "knative.dev/pkg/reconciler/testing"
    44  	. "knative.dev/pkg/webhook/testing"
    45  )
    46  
    47  func TestReconcile(t *testing.T) {
    48  	name, path := "foo.bar.baz", "/blah"
    49  	secretName := "webhook-secret"
    50  
    51  	secret := &corev1.Secret{
    52  		ObjectMeta: metav1.ObjectMeta{
    53  			Name:      secretName,
    54  			Namespace: system.Namespace(),
    55  		},
    56  		Data: map[string][]byte{
    57  			certresources.ServerKey:  []byte("present"),
    58  			certresources.ServerCert: []byte("present"),
    59  			certresources.CACert:     []byte("present"),
    60  		},
    61  	}
    62  
    63  	ns := &corev1.Namespace{
    64  		ObjectMeta: metav1.ObjectMeta{
    65  			Name: system.Namespace(),
    66  		},
    67  	}
    68  	nsRef := *metav1.NewControllerRef(ns, corev1.SchemeGroupVersion.WithKind("Namespace"))
    69  	nsRef.Controller = ptr.Bool(false)
    70  	expectedOwnerReferences := []metav1.OwnerReference{nsRef}
    71  
    72  	ruleScope := admissionregistrationv1.NamespacedScope
    73  
    74  	// These are the rules we expect given the context of "validations".
    75  	expectedRules := []admissionregistrationv1.RuleWithOperations{{
    76  		Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
    77  		Rule: admissionregistrationv1.Rule{
    78  			APIGroups:   []string{""},
    79  			APIVersions: []string{"v1"},
    80  			Resources:   []string{"configmaps/*"},
    81  			Scope:       &ruleScope,
    82  		},
    83  	}}
    84  
    85  	// The key to use, which for this singleton reconciler doesn't matter (although the
    86  	// namespace matters for namespace validation).
    87  	key := system.Namespace() + "/does not matter"
    88  
    89  	table := TableTest{{
    90  		Name:    "no secret",
    91  		Key:     key,
    92  		WantErr: true,
    93  	}, {
    94  		Name: "secret missing CA Cert",
    95  		Key:  key,
    96  		Objects: []runtime.Object{&corev1.Secret{
    97  			ObjectMeta: metav1.ObjectMeta{
    98  				Name:      secretName,
    99  				Namespace: system.Namespace(),
   100  			},
   101  			Data: map[string][]byte{
   102  				certresources.ServerKey:  []byte("present"),
   103  				certresources.ServerCert: []byte("present"),
   104  				// certresources.CACert:     []byte("missing"),
   105  			},
   106  		}},
   107  		WantErr: true,
   108  	}, {
   109  		Name:    "secret exists, but VWH does not",
   110  		Key:     key,
   111  		Objects: []runtime.Object{secret},
   112  		WantErr: true,
   113  	}, {
   114  		Name: "secret and VWH exist, missing service reference",
   115  		Key:  key,
   116  		Objects: []runtime.Object{
   117  			secret, ns,
   118  			&admissionregistrationv1.ValidatingWebhookConfiguration{
   119  				ObjectMeta: metav1.ObjectMeta{
   120  					Name: name,
   121  				},
   122  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   123  					Name: name,
   124  				}},
   125  			},
   126  		},
   127  		WantErr: true,
   128  	}, {
   129  		Name: "secret and VWH exist, missing other stuff",
   130  		Key:  key,
   131  		Objects: []runtime.Object{
   132  			secret, ns,
   133  			&admissionregistrationv1.ValidatingWebhookConfiguration{
   134  				ObjectMeta: metav1.ObjectMeta{
   135  					Name: name,
   136  				},
   137  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   138  					Name: name,
   139  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   140  						Service: &admissionregistrationv1.ServiceReference{
   141  							Namespace: system.Namespace(),
   142  							Name:      "webhook",
   143  						},
   144  					},
   145  				}},
   146  			},
   147  		},
   148  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   149  			Object: &admissionregistrationv1.ValidatingWebhookConfiguration{
   150  				ObjectMeta: metav1.ObjectMeta{
   151  					Name:            name,
   152  					OwnerReferences: expectedOwnerReferences,
   153  				},
   154  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   155  					Name: name,
   156  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   157  						Service: &admissionregistrationv1.ServiceReference{
   158  							Namespace: system.Namespace(),
   159  							Name:      "webhook",
   160  							// Path is added.
   161  							Path: ptr.String(path),
   162  						},
   163  						// CABundle is added.
   164  						CABundle: []byte("present"),
   165  					},
   166  					// Rules are added.
   167  					Rules: expectedRules,
   168  				}},
   169  			},
   170  		}},
   171  	}, {
   172  		Name: "secret and VWH exist, added fields are incorrect",
   173  		Key:  key,
   174  		Objects: []runtime.Object{
   175  			secret, ns,
   176  			&admissionregistrationv1.ValidatingWebhookConfiguration{
   177  				ObjectMeta: metav1.ObjectMeta{
   178  					Name: name,
   179  				},
   180  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   181  					Name: name,
   182  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   183  						Service: &admissionregistrationv1.ServiceReference{
   184  							Namespace: system.Namespace(),
   185  							Name:      "webhook",
   186  							// Incorrect
   187  							Path: ptr.String("incorrect"),
   188  						},
   189  						// Incorrect
   190  						CABundle: []byte("incorrect"),
   191  					},
   192  					// Incorrect
   193  					Rules: []admissionregistrationv1.RuleWithOperations{{
   194  						Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   195  						Rule: admissionregistrationv1.Rule{
   196  							APIGroups:   []string{"pkg.knative.dev"},
   197  							APIVersions: []string{"v1alpha1"},
   198  							Resources:   []string{"innerdefaultresources/*"},
   199  						},
   200  					}},
   201  				}},
   202  			},
   203  		},
   204  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   205  			Object: &admissionregistrationv1.ValidatingWebhookConfiguration{
   206  				ObjectMeta: metav1.ObjectMeta{
   207  					Name:            name,
   208  					OwnerReferences: expectedOwnerReferences,
   209  				},
   210  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   211  					Name: name,
   212  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   213  						Service: &admissionregistrationv1.ServiceReference{
   214  							Namespace: system.Namespace(),
   215  							Name:      "webhook",
   216  							// Path is fixed.
   217  							Path: ptr.String(path),
   218  						},
   219  						// CABundle is fixed.
   220  						CABundle: []byte("present"),
   221  					},
   222  					// Rules are fixed.
   223  					Rules: expectedRules,
   224  				}},
   225  			},
   226  		}},
   227  	}, {
   228  		Name:    "failure updating VWH",
   229  		Key:     key,
   230  		WantErr: true,
   231  		WithReactors: []clientgotesting.ReactionFunc{
   232  			InduceFailure("update", "validatingwebhookconfigurations"),
   233  		},
   234  		Objects: []runtime.Object{
   235  			secret, ns,
   236  			&admissionregistrationv1.ValidatingWebhookConfiguration{
   237  				ObjectMeta: metav1.ObjectMeta{
   238  					Name: name,
   239  				},
   240  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   241  					Name: name,
   242  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   243  						Service: &admissionregistrationv1.ServiceReference{
   244  							Namespace: system.Namespace(),
   245  							Name:      "webhook",
   246  							// Incorrect
   247  							Path: ptr.String("incorrect"),
   248  						},
   249  						// Incorrect
   250  						CABundle: []byte("incorrect"),
   251  					},
   252  					// Incorrect
   253  					Rules: []admissionregistrationv1.RuleWithOperations{{
   254  						Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
   255  						Rule: admissionregistrationv1.Rule{
   256  							APIGroups:   []string{"pkg.knative.dev"},
   257  							APIVersions: []string{"v1alpha1"},
   258  							Resources:   []string{"innerdefaultresources/*"},
   259  						},
   260  					}},
   261  				}},
   262  			},
   263  		},
   264  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   265  			Object: &admissionregistrationv1.ValidatingWebhookConfiguration{
   266  				ObjectMeta: metav1.ObjectMeta{
   267  					Name:            name,
   268  					OwnerReferences: expectedOwnerReferences,
   269  				},
   270  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   271  					Name: name,
   272  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   273  						Service: &admissionregistrationv1.ServiceReference{
   274  							Namespace: system.Namespace(),
   275  							Name:      "webhook",
   276  							// Path is fixed.
   277  							Path: ptr.String(path),
   278  						},
   279  						// CABundle is fixed.
   280  						CABundle: []byte("present"),
   281  					},
   282  					// Rules are fixed.
   283  					Rules: expectedRules,
   284  				}},
   285  			},
   286  		}},
   287  	}, {
   288  		Name: ":fire: everything is fine :fire:",
   289  		Key:  key,
   290  		Objects: []runtime.Object{
   291  			secret, ns,
   292  			&admissionregistrationv1.ValidatingWebhookConfiguration{
   293  				ObjectMeta: metav1.ObjectMeta{
   294  					Name:            name,
   295  					OwnerReferences: expectedOwnerReferences,
   296  				},
   297  				Webhooks: []admissionregistrationv1.ValidatingWebhook{{
   298  					Name: name,
   299  					ClientConfig: admissionregistrationv1.WebhookClientConfig{
   300  						Service: &admissionregistrationv1.ServiceReference{
   301  							Namespace: system.Namespace(),
   302  							Name:      "webhook",
   303  							// Path is fine.
   304  							Path: ptr.String(path),
   305  						},
   306  						// CABundle is fine.
   307  						CABundle: []byte("present"),
   308  					},
   309  					// Rules are fine.
   310  					Rules: expectedRules,
   311  				}},
   312  			},
   313  		},
   314  	}}
   315  
   316  	table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
   317  		wh := &reconciler{
   318  			key: types.NamespacedName{
   319  				Name: name,
   320  			},
   321  			path: path,
   322  
   323  			client:       kubeclient.Get(ctx),
   324  			vwhlister:    listers.GetValidatingWebhookConfigurationLister(),
   325  			secretlister: listers.GetSecretLister(),
   326  
   327  			constructors: make(map[string]reflect.Value),
   328  			secretName:   secretName,
   329  		}
   330  
   331  		for configName, constructor := range validations {
   332  			wh.registerConfig(configName, constructor)
   333  		}
   334  
   335  		return wh
   336  	}))
   337  }
   338  
   339  func TestNew(t *testing.T) {
   340  	ctx, _ := SetupFakeContext(t)
   341  	ctx = webhook.WithOptions(ctx, webhook.Options{})
   342  
   343  	c := NewAdmissionController(ctx, "foo", "/bar", validations)
   344  	if c == nil {
   345  		t.Fatal("Expected NewController to return a non-nil value")
   346  	}
   347  
   348  	if want, got := 0, c.WorkQueue().Len(); want != got {
   349  		t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want)
   350  	}
   351  
   352  	la, ok := c.Reconciler.(pkgreconciler.LeaderAware)
   353  	if !ok {
   354  		t.Fatalf("%T is not leader aware", c.Reconciler)
   355  	}
   356  
   357  	if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil {
   358  		t.Error("Promote() =", err)
   359  	}
   360  
   361  	// Queue has async moving parts so if we check at the wrong moment, this might still be 0.
   362  	if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) {
   363  		return c.WorkQueue().Len() == 1, nil
   364  	}) != nil {
   365  		t.Error("Queue length was never 1")
   366  	}
   367  }