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 }