1
16
17 package clustertrustbundle
18
19 import (
20 "bytes"
21 "context"
22 "crypto/ed25519"
23 "crypto/rand"
24 "crypto/x509"
25 "crypto/x509/pkix"
26 "encoding/pem"
27 "fmt"
28 "math/big"
29 "sort"
30 "strings"
31 "testing"
32 "time"
33
34 "github.com/google/go-cmp/cmp"
35 certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/client-go/informers"
38 "k8s.io/client-go/kubernetes/fake"
39 "k8s.io/client-go/tools/cache"
40 )
41
42 func TestBeforeSynced(t *testing.T) {
43 kc := fake.NewSimpleClientset()
44
45 informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
46
47 ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
48 ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
49
50 _, err := ctbManager.GetTrustAnchorsByName("foo", false)
51 if err == nil {
52 t.Fatalf("Got nil error, wanted non-nil")
53 }
54 }
55
56 func TestGetTrustAnchorsByName(t *testing.T) {
57 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
58 defer cancel()
59
60 ctb1 := &certificatesv1alpha1.ClusterTrustBundle{
61 ObjectMeta: metav1.ObjectMeta{
62 Name: "ctb1",
63 },
64 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
65 TrustBundle: mustMakeRoot(t, "root1"),
66 },
67 }
68
69 ctb2 := &certificatesv1alpha1.ClusterTrustBundle{
70 ObjectMeta: metav1.ObjectMeta{
71 Name: "ctb2",
72 },
73 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
74 TrustBundle: mustMakeRoot(t, "root2"),
75 },
76 }
77
78 kc := fake.NewSimpleClientset(ctb1, ctb2)
79
80 informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
81
82 ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
83 ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
84
85 informerFactory.Start(ctx.Done())
86 if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
87 t.Fatalf("Timed out waiting for informer to sync")
88 }
89
90 gotBundle, err := ctbManager.GetTrustAnchorsByName("ctb1", false)
91 if err != nil {
92 t.Fatalf("Error while calling GetTrustAnchorsByName: %v", err)
93 }
94
95 if diff := diffBundles(gotBundle, []byte(ctb1.Spec.TrustBundle)); diff != "" {
96 t.Fatalf("Got bad bundle; diff (-got +want)\n%s", diff)
97 }
98
99 gotBundle, err = ctbManager.GetTrustAnchorsByName("ctb2", false)
100 if err != nil {
101 t.Fatalf("Error while calling GetTrustAnchorsByName: %v", err)
102 }
103
104 if diff := diffBundles(gotBundle, []byte(ctb2.Spec.TrustBundle)); diff != "" {
105 t.Fatalf("Got bad bundle; diff (-got +want)\n%s", diff)
106 }
107
108 _, err = ctbManager.GetTrustAnchorsByName("not-found", false)
109 if err == nil {
110 t.Fatalf("While looking up nonexisting ClusterTrustBundle, got nil error, wanted non-nil")
111 }
112
113 _, err = ctbManager.GetTrustAnchorsByName("not-found", true)
114 if err != nil {
115 t.Fatalf("Unexpected error while calling GetTrustAnchorsByName for nonexistent CTB with allowMissing: %v", err)
116 }
117 }
118
119 func TestGetTrustAnchorsByNameCaching(t *testing.T) {
120 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
121 defer cancel()
122
123 ctb1 := &certificatesv1alpha1.ClusterTrustBundle{
124 ObjectMeta: metav1.ObjectMeta{
125 Name: "foo",
126 },
127 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
128 TrustBundle: mustMakeRoot(t, "root1"),
129 },
130 }
131
132 ctb2 := &certificatesv1alpha1.ClusterTrustBundle{
133 ObjectMeta: metav1.ObjectMeta{
134 Name: "foo",
135 },
136 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
137 TrustBundle: mustMakeRoot(t, "root2"),
138 },
139 }
140
141 kc := fake.NewSimpleClientset(ctb1)
142
143 informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
144
145 ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
146 ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
147
148 informerFactory.Start(ctx.Done())
149 if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
150 t.Fatalf("Timed out waiting for informer to sync")
151 }
152
153 t.Run("foo should yield the first certificate", func(t *testing.T) {
154 gotBundle, err := ctbManager.GetTrustAnchorsByName("foo", false)
155 if err != nil {
156 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
157 }
158
159 wantBundle := ctb1.Spec.TrustBundle
160
161 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
162 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
163 }
164 })
165
166 t.Run("foo should still yield the first certificate", func(t *testing.T) {
167 gotBundle, err := ctbManager.GetTrustAnchorsByName("foo", false)
168 if err != nil {
169 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
170 }
171
172 wantBundle := ctb1.Spec.TrustBundle
173
174 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
175 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
176 }
177 })
178
179 if err := kc.CertificatesV1alpha1().ClusterTrustBundles().Delete(ctx, ctb1.ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
180 t.Fatalf("Error while deleting the old CTB: %v", err)
181 }
182 if _, err := kc.CertificatesV1alpha1().ClusterTrustBundles().Create(ctx, ctb2, metav1.CreateOptions{}); err != nil {
183 t.Fatalf("Error while adding new CTB: %v", err)
184 }
185
186
187
188
189 time.Sleep(5 * time.Second)
190
191 t.Run("foo should yield the new certificate", func(t *testing.T) {
192 gotBundle, err := ctbManager.GetTrustAnchorsByName("foo", false)
193 if err != nil {
194 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
195 }
196
197 wantBundle := ctb2.Spec.TrustBundle
198
199 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
200 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
201 }
202 })
203 }
204
205 func TestGetTrustAnchorsBySignerName(t *testing.T) {
206 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
207 defer cancel()
208
209 ctb1 := mustMakeCTB("signer-a-label-a-1", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "0"))
210 ctb2 := mustMakeCTB("signer-a-label-a-2", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "1"))
211 ctb2dup := mustMakeCTB("signer-a-label-2-dup", "foo.bar/a", map[string]string{"label": "a"}, ctb2.Spec.TrustBundle)
212 ctb3 := mustMakeCTB("signer-a-label-b-1", "foo.bar/a", map[string]string{"label": "b"}, mustMakeRoot(t, "2"))
213 ctb4 := mustMakeCTB("signer-b-label-a-1", "foo.bar/b", map[string]string{"label": "a"}, mustMakeRoot(t, "3"))
214
215 kc := fake.NewSimpleClientset(ctb1, ctb2, ctb2dup, ctb3, ctb4)
216
217 informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
218
219 ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
220 ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
221
222 informerFactory.Start(ctx.Done())
223 if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
224 t.Fatalf("Timed out waiting for informer to sync")
225 }
226
227 t.Run("big labelselector should cause error", func(t *testing.T) {
228 longString := strings.Builder{}
229 for i := 0; i < 63; i++ {
230 longString.WriteString("v")
231 }
232 matchLabels := map[string]string{}
233 for i := 0; i < 100*1024/63+1; i++ {
234 matchLabels[fmt.Sprintf("key-%d", i)] = longString.String()
235 }
236
237 _, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: matchLabels}, false)
238 if err == nil || !strings.Contains(err.Error(), "label selector length") {
239 t.Fatalf("Bad error, got %v, wanted it to contain \"label selector length\"", err)
240 }
241 })
242
243 t.Run("signer-a label-a should yield two sorted certificates", func(t *testing.T) {
244 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
245 if err != nil {
246 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
247 }
248
249 wantBundle := ctb1.Spec.TrustBundle + ctb2.Spec.TrustBundle
250
251 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
252 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
253 }
254 })
255
256 t.Run("signer-a with nil selector should yield zero certificates", func(t *testing.T) {
257 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", nil, true)
258 if err != nil {
259 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
260 }
261
262 wantBundle := ""
263
264 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
265 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
266 }
267 })
268
269 t.Run("signer-b with empty selector should yield one certificates", func(t *testing.T) {
270 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{}, false)
271 if err != nil {
272 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
273 }
274
275 if diff := diffBundles(gotBundle, []byte(ctb4.Spec.TrustBundle)); diff != "" {
276 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
277 }
278 })
279
280 t.Run("signer-a label-b should yield one certificate", func(t *testing.T) {
281 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "b"}}, false)
282 if err != nil {
283 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
284 }
285
286 if diff := diffBundles(gotBundle, []byte(ctb3.Spec.TrustBundle)); diff != "" {
287 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
288 }
289 })
290
291 t.Run("signer-b label-a should yield one certificate", func(t *testing.T) {
292 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
293 if err != nil {
294 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
295 }
296
297 if diff := diffBundles(gotBundle, []byte(ctb4.Spec.TrustBundle)); diff != "" {
298 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
299 }
300 })
301
302 t.Run("signer-b label-b allowMissing=true should yield zero certificates", func(t *testing.T) {
303 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "b"}}, true)
304 if err != nil {
305 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
306 }
307
308 if diff := diffBundles(gotBundle, []byte{}); diff != "" {
309 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
310 }
311 })
312
313 t.Run("signer-b label-b allowMissing=false should yield zero certificates (error)", func(t *testing.T) {
314 _, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "b"}}, false)
315 if err == nil {
316 t.Fatalf("Got nil error while calling GetTrustAnchorsBySigner, wanted non-nil")
317 }
318 })
319 }
320
321 func TestGetTrustAnchorsBySignerNameCaching(t *testing.T) {
322 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
323 defer cancel()
324
325 ctb1 := mustMakeCTB("signer-a-label-a-1", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "0"))
326 ctb2 := mustMakeCTB("signer-a-label-a-2", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "1"))
327
328 kc := fake.NewSimpleClientset(ctb1)
329
330 informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
331
332 ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
333 ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
334
335 informerFactory.Start(ctx.Done())
336 if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
337 t.Fatalf("Timed out waiting for informer to sync")
338 }
339
340 t.Run("signer-a label-a should yield one certificate", func(t *testing.T) {
341 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
342 if err != nil {
343 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
344 }
345
346 wantBundle := ctb1.Spec.TrustBundle
347
348 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
349 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
350 }
351 })
352
353 t.Run("signer-a label-a should yield the same result when called again", func(t *testing.T) {
354 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
355 if err != nil {
356 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
357 }
358
359 wantBundle := ctb1.Spec.TrustBundle
360
361 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
362 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
363 }
364 })
365
366 if err := kc.CertificatesV1alpha1().ClusterTrustBundles().Delete(ctx, ctb1.ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
367 t.Fatalf("Error while deleting the old CTB: %v", err)
368 }
369 if _, err := kc.CertificatesV1alpha1().ClusterTrustBundles().Create(ctx, ctb2, metav1.CreateOptions{}); err != nil {
370 t.Fatalf("Error while adding new CTB: %v", err)
371 }
372
373
374
375
376 time.Sleep(5 * time.Second)
377
378 t.Run("signer-a label-a should return the new certificate", func(t *testing.T) {
379 gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
380 if err != nil {
381 t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
382 }
383
384 wantBundle := ctb2.Spec.TrustBundle
385
386 if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
387 t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
388 }
389 })
390 }
391
392 func mustMakeRoot(t *testing.T, cn string) string {
393 pub, priv, err := ed25519.GenerateKey(rand.Reader)
394 if err != nil {
395 t.Fatalf("Error while generating key: %v", err)
396 }
397
398 template := &x509.Certificate{
399 SerialNumber: big.NewInt(0),
400 Subject: pkix.Name{
401 CommonName: cn,
402 },
403 IsCA: true,
404 BasicConstraintsValid: true,
405 }
406
407 cert, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv)
408 if err != nil {
409 t.Fatalf("Error while making certificate: %v", err)
410 }
411
412 return string(pem.EncodeToMemory(&pem.Block{
413 Type: "CERTIFICATE",
414 Headers: nil,
415 Bytes: cert,
416 }))
417 }
418
419 func mustMakeCTB(name, signerName string, labels map[string]string, bundle string) *certificatesv1alpha1.ClusterTrustBundle {
420 return &certificatesv1alpha1.ClusterTrustBundle{
421 ObjectMeta: metav1.ObjectMeta{
422 Name: name,
423 Labels: labels,
424 },
425 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
426 SignerName: signerName,
427 TrustBundle: bundle,
428 },
429 }
430 }
431
432 func diffBundles(a, b []byte) string {
433 var block *pem.Block
434
435 aBlocks := []*pem.Block{}
436 for {
437 block, a = pem.Decode(a)
438 if block == nil {
439 break
440 }
441 aBlocks = append(aBlocks, block)
442 }
443 sort.Slice(aBlocks, func(i, j int) bool {
444 if aBlocks[i].Type < aBlocks[j].Type {
445 return true
446 } else if aBlocks[i].Type == aBlocks[j].Type {
447 comp := bytes.Compare(aBlocks[i].Bytes, aBlocks[j].Bytes)
448 return comp <= 0
449 } else {
450 return false
451 }
452 })
453
454 bBlocks := []*pem.Block{}
455 for {
456 block, b = pem.Decode(b)
457 if block == nil {
458 break
459 }
460 bBlocks = append(bBlocks, block)
461 }
462 sort.Slice(bBlocks, func(i, j int) bool {
463 if bBlocks[i].Type < bBlocks[j].Type {
464 return true
465 } else if bBlocks[i].Type == bBlocks[j].Type {
466 comp := bytes.Compare(bBlocks[i].Bytes, bBlocks[j].Bytes)
467 return comp <= 0
468 } else {
469 return false
470 }
471 })
472
473 return cmp.Diff(aBlocks, bBlocks)
474 }
475
View as plain text