istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/trustbundle/trustbundle_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 trustbundle
    16  
    17  import (
    18  	"crypto/x509"
    19  	"fmt"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"os"
    23  	"path"
    24  	"sort"
    25  	"testing"
    26  	"time"
    27  
    28  	meshconfig "istio.io/api/mesh/v1alpha1"
    29  	"istio.io/istio/pkg/slices"
    30  	"istio.io/istio/pkg/test"
    31  	"istio.io/istio/pkg/test/env"
    32  	"istio.io/istio/pkg/test/util/retry"
    33  )
    34  
    35  func readCertFromFile(filename string) string {
    36  	csrBytes, err := os.ReadFile(filename)
    37  	if err != nil {
    38  		return ""
    39  	}
    40  	return string(csrBytes)
    41  }
    42  
    43  var (
    44  	malformedCert      = "Malformed"
    45  	rootCACert         = readCertFromFile(path.Join(env.IstioSrc, "samples/certs", "root-cert.pem"))
    46  	nonCaCert          = readCertFromFile(path.Join(env.IstioSrc, "samples/certs", "workload-bar-cert.pem"))
    47  	intermediateCACert = readCertFromFile(path.Join(env.IstioSrc, "samples/certs", "ca-cert.pem"))
    48  
    49  	// borrowed from the spiffe package, spiffe_test.go
    50  	validSpiffeX509Bundle = `
    51  {
    52  	"spiffe_sequence": 1,
    53  	"spiffe_refresh_hint": 450000,
    54  	"keys": [
    55  		{
    56  		"kty": "RSA",
    57  		"use": "x509-svid",
    58  		"n": "r10W2IcjT-vvSTpaFsS4OAcPOX87kw-zKZuJgXhxDhkOQyBdPZpUfK4H8yZ2q14Laym4bmiMLocIeGP70k` +
    59  		`UXcp9T4SP-P0DmBTPx3hVgP3YteHzaKsja056VtDs9kAufmFGemTSCenMt7aSlryUbLRO0H-__fTeNkCXR7uIoq` +
    60  		`RfU6jL0nN4EBh02q724iGuX6dpJcQam5bEJjq6Kn4Ry4qn1xHXqQXM4o2f6xDT13sp4U32stpmKh0HOd1WWKr0W` +
    61  		`RYnAh4GnToKr21QySZi9QWTea3zqeFmti-Isji1dKZkgZA2S89BdTWSLe6S_9lV0mtdXvDaT8RmaIX72jE_Abhn` +
    62  		`bUYV84pNYv-T2LtIKoi5PjWk0raaYoexAjtCWiu3PnizxjYOnNwpzgQN9Qh_rY2jv74cgzG50_Ft1B7XUiakNFx` +
    63  		`AiD1k6pNuiu4toY0Es7qt1yeqaC2zcIuuV7HUv1AbFBkIdF5quJHVtZ5AE1MCh1ipLPq-lIjmFdQKSRdbssVw8y` +
    64  		`q9FtFVyVqTz9GnQtoctCIPGQqmJDWmt8E7gjFhweUQo-fGgGuTlZRl9fiPQ6luPyGQ1WL6wH79G9eu4UtmgUDNw` +
    65  		`q7kpYq0_NQ5vw_1WQSY3LsPclfKzkZ-Lw2RVef-SFVVvUFMcd_3ALeeEnnSe4GSY-7vduPUAE5qMH7M",
    66  		"e": "AQAB",
    67  		"x5c": ["MIIGlDCCBHygAwIBAgIQEW25APa7S9Sj/Nj6V6GxQTANBgkqhkiG9w0BAQsFADCBwTELMAkGA1UEBhM` +
    68  		`CVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZS` +
    69  		`BMTEMxDjAMBgNVBAsTBUNsb3VkMWAwXgYDVQQDDFdpc3Rpb192MV9jbG91ZF93b3JrbG9hZF9yb290LXNpZ25lc` +
    70  		`i0wLTIwMTgtMDQtMjVUMTQ6MTE6MzMtMDc6MDAgSzoxLCAxOkg1MnZnd0VtM3RjOjA6MTgwIBcNMTgwNDI1MjEx` +
    71  		`MTMzWhgPMjExODA0MjUyMjExMzNaMIHBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1U` +
    72  		`EBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIExMQzEOMAwGA1UECxMFQ2xvdWQxYDBeBgNVBAMMV2` +
    73  		`lzdGlvX3YxX2Nsb3VkX3dvcmtsb2FkX3Jvb3Qtc2lnbmVyLTAtMjAxOC0wNC0yNVQxNDoxMTozMy0wNzowMCBLO` +
    74  		`jEsIDE6SDUydmd3RW0zdGM6MDoxODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK9dFtiHI0/r70k6` +
    75  		`WhbEuDgHDzl/O5MPsymbiYF4cQ4ZDkMgXT2aVHyuB/MmdqteC2spuG5ojC6HCHhj+9JFF3KfU+Ej/j9A5gUz8d4` +
    76  		`VYD92LXh82irI2tOelbQ7PZALn5hRnpk0gnpzLe2kpa8lGy0TtB/v/303jZAl0e7iKKkX1Ooy9JzeBAYdNqu9uI` +
    77  		`hrl+naSXEGpuWxCY6uip+EcuKp9cR16kFzOKNn+sQ09d7KeFN9rLaZiodBzndVliq9FkWJwIeBp06Cq9tUMkmYv` +
    78  		`UFk3mt86nhZrYviLI4tXSmZIGQNkvPQXU1ki3ukv/ZVdJrXV7w2k/EZmiF+9oxPwG4Z21GFfOKTWL/k9i7SCqIu` +
    79  		`T41pNK2mmKHsQI7Qlortz54s8Y2DpzcKc4EDfUIf62No7++HIMxudPxbdQe11ImpDRcQIg9ZOqTboruLaGNBLO6` +
    80  		`rdcnqmgts3CLrlex1L9QGxQZCHReariR1bWeQBNTAodYqSz6vpSI5hXUCkkXW7LFcPMqvRbRVclak8/Rp0LaHLQ` +
    81  		`iDxkKpiQ1prfBO4IxYcHlEKPnxoBrk5WUZfX4j0Opbj8hkNVi+sB+/RvXruFLZoFAzcKu5KWKtPzUOb8P9VkEmN` +
    82  		`y7D3JXys5Gfi8NkVXn/khVVb1BTHHf9wC3nhJ50nuBkmPu73bj1ABOajB+zAgMBAAGjgYMwgYAwDgYDVR0PAQH/` +
    83  		`BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ` +
    84  		`/VsuyjgRDAEmcZjyJ77619Js9ijAfBgNVHSMEGDAWgBQ/VsuyjgRDAEmcZjyJ77619Js9ijANBgkqhkiG9w0BAQ` +
    85  		`sFAAOCAgEAUc5QJOqxmMJY0E2rcHEWQYRah1vat3wuIHtEZ3SkSumyj+y9eyIHb9XTTyc4SyGyX1n8Rary8oSgQ` +
    86  		`V4cbyJTFXEEQOGLHB9/98EKThgJtfPsos2WKe/59S8yN05onpxcaL9y4S295Kv9kcSQxLm5UfjlqsKeHJZymvxi` +
    87  		`YzmBox7LA1zqcLYZvslJNkJxKAk5JA66iyDSQqOK7jIixn8pi305dFGCZglUFStwWqY6Rc9rR8EycVhSx2AhrvT` +
    88  		`7OQTVdKLfoKA84D8JZJPB7hrxqKf7JJFs87Kjt7c/5bXPFJ2osmjoNYnbHjiq64bh20sSCd630qvhhePLwjjOlB` +
    89  		`PiFyK36o/hQN871AEm1SCHy+aQcfJqF5KTgPnZQy5D+D/CGau+BfkO+WCGDVxRleYBJ4g2NbATolygB2KWXrj07` +
    90  		`U/WaWqV2hERbkmxXFh6cUdlkX2MeoG4v6ZD2OKAPx5DpJCfp0TEq6PznP+Z1mLd/ZjGsOF8R2WGQJEuU8HRzvsr` +
    91  		`0wsX9UyLMqf5XViDK11V/W+dcIvjHCayBpX2se3dfex5jFht+JcQc+iwB8caSXkR6tGSiargEtSJODORacO9IB8` +
    92  		`b6W8Sm//JWf/8zyiCcMm1i2yVVphwE1kczFwunAh0JB896VaXGVxXeKEAMQoXHjgDdCYp8/Etxjb8UkCmyjU="]
    93  		}
    94  	]
    95  }`
    96  )
    97  
    98  func TestIsEqSpliceStr(t *testing.T) {
    99  	testCases := []struct {
   100  		certs1  []string
   101  		certs2  []string
   102  		expSame bool
   103  	}{
   104  		{
   105  			certs1:  []string{"a", "b"},
   106  			certs2:  []string{},
   107  			expSame: false,
   108  		},
   109  		{
   110  			certs1:  []string{"a", "b"},
   111  			certs2:  []string{"b"},
   112  			expSame: false,
   113  		},
   114  		{
   115  			certs1:  []string{"a", "b"},
   116  			certs2:  []string{"a", "b"},
   117  			expSame: true,
   118  		},
   119  	}
   120  	for _, tc := range testCases {
   121  		certSame := slices.Equal(tc.certs1, tc.certs2)
   122  		if (certSame && !tc.expSame) || (!certSame && tc.expSame) {
   123  			t.Errorf("cert compare testcase failed. tc: %v", tc)
   124  		}
   125  	}
   126  }
   127  
   128  func TestVerifyTrustAnchor(t *testing.T) {
   129  	testCases := []struct {
   130  		errExp bool
   131  		cert   string
   132  	}{
   133  		{
   134  			cert:   rootCACert,
   135  			errExp: false,
   136  		},
   137  		{
   138  			cert:   malformedCert,
   139  			errExp: true,
   140  		},
   141  		{
   142  			cert:   nonCaCert,
   143  			errExp: true,
   144  		},
   145  		{
   146  			cert:   intermediateCACert,
   147  			errExp: false,
   148  		},
   149  	}
   150  	for i, tc := range testCases {
   151  		err := verifyTrustAnchor(tc.cert)
   152  		if tc.errExp && err == nil {
   153  			t.Errorf("test case %v failed. Expected Error but got none", i)
   154  		} else if !tc.errExp && err != nil {
   155  			t.Errorf("test case %v failed. Expected no error but got %v", i, err)
   156  		}
   157  	}
   158  }
   159  
   160  func TestUpdateTrustAnchor(t *testing.T) {
   161  	cbCounter := 0
   162  	tb := NewTrustBundle(nil)
   163  	tb.UpdateCb(func() { cbCounter++ })
   164  
   165  	var trustedCerts []string
   166  	var err error
   167  
   168  	// Add First Cert update
   169  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   170  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{rootCACert}},
   171  		Source:            SourceMeshConfig,
   172  	})
   173  	if err != nil {
   174  		t.Errorf("Basic trustbundle update test failed. Error: %v", err)
   175  	}
   176  	trustedCerts = tb.GetTrustBundle()
   177  	if !slices.Equal(trustedCerts, []string{rootCACert}) || cbCounter != 1 {
   178  		t.Errorf("Basic trustbundle update test failed. Callback value is %v", cbCounter)
   179  	}
   180  
   181  	// Add Second Cert update
   182  	// ensure intermediate CA certs accepted, it replaces the first completely, and lib dedupes duplicate cert
   183  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   184  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{intermediateCACert, intermediateCACert}},
   185  		Source:            SourceMeshConfig,
   186  	})
   187  	if err != nil {
   188  		t.Errorf("trustbundle intermediate cert update test failed. Error: %v", err)
   189  	}
   190  	trustedCerts = tb.GetTrustBundle()
   191  	if !slices.Equal(trustedCerts, []string{intermediateCACert}) || cbCounter != 2 {
   192  		t.Errorf("trustbundle intermediate cert update test failed. Callback value is %v", cbCounter)
   193  	}
   194  
   195  	// Try adding one more cert to a different source
   196  	// Ensure both certs are not present
   197  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   198  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{rootCACert}},
   199  		Source:            SourceIstioCA,
   200  	})
   201  	if err != nil {
   202  		t.Errorf("multicert update failed. Error: %v", err)
   203  	}
   204  	trustedCerts = tb.GetTrustBundle()
   205  	result := []string{intermediateCACert, rootCACert}
   206  	sort.Strings(result)
   207  	if !slices.Equal(trustedCerts, result) || cbCounter != 3 {
   208  		t.Errorf("multicert update failed. Callback value is %v", cbCounter)
   209  	}
   210  
   211  	// Try added same cert again. Ensure cb doesn't increment
   212  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   213  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{rootCACert}},
   214  		Source:            SourceIstioCA,
   215  	})
   216  	if err != nil {
   217  		t.Errorf("duplicate multicert update failed. Error: %v", err)
   218  	}
   219  	trustedCerts = tb.GetTrustBundle()
   220  	if !slices.Equal(trustedCerts, result) || cbCounter != 3 {
   221  		t.Errorf("duplicate multicert update failed. Callback value is %v", cbCounter)
   222  	}
   223  
   224  	// Try added one good cert, one bogus Cert
   225  	// Verify Update should not go through and no change to cb
   226  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   227  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{malformedCert}},
   228  		Source:            SourceIstioCA,
   229  	})
   230  	if err == nil {
   231  		t.Errorf("bad cert update failed. Expected error")
   232  	}
   233  	trustedCerts = tb.GetTrustBundle()
   234  	if !slices.Equal(trustedCerts, result) || cbCounter != 3 {
   235  		t.Errorf("bad cert update failed. Callback value is %v", cbCounter)
   236  	}
   237  
   238  	// Finally, remove all certs and ensure trustBundle is clean
   239  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   240  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{}},
   241  		Source:            SourceIstioCA,
   242  	})
   243  	if err != nil {
   244  		t.Errorf("clear cert update failed. Error: %v", err)
   245  	}
   246  	err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
   247  		TrustAnchorConfig: TrustAnchorConfig{Certs: []string{}},
   248  		Source:            SourceMeshConfig,
   249  	})
   250  	if err != nil {
   251  		t.Errorf("clear cert update failed. Error: %v", err)
   252  	}
   253  	trustedCerts = tb.GetTrustBundle()
   254  	if !slices.Equal(trustedCerts, []string{}) || cbCounter != 5 {
   255  		t.Errorf("cert removal update failed. Callback value is %v", cbCounter)
   256  	}
   257  }
   258  
   259  func expectTbCount(t *testing.T, tb *TrustBundle, expAnchorCount int, ti time.Duration, strPrefix string) {
   260  	t.Helper()
   261  	retry.UntilSuccessOrFail(t, func() error {
   262  		certs := tb.GetTrustBundle()
   263  		if len(certs) != expAnchorCount {
   264  			return fmt.Errorf("%s. Got %v, expected %v", strPrefix, len(certs), expAnchorCount)
   265  		}
   266  		return nil
   267  	}, retry.Timeout(ti))
   268  }
   269  
   270  func TestAddMeshConfigUpdate(t *testing.T) {
   271  	caCertPool, err := x509.SystemCertPool()
   272  	if err != nil {
   273  		t.Fatalf("failed to get SystemCertPool: %v", err)
   274  	}
   275  	stop := test.NewStop(t)
   276  
   277  	// Mock response from TLS Spiffe Server
   278  	validHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   279  		w.WriteHeader(http.StatusOK)
   280  		_, _ = w.Write([]byte(validSpiffeX509Bundle))
   281  	})
   282  
   283  	server1 := httptest.NewTLSServer(validHandler)
   284  	caCertPool.AddCert(server1.Certificate())
   285  	defer server1.Close()
   286  
   287  	server2 := httptest.NewTLSServer(validHandler)
   288  	caCertPool.AddCert(server2.Certificate())
   289  	defer server2.Close()
   290  
   291  	tb := NewTrustBundle(caCertPool)
   292  
   293  	// Change global remote timeout interval for the duration of the unit test
   294  	remoteTimeout = 30 * time.Millisecond
   295  
   296  	// Test1: Ensure that MeshConfig PEM certs are updated correctly
   297  	tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{
   298  		{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: rootCACert}},
   299  	}})
   300  	expectTbCount(t, tb, 1, 1*time.Second, "meshConfig pem trustAnchor not updated in bundle")
   301  
   302  	// Test2: Append server1 as spiffe endpoint to existing MeshConfig
   303  
   304  	// Start processing remote anchor update with poll frequency.
   305  	go tb.ProcessRemoteTrustAnchors(stop, 200*time.Millisecond)
   306  	tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{
   307  		{CertificateData: &meshconfig.MeshConfig_CertificateData_SpiffeBundleUrl{SpiffeBundleUrl: server1.Listener.Addr().String()}},
   308  		{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: rootCACert}},
   309  	}})
   310  	if !slices.Equal(tb.endpoints, []string{server1.Listener.Addr().String()}) {
   311  		t.Errorf("server1 endpoint not correctly updated in trustbundle. Trustbundle endpoints: %v", tb.endpoints)
   312  	}
   313  	// Check server1's anchor has been added along with meshConfig pem cert
   314  	expectTbCount(t, tb, 2, 3*time.Second, "server1(running) trustAnchor not updated in bundle")
   315  
   316  	// Test3: Stop server1
   317  	server1.Close()
   318  	// Check server1's valid trustAnchor is no longer in the trustbundle within poll frequency window
   319  	expectTbCount(t, tb, 1, 6*time.Second, "server1(stopped) trustAnchor not removed from bundle")
   320  
   321  	// Test4: Update with server1, server2 and mesh pem ca
   322  	tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{
   323  		{CertificateData: &meshconfig.MeshConfig_CertificateData_SpiffeBundleUrl{SpiffeBundleUrl: server2.Listener.Addr().String()}},
   324  		{CertificateData: &meshconfig.MeshConfig_CertificateData_SpiffeBundleUrl{SpiffeBundleUrl: server1.Listener.Addr().String()}},
   325  		{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: rootCACert}},
   326  	}})
   327  	if !slices.Equal(tb.endpoints, []string{server2.Listener.Addr().String(), server1.Listener.Addr().String()}) {
   328  		t.Errorf("server2 endpoint not correctly updated in trustbundle. Trustbundle endpoints: %v", tb.endpoints)
   329  	}
   330  	// Check only server 2's trustanchor is present along with meshConfig pem and not server 1 (since it is down)
   331  	expectTbCount(t, tb, 2, 3*time.Second, "server2(running) trustAnchor not updated in bundle")
   332  
   333  	// Test5. remove everything
   334  	tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{}})
   335  	expectTbCount(t, tb, 0, 3*time.Second, "trustAnchor not updated in bundle after meshConfig cleared")
   336  }