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 }