k8s.io/kubernetes@v1.29.3/pkg/kubelet/clustertrustbundle/clustertrustbundle_manager_test.go (about) 1 /* 2 Copyright 2023 The Kubernetes 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 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 { // EQUALS 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 // We need to sleep long enough for the informer to notice the new 187 // ClusterTrustBundle, but much less than the 5 minutes of the cache TTL. 188 // This shows us that the informer is properly clearing the cache. 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 { // EQUALS 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 // We need to sleep long enough for the informer to notice the new 374 // ClusterTrustBundle, but much less than the 5 minutes of the cache TTL. 375 // This shows us that the informer is properly clearing the cache. 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 }