blob: e55d459a97fd8370ec9c58233e541e2695749d93 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package trustbundle
import (
"crypto/x509"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path"
"sort"
"testing"
"time"
)
import (
meshconfig "istio.io/api/mesh/v1alpha1"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test/env"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/retry"
)
func readCertFromFile(filename string) string {
csrBytes, err := os.ReadFile(filename)
if err != nil {
return ""
}
return string(csrBytes)
}
var (
malformedCert = "Malformed"
rootCACert = readCertFromFile(path.Join(env.IstioSrc, "samples/certs", "root-cert.pem"))
nonCaCert = readCertFromFile(path.Join(env.IstioSrc, "samples/certs", "workload-bar-cert.pem"))
intermediateCACert = readCertFromFile(path.Join(env.IstioSrc, "samples/certs", "ca-cert.pem"))
// borrowed from the spiffe package, spiffe_test.go
validSpiffeX509Bundle = `
{
"spiffe_sequence": 1,
"spiffe_refresh_hint": 450000,
"keys": [
{
"kty": "RSA",
"use": "x509-svid",
"n": "r10W2IcjT-vvSTpaFsS4OAcPOX87kw-zKZuJgXhxDhkOQyBdPZpUfK4H8yZ2q14Laym4bmiMLocIeGP70k` +
`UXcp9T4SP-P0DmBTPx3hVgP3YteHzaKsja056VtDs9kAufmFGemTSCenMt7aSlryUbLRO0H-__fTeNkCXR7uIoq` +
`RfU6jL0nN4EBh02q724iGuX6dpJcQam5bEJjq6Kn4Ry4qn1xHXqQXM4o2f6xDT13sp4U32stpmKh0HOd1WWKr0W` +
`RYnAh4GnToKr21QySZi9QWTea3zqeFmti-Isji1dKZkgZA2S89BdTWSLe6S_9lV0mtdXvDaT8RmaIX72jE_Abhn` +
`bUYV84pNYv-T2LtIKoi5PjWk0raaYoexAjtCWiu3PnizxjYOnNwpzgQN9Qh_rY2jv74cgzG50_Ft1B7XUiakNFx` +
`AiD1k6pNuiu4toY0Es7qt1yeqaC2zcIuuV7HUv1AbFBkIdF5quJHVtZ5AE1MCh1ipLPq-lIjmFdQKSRdbssVw8y` +
`q9FtFVyVqTz9GnQtoctCIPGQqmJDWmt8E7gjFhweUQo-fGgGuTlZRl9fiPQ6luPyGQ1WL6wH79G9eu4UtmgUDNw` +
`q7kpYq0_NQ5vw_1WQSY3LsPclfKzkZ-Lw2RVef-SFVVvUFMcd_3ALeeEnnSe4GSY-7vduPUAE5qMH7M",
"e": "AQAB",
"x5c": ["MIIGlDCCBHygAwIBAgIQEW25APa7S9Sj/Nj6V6GxQTANBgkqhkiG9w0BAQsFADCBwTELMAkGA1UEBhM` +
`CVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZS` +
`BMTEMxDjAMBgNVBAsTBUNsb3VkMWAwXgYDVQQDDFdpc3Rpb192MV9jbG91ZF93b3JrbG9hZF9yb290LXNpZ25lc` +
`i0wLTIwMTgtMDQtMjVUMTQ6MTE6MzMtMDc6MDAgSzoxLCAxOkg1MnZnd0VtM3RjOjA6MTgwIBcNMTgwNDI1MjEx` +
`MTMzWhgPMjExODA0MjUyMjExMzNaMIHBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1U` +
`EBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIExMQzEOMAwGA1UECxMFQ2xvdWQxYDBeBgNVBAMMV2` +
`lzdGlvX3YxX2Nsb3VkX3dvcmtsb2FkX3Jvb3Qtc2lnbmVyLTAtMjAxOC0wNC0yNVQxNDoxMTozMy0wNzowMCBLO` +
`jEsIDE6SDUydmd3RW0zdGM6MDoxODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK9dFtiHI0/r70k6` +
`WhbEuDgHDzl/O5MPsymbiYF4cQ4ZDkMgXT2aVHyuB/MmdqteC2spuG5ojC6HCHhj+9JFF3KfU+Ej/j9A5gUz8d4` +
`VYD92LXh82irI2tOelbQ7PZALn5hRnpk0gnpzLe2kpa8lGy0TtB/v/303jZAl0e7iKKkX1Ooy9JzeBAYdNqu9uI` +
`hrl+naSXEGpuWxCY6uip+EcuKp9cR16kFzOKNn+sQ09d7KeFN9rLaZiodBzndVliq9FkWJwIeBp06Cq9tUMkmYv` +
`UFk3mt86nhZrYviLI4tXSmZIGQNkvPQXU1ki3ukv/ZVdJrXV7w2k/EZmiF+9oxPwG4Z21GFfOKTWL/k9i7SCqIu` +
`T41pNK2mmKHsQI7Qlortz54s8Y2DpzcKc4EDfUIf62No7++HIMxudPxbdQe11ImpDRcQIg9ZOqTboruLaGNBLO6` +
`rdcnqmgts3CLrlex1L9QGxQZCHReariR1bWeQBNTAodYqSz6vpSI5hXUCkkXW7LFcPMqvRbRVclak8/Rp0LaHLQ` +
`iDxkKpiQ1prfBO4IxYcHlEKPnxoBrk5WUZfX4j0Opbj8hkNVi+sB+/RvXruFLZoFAzcKu5KWKtPzUOb8P9VkEmN` +
`y7D3JXys5Gfi8NkVXn/khVVb1BTHHf9wC3nhJ50nuBkmPu73bj1ABOajB+zAgMBAAGjgYMwgYAwDgYDVR0PAQH/` +
`BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ` +
`/VsuyjgRDAEmcZjyJ77619Js9ijAfBgNVHSMEGDAWgBQ/VsuyjgRDAEmcZjyJ77619Js9ijANBgkqhkiG9w0BAQ` +
`sFAAOCAgEAUc5QJOqxmMJY0E2rcHEWQYRah1vat3wuIHtEZ3SkSumyj+y9eyIHb9XTTyc4SyGyX1n8Rary8oSgQ` +
`V4cbyJTFXEEQOGLHB9/98EKThgJtfPsos2WKe/59S8yN05onpxcaL9y4S295Kv9kcSQxLm5UfjlqsKeHJZymvxi` +
`YzmBox7LA1zqcLYZvslJNkJxKAk5JA66iyDSQqOK7jIixn8pi305dFGCZglUFStwWqY6Rc9rR8EycVhSx2AhrvT` +
`7OQTVdKLfoKA84D8JZJPB7hrxqKf7JJFs87Kjt7c/5bXPFJ2osmjoNYnbHjiq64bh20sSCd630qvhhePLwjjOlB` +
`PiFyK36o/hQN871AEm1SCHy+aQcfJqF5KTgPnZQy5D+D/CGau+BfkO+WCGDVxRleYBJ4g2NbATolygB2KWXrj07` +
`U/WaWqV2hERbkmxXFh6cUdlkX2MeoG4v6ZD2OKAPx5DpJCfp0TEq6PznP+Z1mLd/ZjGsOF8R2WGQJEuU8HRzvsr` +
`0wsX9UyLMqf5XViDK11V/W+dcIvjHCayBpX2se3dfex5jFht+JcQc+iwB8caSXkR6tGSiargEtSJODORacO9IB8` +
`b6W8Sm//JWf/8zyiCcMm1i2yVVphwE1kczFwunAh0JB896VaXGVxXeKEAMQoXHjgDdCYp8/Etxjb8UkCmyjU="]
}
]
}`
)
func TestIsEqSpliceStr(t *testing.T) {
testCases := []struct {
certs1 []string
certs2 []string
expSame bool
}{
{
certs1: []string{"a", "b"},
certs2: []string{},
expSame: false,
},
{
certs1: []string{"a", "b"},
certs2: []string{"b"},
expSame: false,
},
{
certs1: []string{"a", "b"},
certs2: []string{"a", "b"},
expSame: true,
},
}
for _, tc := range testCases {
certSame := isEqSliceStr(tc.certs1, tc.certs2)
if (certSame && !tc.expSame) || (!certSame && tc.expSame) {
t.Errorf("cert compare testcase failed. tc: %v", tc)
}
}
}
func TestVerifyTrustAnchor(t *testing.T) {
testCases := []struct {
errExp bool
cert string
}{
{
cert: rootCACert,
errExp: false,
},
{
cert: malformedCert,
errExp: true,
},
{
cert: nonCaCert,
errExp: true,
},
{
cert: intermediateCACert,
errExp: false,
},
}
for i, tc := range testCases {
err := verifyTrustAnchor(tc.cert)
if tc.errExp && err == nil {
t.Errorf("test case %v failed. Expected Error but got none", i)
} else if !tc.errExp && err != nil {
t.Errorf("test case %v failed. Expected no error but got %v", i, err)
}
}
}
func TestUpdateTrustAnchor(t *testing.T) {
cbCounter := 0
tb := NewTrustBundle(nil)
tb.UpdateCb(func() { cbCounter++ })
var trustedCerts []string
var err error
// Add First Cert update
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{rootCACert}},
Source: SourceMeshConfig,
})
if err != nil {
t.Errorf("Basic trustbundle update test failed. Error: %v", err)
}
trustedCerts = tb.GetTrustBundle()
if !isEqSliceStr(trustedCerts, []string{rootCACert}) || cbCounter != 1 {
t.Errorf("Basic trustbundle update test failed. Callback value is %v", cbCounter)
}
// Add Second Cert update
// ensure intermediate CA certs accepted, it replaces the first completely, and lib dedupes duplicate cert
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{intermediateCACert, intermediateCACert}},
Source: SourceMeshConfig,
})
if err != nil {
t.Errorf("trustbundle intermediate cert update test failed. Error: %v", err)
}
trustedCerts = tb.GetTrustBundle()
if !isEqSliceStr(trustedCerts, []string{intermediateCACert}) || cbCounter != 2 {
t.Errorf("trustbundle intermediate cert update test failed. Callback value is %v", cbCounter)
}
// Try adding one more cert to a different source
// Ensure both certs are not present
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{rootCACert}},
Source: SourceIstioCA,
})
if err != nil {
t.Errorf("multicert update failed. Error: %v", err)
}
trustedCerts = tb.GetTrustBundle()
result := []string{intermediateCACert, rootCACert}
sort.Strings(result)
if !isEqSliceStr(trustedCerts, result) || cbCounter != 3 {
t.Errorf("multicert update failed. Callback value is %v", cbCounter)
}
// Try added same cert again. Ensure cb doesn't increment
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{rootCACert}},
Source: SourceIstioCA,
})
if err != nil {
t.Errorf("duplicate multicert update failed. Error: %v", err)
}
trustedCerts = tb.GetTrustBundle()
if !isEqSliceStr(trustedCerts, result) || cbCounter != 3 {
t.Errorf("duplicate multicert update failed. Callback value is %v", cbCounter)
}
// Try added one good cert, one bogus Cert
// Verify Update should not go through and no change to cb
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{malformedCert}},
Source: SourceIstioCA,
})
if err == nil {
t.Errorf("bad cert update failed. Expected error")
}
trustedCerts = tb.GetTrustBundle()
if !isEqSliceStr(trustedCerts, result) || cbCounter != 3 {
t.Errorf("bad cert update failed. Callback value is %v", cbCounter)
}
// Finally, remove all certs and ensure trustBundle is clean
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{}},
Source: SourceIstioCA,
})
if err != nil {
t.Errorf("clear cert update failed. Error: %v", err)
}
err = tb.UpdateTrustAnchor(&TrustAnchorUpdate{
TrustAnchorConfig: TrustAnchorConfig{Certs: []string{}},
Source: SourceMeshConfig,
})
if err != nil {
t.Errorf("clear cert update failed. Error: %v", err)
}
trustedCerts = tb.GetTrustBundle()
if !isEqSliceStr(trustedCerts, []string{}) || cbCounter != 5 {
t.Errorf("cert removal update failed. Callback value is %v", cbCounter)
}
}
func expectTbCount(t *testing.T, tb *TrustBundle, expAnchorCount int, ti time.Duration, strPrefix string) {
t.Helper()
retry.UntilSuccessOrFail(t, func() error {
certs := tb.GetTrustBundle()
if len(certs) != expAnchorCount {
return fmt.Errorf("%s. Got %v, expected %v", strPrefix, len(certs), expAnchorCount)
}
return nil
}, retry.Timeout(ti))
}
func TestAddMeshConfigUpdate(t *testing.T) {
caCertPool, err := x509.SystemCertPool()
if err != nil {
t.Fatalf("failed to get SystemCertPool: %v", err)
}
stop := make(chan struct{})
t.Cleanup(func() { close(stop) })
// Mock response from TLS Spiffe Server
validHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(validSpiffeX509Bundle))
})
server1 := httptest.NewTLSServer(validHandler)
caCertPool.AddCert(server1.Certificate())
defer server1.Close()
server2 := httptest.NewTLSServer(validHandler)
caCertPool.AddCert(server2.Certificate())
defer server2.Close()
tb := NewTrustBundle(caCertPool)
// Change global remote timeout interval for the duration of the unit test
remoteTimeout = 30 * time.Millisecond
// Test1: Ensure that MeshConfig PEM certs are updated correctly
tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{
{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: rootCACert}},
}})
expectTbCount(t, tb, 1, 1*time.Second, "meshConfig pem trustAnchor not updated in bundle")
// Test2: Append server1 as spiffe endpoint to existing MeshConfig
// Start processing remote anchor update with poll frequency.
go tb.ProcessRemoteTrustAnchors(stop, 200*time.Millisecond)
tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{
{CertificateData: &meshconfig.MeshConfig_CertificateData_SpiffeBundleUrl{SpiffeBundleUrl: server1.Listener.Addr().String()}},
{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: rootCACert}},
}})
if !isEqSliceStr(tb.endpoints, []string{server1.Listener.Addr().String()}) {
t.Errorf("server1 endpoint not correctly updated in trustbundle. Trustbundle endpoints: %v", tb.endpoints)
}
// Check server1's anchor has been added along with meshConfig pem cert
expectTbCount(t, tb, 2, 3*time.Second, "server1(running) trustAnchor not updated in bundle")
// Test3: Stop server1
server1.Close()
// Check server1's valid trustAnchor is no longer in the trustbundle within poll frequency window
expectTbCount(t, tb, 1, 6*time.Second, "server1(stopped) trustAnchor not removed from bundle")
// Test4: Update with server1, server2 and mesh pem ca
tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{
{CertificateData: &meshconfig.MeshConfig_CertificateData_SpiffeBundleUrl{SpiffeBundleUrl: server2.Listener.Addr().String()}},
{CertificateData: &meshconfig.MeshConfig_CertificateData_SpiffeBundleUrl{SpiffeBundleUrl: server1.Listener.Addr().String()}},
{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: rootCACert}},
}})
if !isEqSliceStr(tb.endpoints, []string{server2.Listener.Addr().String(), server1.Listener.Addr().String()}) {
t.Errorf("server2 endpoint not correctly updated in trustbundle. Trustbundle endpoints: %v", tb.endpoints)
}
// Check only server 2's trustanchor is present along with meshConfig pem and not server 1 (since it is down)
expectTbCount(t, tb, 2, 3*time.Second, "server2(running) trustAnchor not updated in bundle")
// Test5. remove everything
tb.AddMeshConfigUpdate(&meshconfig.MeshConfig{CaCertificates: []*meshconfig.MeshConfig_CertificateData{}})
expectTbCount(t, tb, 0, 3*time.Second, "trustAnchor not updated in bundle after meshConfig cleared")
}