istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/nodeagent/cache/secretcache_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cache 16 17 import ( 18 "bytes" 19 "fmt" 20 "os" 21 "path/filepath" 22 "reflect" 23 "sort" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 29 "istio.io/istio/pkg/file" 30 "istio.io/istio/pkg/log" 31 "istio.io/istio/pkg/security" 32 "istio.io/istio/pkg/test/util/assert" 33 "istio.io/istio/pkg/test/util/retry" 34 "istio.io/istio/pkg/testcerts" 35 "istio.io/istio/security/pkg/nodeagent/caclient/providers/mock" 36 "istio.io/istio/security/pkg/nodeagent/cafile" 37 pkiutil "istio.io/istio/security/pkg/pki/util" 38 ) 39 40 func TestWorkloadAgentGenerateSecret(t *testing.T) { 41 fakeCACli, err := mock.NewMockCAClient(time.Hour, true) 42 var got, want []byte 43 if err != nil { 44 t.Fatalf("Error creating Mock CA client: %v", err) 45 } 46 sc := createCache(t, fakeCACli, func(resourceName string) {}, security.Options{WorkloadRSAKeySize: 2048}) 47 gotSecret, err := sc.GenerateSecret(security.WorkloadKeyCertResourceName) 48 if err != nil { 49 t.Fatalf("Failed to get secrets: %v", err) 50 } 51 52 if got, want := gotSecret.CertificateChain, []byte(strings.Join(fakeCACli.GeneratedCerts[0], "")); !bytes.Equal(got, want) { 53 t.Errorf("Got unexpected certificate chain #1. Got: %v, want: %v", string(got), string(want)) 54 } 55 56 gotSecretRoot, err := sc.GenerateSecret(security.RootCertReqResourceName) 57 if err != nil { 58 t.Fatalf("Failed to get secrets: %v", err) 59 } 60 // Root cert is the last element in the generated certs. 61 got, want = gotSecretRoot.RootCert, []byte(strings.TrimSuffix(fakeCACli.GeneratedCerts[0][2], "\n")) 62 if !bytes.Equal(got, want) { 63 t.Errorf("Got unexpected root certificate. Got: %v\n want: %v", string(got), string(want)) 64 } 65 66 // Try to get secret again, verify secret is not generated. 67 gotSecret, err = sc.GenerateSecret(security.WorkloadKeyCertResourceName) 68 if err != nil { 69 t.Fatalf("Failed to get secrets: %v", err) 70 } 71 72 if got, want := gotSecret.CertificateChain, []byte(strings.Join(fakeCACli.GeneratedCerts[0], "")); !bytes.Equal(got, want) { 73 t.Errorf("Got unexpected certificate chain #1. Got: %v, want: %v", string(got), string(want)) 74 } 75 76 // Root cert is the last element in the generated certs. 77 want = []byte(fakeCACli.GeneratedCerts[0][2]) 78 if got := sc.cache.GetRoot(); !bytes.Equal(got, want) { 79 t.Errorf("Got unexpected root certificate. Got: %v\n want: %v", string(got), string(want)) 80 } 81 } 82 83 func createCache(t *testing.T, caClient security.Client, notifyCb func(resourceName string), options security.Options) *SecretManagerClient { 84 t.Helper() 85 sc, err := NewSecretManagerClient(caClient, &options) 86 if err != nil { 87 t.Fatal(err) 88 } 89 sc.RegisterSecretHandler(notifyCb) 90 t.Cleanup(sc.Close) 91 return sc 92 } 93 94 type UpdateTracker struct { 95 t *testing.T 96 hits map[string]int 97 mu sync.Mutex 98 } 99 100 func NewUpdateTracker(t *testing.T) *UpdateTracker { 101 return &UpdateTracker{ 102 t: t, 103 hits: map[string]int{}, 104 mu: sync.Mutex{}, 105 } 106 } 107 108 func (u *UpdateTracker) Callback(name string) { 109 u.mu.Lock() 110 defer u.mu.Unlock() 111 u.hits[name]++ 112 } 113 114 func (u *UpdateTracker) Expect(want map[string]int) { 115 u.t.Helper() 116 retry.UntilSuccessOrFail(u.t, func() error { 117 u.mu.Lock() 118 defer u.mu.Unlock() 119 if !reflect.DeepEqual(u.hits, want) { 120 return fmt.Errorf("wanted %+v got %+v", want, u.hits) 121 } 122 return nil 123 }, retry.Timeout(time.Second*5)) 124 } 125 126 func (u *UpdateTracker) Reset() { 127 u.mu.Lock() 128 defer u.mu.Unlock() 129 u.hits = map[string]int{} 130 } 131 132 func TestWorkloadAgentRefreshSecret(t *testing.T) { 133 cacheLog.SetOutputLevel(log.DebugLevel) 134 fakeCACli, err := mock.NewMockCAClient(time.Millisecond*200, false) 135 if err != nil { 136 t.Fatalf("Error creating Mock CA client: %v", err) 137 } 138 u := NewUpdateTracker(t) 139 sc := createCache(t, fakeCACli, u.Callback, security.Options{WorkloadRSAKeySize: 2048}) 140 141 _, err = sc.GenerateSecret(security.WorkloadKeyCertResourceName) 142 if err != nil { 143 t.Fatalf("failed to get secrets: %v", err) 144 } 145 146 // First update will trigger root cert immediately, then workload cert once it expires in 200ms 147 u.Expect(map[string]int{security.WorkloadKeyCertResourceName: 1, security.RootCertReqResourceName: 1}) 148 149 _, err = sc.GenerateSecret(security.WorkloadKeyCertResourceName) 150 if err != nil { 151 t.Fatalf("failed to get secrets: %v", err) 152 } 153 154 u.Expect(map[string]int{security.WorkloadKeyCertResourceName: 2, security.RootCertReqResourceName: 1}) 155 } 156 157 // Compare times, with 5s error allowance 158 func almostEqual(t1, t2 time.Duration) bool { 159 diff := t1 - t2 160 if diff < 0 { 161 diff *= -1 162 } 163 return diff < time.Second*5 164 } 165 166 func TestRotateTime(t *testing.T) { 167 now := time.Now() 168 cases := []struct { 169 name string 170 created time.Time 171 expire time.Time 172 gracePeriod float64 173 expected time.Duration 174 }{ 175 { 176 name: "already expired", 177 created: now.Add(-time.Second * 2), 178 expire: now.Add(-time.Second), 179 gracePeriod: 0.5, 180 expected: 0, 181 }, 182 { 183 name: "grace period .50", 184 created: now, 185 expire: now.Add(time.Hour), 186 gracePeriod: 0.5, 187 expected: time.Minute * 30, 188 }, 189 { 190 name: "grace period .25", 191 created: now, 192 expire: now.Add(time.Hour), 193 gracePeriod: 0.25, 194 expected: time.Minute * 45, 195 }, 196 { 197 name: "grace period .75", 198 created: now, 199 expire: now.Add(time.Hour), 200 gracePeriod: 0.75, 201 expected: time.Minute * 15, 202 }, 203 { 204 name: "grace period 1", 205 created: now, 206 expire: now.Add(time.Hour), 207 gracePeriod: 1, 208 expected: 0, 209 }, 210 { 211 name: "grace period 0", 212 created: now, 213 expire: now.Add(time.Hour), 214 gracePeriod: 0, 215 expected: time.Hour, 216 }, 217 { 218 name: "grace period .25 shifted", 219 created: now.Add(time.Minute * 30), 220 expire: now.Add(time.Minute * 90), 221 gracePeriod: 0.25, 222 expected: time.Minute * 75, 223 }, 224 } 225 for _, tt := range cases { 226 t.Run(tt.name, func(t *testing.T) { 227 sc := &SecretManagerClient{configOptions: &security.Options{SecretRotationGracePeriodRatio: tt.gracePeriod}} 228 got := rotateTime(security.SecretItem{CreatedTime: tt.created, ExpireTime: tt.expire}, sc.configOptions.SecretRotationGracePeriodRatio) 229 if !almostEqual(got, tt.expected) { 230 t.Fatalf("expected %v got %v", tt.expected, got) 231 } 232 }) 233 } 234 } 235 236 func TestRootCertificateExists(t *testing.T) { 237 testCases := map[string]struct { 238 certPath string 239 expectResult bool 240 }{ 241 "cert not exist": { 242 certPath: "./invalid-path/invalid-file", 243 expectResult: false, 244 }, 245 "cert valid": { 246 certPath: "./testdata/cert-chain.pem", 247 expectResult: true, 248 }, 249 } 250 251 sc := createCache(t, nil, func(resourceName string) {}, security.Options{}) 252 for _, tc := range testCases { 253 ret := sc.rootCertificateExist(tc.certPath) 254 if tc.expectResult != ret { 255 t.Errorf("unexpected result is returned!") 256 } 257 } 258 } 259 260 func TestKeyCertificateExist(t *testing.T) { 261 testCases := map[string]struct { 262 certPath string 263 keyPath string 264 expectResult bool 265 }{ 266 "cert not exist": { 267 certPath: "./invalid-path/invalid-file", 268 keyPath: "./testdata/cert-chain.pem", 269 expectResult: false, 270 }, 271 "key not exist": { 272 certPath: "./testdata/cert-chain.pem", 273 keyPath: "./invalid-path/invalid-file", 274 expectResult: false, 275 }, 276 "key and cert valid": { 277 certPath: "./testdata/cert-chain.pem", 278 keyPath: "./testdata/cert-chain.pem", 279 expectResult: true, 280 }, 281 } 282 sc := createCache(t, nil, func(resourceName string) {}, security.Options{}) 283 for _, tc := range testCases { 284 ret := sc.keyCertificateExist(tc.certPath, tc.keyPath) 285 if tc.expectResult != ret { 286 t.Errorf("unexpected result is returned!") 287 } 288 } 289 } 290 291 func setupTestDir(t *testing.T, sc *SecretManagerClient) { 292 dir := t.TempDir() 293 294 for _, f := range []string{"root-cert.pem", "key.pem", "cert-chain.pem"} { 295 if err := file.AtomicCopy(filepath.Join("./testdata", f), dir, f); err != nil { 296 t.Fatal(err) 297 } 298 } 299 sc.existingCertificateFile = security.SdsCertificateConfig{ 300 CertificatePath: filepath.Join(dir, "cert-chain.pem"), 301 PrivateKeyPath: filepath.Join(dir, "key.pem"), 302 CaCertificatePath: filepath.Join(dir, "root-cert.pem"), 303 } 304 } 305 306 // TestWorkloadAgentGenerateSecretFromFile tests generating secrets from existing files on a 307 // secretcache instance. 308 func TestFileSecrets(t *testing.T) { 309 t.Run("file", func(t *testing.T) { 310 runFileAgentTest(t, false) 311 }) 312 t.Run("file sds", func(t *testing.T) { 313 runFileAgentTest(t, true) 314 }) 315 } 316 317 func runFileAgentTest(t *testing.T, sds bool) { 318 fakeCACli, err := mock.NewMockCAClient(time.Hour, false) 319 if err != nil { 320 t.Fatalf("Error creating Mock CA client: %v", err) 321 } 322 opt := security.Options{} 323 324 u := NewUpdateTracker(t) 325 sc := createCache(t, fakeCACli, u.Callback, opt) 326 327 setupTestDir(t, sc) 328 329 workloadResource := security.WorkloadKeyCertResourceName 330 rootResource := security.RootCertReqResourceName 331 if sds { 332 workloadResource = sc.existingCertificateFile.GetResourceName() 333 rootResource = sc.existingCertificateFile.GetRootResourceName() 334 } 335 336 certchain, err := os.ReadFile(sc.existingCertificateFile.CertificatePath) 337 if err != nil { 338 t.Fatalf("Error reading the cert chain file: %v", err) 339 } 340 privateKey, err := os.ReadFile(sc.existingCertificateFile.PrivateKeyPath) 341 if err != nil { 342 t.Fatalf("Error reading the private key file: %v", err) 343 } 344 rootCert, err := os.ReadFile(sc.existingCertificateFile.CaCertificatePath) 345 if err != nil { 346 t.Fatalf("Error reading the root cert file: %v", err) 347 } 348 349 // Check we can load key, cert, and root 350 checkSecret(t, sc, workloadResource, security.SecretItem{ 351 ResourceName: workloadResource, 352 CertificateChain: certchain, 353 PrivateKey: privateKey, 354 }) 355 checkSecret(t, sc, rootResource, security.SecretItem{ 356 ResourceName: rootResource, 357 RootCert: rootCert, 358 }) 359 // We shouldn't get an pushes; these only happen on changes 360 u.Expect(map[string]int{}) 361 u.Reset() 362 363 if err := file.AtomicWrite(sc.existingCertificateFile.CertificatePath, testcerts.RotatedCert, os.FileMode(0o644)); err != nil { 364 t.Fatal(err) 365 } 366 if err := file.AtomicWrite(sc.existingCertificateFile.PrivateKeyPath, testcerts.RotatedKey, os.FileMode(0o644)); err != nil { 367 t.Fatal(err) 368 } 369 370 // Expect update callback 371 u.Expect(map[string]int{workloadResource: 1}) 372 // On the next generate call, we should get the new cert 373 checkSecret(t, sc, workloadResource, security.SecretItem{ 374 ResourceName: workloadResource, 375 CertificateChain: testcerts.RotatedCert, 376 PrivateKey: testcerts.RotatedKey, 377 }) 378 379 if err := file.AtomicWrite(sc.existingCertificateFile.PrivateKeyPath, testcerts.RotatedKey, os.FileMode(0o644)); err != nil { 380 t.Fatal(err) 381 } 382 // We do NOT expect update callback. We only watch the cert file, since the key and cert must be updated 383 // in tandem. 384 u.Expect(map[string]int{workloadResource: 1}) 385 u.Reset() 386 387 checkSecret(t, sc, workloadResource, security.SecretItem{ 388 ResourceName: workloadResource, 389 CertificateChain: testcerts.RotatedCert, 390 PrivateKey: testcerts.RotatedKey, 391 }) 392 393 if err := file.AtomicWrite(sc.existingCertificateFile.CaCertificatePath, testcerts.CACert, os.FileMode(0o644)); err != nil { 394 t.Fatal(err) 395 } 396 // We expect to get an update notification, and the new root cert to be read 397 u.Expect(map[string]int{rootResource: 1}) 398 u.Reset() 399 400 checkSecret(t, sc, rootResource, security.SecretItem{ 401 ResourceName: rootResource, 402 RootCert: testcerts.CACert, 403 }) 404 405 // Remove the file and add it again and validate that proxy is updated with new cert. 406 if err := os.Remove(sc.existingCertificateFile.CaCertificatePath); err != nil { 407 t.Fatal(err) 408 } 409 410 if err := file.AtomicWrite(sc.existingCertificateFile.CaCertificatePath, testcerts.CACert, os.FileMode(0o644)); err != nil { 411 t.Fatal(err) 412 } 413 // We expect to get an update notification, and the new root cert to be read 414 // We do not expect update callback for REMOVE events. 415 u.Expect(map[string]int{rootResource: 1}) 416 417 checkSecret(t, sc, rootResource, security.SecretItem{ 418 ResourceName: rootResource, 419 RootCert: testcerts.CACert, 420 }) 421 422 // Double check workload cert is untouched 423 checkSecret(t, sc, workloadResource, security.SecretItem{ 424 ResourceName: workloadResource, 425 CertificateChain: testcerts.RotatedCert, 426 PrivateKey: testcerts.RotatedKey, 427 }) 428 } 429 430 func checkSecret(t *testing.T, sc *SecretManagerClient, name string, expected security.SecretItem) { 431 t.Helper() 432 got, err := sc.GenerateSecret(name) 433 if err != nil { 434 t.Fatalf("Failed to get secrets: %v", err) 435 } 436 verifySecret(t, got, &expected) 437 } 438 439 func TestWorkloadAgentGenerateSecretFromFileOverSdsWithBogusFiles(t *testing.T) { 440 originalTimeout := totalTimeout 441 totalTimeout = time.Millisecond * 1 442 defer func() { 443 totalTimeout = originalTimeout 444 }() 445 446 u := NewUpdateTracker(t) 447 sc := createCache(t, nil, u.Callback, security.Options{}) 448 rootCertPath, _ := filepath.Abs("./testdata/root-cert-bogus.pem") 449 keyPath, _ := filepath.Abs("./testdata/key-bogus.pem") 450 certChainPath, _ := filepath.Abs("./testdata/cert-chain-bogus.pem") 451 452 resource := fmt.Sprintf("file-cert:%s~%s", certChainPath, keyPath) 453 454 gotSecret, err := sc.GenerateSecret(resource) 455 456 if err == nil { 457 t.Fatalf("expected to get error") 458 } 459 460 if gotSecret != nil { 461 t.Fatalf("Expected to get nil secret but got %v", gotSecret) 462 } 463 464 rootResource := "file-root:" + rootCertPath 465 gotSecretRoot, err := sc.GenerateSecret(rootResource) 466 467 if err == nil { 468 t.Fatalf("Expected to get error, but did not get") 469 } 470 if !strings.Contains(err.Error(), "no such file or directory") { 471 t.Fatalf("Expected file not found error, but got %v", err) 472 } 473 if gotSecretRoot != nil { 474 t.Fatalf("Expected to get nil secret but got %v", gotSecret) 475 } 476 477 u.Expect(map[string]int{}) 478 } 479 480 func verifySecret(t *testing.T, gotSecret *security.SecretItem, expectedSecret *security.SecretItem) { 481 if expectedSecret.ResourceName != gotSecret.ResourceName { 482 t.Fatalf("resource name:: expected %s but got %s", expectedSecret.ResourceName, 483 gotSecret.ResourceName) 484 } 485 cfg, ok := security.SdsCertificateConfigFromResourceName(expectedSecret.ResourceName) 486 if expectedSecret.ResourceName == security.RootCertReqResourceName || (ok && cfg.IsRootCertificate()) { 487 if !bytes.Equal(expectedSecret.RootCert, gotSecret.RootCert) { 488 t.Fatalf("root cert: expected %v but got %v", expectedSecret.RootCert, 489 gotSecret.RootCert) 490 } 491 } else { 492 if !bytes.Equal(expectedSecret.CertificateChain, gotSecret.CertificateChain) { 493 t.Fatalf("cert chain: expected %s but got %s", string(expectedSecret.CertificateChain), 494 string(gotSecret.CertificateChain)) 495 } 496 if !bytes.Equal(expectedSecret.PrivateKey, gotSecret.PrivateKey) { 497 t.Fatalf("private key: expected %s but got %s", string(expectedSecret.PrivateKey), string(gotSecret.PrivateKey)) 498 } 499 } 500 if !expectedSecret.ExpireTime.IsZero() && expectedSecret.ExpireTime != gotSecret.ExpireTime { 501 t.Fatalf("expiration: expected %v but got %v", 502 expectedSecret.ExpireTime, gotSecret.ExpireTime) 503 } 504 } 505 506 func TestConcatCerts(t *testing.T) { 507 cases := []struct { 508 name string 509 certs []string 510 expected string 511 }{ 512 { 513 name: "no certs", 514 certs: []string{}, 515 expected: "", 516 }, 517 { 518 name: "single cert", 519 certs: []string{"a"}, 520 expected: "a", 521 }, 522 { 523 name: "multiple certs", 524 certs: []string{"a", "b"}, 525 expected: "a\nb", 526 }, 527 { 528 name: "existing newline", 529 certs: []string{"a\n", "b"}, 530 expected: "a\nb", 531 }, 532 } 533 534 for _, c := range cases { 535 t.Run(c.name, func(t *testing.T) { 536 result := string(concatCerts(c.certs)) 537 if result != c.expected { 538 t.Fatalf("expected %q, got %q", c.expected, result) 539 } 540 }) 541 } 542 } 543 544 func TestProxyConfigAnchors(t *testing.T) { 545 fakeCACli, err := mock.NewMockCAClient(time.Hour, false) 546 if err != nil { 547 t.Fatalf("Error creating Mock CA client: %v", err) 548 } 549 u := NewUpdateTracker(t) 550 551 sc := createCache(t, fakeCACli, u.Callback, security.Options{WorkloadRSAKeySize: 2048}) 552 _, err = sc.GenerateSecret(security.WorkloadKeyCertResourceName) 553 if err != nil { 554 t.Errorf("failed to generate certificate for trustAnchor test case") 555 } 556 // Ensure Root cert call back gets invoked once 557 u.Expect(map[string]int{security.RootCertReqResourceName: 1}) 558 u.Reset() 559 560 caClientRootCert := []byte(strings.TrimRight(fakeCACli.GeneratedCerts[0][2], "\n")) 561 // Ensure that contents of the rootCert are correct. 562 checkSecret(t, sc, security.RootCertReqResourceName, security.SecretItem{ 563 ResourceName: security.RootCertReqResourceName, 564 RootCert: caClientRootCert, 565 }) 566 567 rootCert, err := os.ReadFile(filepath.Join("./testdata", "root-cert.pem")) 568 if err != nil { 569 t.Fatalf("Error reading the root cert file: %v", err) 570 } 571 572 // Update the proxyConfig with certs 573 sc.UpdateConfigTrustBundle(rootCert) 574 575 // Ensure Callback gets invoked when updating proxyConfig trust bundle 576 u.Expect(map[string]int{security.RootCertReqResourceName: 1, security.WorkloadKeyCertResourceName: 1}) 577 u.Reset() 578 579 concatCerts := func(certs ...string) []byte { 580 expectedRootBytes := []byte{} 581 sort.Strings(certs) 582 for _, cert := range certs { 583 expectedRootBytes = pkiutil.AppendCertByte(expectedRootBytes, []byte(cert)) 584 } 585 return expectedRootBytes 586 } 587 588 expectedCerts := concatCerts(string(rootCert), string(caClientRootCert)) 589 // Ensure that contents of the rootCert are correct. 590 checkSecret(t, sc, security.RootCertReqResourceName, security.SecretItem{ 591 ResourceName: security.RootCertReqResourceName, 592 RootCert: expectedCerts, 593 }) 594 595 // Add Duplicates 596 sc.UpdateConfigTrustBundle(expectedCerts) 597 // Ensure that contents of the rootCert are correct without the duplicate caClientRootCert 598 checkSecret(t, sc, security.RootCertReqResourceName, security.SecretItem{ 599 ResourceName: security.RootCertReqResourceName, 600 RootCert: expectedCerts, 601 }) 602 603 if !bytes.Equal(sc.mergeConfigTrustBundle([]string{string(caClientRootCert), string(rootCert)}), expectedCerts) { 604 t.Fatalf("deduplicate test failed!") 605 } 606 607 // Update the proxyConfig with fakeCaClient certs 608 sc.UpdateConfigTrustBundle(caClientRootCert) 609 setupTestDir(t, sc) 610 611 rootCert, err = os.ReadFile(sc.existingCertificateFile.CaCertificatePath) 612 if err != nil { 613 t.Fatalf("Error reading the root cert file: %v", err) 614 } 615 616 // Check request for workload root-certs merges configuration with ProxyConfig TrustAnchor 617 checkSecret(t, sc, security.RootCertReqResourceName, security.SecretItem{ 618 ResourceName: security.RootCertReqResourceName, 619 RootCert: concatCerts(string(rootCert), string(caClientRootCert)), 620 }) 621 622 // Check request for non-workload root-certs doesn't configuration with ProxyConfig TrustAnchor 623 checkSecret(t, sc, sc.existingCertificateFile.GetRootResourceName(), security.SecretItem{ 624 ResourceName: sc.existingCertificateFile.GetRootResourceName(), 625 RootCert: rootCert, 626 }) 627 } 628 629 func TestProxyConfigAnchorsTriggerWorkloadCertUpdate(t *testing.T) { 630 cacheLog.SetOutputLevel(log.DebugLevel) 631 fakeCACli, err := mock.NewMockCAClient(time.Millisecond*200, false) 632 if err != nil { 633 t.Fatalf("Error creating Mock CA client: %v", err) 634 } 635 u := NewUpdateTracker(t) 636 sc := createCache(t, fakeCACli, u.Callback, security.Options{WorkloadRSAKeySize: 2048}) 637 _, err = sc.GenerateSecret(security.WorkloadKeyCertResourceName) 638 if err != nil { 639 t.Errorf("failed to generate certificate for trustAnchor test case") 640 } 641 // Ensure Root cert call back gets invoked once, then workload cert once it expires in 200ms 642 u.Expect(map[string]int{security.RootCertReqResourceName: 1, security.WorkloadKeyCertResourceName: 1}) 643 u.Reset() 644 645 rootCert, err := os.ReadFile(filepath.Join("./testdata", "root-cert.pem")) 646 if err != nil { 647 t.Fatalf("Error reading the root cert file: %v", err) 648 } 649 // Update the proxyConfig with certs 650 sc.UpdateConfigTrustBundle(rootCert) 651 652 assert.Equal(t, sc.cache.GetWorkload(), nil) 653 // Ensure Callback gets invoked when updating proxyConfig trust bundle 654 u.Expect(map[string]int{security.RootCertReqResourceName: 1, security.WorkloadKeyCertResourceName: 1}) 655 u.Reset() 656 657 rotateTime = func(_ security.SecretItem, _ float64) time.Duration { 658 return time.Millisecond * 200 659 } 660 fakeCACli, err = mock.NewMockCAClient(time.Millisecond*200, false) 661 if err != nil { 662 t.Fatalf("Error creating Mock CA client: %v", err) 663 } 664 sc = createCache(t, fakeCACli, u.Callback, security.Options{WorkloadRSAKeySize: 2048}) 665 _, err = sc.GenerateSecret(security.WorkloadKeyCertResourceName) 666 if err != nil { 667 t.Errorf("failed to generate certificate for trustAnchor test case") 668 } 669 // Immediately update the proxyConfig root cert 670 sc.UpdateConfigTrustBundle(rootCert) 671 time.Sleep(time.Millisecond * 200) 672 // The rotation task actually will not call `OnSecretUpdate`, otherwise the WorkloadKeyCertResourceName event number should be 2 673 u.Expect(map[string]int{security.RootCertReqResourceName: 2, security.WorkloadKeyCertResourceName: 1}) 674 } 675 676 func TestOSCACertGenerateSecret(t *testing.T) { 677 fakeCACli, err := mock.NewMockCAClient(time.Hour, false) 678 if err != nil { 679 t.Fatalf("Error creating Mock CA client: %v", err) 680 } 681 682 sc := createCache(t, fakeCACli, func(resourceName string) {}, security.Options{CARootPath: cafile.CACertFilePath}) 683 certPath := security.GetOSRootFilePath() 684 expected, err := sc.GenerateSecret("file-root:" + certPath) 685 if err != nil { 686 t.Fatalf("Could not get OS Cert: %v", err) 687 } 688 689 gotSecret, err := sc.GenerateSecret(security.FileRootSystemCACert) 690 if err != nil { 691 t.Fatalf("Error using %s: %v", security.FileRootSystemCACert, err) 692 } 693 if !bytes.Equal(gotSecret.RootCert, expected.RootCert) { 694 t.Fatal("Certs did not match") 695 } 696 } 697 698 func TestOSCACertGenerateSecretEmpty(t *testing.T) { 699 fakeCACli, err := mock.NewMockCAClient(time.Hour, false) 700 if err != nil { 701 t.Fatalf("Error creating Mock CA client: %v", err) 702 } 703 704 sc := createCache(t, fakeCACli, func(resourceName string) {}, security.Options{WorkloadRSAKeySize: 2048}) 705 certPath := security.GetOSRootFilePath() 706 expected, err := sc.GenerateSecret("file-root:" + certPath) 707 if err != nil { 708 t.Fatalf(": %v", err) 709 } 710 711 gotSecret, err := sc.GenerateSecret(security.FileRootSystemCACert) 712 if err != nil && len(gotSecret.RootCert) != 0 { 713 t.Fatalf("Error using %s: %v", security.FileRootSystemCACert, err) 714 } 715 if bytes.Equal(gotSecret.RootCert, expected.RootCert) { 716 t.Fatal("Certs did match") 717 } 718 } 719 720 func TestTryAddFileWatcher(t *testing.T) { 721 var ( 722 dummyResourceName = "default" 723 relativeFilePath = "./testdata/file-to-watch.txt" 724 ) 725 absFilePathOfRelativeFilePath, err := filepath.Abs(relativeFilePath) 726 if err != nil { 727 t.Fatalf("unable to get absolute path to file %s, err: %v", relativeFilePath, err) 728 } 729 fakeCACli, err := mock.NewMockCAClient(time.Hour, true) 730 if err != nil { 731 t.Fatalf("unable to create fake mock ca client: %v", err) 732 } 733 sc := createCache(t, fakeCACli, func(resourceName string) {}, security.Options{WorkloadRSAKeySize: 2048}) 734 cases := []struct { 735 name string 736 filePath string 737 expFilePathToWatch string 738 expErr error 739 }{ 740 { 741 name: "Given a file is expected to be watched, " + 742 "When tryAddFileWatcher is invoked, with file path which does not start with /" + 743 "Then tryAddFileWatcher should watch on the absolute path", 744 filePath: relativeFilePath, 745 expFilePathToWatch: absFilePathOfRelativeFilePath, 746 expErr: nil, 747 }, 748 } 749 750 for _, c := range cases { 751 t.Run(c.name, func(t *testing.T) { 752 err = sc.tryAddFileWatcher(c.filePath, dummyResourceName) 753 if err != c.expErr { 754 t.Fatalf("expected: %v, got: %v", c.expErr, err) 755 } 756 t.Logf("file watch: %v\n", sc.certWatcher.WatchList()) 757 if c.expErr == nil && len(sc.certWatcher.WatchList()) != 1 { 758 t.Fatalf("expected certWatcher to watch 1 file, but it is watching: %d files", len(sc.certWatcher.WatchList())) 759 } 760 for _, v := range sc.certWatcher.WatchList() { 761 if v != c.expFilePathToWatch { 762 t.Fatalf( 763 "expected certWatcher to watch on: %s, but it is watching on: %s", 764 c.expFilePathToWatch, v) 765 } 766 } 767 }) 768 } 769 }