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  }