knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/certificates/certificates_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 certificates
    18  
    19  import (
    20  	"context"
    21  	"errors"
    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  	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/types"
    33  	"k8s.io/apimachinery/pkg/util/wait"
    34  	clientgotesting "k8s.io/client-go/testing"
    35  	"knative.dev/pkg/configmap"
    36  	"knative.dev/pkg/controller"
    37  	"knative.dev/pkg/system"
    38  	"knative.dev/pkg/webhook"
    39  	certresources "knative.dev/pkg/webhook/certificates/resources"
    40  
    41  	. "knative.dev/pkg/reconciler/testing"
    42  	. "knative.dev/pkg/webhook/testing"
    43  )
    44  
    45  func TestReconcile(t *testing.T) {
    46  	const (
    47  		secretName  = "webhook-secret"
    48  		serviceName = "webhook-service"
    49  	)
    50  	secret, err := certresources.MakeSecret(context.Background(),
    51  		secretName, system.Namespace(), serviceName)
    52  	if err != nil {
    53  		t.Fatal("MakeSecret() =", err)
    54  	}
    55  
    56  	// Mutate the MakeSecret to return our secret deterministically.
    57  	certresources.MakeSecret = func(ctx context.Context, name, namespace, serviceName string) (*corev1.Secret, error) {
    58  		return secret, nil
    59  	}
    60  	defer func() {
    61  		certresources.MakeSecret = certresources.MakeSecretInternal
    62  	}()
    63  
    64  	// The key to use, which for this singleton reconciler doesn't matter (although the
    65  	// namespace matters for namespace validation).
    66  	key := system.Namespace() + "/does not matter"
    67  
    68  	table := TableTest{{
    69  		Name:    "well formed secret exists",
    70  		Key:     key,
    71  		Objects: []runtime.Object{secret},
    72  	}, {
    73  		Name: "secret does not exist",
    74  		Key:  key,
    75  	}, {
    76  		Name: "missing server key",
    77  		Key:  key,
    78  		Objects: []runtime.Object{&corev1.Secret{
    79  			ObjectMeta: metav1.ObjectMeta{
    80  				Name:      secretName,
    81  				Namespace: system.Namespace(),
    82  			},
    83  			Data: map[string][]byte{
    84  				// certresources.ServerKey:  []byte("missing"),
    85  				certresources.ServerCert: []byte("present"),
    86  				certresources.CACert:     []byte("present"),
    87  			},
    88  		}},
    89  		WantUpdates: []clientgotesting.UpdateActionImpl{{
    90  			Object: secret,
    91  		}},
    92  	}, {
    93  		Name: "missing server cert",
    94  		Key:  key,
    95  		Objects: []runtime.Object{&corev1.Secret{
    96  			ObjectMeta: metav1.ObjectMeta{
    97  				Name:      secretName,
    98  				Namespace: system.Namespace(),
    99  			},
   100  			Data: map[string][]byte{
   101  				certresources.ServerKey: []byte("present"),
   102  				// certresources.ServerCert: []byte("missing"),
   103  				certresources.CACert: []byte("present"),
   104  			},
   105  		}},
   106  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   107  			Object: secret,
   108  		}},
   109  	}, {
   110  		Name: "missing CA cert",
   111  		Key:  key,
   112  		Objects: []runtime.Object{&corev1.Secret{
   113  			ObjectMeta: metav1.ObjectMeta{
   114  				Name:      secretName,
   115  				Namespace: system.Namespace(),
   116  			},
   117  			Data: map[string][]byte{
   118  				certresources.ServerKey:  []byte("present"),
   119  				certresources.ServerCert: []byte("present"),
   120  				// certresources.CACert: []byte("missing"),
   121  			},
   122  		}},
   123  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   124  			Object: secret,
   125  		}},
   126  	}, {
   127  		Name: "certificate expiring soon",
   128  		Key:  key,
   129  		// 23 hours  falls inside of the grace period of 1 day so the secret will be updated.
   130  		Objects: []runtime.Object{secretWithCertData(t, time.Now().Add(23*time.Hour))},
   131  		WantUpdates: []clientgotesting.UpdateActionImpl{{
   132  			Object: secret,
   133  		}},
   134  	}, {
   135  		Name: "certificate not expiring soon",
   136  		Key:  key,
   137  		// 25 hours falls outside of the grace period of 1 day so the secret will not be updated.
   138  		Objects: []runtime.Object{secretWithCertData(t, time.Now().Add(25*time.Hour))},
   139  	}}
   140  
   141  	table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
   142  		return &reconciler{
   143  			client:       kubeclient.Get(ctx),
   144  			secretlister: listers.GetSecretLister(),
   145  			key: types.NamespacedName{
   146  				Namespace: system.Namespace(),
   147  				Name:      secretName,
   148  			},
   149  			serviceName: serviceName,
   150  		}
   151  	}))
   152  }
   153  
   154  func TestReconcileMakeSecretFailure(t *testing.T) {
   155  	secretName, serviceName := "webhook-secret", "webhook-service"
   156  	secret, err := certresources.MakeSecret(context.Background(),
   157  		secretName, system.Namespace(), serviceName)
   158  	if err != nil {
   159  		t.Fatal("MakeSecret() =", err)
   160  	}
   161  
   162  	// Mutate the MakeSecret to return our secret deterministically.
   163  	certresources.MakeSecret = func(ctx context.Context, name, namespace, serviceName string) (*corev1.Secret, error) {
   164  		return nil, errors.New("this is an error")
   165  	}
   166  	defer func() {
   167  		certresources.MakeSecret = certresources.MakeSecretInternal
   168  	}()
   169  
   170  	// The key to use, which for this singleton reconciler doesn't matter (although the
   171  	// namespace matters for namespace validation).
   172  	key := system.Namespace() + "/does not matter"
   173  
   174  	table := TableTest{{
   175  		Name:    "would return error, but not called",
   176  		Key:     key,
   177  		Objects: []runtime.Object{secret},
   178  	}, {
   179  		Name:    "malformed secret",
   180  		Key:     key,
   181  		WantErr: true,
   182  		Objects: []runtime.Object{&corev1.Secret{
   183  			ObjectMeta: metav1.ObjectMeta{
   184  				Name:      secretName,
   185  				Namespace: system.Namespace(),
   186  			},
   187  			Data: map[string][]byte{
   188  				// certresources.ServerKey:  []byte("missing"),
   189  				certresources.ServerCert: []byte("present"),
   190  				certresources.CACert:     []byte("present"),
   191  			},
   192  		}},
   193  	}, {
   194  		Name: "missing server key",
   195  		Key:  key,
   196  		Objects: []runtime.Object{&corev1.Secret{
   197  			ObjectMeta: metav1.ObjectMeta{
   198  				Name:      secretName,
   199  				Namespace: system.Namespace(),
   200  			},
   201  			Data: map[string][]byte{
   202  				// certresources.ServerKey:  []byte("missing"),
   203  				certresources.ServerCert: []byte("present"),
   204  				certresources.CACert:     []byte("present"),
   205  			},
   206  		}},
   207  		WantErr: true,
   208  	}}
   209  
   210  	table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
   211  		return &reconciler{
   212  			client:       kubeclient.Get(ctx),
   213  			secretlister: listers.GetSecretLister(),
   214  			key: types.NamespacedName{
   215  				Namespace: system.Namespace(),
   216  				Name:      secretName,
   217  			},
   218  			serviceName: serviceName,
   219  		}
   220  	}))
   221  }
   222  
   223  func TestNew(t *testing.T) {
   224  	ctx, _ := SetupFakeContext(t)
   225  	ctx = webhook.WithOptions(ctx, webhook.Options{})
   226  
   227  	c := NewController(ctx, configmap.NewStaticWatcher())
   228  	if c == nil {
   229  		t.Fatal("Expected NewController to return a non-nil value")
   230  	}
   231  
   232  	if want, got := 0, c.WorkQueue().Len(); want != got {
   233  		t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want)
   234  	}
   235  
   236  	la, ok := c.Reconciler.(pkgreconciler.LeaderAware)
   237  	if !ok {
   238  		t.Fatalf("%T is not leader aware", c.Reconciler)
   239  	}
   240  
   241  	if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil {
   242  		t.Error("Promote() =", err)
   243  	}
   244  
   245  	// Queue has async moving parts so if we check at the wrong moment, this might still be 0.
   246  	if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) {
   247  		return c.WorkQueue().Len() == 1, nil
   248  	}) != nil {
   249  		t.Error("Queue length was never 1")
   250  	}
   251  }
   252  
   253  func secretWithCertData(t *testing.T, expiration time.Time) *corev1.Secret {
   254  	const secretName = "webhook-secret"
   255  	serverKey, serverCert, caCert, err := certresources.CreateCerts(context.Background(), "webhook-service", system.Namespace(), expiration)
   256  	if err != nil {
   257  		t.Fatal("Failed to create cert:", err)
   258  	}
   259  	return &corev1.Secret{
   260  		ObjectMeta: metav1.ObjectMeta{
   261  			Name:      secretName,
   262  			Namespace: system.Namespace(),
   263  		},
   264  		Data: map[string][]byte{
   265  			certresources.ServerKey:  serverKey,
   266  			certresources.ServerCert: serverCert,
   267  			certresources.CACert:     caCert,
   268  		},
   269  	}
   270  }