From d6c80294a736a377a8b39e31c253f918eec5aa1d Mon Sep 17 00:00:00 2001 From: erm-g <110920239+erm-g@users.noreply.github.com> Date: Fri, 8 Nov 2024 00:03:15 -0500 Subject: [PATCH] xds: Spiffe Trust Bundle Support (#11627) Adds verification of SPIFFE based identities using SPIFFE trust bundles. For in-progress gRFC A87. --- testing/src/main/resources/certs/README | 29 +++ .../main/resources/certs/client_spiffe.pem | 25 +++ .../main/resources/certs/server1_spiffe.pem | 26 +++ .../main/resources/certs/spiffe-openssl.cnf | 18 +- .../main/resources/certs/spiffebundle.json | 101 ++++++++++ .../main/resources/certs/spiffebundle1.json | 59 ++++++ .../CertProviderClientSslContextProvider.java | 12 +- .../CertProviderServerSslContextProvider.java | 18 +- .../CertProviderSslContextProvider.java | 13 +- .../certprovider/CertificateProvider.java | 21 ++- .../FileWatcherCertificateProvider.java | 78 +++++--- ...ileWatcherCertificateProviderProvider.java | 24 ++- .../trust/XdsTrustManagerFactory.java | 42 ++++- .../security/trust/XdsX509TrustManager.java | 54 +++++- .../grpc/xds/CommonBootstrapperTestUtils.java | 11 +- .../grpc/xds/XdsSecurityClientServerTest.java | 176 ++++++++++++++---- .../security/CommonTlsContextTestsUtil.java | 4 + .../SecurityProtocolNegotiatorsTest.java | 8 +- .../security/TlsContextManagerTest.java | 8 +- ...atcherCertificateProviderProviderTest.java | 100 +++++++++- .../FileWatcherCertificateProviderTest.java | 119 +++++++++--- .../trust/XdsTrustManagerFactoryTest.java | 42 +++++ .../trust/XdsX509TrustManagerTest.java | 85 +++++++++ 23 files changed, 953 insertions(+), 120 deletions(-) create mode 100644 testing/src/main/resources/certs/client_spiffe.pem create mode 100644 testing/src/main/resources/certs/server1_spiffe.pem create mode 100644 testing/src/main/resources/certs/spiffebundle.json create mode 100644 testing/src/main/resources/certs/spiffebundle1.json diff --git a/testing/src/main/resources/certs/README b/testing/src/main/resources/certs/README index 1fa6b73395..13e375784c 100644 --- a/testing/src/main/resources/certs/README +++ b/testing/src/main/resources/certs/README @@ -67,6 +67,35 @@ ecdsa.key is used to test keys with algorithm other than RSA: $ openssl ecparam -name secp256k1 -genkey -noout -out ecdsa.pem $ openssl pkcs8 -topk8 -in ecdsa.pem -out ecdsa.key -nocrypt +SPIFFE test credentials: +======================= + +The SPIFFE related extensions are listed in spiffe-openssl.cnf config. Both +client_spiffe.pem and server1_spiffe.pem are generated in the same way with +original client.pem and server1.pem but with using that config. Here are the +exact commands (we pass "-subj" as argument in this case): +---------------------- +$ openssl req -new -key client.key -out spiffe-cert.csr \ + -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=testclient/ \ + -config spiffe-openssl.cnf -reqexts spiffe_client_e2e +$ openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial \ + -in spiffe-cert.csr -out client_spiffe.pem -extensions spiffe_client_e2e \ + -extfile spiffe-openssl.cnf -days 3650 -sha256 +$ openssl req -new -key server1.key -out spiffe-cert.csr \ + -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=*.test.google.com/ \ + -config spiffe-openssl.cnf -reqexts spiffe_server_e2e +$ openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial \ + -in spiffe-cert.csr -out server1_spiffe.pem -extensions spiffe_server_e2e \ + -extfile spiffe-openssl.cnf -days 3650 -sha256 + +Additionally, SPIFFE trust bundle map files spiffebundle.json and \ +spiffebundle1.json are manually created for end to end testing. The \ +spiffebundle.json contains "example.com" trust domain (only this entry is used \ +in e2e tests) matching URI SAN of server1_spiffe.pem, and the CA certificate \ +there is ca.pem. The spiffebundle.json file contains "foo.bar.com" trust \ +domain (only this entry is used in e2e tests) matching URI SAN of \ +client_spiffe.pem, and the CA certificate there is also ca.pem. + Clean up: --------- $ rm *.rsa diff --git a/testing/src/main/resources/certs/client_spiffe.pem b/testing/src/main/resources/certs/client_spiffe.pem new file mode 100644 index 0000000000..c70981a403 --- /dev/null +++ b/testing/src/main/resources/certs/client_spiffe.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIUVXGlXjNENtOZbI12epjgIhMaShUwDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTI0 +MTAyNDE2NDAzN1oXDTM0MTAyMjE2NDAzN1owaDELMAkGA1UEBhMCQVUxEzARBgNV +BAgMClNvbWUtU3RhdGUxDDAKBgNVBAcMA1NWTDEhMB8GA1UECgwYSW50ZXJuZXQg +V2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDDAp0ZXN0Y2xpZW50MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsqmEafg11ae9jRW0B/IXYU2S8nGVzpSYZjLK +yZq459qe6SP/Jk2f9BQvkhlgRmVfhC4h65gl+c32iC6/SLsOxoa91c6Hn4vK+tqy +7qVTzYv6naso1pNnRAhwvWd/gINysyk8nq11oynL8ilZjNGcRNEV4Q1v0aEG6mbF +NhioNQdq4VFPCjdIFZip9KyRzsc0VUmHmC2KeWJ+yq7TyXCsqPWlbhK+3RgDc6ch +epYP52AVnPvUhsJKC3RbyrwAWCTMq2zYR1EH79H82mdD/OnX0xDaw8cwC68xp6nM +dyk68CY5Gf2kq9bcg9P7V77pERYj8VgSYYx0O9BqkxUGNfUW4QIDAQABo4HlMIHi +MEQGA1UdEQQ9MDuGOXNwaWZmZTovL2Zvby5iYXIuY29tLzllZWJjY2QyLTEyYmYt +NDBhNi1iMjYyLTY1ZmUwNDg3ZDQ1MzAdBgNVHQ4EFgQU28U8sUTGNEDyeCrvJDJd +AALabSMwewYDVR0jBHQwcqFapFgwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNv +bWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0G +A1UEAwwGdGVzdGNhghRas/RW8dzL4s/pS5g22Iv2AGEPmjANBgkqhkiG9w0BAQsF +AAOCAQEAE3LLE8GR283q/aE646SgAfltqpESP38NmYdJMdZgWRxbOqdWabYDfibt +9r8j+IRvVuuTWuH2eNS5wXJtS1BZ+z24wTLa+a2KjOV12gChP+3N7jhqId4eolSL +1fjscPY6luZP4Pm3D73lBvIoBvXpDGyrxleiUCEEkKXmTOA8doFvbrcbwm+yUJOP +VKUKvAzTNztb0BGDzKKU4E2yK5PSyv2n5m2NpzxYYfHoGeVcxvj7nCnSfoX/EWHb +d8ztJYDg9X0iNcfQXt7PZ+j6VcxfDpGCDxe2rFQoYvlWjhr3xOi/1e5A1zx1Ly07 +m9MB4hntu4e2656ZDWbgOHLpO0q1iQ== +-----END CERTIFICATE----- diff --git a/testing/src/main/resources/certs/server1_spiffe.pem b/testing/src/main/resources/certs/server1_spiffe.pem new file mode 100644 index 0000000000..76cb41d692 --- /dev/null +++ b/testing/src/main/resources/certs/server1_spiffe.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEZDCCA0ygAwIBAgIUVXGlXjNENtOZbI12epjgIhMaShMwDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTI0 +MTAyMTAyMTQxNVoXDTM0MTAxOTAyMTQxNVowZTELMAkGA1UEBhMCVVMxETAPBgNV +BAgMCElsbGlub2lzMRAwDgYDVQQHDAdDaGljYWdvMRUwEwYDVQQKDAxFeGFtcGxl +LCBDby4xGjAYBgNVBAMMESoudGVzdC5nb29nbGUuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5xOONxJJ8b8Qauvob5/7dPYZfIcd+uhAWL2ZlTPz +Qvu4oF0QI4iYgP5iGgry9zEtCM+YQS8UhiAlPlqa6ANxgiBSEyMHH/xE8lo/+caY +GeACqy640Jpl/JocFGo3xd1L8DCawjlaj6eu7T7T/tpAV2qq13b5710eNRbCAfFe +8yALiGQemx0IYhlZXNbIGWLBNhBhvVjJh7UvOqpADk4xtl8o5j0xgMIRg6WJGK6c +6ffSIg4eP1XmovNYZ9LLEJG68tF0Q/yIN43B4dt1oq4jzSdCbG4F1EiykT2TmwPV +YDi8tml6DfOCDGnit8svnMEmBv/fcPd31GSbXjF8M+KGGQIDAQABo4IBGTCCARUw +dwYDVR0RBHAwboIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6b29pLnRlc3QuZ29v +Z2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQOGJnNwaWZmZTovL2V4YW1w +bGUuY29tL3dvcmtsb2FkLzllZWJjY2QyMB0GA1UdDgQWBBRvRpAYHQYP6dFPf5V7 +/MyCftnNjTB7BgNVHSMEdDByoVqkWDBWMQswCQYDVQQGEwJBVTETMBEGA1UECAwK +U29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8w +DQYDVQQDDAZ0ZXN0Y2GCFFqz9Fbx3Mviz+lLmDbYi/YAYQ+aMA0GCSqGSIb3DQEB +CwUAA4IBAQBJ1bnbBHa1n15vvhpGIzokuiJ+9q/zim63UuVDnkhrQM2N+RQbStGT +Tis2tNse1bh460dJFm6ArgHWogzx6fQZzgaDeCOAXvrAe4jM9IHr9K7lkq/33CZS +BDV+jCmm2sRsqSMkKUcX6JhyqWGFHuTDAKJzsEV2MlcswleKlGHDkeelAaxlLzpz +RHOSQd0N9xAs18lzx95SQEx90PtrBOmvIDDiI5o5z9Oz12Iy1toiksFl4jmknkDD +5VF3AyCRgN8NPW0uNC8D2vo4L+tgj9U6NPlmMOrjRsEH257LJ1wopAGr+yezkIId +QQodGSVm5cOuw/K7Ma4nBDjVJkjcdY3t +-----END CERTIFICATE----- diff --git a/testing/src/main/resources/certs/spiffe-openssl.cnf b/testing/src/main/resources/certs/spiffe-openssl.cnf index 40c473da7e..f03af40a78 100644 --- a/testing/src/main/resources/certs/spiffe-openssl.cnf +++ b/testing/src/main/resources/certs/spiffe-openssl.cnf @@ -4,9 +4,25 @@ subjectAltName = @alt_names [spiffe_client_multi] subjectAltName = @alt_names_multi +[spiffe_server_e2e] +subjectAltName = @alt_names_server_e2e + +[spiffe_client_e2e] +subjectAltName = @alt_names_client_e2e + [alt_names] URI = spiffe://foo.bar.com/client/workload/1 [alt_names_multi] URI.1 = spiffe://foo.bar.com/client/workload/1 -URI.2 = spiffe://foo.bar.com/client/workload/2 \ No newline at end of file +URI.2 = spiffe://foo.bar.com/client/workload/2 + +[alt_names_server_e2e] +DNS.1 = *.test.google.fr +DNS.2 = waterzooi.test.google.be +DNS.3 = *.test.youtube.com +IP.1 = "192.168.1.3" +URI = spiffe://example.com/workload/9eebccd2 + +[alt_names_client_e2e] +URI = spiffe://foo.bar.com/9eebccd2-12bf-40a6-b262-65fe0487d453 \ No newline at end of file diff --git a/testing/src/main/resources/certs/spiffebundle.json b/testing/src/main/resources/certs/spiffebundle.json new file mode 100644 index 0000000000..5bc8fcfb43 --- /dev/null +++ b/testing/src/main/resources/certs/spiffebundle.json @@ -0,0 +1,101 @@ +{ + "trust_domains": { + "example.com": { + "spiffe_sequence": 12035488, + "keys": [ + { + "kty": "RSA", + "use": "x509-svid", + "x5c": ["MIIDWjCCAkKgAwIBAgIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw + MDMxNzE4NTk1MVoXDTMwMDMxNTE4NTk1MVowVjELMAkGA1UEBhMCQVUxEzARBgNV + BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 + ZDEPMA0GA1UEAwwGdGVzdGNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAsGL0oXflF0LzoM+Bh+qUU9yhqzw2w8OOX5mu/iNCyUOBrqaHi7mGHx73GD01 + diNzCzvlcQqdNIH6NQSL7DTpBjca66jYT9u73vZe2MDrr1nVbuLvfu9850cdxiUO + Inv5xf8+sTHG0C+a+VAvMhsLiRjsq+lXKRJyk5zkbbsETybqpxoJ+K7CoSy3yc/k + QIY3TipwEtwkKP4hzyo6KiGd/DPexie4nBUInN3bS1BUeNZ5zeaIC2eg3bkeeW7c + qT55b+Yen6CxY0TEkzBK6AKt/WUialKMgT0wbTxRZO7kUCH3Sq6e/wXeFdJ+HvdV + LPlAg5TnMaNpRdQih/8nRFpsdwIDAQABoyAwHjAMBgNVHRMEBTADAQH/MA4GA1Ud + DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAkTrKZjBrJXHps/HrjNCFPb5a + THuGPCSsepe1wkKdSp1h4HGRpLoCgcLysCJ5hZhRpHkRihhef+rFHEe60UePQO3S + CVTtdJB4CYWpcNyXOdqefrbJW5QNljxgi6Fhvs7JJkBqdXIkWXtFk2eRgOIP2Eo9 + /OHQHlYnwZFrk6sp4wPyR+A95S0toZBcyDVz7u+hOW0pGK3wviOe9lvRgj/H3Pwt + bewb0l+MhRig0/DVHamyVxrDRbqInU1/GTNCwcZkXKYFWSf92U+kIcTth24Q1gcw + eZiLl5FfrWokUNytFElXob0V0a5/kbhiLc3yWmvWqHTpqCALbVyF+rKJo2f5Kw=="], + "n": "", + "e": "AQAB" + } + ] + }, + "test.example.com": { + "keys": [ + { + "kty": "RSA", + "use": "x509-svid", + "x5c": ["MIIFsjCCA5qgAwIBAgIURygVMMzdr+Q7rsUaz189JozyHMwwDQYJKoZIhvcNAQEL + BQAwTjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTAL + BgNVBAoMBGdSUEMxFTATBgNVBAMMDHRlc3QtY2xpZW50MTAeFw0yMTEyMjMxODQy + NTJaFw0zMTEyMjExODQyNTJaME4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM + MAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRUwEwYDVQQDDAx0ZXN0LWNsaWVu + dDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ4AqpGetyVSqGUuBJ + LVFla+7bEfca7UYzfVSSZLZ/X+JDmWIVN8UIPuFib5jhMEc3XaUnFXUmM7zEtz/Z + G5hapwLwOb2C3ZxOP6PQjYCJxbkLie+b43UQrFu1xxd3vMhVJgcj/AIxEpmszuqO + a6kUrkYifjJADQ+64kZgl66bsTdXMCzpxyFl9xUfff59L8OX+HUfAcoZz3emjg3Z + JPYURQEmjdZTOau1EjFilwHgd989Jt7NKgx30NXoHmw7nusVBIY94fL2VKN3f1XV + m0dHu5NI279Q6zr0ZBU7k5T3IeHnzsUesQS4NGlklDWoVTKk73Uv9Pna8yQsSW75 + 7PEbHOGp9Knu4bnoGPOlsG81yIPipO6hTgGFK24pF97M9kpGbWqYX4+2vLlrCAfc + msHqaUPmQlYeRVTT6vw7ctYo2kyUYGtnODXk76LqewRBVvkzx75QUhfjAyb740Yc + DmIenc56Tq6gebJHjhEmVSehR6xIpXP7SVeurTyhPsEQnpJHtgs4dcwWOZp7BvPN + zHXmJqfr7vsshie3vS5kQ0u1e1yqAqXgyDjqKXOkx+dpgUTehSJHhPNHvTc5LXRs + vvXKYz6FrwR/DZ8t7BNEvPeLjFgxpH7QVJFLCvCbXs5K6yYbsnLfxFIBPRnrbJkI + sK+sQwnRdnsiUdPsTkG5B2lQfQIDAQABo4GHMIGEMB0GA1UdDgQWBBQ2lBp0PiRH + HvQ5IRURm8aHsj4RETAfBgNVHSMEGDAWgBQ2lBp0PiRHHvQ5IRURm8aHsj4RETAP + BgNVHRMBAf8EBTADAQH/MDEGA1UdEQQqMCiGJnNwaWZmZTovL2Zvby5iYXIuY29t + L2NsaWVudC93b3JrbG9hZC8xMA0GCSqGSIb3DQEBCwUAA4ICAQA1mSkgRclAl+E/ + aS9zJ7t8+Y4n3T24nOKKveSIjxXm/zjhWqVsLYBI6kglWtih2+PELvU8JdPqNZK3 + 4Kl0Q6FWpVSGDdWN1i6NyORt2ocggL3ke3iXxRk3UpUKJmqwz81VhA2KUHnMlyE0 + IufFfZNwNWWHBv13uJfRbjeQpKPhU+yf4DeXrsWcvrZlGvAET+mcplafUzCp7Iv+ + PcISJtUerbxbVtuHVeZCLlgDXWkLAWJN8rf0dIG4x060LJ+j6j9uRVhb9sZn1HJV + +j4XdIYm1VKilluhOtNwP2d3Ox/JuTBxf7hFHXZPfMagQE5k5PzmxRaCAEMJ1l2D + vUbZw+shJfSNoWcBo2qadnUaWT3BmmJRBDh7ZReib/RQ1Rd4ygOyzP3E0vkV4/gq + yjLdApXh5PZP8KLQZ+1JN/sdWt7VfIt9wYOpkIqujdll51ESHzwQeAK9WVCB4UvV + z6zdhItB9CRbXPreWC+wCB1xDovIzFKOVsLs5+Gqs1m7VinG2LxbDqaKyo/FB0Hx + x0acBNzezLWoDwXYQrN0T0S4pnqhKD1CYPpdArBkNezUYAjS725FkApuK+mnBX3U + 0msBffEaUEOkcyar1EW2m/33vpetD/k3eQQkmvQf4Hbiu9AF+9cNDm/hMuXEw5EX + GA91fn0891b5eEW8BJHXX0jri0aN8g=="], + "n": "", + "e": "AQAB" + }, + { + "kty": "RSA", + "use": "x509-svid", + "x5c": ["MIIELTCCAxWgAwIBAgIUVXGlXjNENtOZbI12epjgIhMaShEwDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTI0 + MDkxNzE2MTk0NFoXDTM0MDkxNTE2MTk0NFowTjELMAkGA1UEBhMCVVMxCzAJBgNV + BAgMAkNBMQwwCgYDVQQHDANTVkwxDTALBgNVBAoMBGdSUEMxFTATBgNVBAMMDHRl + c3QtY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOcTjjcS + SfG/EGrr6G+f+3T2GXyHHfroQFi9mZUz80L7uKBdECOImID+YhoK8vcxLQjPmEEv + FIYgJT5amugDcYIgUhMjBx/8RPJaP/nGmBngAqsuuNCaZfyaHBRqN8XdS/AwmsI5 + Wo+nru0+0/7aQFdqqtd2+e9dHjUWwgHxXvMgC4hkHpsdCGIZWVzWyBliwTYQYb1Y + yYe1LzqqQA5OMbZfKOY9MYDCEYOliRiunOn30iIOHj9V5qLzWGfSyxCRuvLRdEP8 + iDeNweHbdaKuI80nQmxuBdRIspE9k5sD1WA4vLZpeg3zggxp4rfLL5zBJgb/33D3 + d9Rkm14xfDPihhkCAwEAAaOB+jCB9zBZBgNVHREEUjBQhiZzcGlmZmU6Ly9mb28u + YmFyLmNvbS9jbGllbnQvd29ya2xvYWQvMYYmc3BpZmZlOi8vZm9vLmJhci5jb20v + Y2xpZW50L3dvcmtsb2FkLzIwHQYDVR0OBBYEFG9GkBgdBg/p0U9/lXv8zIJ+2c2N + MHsGA1UdIwR0MHKhWqRYMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0 + YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMM + BnRlc3RjYYIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQELBQADggEB + AJ4Cbxv+02SpUgkEu4hP/1+8DtSBXUxNxI0VG4e3Ap2+Rhjm3YiFeS/UeaZhNrrw + UEjkSTPFODyXR7wI7UO9OO1StyD6CMkp3SEvevU5JsZtGL6mTiTLTi3Qkywa91Bt + GlyZdVMghA1bBJLBMwiD5VT5noqoJBD7hDy6v9yNmt1Sw2iYBJPqI3Gnf5bMjR3s + UICaxmFyqaMCZsPkfJh0DmZpInGJys3m4QqGz6ZE2DWgcSr1r/ML7/5bSPjjr8j4 + WFFSqFR3dMu8CbGnfZTCTXa4GTX/rARXbAO67Z/oJbJBK7VKayskL+PzKuohb9ox + jGL772hQMbwtFCOFXu5VP0s="] + } + ] + } + } +} \ No newline at end of file diff --git a/testing/src/main/resources/certs/spiffebundle1.json b/testing/src/main/resources/certs/spiffebundle1.json new file mode 100644 index 0000000000..f79af09a3e --- /dev/null +++ b/testing/src/main/resources/certs/spiffebundle1.json @@ -0,0 +1,59 @@ +{ + "trust_domains": { + "example.com": { + "spiffe_sequence": 12035488, + "keys": [ + { + "kty": "RSA", + "use": "x509-svid", + "x5c": ["MIIDWjCCAkKgAwIBAgIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw + MDMxNzE4NTk1MVoXDTMwMDMxNTE4NTk1MVowVjELMAkGA1UEBhMCQVUxEzARBgNV + BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 + ZDEPMA0GA1UEAwwGdGVzdGNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAsGL0oXflF0LzoM+Bh+qUU9yhqzw2w8OOX5mu/iNCyUOBrqaHi7mGHx73GD01 + diNzCzvlcQqdNIH6NQSL7DTpBjca66jYT9u73vZe2MDrr1nVbuLvfu9850cdxiUO + Inv5xf8+sTHG0C+a+VAvMhsLiRjsq+lXKRJyk5zkbbsETybqpxoJ+K7CoSy3yc/k + QIY3TipwEtwkKP4hzyo6KiGd/DPexie4nBUInN3bS1BUeNZ5zeaIC2eg3bkeeW7c + qT55b+Yen6CxY0TEkzBK6AKt/WUialKMgT0wbTxRZO7kUCH3Sq6e/wXeFdJ+HvdV + LPlAg5TnMaNpRdQih/8nRFpsdwIDAQABoyAwHjAMBgNVHRMEBTADAQH/MA4GA1Ud + DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAkTrKZjBrJXHps/HrjNCFPb5a + THuGPCSsepe1wkKdSp1h4HGRpLoCgcLysCJ5hZhRpHkRihhef+rFHEe60UePQO3S + CVTtdJB4CYWpcNyXOdqefrbJW5QNljxgi6Fhvs7JJkBqdXIkWXtFk2eRgOIP2Eo9 + /OHQHlYnwZFrk6sp4wPyR+A95S0toZBcyDVz7u+hOW0pGK3wviOe9lvRgj/H3Pwt + bewb0l+MhRig0/DVHamyVxrDRbqInU1/GTNCwcZkXKYFWSf92U+kIcTth24Q1gcw + eZiLl5FfrWokUNytFElXob0V0a5/kbhiLc3yWmvWqHTpqCALbVyF+rKJo2f5Kw=="], + "n": "", + "e": "AQAB" + } + ] + }, + "foo.bar.com": { + "keys": [ + { + "kty": "RSA", + "use": "x509-svid", + "x5c": ["MIIDWjCCAkKgAwIBAgIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw + MDMxNzE4NTk1MVoXDTMwMDMxNTE4NTk1MVowVjELMAkGA1UEBhMCQVUxEzARBgNV + BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 + ZDEPMA0GA1UEAwwGdGVzdGNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAsGL0oXflF0LzoM+Bh+qUU9yhqzw2w8OOX5mu/iNCyUOBrqaHi7mGHx73GD01 + diNzCzvlcQqdNIH6NQSL7DTpBjca66jYT9u73vZe2MDrr1nVbuLvfu9850cdxiUO + Inv5xf8+sTHG0C+a+VAvMhsLiRjsq+lXKRJyk5zkbbsETybqpxoJ+K7CoSy3yc/k + QIY3TipwEtwkKP4hzyo6KiGd/DPexie4nBUInN3bS1BUeNZ5zeaIC2eg3bkeeW7c + qT55b+Yen6CxY0TEkzBK6AKt/WUialKMgT0wbTxRZO7kUCH3Sq6e/wXeFdJ+HvdV + LPlAg5TnMaNpRdQih/8nRFpsdwIDAQABoyAwHjAMBgNVHRMEBTADAQH/MA4GA1Ud + DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAkTrKZjBrJXHps/HrjNCFPb5a + THuGPCSsepe1wkKdSp1h4HGRpLoCgcLysCJ5hZhRpHkRihhef+rFHEe60UePQO3S + CVTtdJB4CYWpcNyXOdqefrbJW5QNljxgi6Fhvs7JJkBqdXIkWXtFk2eRgOIP2Eo9 + /OHQHlYnwZFrk6sp4wPyR+A95S0toZBcyDVz7u+hOW0pGK3wviOe9lvRgj/H3Pwt + bewb0l+MhRig0/DVHamyVxrDRbqInU1/GTNCwcZkXKYFWSf92U+kIcTth24Q1gcw + eZiLl5FfrWokUNytFElXob0V0a5/kbhiLc3yWmvWqHTpqCALbVyF+rKJo2f5Kw=="] + } + ] + } + } +} \ No newline at end of file diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java index 8aa74b2b50..d7c2267c48 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java @@ -58,15 +58,21 @@ final class CertProviderClientSslContextProvider extends CertProviderSslContextP // Null rootCertInstance implies hasSystemRootCerts because of the check in // CertProviderClientSslContextProviderFactory. if (rootCertInstance != null) { - sslContextBuilder.trustManager( + if (savedSpiffeTrustMap != null) { + sslContextBuilder = sslContextBuilder.trustManager( new XdsTrustManagerFactory( - savedTrustedRoots.toArray(new X509Certificate[0]), + savedSpiffeTrustMap, certificateValidationContextdationContext)); + } else { + sslContextBuilder = sslContextBuilder.trustManager( + new XdsTrustManagerFactory( + savedTrustedRoots.toArray(new X509Certificate[0]), + certificateValidationContextdationContext)); + } } if (isMtls()) { sslContextBuilder.keyManager(savedKey, savedCertChain); } return sslContextBuilder; } - } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java index e43452a53e..ef65bbfb6f 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java @@ -59,13 +59,17 @@ final class CertProviderServerSslContextProvider extends CertProviderSslContextP CertificateValidationContext certificateValidationContextdationContext) throws CertStoreException, CertificateException, IOException { SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(savedKey, savedCertChain); - setClientAuthValues( - sslContextBuilder, - isMtls() - ? new XdsTrustManagerFactory( - savedTrustedRoots.toArray(new X509Certificate[0]), - certificateValidationContextdationContext) - : null); + XdsTrustManagerFactory trustManagerFactory = null; + if (isMtls() && savedSpiffeTrustMap != null) { + trustManagerFactory = new XdsTrustManagerFactory( + savedSpiffeTrustMap, + certificateValidationContextdationContext); + } else if (isMtls()) { + trustManagerFactory = new XdsTrustManagerFactory( + savedTrustedRoots.toArray(new X509Certificate[0]), + certificateValidationContextdationContext); + } + setClientAuthValues(sslContextBuilder, trustManagerFactory); sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder); return sslContextBuilder; } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java index b68f705fed..f9cd14f2ef 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java @@ -41,6 +41,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider @Nullable protected PrivateKey savedKey; @Nullable protected List savedCertChain; @Nullable protected List savedTrustedRoots; + @Nullable protected Map> savedSpiffeTrustMap; private final boolean isUsingSystemRootCerts; protected CertProviderSslContextProvider( @@ -152,14 +153,21 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider updateSslContextWhenReady(); } + @Override + public final void updateSpiffeTrustMap(Map> spiffeTrustMap) { + savedSpiffeTrustMap = spiffeTrustMap; + updateSslContextWhenReady(); + } + private void updateSslContextWhenReady() { if (isMtls()) { - if (savedKey != null && (savedTrustedRoots != null || isUsingSystemRootCerts)) { + if (savedKey != null + && (savedTrustedRoots != null || isUsingSystemRootCerts || savedSpiffeTrustMap != null)) { updateSslContext(); clearKeysAndCerts(); } } else if (isClientSideTls()) { - if (savedTrustedRoots != null) { + if (savedTrustedRoots != null || savedSpiffeTrustMap != null) { updateSslContext(); clearKeysAndCerts(); } @@ -174,6 +182,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider private void clearKeysAndCerts() { savedKey = null; savedTrustedRoots = null; + savedSpiffeTrustMap = null; savedCertChain = null; } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java index a0d5d0fc69..009bb7bf56 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java @@ -26,6 +26,7 @@ import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -45,6 +46,8 @@ public abstract class CertificateProvider implements Closeable { void updateTrustedRoots(List trustedRoots); + void updateSpiffeTrustMap(Map> spiffeTrustMap); + void onError(Status errorStatus); } @@ -53,6 +56,7 @@ public abstract class CertificateProvider implements Closeable { private PrivateKey privateKey; private List certChain; private List trustedRoots; + private Map> spiffeTrustMap; @VisibleForTesting final Set downstreamWatchers = new HashSet<>(); @@ -65,6 +69,9 @@ public abstract class CertificateProvider implements Closeable { if (trustedRoots != null) { sendLastTrustedRootsUpdate(watcher); } + if (spiffeTrustMap != null) { + sendLastSpiffeTrustMapUpdate(watcher); + } } synchronized void removeWatcher(Watcher watcher) { @@ -83,6 +90,10 @@ public abstract class CertificateProvider implements Closeable { watcher.updateTrustedRoots(trustedRoots); } + private void sendLastSpiffeTrustMapUpdate(Watcher watcher) { + watcher.updateSpiffeTrustMap(spiffeTrustMap); + } + @Override public synchronized void updateCertificate(PrivateKey key, List certChain) { checkNotNull(key, "key"); @@ -103,6 +114,14 @@ public abstract class CertificateProvider implements Closeable { } } + @Override + public void updateSpiffeTrustMap(Map> spiffeTrustMap) { + this.spiffeTrustMap = spiffeTrustMap; + for (Watcher watcher : downstreamWatchers) { + sendLastSpiffeTrustMapUpdate(watcher); + } + } + @Override public synchronized void onError(Status errorStatus) { for (Watcher watcher : downstreamWatchers) { @@ -147,7 +166,7 @@ public abstract class CertificateProvider implements Closeable { @Override public abstract void close(); - /** Starts the cert refresh and watcher update cycle. */ + /** Starts the async cert refresh and watcher update cycle. */ public abstract void start(); private final DistributorWatcher watcher; diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java index dd945ce850..304124cc7f 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java @@ -16,10 +16,12 @@ package io.grpc.xds.internal.security.certprovider; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; import io.grpc.Status; +import io.grpc.internal.SpiffeUtil; import io.grpc.internal.TimeProvider; import io.grpc.xds.internal.security.trust.CertificateUtils; import java.io.ByteArrayInputStream; @@ -30,6 +32,7 @@ import java.nio.file.attribute.FileTime; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.HashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -47,11 +50,13 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement private final Path certFile; private final Path keyFile; private final Path trustFile; + private final Path spiffeTrustMapFile; private final long refreshIntervalInSeconds; @VisibleForTesting ScheduledFuture scheduledFuture; private FileTime lastModifiedTimeCert; private FileTime lastModifiedTimeKey; private FileTime lastModifiedTimeRoot; + private FileTime lastModifiedTimespiffeTrustMap; private boolean shutdown; FileWatcherCertificateProvider( @@ -60,6 +65,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement String certFile, String keyFile, String trustFile, + String spiffeTrustMapFile, long refreshIntervalInSeconds, ScheduledExecutorService scheduledExecutorService, TimeProvider timeProvider) { @@ -69,7 +75,15 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement this.timeProvider = checkNotNull(timeProvider, "timeProvider"); this.certFile = Paths.get(checkNotNull(certFile, "certFile")); this.keyFile = Paths.get(checkNotNull(keyFile, "keyFile")); - this.trustFile = Paths.get(checkNotNull(trustFile, "trustFile")); + checkArgument((trustFile != null || spiffeTrustMapFile != null), + "either trustFile or spiffeTrustMapFile must be present"); + if (spiffeTrustMapFile != null) { + this.spiffeTrustMapFile = Paths.get(spiffeTrustMapFile); + this.trustFile = null; + } else { + this.spiffeTrustMapFile = null; + this.trustFile = Paths.get(trustFile); + } this.refreshIntervalInSeconds = refreshIntervalInSeconds; } @@ -107,39 +121,48 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement byte[] keyFileContents = Files.readAllBytes(keyFile); FileTime currentCertTime2 = Files.getLastModifiedTime(certFile); FileTime currentKeyTime2 = Files.getLastModifiedTime(keyFile); - if (!currentCertTime2.equals(currentCertTime)) { - return; + if (currentCertTime2.equals(currentCertTime) && currentKeyTime2.equals(currentKeyTime)) { + try (ByteArrayInputStream certStream = new ByteArrayInputStream(certFileContents); + ByteArrayInputStream keyStream = new ByteArrayInputStream(keyFileContents)) { + PrivateKey privateKey = CertificateUtils.getPrivateKey(keyStream); + X509Certificate[] certs = CertificateUtils.toX509Certificates(certStream); + getWatcher().updateCertificate(privateKey, Arrays.asList(certs)); + } + lastModifiedTimeCert = currentCertTime; + lastModifiedTimeKey = currentKeyTime; } - if (!currentKeyTime2.equals(currentKeyTime)) { - return; - } - try (ByteArrayInputStream certStream = new ByteArrayInputStream(certFileContents); - ByteArrayInputStream keyStream = new ByteArrayInputStream(keyFileContents)) { - PrivateKey privateKey = CertificateUtils.getPrivateKey(keyStream); - X509Certificate[] certs = CertificateUtils.toX509Certificates(certStream); - getWatcher().updateCertificate(privateKey, Arrays.asList(certs)); - } - lastModifiedTimeCert = currentCertTime; - lastModifiedTimeKey = currentKeyTime; } } catch (Throwable t) { generateErrorIfCurrentCertExpired(t); } try { - FileTime currentRootTime = Files.getLastModifiedTime(trustFile); - if (currentRootTime.equals(lastModifiedTimeRoot)) { - return; + if (spiffeTrustMapFile != null) { + FileTime currentSpiffeTime = Files.getLastModifiedTime(spiffeTrustMapFile); + if (!currentSpiffeTime.equals(lastModifiedTimespiffeTrustMap)) { + SpiffeUtil.SpiffeBundle trustBundle = SpiffeUtil + .loadTrustBundleFromFile(spiffeTrustMapFile.toString()); + getWatcher().updateSpiffeTrustMap(new HashMap<>(trustBundle.getBundleMap())); + lastModifiedTimespiffeTrustMap = currentSpiffeTime; + } } - byte[] rootFileContents = Files.readAllBytes(trustFile); - FileTime currentRootTime2 = Files.getLastModifiedTime(trustFile); - if (!currentRootTime2.equals(currentRootTime)) { - return; + } catch (Throwable t) { + getWatcher().onError(Status.fromThrowable(t)); + } + try { + if (trustFile != null) { + FileTime currentRootTime = Files.getLastModifiedTime(trustFile); + if (!currentRootTime.equals(lastModifiedTimeRoot)) { + byte[] rootFileContents = Files.readAllBytes(trustFile); + FileTime currentRootTime2 = Files.getLastModifiedTime(trustFile); + if (currentRootTime2.equals(currentRootTime)) { + try (ByteArrayInputStream rootStream = new ByteArrayInputStream(rootFileContents)) { + X509Certificate[] caCerts = CertificateUtils.toX509Certificates(rootStream); + getWatcher().updateTrustedRoots(Arrays.asList(caCerts)); + } + lastModifiedTimeRoot = currentRootTime; + } + } } - try (ByteArrayInputStream rootStream = new ByteArrayInputStream(rootFileContents)) { - X509Certificate[] caCerts = CertificateUtils.toX509Certificates(rootStream); - getWatcher().updateTrustedRoots(Arrays.asList(caCerts)); - } - lastModifiedTimeRoot = currentRootTime; } catch (Throwable t) { getWatcher().onError(Status.fromThrowable(t)); } @@ -195,6 +218,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement String certFile, String keyFile, String trustFile, + String spiffeTrustMapFile, long refreshIntervalInSeconds, ScheduledExecutorService scheduledExecutorService, TimeProvider timeProvider) { @@ -204,6 +228,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement certFile, keyFile, trustFile, + spiffeTrustMapFile, refreshIntervalInSeconds, scheduledExecutorService, timeProvider); @@ -220,6 +245,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement String certFile, String keyFile, String trustFile, + String spiffeTrustMapFile, long refreshIntervalInSeconds, ScheduledExecutorService scheduledExecutorService, TimeProvider timeProvider); diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java index c4b140442c..53864ae33f 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.protobuf.Duration; import com.google.protobuf.util.Durations; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.JsonUtil; import io.grpc.internal.TimeProvider; import java.text.ParseException; @@ -33,11 +34,15 @@ import java.util.concurrent.ScheduledExecutorService; /** * Provider of {@link FileWatcherCertificateProvider}s. */ -final class FileWatcherCertificateProviderProvider implements CertificateProviderProvider { +public final class FileWatcherCertificateProviderProvider implements CertificateProviderProvider { + @VisibleForTesting + public static boolean enableSpiffe = GrpcUtil.getFlag("GRPC_EXPERIMENTAL_SPIFFE_TRUST_BUNDLE_MAP", + false); private static final String CERT_FILE_KEY = "certificate_file"; private static final String KEY_FILE_KEY = "private_key_file"; private static final String ROOT_FILE_KEY = "ca_certificate_file"; + private static final String SPIFFE_TRUST_MAP_FILE_KEY = "spiffe_trust_bundle_map_file"; private static final String REFRESH_INTERVAL_KEY = "refresh_interval"; @VisibleForTesting static final long REFRESH_INTERVAL_DEFAULT = 600L; @@ -82,6 +87,7 @@ final class FileWatcherCertificateProviderProvider implements CertificateProvide configObj.certFile, configObj.keyFile, configObj.rootFile, + configObj.spiffeTrustMapFile, configObj.refrehInterval, scheduledExecutorServiceFactory.create(), timeProvider); @@ -98,7 +104,20 @@ final class FileWatcherCertificateProviderProvider implements CertificateProvide Config configObj = new Config(); configObj.certFile = checkForNullAndGet(map, CERT_FILE_KEY); configObj.keyFile = checkForNullAndGet(map, KEY_FILE_KEY); - configObj.rootFile = checkForNullAndGet(map, ROOT_FILE_KEY); + if (enableSpiffe) { + if (!map.containsKey(ROOT_FILE_KEY) && !map.containsKey(SPIFFE_TRUST_MAP_FILE_KEY)) { + throw new NullPointerException( + String.format("either '%s' or '%s' is required in the config", + ROOT_FILE_KEY, SPIFFE_TRUST_MAP_FILE_KEY)); + } + if (map.containsKey(SPIFFE_TRUST_MAP_FILE_KEY)) { + configObj.spiffeTrustMapFile = JsonUtil.getString(map, SPIFFE_TRUST_MAP_FILE_KEY); + } else { + configObj.rootFile = JsonUtil.getString(map, ROOT_FILE_KEY); + } + } else { + configObj.rootFile = checkForNullAndGet(map, ROOT_FILE_KEY); + } String refreshIntervalString = JsonUtil.getString(map, REFRESH_INTERVAL_KEY); if (refreshIntervalString != null) { try { @@ -139,6 +158,7 @@ final class FileWatcherCertificateProviderProvider implements CertificateProvide String certFile; String keyFile; String rootFile; + String spiffeTrustMapFile; Long refrehInterval; } } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java index c9d83902ec..8cb4411706 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java @@ -17,6 +17,7 @@ package io.grpc.xds.internal.security.trust; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; @@ -33,6 +34,9 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.ManagerFactoryParameters; @@ -63,6 +67,11 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory { this(certs, staticCertificateValidationContext, true); } + public XdsTrustManagerFactory(Map> spiffeTrustMap, + CertificateValidationContext staticCertificateValidationContext) throws CertStoreException { + this(spiffeTrustMap, staticCertificateValidationContext, true); + } + private XdsTrustManagerFactory( X509Certificate[] certs, CertificateValidationContext certificateValidationContext, @@ -76,6 +85,19 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory { xdsX509TrustManager = createX509TrustManager(certs, certificateValidationContext); } + private XdsTrustManagerFactory( + Map> spiffeTrustMap, + CertificateValidationContext certificateValidationContext, + boolean validationContextIsStatic) + throws CertStoreException { + if (validationContextIsStatic) { + checkArgument( + certificateValidationContext == null || !certificateValidationContext.hasTrustedCa(), + "only static certificateValidationContext expected"); + xdsX509TrustManager = createX509TrustManager(spiffeTrustMap, certificateValidationContext); + } + } + private static X509Certificate[] getTrustedCaFromCertContext( CertificateValidationContext certificateValidationContext) throws CertificateException, IOException { @@ -100,6 +122,24 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory { @VisibleForTesting static XdsX509TrustManager createX509TrustManager( X509Certificate[] certs, CertificateValidationContext certContext) throws CertStoreException { + return new XdsX509TrustManager(certContext, createTrustManager(certs)); + } + + @VisibleForTesting + static XdsX509TrustManager createX509TrustManager( + Map> spiffeTrustMapFile, + CertificateValidationContext certContext) throws CertStoreException { + checkNotNull(spiffeTrustMapFile, "spiffeTrustMapFile"); + Map delegates = new HashMap<>(); + for (Map.Entry> entry:spiffeTrustMapFile.entrySet()) { + delegates.put(entry.getKey(), createTrustManager( + entry.getValue().toArray(new X509Certificate[0]))); + } + return new XdsX509TrustManager(certContext, delegates); + } + + private static X509ExtendedTrustManager createTrustManager(X509Certificate[] certs) + throws CertStoreException { TrustManagerFactory tmf = null; try { tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); @@ -131,7 +171,7 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory { if (myDelegate == null) { throw new CertStoreException("Native X509 TrustManager not found."); } - return new XdsX509TrustManager(certContext, myDelegate); + return myDelegate; } @Override diff --git a/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java index 6181d70fa5..dcd3f2006a 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java @@ -19,18 +19,25 @@ package io.grpc.xds.internal.security.trust; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import com.google.re2j.Pattern; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.grpc.internal.SpiffeUtil; import java.net.Socket; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; import javax.annotation.Nullable; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; @@ -51,6 +58,7 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509 private static final int ALT_IPA_NAME = 7; private final X509ExtendedTrustManager delegate; + private final Map spiffeTrustMapDelegates; private final CertificateValidationContext certContext; XdsX509TrustManager(@Nullable CertificateValidationContext certContext, @@ -58,6 +66,15 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509 checkNotNull(delegate, "delegate"); this.certContext = certContext; this.delegate = delegate; + this.spiffeTrustMapDelegates = null; + } + + XdsX509TrustManager(@Nullable CertificateValidationContext certContext, + Map spiffeTrustMapDelegates) { + checkNotNull(spiffeTrustMapDelegates, "spiffeTrustMapDelegates"); + this.spiffeTrustMapDelegates = ImmutableMap.copyOf(spiffeTrustMapDelegates); + this.certContext = certContext; + this.delegate = null; } private static boolean verifyDnsNameInPattern( @@ -204,21 +221,21 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509 @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { - delegate.checkClientTrusted(chain, authType, socket); + chooseDelegate(chain).checkClientTrusted(chain, authType, socket); verifySubjectAltNameInChain(chain); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { - delegate.checkClientTrusted(chain, authType, sslEngine); + chooseDelegate(chain).checkClientTrusted(chain, authType, sslEngine); verifySubjectAltNameInChain(chain); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - delegate.checkClientTrusted(chain, authType); + chooseDelegate(chain).checkClientTrusted(chain, authType); verifySubjectAltNameInChain(chain); } @@ -233,7 +250,7 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509 sslSocket.setSSLParameters(sslParams); } } - delegate.checkServerTrusted(chain, authType, socket); + chooseDelegate(chain).checkServerTrusted(chain, authType, socket); verifySubjectAltNameInChain(chain); } @@ -245,19 +262,44 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509 sslParams.setEndpointIdentificationAlgorithm(""); sslEngine.setSSLParameters(sslParams); } - delegate.checkServerTrusted(chain, authType, sslEngine); + chooseDelegate(chain).checkServerTrusted(chain, authType, sslEngine); verifySubjectAltNameInChain(chain); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - delegate.checkServerTrusted(chain, authType); + chooseDelegate(chain).checkServerTrusted(chain, authType); verifySubjectAltNameInChain(chain); } + private X509ExtendedTrustManager chooseDelegate(X509Certificate[] chain) + throws CertificateException { + if (spiffeTrustMapDelegates != null) { + Optional spiffeId = SpiffeUtil.extractSpiffeId(chain); + if (!spiffeId.isPresent()) { + throw new CertificateException("Failed to extract SPIFFE ID from peer leaf certificate"); + } + String trustDomain = spiffeId.get().getTrustDomain(); + if (!spiffeTrustMapDelegates.containsKey(trustDomain)) { + throw new CertificateException(String.format("Spiffe Trust Map doesn't contain trust" + + " domain '%s' from peer leaf certificate", trustDomain)); + } + return spiffeTrustMapDelegates.get(trustDomain); + } else { + return delegate; + } + } + @Override public X509Certificate[] getAcceptedIssuers() { + if (spiffeTrustMapDelegates != null) { + Set result = new HashSet<>(); + for (X509ExtendedTrustManager tm: spiffeTrustMapDelegates.values()) { + result.addAll(Arrays.asList(tm.getAcceptedIssuers())); + } + return result.toArray(new X509Certificate[0]); + } return delegate.getAcceptedIssuers(); } } diff --git a/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java b/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java index 0b2f3c7136..6a8eced298 100644 --- a/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java @@ -88,7 +88,7 @@ public class CommonBootstrapperTestUtils { String certInstanceName1, @Nullable String privateKey1, @Nullable String cert1, @Nullable String trustCa1, String certInstanceName2, String privateKey2, String cert2, - String trustCa2) { + String trustCa2, @Nullable String spiffeTrustMap) { // get temp file for each file try { if (privateKey1 != null) { @@ -109,6 +109,9 @@ public class CommonBootstrapperTestUtils { if (trustCa2 != null) { trustCa2 = CommonTlsContextTestsUtil.getTempFileNameForResourcesFile(trustCa2); } + if (spiffeTrustMap != null) { + spiffeTrustMap = CommonTlsContextTestsUtil.getTempFileNameForResourcesFile(spiffeTrustMap); + } } catch (IOException ioe) { throw new RuntimeException(ioe); } @@ -116,6 +119,9 @@ public class CommonBootstrapperTestUtils { config.put("certificate_file", cert1); config.put("private_key_file", privateKey1); config.put("ca_certificate_file", trustCa1); + if (spiffeTrustMap != null) { + config.put("spiffe_trust_bundle_map_file", spiffeTrustMap); + } Bootstrapper.CertificateProviderInfo certificateProviderInfo = Bootstrapper.CertificateProviderInfo.create("file_watcher", config); HashMap certProviders = @@ -126,6 +132,9 @@ public class CommonBootstrapperTestUtils { config.put("certificate_file", cert2); config.put("private_key_file", privateKey2); config.put("ca_certificate_file", trustCa2); + if (spiffeTrustMap != null) { + config.put("spiffe_trust_bundle_map_file", spiffeTrustMap); + } certificateProviderInfo = Bootstrapper.CertificateProviderInfo.create("file_watcher", config); certProviders.put(certInstanceName2, certificateProviderInfo); diff --git a/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java index f56a7c367b..8e1220fe9d 100644 --- a/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java @@ -24,8 +24,12 @@ import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_SERVER import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_SPIFFE_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_SPIFFE_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SPIFFE_TRUST_MAP_1_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SPIFFE_TRUST_MAP_FILE; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; @@ -67,6 +71,7 @@ import io.grpc.xds.internal.Matchers.HeaderMatcher; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import io.grpc.xds.internal.security.SslContextProviderSupplier; import io.grpc.xds.internal.security.TlsContextManagerImpl; +import io.grpc.xds.internal.security.certprovider.FileWatcherCertificateProviderProvider; import io.netty.handler.ssl.NotSslRecordException; import java.io.File; import java.io.FileOutputStream; @@ -85,6 +90,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; @@ -92,18 +98,25 @@ import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; /** * Unit tests for {@link XdsChannelCredentials} and {@link XdsServerBuilder} for plaintext/TLS/mTLS * modes. */ -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public class XdsSecurityClientServerTest { + @Parameter + public Boolean enableSpiffe; + private Boolean originalEnableSpiffe; + @Rule public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); private int port; private FakeNameResolverFactory fakeNameResolverFactory; @@ -115,11 +128,27 @@ public class XdsSecurityClientServerTest { private FakeXdsClientPoolFactory fakePoolFactory = new FakeXdsClientPoolFactory(xdsClient); private static final String OVERRIDE_AUTHORITY = "foo.test.google.fr"; + @Parameters(name = "enableSpiffe={0}") + public static Collection data() { + return ImmutableList.of(true, false); + } + + @Before + public void setUp() throws IOException { + saveEnvironment(); + FileWatcherCertificateProviderProvider.enableSpiffe = enableSpiffe; + } + + private void saveEnvironment() { + originalEnableSpiffe = FileWatcherCertificateProviderProvider.enableSpiffe; + } + @After public void tearDown() throws IOException { if (fakeNameResolverFactory != null) { NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverFactory); } + FileWatcherCertificateProviderProvider.enableSpiffe = originalEnableSpiffe; } @Test @@ -146,13 +175,13 @@ public class XdsSecurityClientServerTest { @Test public void tlsClientServer_noClientAuthentication() throws Exception { DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); buildServerWithTlsContext(downstreamTlsContext); // for TLS, client only needs trustCa UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - CLIENT_KEY_FILE, - CLIENT_PEM_FILE, false); + CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, false); SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); @@ -169,7 +198,8 @@ public class XdsSecurityClientServerTest { try { setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString()); DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); buildServerWithTlsContext(downstreamTlsContext); UpstreamTlsContext upstreamTlsContext = @@ -195,7 +225,8 @@ public class XdsSecurityClientServerTest { try { setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString()); DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); buildServerWithTlsContext(downstreamTlsContext); UpstreamTlsContext upstreamTlsContext = @@ -221,7 +252,8 @@ public class XdsSecurityClientServerTest { try { setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString()); DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); buildServerWithTlsContext(downstreamTlsContext); UpstreamTlsContext upstreamTlsContext = @@ -252,17 +284,59 @@ public class XdsSecurityClientServerTest { return trustStoreFile.toPath(); } + @Test + public void tlsClientServer_Spiffe_noClientAuthentication() throws Exception { + DownstreamTlsContext downstreamTlsContext = + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_SPIFFE_PEM_FILE, null, null, null, + null, null, false, false); + buildServerWithTlsContext(downstreamTlsContext); + + // for TLS, client only needs trustCa, so BAD certs don't matter + UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( + BAD_CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, SPIFFE_TRUST_MAP_FILE, false); + + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = + getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); + assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy"); + } + + @Test + public void tlsClientServer_Spiffe_noClientAuthentication_wrongServerCert() throws Exception { + if (!enableSpiffe) { + return; + } + DownstreamTlsContext downstreamTlsContext = + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); + buildServerWithTlsContext(downstreamTlsContext); + + // for TLS, client only needs trustCa, so BAD certs don't matter + UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( + BAD_CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, SPIFFE_TRUST_MAP_FILE, false); + + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = + getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); + try { + unaryRpc("buddy", blockingStub); + fail("exception expected"); + } catch (StatusRuntimeException sre) { + assertThat(sre.getStatus().getCode()).isEqualTo(Status.UNAVAILABLE.getCode()); + assertThat(sre.getCause().getCause().getMessage()) + .contains("Failed to extract SPIFFE ID from peer leaf certificate"); + } + } + @Test public void requireClientAuth_noClientCert_expectException() throws Exception { DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, true, true); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, true, true); buildServerWithTlsContext(downstreamTlsContext); // for TLS, client only uses trustCa UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - CLIENT_KEY_FILE, - CLIENT_PEM_FILE, false); + CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, false); SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); @@ -284,12 +358,12 @@ public class XdsSecurityClientServerTest { @Test public void noClientAuth_sendBadClientCert_passes() throws Exception { DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); buildServerWithTlsContext(downstreamTlsContext); UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - BAD_CLIENT_KEY_FILE, - BAD_CLIENT_PEM_FILE, true); + BAD_CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, null, true); SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); @@ -299,8 +373,7 @@ public class XdsSecurityClientServerTest { @Test public void mtls_badClientCert_expectException() throws Exception { UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - BAD_CLIENT_KEY_FILE, - BAD_CLIENT_PEM_FILE, true); + BAD_CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, null, true); try { performMtlsTestAndGetListenerWatcher(upstreamTlsContext, null, null, null, null); fail("exception expected"); @@ -316,20 +389,58 @@ public class XdsSecurityClientServerTest { } } - /** mTLS - client auth enabled - using {@link XdsChannelCredentials} API. */ + /** mTLS with Spiffe Trust Bundle - client auth enabled - using {@link XdsChannelCredentials} + * API. */ + @Test + public void mtlsClientServer_Spiffe_withClientAuthentication_withXdsChannelCreds() + throws Exception { + DownstreamTlsContext downstreamTlsContext = + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_SPIFFE_PEM_FILE, null, null, null, + null, SPIFFE_TRUST_MAP_1_FILE, true, true); + buildServerWithTlsContext(downstreamTlsContext); + + UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( + CLIENT_KEY_FILE, CLIENT_SPIFFE_PEM_FILE, SPIFFE_TRUST_MAP_1_FILE, true); + + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = + getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); + assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy"); + } + + @Test + public void mtlsClientServer_Spiffe_badClientCert_expectException() + throws Exception { + DownstreamTlsContext downstreamTlsContext = + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_SPIFFE_PEM_FILE, null, null, null, + null, SPIFFE_TRUST_MAP_1_FILE, true, true); + buildServerWithTlsContext(downstreamTlsContext); + + UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( + CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, SPIFFE_TRUST_MAP_1_FILE, true); + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = + getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); + try { + assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy"); + fail("exception expected"); + } catch (StatusRuntimeException sre) { + assertThat(sre.getStatus().getCode()).isEqualTo(Status.UNAVAILABLE.getCode()); + assertThat(sre.getMessage()).contains("ssl exception"); + } + } + @Test public void mtlsClientServer_withClientAuthentication_withXdsChannelCreds() throws Exception { UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - CLIENT_KEY_FILE, - CLIENT_PEM_FILE, true); + CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, true); performMtlsTestAndGetListenerWatcher(upstreamTlsContext, null, null, null, null); } @Test public void tlsServer_plaintextClient_expectException() throws Exception { DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null, + null, false, false); buildServerWithTlsContext(downstreamTlsContext); SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = @@ -349,8 +460,7 @@ public class XdsSecurityClientServerTest { // for TLS, client only needs trustCa UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - CLIENT_KEY_FILE, - CLIENT_PEM_FILE, false); + CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, false); SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); @@ -368,8 +478,7 @@ public class XdsSecurityClientServerTest { public void mtlsClientServer_changeServerContext_expectException() throws Exception { UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( - CLIENT_KEY_FILE, - CLIENT_PEM_FILE, true); + CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, true); performMtlsTestAndGetListenerWatcher(upstreamTlsContext, "cert-instance-name2", BAD_SERVER_KEY_FILE, BAD_SERVER_PEM_FILE, CA_PEM_FILE); @@ -396,8 +505,8 @@ public class XdsSecurityClientServerTest { String privateKey2, String cert2, String trustCa2) throws Exception { DownstreamTlsContext downstreamTlsContext = - setBootstrapInfoAndBuildDownstreamTlsContext(certInstanceName2, privateKey2, cert2, - trustCa2, true, true); + setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, certInstanceName2, + privateKey2, cert2, trustCa2, null, true, false); buildServerWithFallbackServerCredentials( InsecureServerCredentials.create(), downstreamTlsContext); @@ -408,22 +517,21 @@ public class XdsSecurityClientServerTest { } private DownstreamTlsContext setBootstrapInfoAndBuildDownstreamTlsContext( - String certInstanceName2, - String privateKey2, - String cert2, String trustCa2, boolean hasRootCert, boolean requireClientCertificate) { + String cert1, String certInstanceName2, String privateKey2, + String cert2, String trustCa2, String spiffeFile, + boolean hasRootCert, boolean requireClientCertificate) { bootstrapInfoForServer = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, - SERVER_1_PEM_FILE, CA_PEM_FILE, certInstanceName2, privateKey2, cert2, trustCa2); + cert1, CA_PEM_FILE, certInstanceName2, privateKey2, cert2, trustCa2, spiffeFile); return CommonTlsContextTestsUtil.buildDownstreamTlsContext( "google_cloud_private_spiffe-server", hasRootCert, requireClientCertificate); } private UpstreamTlsContext setBootstrapInfoAndBuildUpstreamTlsContext(String clientKeyFile, - String clientPemFile, - boolean hasIdentityCert) { + String clientPemFile, String spiffeFile, boolean hasIdentityCert) { bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", clientKeyFile, clientPemFile, - CA_PEM_FILE, null, null, null, null); + CA_PEM_FILE, null, null, null, null, spiffeFile); return CommonTlsContextTestsUtil .buildUpstreamTlsContext("google_cloud_private_spiffe-client", hasIdentityCert); } @@ -434,7 +542,7 @@ public class XdsSecurityClientServerTest { boolean useCombinedValidationContext) { bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", clientKeyFile, clientPemFile, - CA_PEM_FILE, null, null, null, null); + CA_PEM_FILE, null, null, null, null, null); if (useCombinedValidationContext) { return CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( "google_cloud_private_spiffe-client", "ROOT", null, diff --git a/xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java index db82a8682e..a6cf2c52a1 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java @@ -48,10 +48,14 @@ public class CommonTlsContextTestsUtil { public static final String SERVER_0_PEM_FILE = "server0.pem"; public static final String SERVER_0_KEY_FILE = "server0.key"; public static final String SERVER_1_PEM_FILE = "server1.pem"; + public static final String SERVER_1_SPIFFE_PEM_FILE = "server1_spiffe.pem"; public static final String SERVER_1_KEY_FILE = "server1.key"; public static final String CLIENT_PEM_FILE = "client.pem"; + public static final String CLIENT_SPIFFE_PEM_FILE = "client_spiffe.pem"; public static final String CLIENT_KEY_FILE = "client.key"; public static final String CA_PEM_FILE = "ca.pem"; + public static final String SPIFFE_TRUST_MAP_FILE = "spiffebundle.json"; + public static final String SPIFFE_TRUST_MAP_1_FILE = "spiffebundle1.json"; /** Bad/untrusted server certs. */ public static final String BAD_SERVER_PEM_FILE = "badserver.pem"; public static final String BAD_SERVER_KEY_FILE = "badserver.key"; diff --git a/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java index da7f8113df..fdfcd36993 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java @@ -149,7 +149,7 @@ public class SecurityProtocolNegotiatorsTest { CommonCertProviderTestUtils.register(executor); Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, - CA_PEM_FILE, null, null, null, null); + CA_PEM_FILE, null, null, null, null, null); UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil .buildUpstreamTlsContext("google_cloud_private_spiffe-client", true); @@ -216,7 +216,7 @@ public class SecurityProtocolNegotiatorsTest { pipeline = channel.pipeline(); Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, - SERVER_1_PEM_FILE, CA_PEM_FILE, null, null, null, null); + SERVER_1_PEM_FILE, CA_PEM_FILE, null, null, null, null, null); DownstreamTlsContext downstreamTlsContext = CommonTlsContextTestsUtil.buildDownstreamTlsContext( "google_cloud_private_spiffe-server", true, true); @@ -361,7 +361,7 @@ public class SecurityProtocolNegotiatorsTest { CommonCertProviderTestUtils.register(executor); Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, - CA_PEM_FILE, null, null, null, null); + CA_PEM_FILE, null, null, null, null, null); UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil .buildUpstreamTlsContext("google_cloud_private_spiffe-client", true); @@ -412,7 +412,7 @@ public class SecurityProtocolNegotiatorsTest { CommonCertProviderTestUtils.register(executor); Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, - CA_PEM_FILE, null, null, null, null); + CA_PEM_FILE, null, null, null, null, null); UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil .buildUpstreamTlsContext("google_cloud_private_spiffe-client", true); diff --git a/xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java b/xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java index 4d04eeb41e..29d131cb8d 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java @@ -57,7 +57,7 @@ public class TlsContextManagerTest { public void createServerSslContextProvider() { Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, - SERVER_1_PEM_FILE, CA_PEM_FILE, null, null, null, null); + SERVER_1_PEM_FILE, CA_PEM_FILE, null, null, null, null, null); DownstreamTlsContext downstreamTlsContext = CommonTlsContextTestsUtil.buildDownstreamTlsContext( "google_cloud_private_spiffe-server", false, false); @@ -76,7 +76,7 @@ public class TlsContextManagerTest { public void createClientSslContextProvider() { Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, - CA_PEM_FILE, null, null, null, null); + CA_PEM_FILE, null, null, null, null, null); UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil .buildUpstreamTlsContext("google_cloud_private_spiffe-client", false); @@ -96,7 +96,7 @@ public class TlsContextManagerTest { Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, SERVER_1_PEM_FILE, CA_PEM_FILE, "cert-instance2", SERVER_0_KEY_FILE, SERVER_0_PEM_FILE, - CA_PEM_FILE); + CA_PEM_FILE, null); DownstreamTlsContext downstreamTlsContext = CommonTlsContextTestsUtil.buildDownstreamTlsContext( "google_cloud_private_spiffe-server", false, false); @@ -120,7 +120,7 @@ public class TlsContextManagerTest { public void createClientSslContextProvider_differentInstance() { Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils .buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, - CA_PEM_FILE, "cert-instance-2", CLIENT_KEY_FILE, CLIENT_PEM_FILE, CA_PEM_FILE); + CA_PEM_FILE, "cert-instance-2", CLIENT_KEY_FILE, CLIENT_PEM_FILE, CA_PEM_FILE, null); UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil .buildUpstreamTlsContext("google_cloud_private_spiffe-client", false); diff --git a/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java index a0bdd61800..304a2dd544 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java @@ -24,22 +24,28 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableList; import io.grpc.internal.JsonParser; import io.grpc.internal.TimeProvider; import java.io.IOException; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; +import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; /** Unit tests for {@link FileWatcherCertificateProviderProvider}. */ -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public class FileWatcherCertificateProviderProviderTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @@ -48,13 +54,28 @@ public class FileWatcherCertificateProviderProviderTest { scheduledExecutorServiceFactory; @Mock private TimeProvider timeProvider; + @Parameter + public boolean enableSpiffe; + private boolean originalEnableSpiffe; private FileWatcherCertificateProviderProvider provider; + @Parameters(name = "enableSpiffe={0}") + public static Collection data() { + return ImmutableList.of(true, false); + } + @Before public void setUp() throws IOException { provider = new FileWatcherCertificateProviderProvider( fileWatcherCertificateProviderFactory, scheduledExecutorServiceFactory, timeProvider); + originalEnableSpiffe = FileWatcherCertificateProviderProvider.enableSpiffe; + FileWatcherCertificateProviderProvider.enableSpiffe = enableSpiffe; + } + + @After + public void restoreEnvironment() { + FileWatcherCertificateProviderProvider.enableSpiffe = originalEnableSpiffe; } @Test @@ -85,6 +106,30 @@ public class FileWatcherCertificateProviderProviderTest { eq("/var/run/gke-spiffe/certs/certificates.pem"), eq("/var/run/gke-spiffe/certs/private_key.pem"), eq("/var/run/gke-spiffe/certs/ca_certificates.pem"), + eq(null), + eq(600L), + eq(mockService), + eq(timeProvider)); + } + + @Test + public void createProvider_minimalSpiffeConfig() throws IOException { + Assume.assumeTrue(enableSpiffe); + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + @SuppressWarnings("unchecked") + Map map = (Map) JsonParser.parse(MINIMAL_FILE_WATCHER_WITH_SPIFFE_CONFIG); + ScheduledExecutorService mockService = mock(ScheduledExecutorService.class); + when(scheduledExecutorServiceFactory.create()).thenReturn(mockService); + provider.createCertificateProvider(map, distWatcher, true); + verify(fileWatcherCertificateProviderFactory, times(1)) + .create( + eq(distWatcher), + eq(true), + eq("/var/run/gke-spiffe/certs/certificates.pem"), + eq("/var/run/gke-spiffe/certs/private_key.pem"), + eq(null), + eq("/var/run/gke-spiffe/certs/spiffe_bundle.json"), eq(600L), eq(mockService), eq(timeProvider)); @@ -106,6 +151,30 @@ public class FileWatcherCertificateProviderProviderTest { eq("/var/run/gke-spiffe/certs/certificates2.pem"), eq("/var/run/gke-spiffe/certs/private_key3.pem"), eq("/var/run/gke-spiffe/certs/ca_certificates4.pem"), + eq(null), + eq(7890L), + eq(mockService), + eq(timeProvider)); + } + + @Test + public void createProvider_spiffeConfig() throws IOException { + Assume.assumeTrue(enableSpiffe); + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + @SuppressWarnings("unchecked") + Map map = (Map) JsonParser.parse(FULL_FILE_WATCHER_WITH_SPIFFE_CONFIG); + ScheduledExecutorService mockService = mock(ScheduledExecutorService.class); + when(scheduledExecutorServiceFactory.create()).thenReturn(mockService); + provider.createCertificateProvider(map, distWatcher, true); + verify(fileWatcherCertificateProviderFactory, times(1)) + .create( + eq(distWatcher), + eq(true), + eq("/var/run/gke-spiffe/certs/certificates2.pem"), + eq("/var/run/gke-spiffe/certs/private_key3.pem"), + eq(null), + eq("/var/run/gke-spiffe/certs/spiffe_bundle.json"), eq(7890L), eq(mockService), eq(timeProvider)); @@ -157,15 +226,18 @@ public class FileWatcherCertificateProviderProviderTest { @Test public void createProvider_missingRoot_expectException() throws IOException { + String expectedMessage = enableSpiffe ? "either 'ca_certificate_file' or " + + "'spiffe_trust_bundle_map_file' is required in the config" + : "'ca_certificate_file' is required in the config"; CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); @SuppressWarnings("unchecked") - Map map = (Map) JsonParser.parse(MISSING_ROOT_CONFIG); + Map map = (Map) JsonParser.parse(MISSING_ROOT_AND_SPIFFE_CONFIG); try { provider.createCertificateProvider(map, distWatcher, true); fail("exception expected"); } catch (NullPointerException npe) { - assertThat(npe).hasMessageThat().isEqualTo("'ca_certificate_file' is required in the config"); + assertThat(npe).hasMessageThat().isEqualTo(expectedMessage); } } @@ -176,6 +248,14 @@ public class FileWatcherCertificateProviderProviderTest { + " \"ca_certificate_file\": \"/var/run/gke-spiffe/certs/ca_certificates.pem\"" + " }"; + private static final String MINIMAL_FILE_WATCHER_WITH_SPIFFE_CONFIG = + "{\n" + + " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates.pem\"," + + " \"private_key_file\": \"/var/run/gke-spiffe/certs/private_key.pem\"," + + " \"spiffe_trust_bundle_map_file\":" + + " \"/var/run/gke-spiffe/certs/spiffe_bundle.json\"" + + " }"; + private static final String FULL_FILE_WATCHER_CONFIG = "{\n" + " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates2.pem\"," @@ -184,6 +264,16 @@ public class FileWatcherCertificateProviderProviderTest { + " \"refresh_interval\": \"7890s\"" + " }"; + private static final String FULL_FILE_WATCHER_WITH_SPIFFE_CONFIG = + "{\n" + + " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates2.pem\"," + + " \"private_key_file\": \"/var/run/gke-spiffe/certs/private_key3.pem\"," + + " \"ca_certificate_file\": \"/var/run/gke-spiffe/certs/ca_certificates4.pem\"," + + " \"spiffe_trust_bundle_map_file\":" + + " \"/var/run/gke-spiffe/certs/spiffe_bundle.json\"," + + " \"refresh_interval\": \"7890s\"" + + " }"; + private static final String MISSING_CERT_CONFIG = "{\n" + " \"private_key_file\": \"/var/run/gke-spiffe/certs/private_key.pem\"," @@ -196,7 +286,7 @@ public class FileWatcherCertificateProviderProviderTest { + " \"ca_certificate_file\": \"/var/run/gke-spiffe/certs/ca_certificates.pem\"" + " }"; - private static final String MISSING_ROOT_CONFIG = + private static final String MISSING_ROOT_AND_SPIFFE_CONFIG = "{\n" + " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates.pem\"," + " \"private_key_file\": \"/var/run/gke-spiffe/certs/private_key.pem\"" diff --git a/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java index 210ec05673..620ee0a7ff 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java @@ -23,6 +23,7 @@ import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SPIFFE_TRUST_MAP_1_FILE; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -47,6 +48,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.Delayed; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -73,6 +75,7 @@ public class FileWatcherCertificateProviderTest { private static final String CERT_FILE = "cert.pem"; private static final String KEY_FILE = "key.pem"; private static final String ROOT_FILE = "root.pem"; + private static final String SPIFFE_TRUST_MAP_FILE = "spiffebundle.json"; @Mock private CertificateProvider.Watcher mockWatcher; @Mock private ScheduledExecutorService timeService; @@ -84,28 +87,33 @@ public class FileWatcherCertificateProviderTest { private String certFile; private String keyFile; private String rootFile; + private String spiffeTrustMapFile; private FileWatcherCertificateProvider provider; + private DistributorWatcher watcher; @Before public void setUp() throws IOException { - DistributorWatcher watcher = new DistributorWatcher(); + watcher = new DistributorWatcher(); watcher.addWatcher(mockWatcher); certFile = new File(tempFolder.getRoot(), CERT_FILE).getAbsolutePath(); keyFile = new File(tempFolder.getRoot(), KEY_FILE).getAbsolutePath(); rootFile = new File(tempFolder.getRoot(), ROOT_FILE).getAbsolutePath(); + spiffeTrustMapFile = new File(tempFolder.getRoot(), SPIFFE_TRUST_MAP_FILE).getAbsolutePath(); provider = - new FileWatcherCertificateProvider( - watcher, true, certFile, keyFile, rootFile, 600L, timeService, timeProvider); + new FileWatcherCertificateProvider(watcher, true, certFile, keyFile, rootFile, null, 600L, + timeService, timeProvider); } private void populateTarget( String certFileSource, String keyFileSource, String rootFileSource, + String spiffeTrustMapFileSource, boolean deleteCurCert, boolean deleteCurKey, + boolean deleteCurSpiffeTrustMap, boolean deleteCurRoot) throws IOException { if (deleteCurCert) { @@ -135,6 +143,17 @@ public class FileWatcherCertificateProviderTest { Files.setLastModifiedTime( Paths.get(rootFile), FileTime.fromMillis(timeProvider.currentTimeMillis())); } + if (deleteCurSpiffeTrustMap) { + Files.delete(Paths.get(spiffeTrustMapFile)); + } + if (spiffeTrustMapFileSource != null) { + spiffeTrustMapFileSource = CommonTlsContextTestsUtil + .getTempFileNameForResourcesFile(spiffeTrustMapFileSource); + Files.copy(Paths.get(spiffeTrustMapFileSource), + Paths.get(spiffeTrustMapFile), REPLACE_EXISTING); + Files.setLastModifiedTime( + Paths.get(spiffeTrustMapFile), FileTime.fromMillis(timeProvider.currentTimeMillis())); + } } @Test @@ -144,9 +163,9 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, false, false, false); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, null, false, false, false, false); provider.checkAndReloadCertificates(); - verifyWatcherUpdates(CLIENT_PEM_FILE, CA_PEM_FILE); + verifyWatcherUpdates(CLIENT_PEM_FILE, CA_PEM_FILE, null); verifyTimeServiceAndScheduledFuture(); reset(mockWatcher, timeService); @@ -165,7 +184,7 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, false, false, false); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, null, false, false, false, false); provider.checkAndReloadCertificates(); reset(mockWatcher, timeService); @@ -173,9 +192,10 @@ public class FileWatcherCertificateProviderTest { .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); timeProvider.forwardTime(1, TimeUnit.SECONDS); - populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, SERVER_1_PEM_FILE, false, false, false); + populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, SERVER_1_PEM_FILE, null, false, false, + false, false); provider.checkAndReloadCertificates(); - verifyWatcherUpdates(SERVER_0_PEM_FILE, SERVER_1_PEM_FILE); + verifyWatcherUpdates(SERVER_0_PEM_FILE, SERVER_1_PEM_FILE, null); verifyTimeServiceAndScheduledFuture(); } @@ -186,12 +206,13 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, false, false, false); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, null, false, false, false, false); provider.close(); provider.checkAndReloadCertificates(); verify(mockWatcher, never()) .updateCertificate(any(PrivateKey.class), ArgumentMatchers.anyList()); verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.anyList()); + verify(mockWatcher, never()).updateSpiffeTrustMap(ArgumentMatchers.anyMap()); verify(timeService, never()).schedule(any(Runnable.class), any(Long.TYPE), any(TimeUnit.class)); verify(timeService, times(1)).shutdownNow(); } @@ -204,7 +225,7 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, false, false, false); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, null, false, false, false, false); provider.checkAndReloadCertificates(); reset(mockWatcher, timeService); @@ -212,9 +233,9 @@ public class FileWatcherCertificateProviderTest { .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); timeProvider.forwardTime(1, TimeUnit.SECONDS); - populateTarget(null, null, SERVER_1_PEM_FILE, false, false, false); + populateTarget(null, null, SERVER_1_PEM_FILE, null, false, false, false, false); provider.checkAndReloadCertificates(); - verifyWatcherUpdates(null, SERVER_1_PEM_FILE); + verifyWatcherUpdates(null, SERVER_1_PEM_FILE, null); verifyTimeServiceAndScheduledFuture(); } @@ -226,7 +247,7 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, false, false, false); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, CA_PEM_FILE, null, false, false, false, false); provider.checkAndReloadCertificates(); reset(mockWatcher, timeService); @@ -234,9 +255,44 @@ public class FileWatcherCertificateProviderTest { .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); timeProvider.forwardTime(1, TimeUnit.SECONDS); - populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, null, false, false, false); + populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, null, null, false, false, false, false); provider.checkAndReloadCertificates(); - verifyWatcherUpdates(SERVER_0_PEM_FILE, null); + verifyWatcherUpdates(SERVER_0_PEM_FILE, null, null); + verifyTimeServiceAndScheduledFuture(); + } + + @Test + public void spiffeTrustMapFileUpdateOnly() throws Exception { + provider = new FileWatcherCertificateProvider(watcher, true, certFile, keyFile, null, + spiffeTrustMapFile, 600L, timeService, timeProvider); + TestScheduledFuture scheduledFuture = + new TestScheduledFuture<>(); + doReturn(scheduledFuture) + .when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, null, null, false, false, false, false); + provider.checkAndReloadCertificates(); + verify(mockWatcher, never()).updateSpiffeTrustMap(ArgumentMatchers.anyMap()); + + reset(timeService); + doReturn(scheduledFuture) + .when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + timeProvider.forwardTime(1, TimeUnit.SECONDS); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, null, SPIFFE_TRUST_MAP_FILE, false, + false, false, false); + provider.checkAndReloadCertificates(); + verify(mockWatcher, times(1)).updateSpiffeTrustMap(ArgumentMatchers.anyMap()); + + reset(timeService); + doReturn(scheduledFuture) + .when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + timeProvider.forwardTime(1, TimeUnit.SECONDS); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, null, SPIFFE_TRUST_MAP_1_FILE, false, + false, false, false); + provider.checkAndReloadCertificates(); + verify(mockWatcher, times(2)).updateSpiffeTrustMap(ArgumentMatchers.anyMap()); verifyTimeServiceAndScheduledFuture(); } @@ -247,7 +303,7 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(null, CLIENT_KEY_FILE, CA_PEM_FILE, false, false, false); + populateTarget(null, CLIENT_KEY_FILE, CA_PEM_FILE, null, false, false, false, false); provider.checkAndReloadCertificates(); verifyWatcherErrorUpdates(Status.Code.UNKNOWN, NoSuchFileException.class, 0, 1, "cert.pem"); } @@ -255,13 +311,14 @@ public class FileWatcherCertificateProviderTest { @Test public void getCertificate_missingCertFile() throws IOException, InterruptedException { commonErrorTest( - null, CLIENT_KEY_FILE, CA_PEM_FILE, NoSuchFileException.class, 0, 1, 0, 0, "cert.pem"); + null, CLIENT_KEY_FILE, CA_PEM_FILE, null, NoSuchFileException.class, 0, 1, 0, 0, + "cert.pem"); } @Test public void getCertificate_missingKeyFile() throws IOException, InterruptedException { commonErrorTest( - CLIENT_PEM_FILE, null, CA_PEM_FILE, NoSuchFileException.class, 0, 1, 0, 0, "key.pem"); + CLIENT_PEM_FILE, null, CA_PEM_FILE, null, NoSuchFileException.class, 0, 1, 0, 0, "key.pem"); } @Test @@ -270,6 +327,7 @@ public class FileWatcherCertificateProviderTest { CLIENT_PEM_FILE, SERVER_0_PEM_FILE, CA_PEM_FILE, + null, java.security.spec.InvalidKeySpecException.class, 0, 1, @@ -285,12 +343,13 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, SERVER_1_PEM_FILE, false, false, false); + populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, SERVER_1_PEM_FILE, null, false, false, + false, false); provider.checkAndReloadCertificates(); reset(mockWatcher); timeProvider.forwardTime(1, TimeUnit.SECONDS); - populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, null, false, false, true); + populateTarget(CLIENT_PEM_FILE, CLIENT_KEY_FILE, null, null, false, false, false, true); timeProvider.forwardTime( CERT0_EXPIRY_TIME_MILLIS - 610_000L - timeProvider.currentTimeMillis(), TimeUnit.MILLISECONDS); @@ -302,6 +361,7 @@ public class FileWatcherCertificateProviderTest { String certFile, String keyFile, String rootFile, + String spiffeFile, Class throwableType, int firstUpdateCertCount, int firstUpdateRootCount, @@ -314,13 +374,15 @@ public class FileWatcherCertificateProviderTest { doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, SERVER_1_PEM_FILE, false, false, false); + populateTarget(SERVER_0_PEM_FILE, SERVER_0_KEY_FILE, SERVER_1_PEM_FILE, + SPIFFE_TRUST_MAP_1_FILE, false, false, false, false); provider.checkAndReloadCertificates(); reset(mockWatcher); timeProvider.forwardTime(1, TimeUnit.SECONDS); populateTarget( - certFile, keyFile, rootFile, certFile == null, keyFile == null, rootFile == null); + certFile, keyFile, rootFile, spiffeFile, certFile == null, keyFile == null, + rootFile == null, spiffeFile == null); timeProvider.forwardTime( CERT0_EXPIRY_TIME_MILLIS - 610_000L - timeProvider.currentTimeMillis(), TimeUnit.MILLISECONDS); @@ -372,7 +434,7 @@ public class FileWatcherCertificateProviderTest { assertThat(provider.scheduledFuture.isCancelled()).isFalse(); } - private void verifyWatcherUpdates(String certPemFile, String rootPemFile) + private void verifyWatcherUpdates(String certPemFile, String rootPemFile, String spiffeFile) throws IOException, CertificateException { if (certPemFile != null) { @SuppressWarnings("unchecked") @@ -399,6 +461,17 @@ public class FileWatcherCertificateProviderTest { } else { verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.anyList()); } + if (spiffeFile != null) { + @SuppressWarnings("unchecked") + ArgumentCaptor>> spiffeCaptor = + ArgumentCaptor.forClass(Map.class); + verify(mockWatcher, times(1)).updateSpiffeTrustMap(spiffeCaptor.capture()); + Map> trustMap = spiffeCaptor.getValue(); + assertThat(trustMap).hasSize(2); + verify(mockWatcher, never()).onError(any(Status.class)); + } else { + verify(mockWatcher, never()).updateSpiffeTrustMap(ArgumentMatchers.anyMap()); + } } static class TestScheduledFuture implements ScheduledFuture { diff --git a/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java index 77749814cf..db83961cfc 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java @@ -23,6 +23,8 @@ import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FIL import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; import io.envoyproxy.envoy.config.core.v3.DataSource; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; @@ -105,6 +107,46 @@ public class XdsTrustManagerFactoryTest { .isEqualTo(CertificateUtils.toX509Certificates(TlsTesting.loadCert(CA_PEM_FILE))[0]); } + @Test + public void constructor_fromSpiffeTrustMap() + throws CertificateException, IOException, CertStoreException { + X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); + CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", + "san2"); + // Single domain and single cert + XdsTrustManagerFactory factory = new XdsTrustManagerFactory(ImmutableMap + .of("example.com", ImmutableList.of(x509Cert)), staticValidationContext); + assertThat(factory).isNotNull(); + TrustManager[] tms = factory.getTrustManagers(); + assertThat(tms).isNotNull(); + assertThat(tms).hasLength(1); + TrustManager myTm = tms[0]; + assertThat(myTm).isInstanceOf(XdsX509TrustManager.class); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) myTm; + assertThat(xdsX509TrustManager.getAcceptedIssuers()).isNotNull(); + assertThat(xdsX509TrustManager.getAcceptedIssuers()).hasLength(1); + assertThat(xdsX509TrustManager.getAcceptedIssuers()[0].getIssuerX500Principal().getName()) + .isEqualTo("CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"); + // Multiple domains and multiple certs for one of it + X509Certificate anotherCert = TestUtils.loadX509Cert(CLIENT_PEM_FILE); + factory = new XdsTrustManagerFactory(ImmutableMap + .of("example.com", ImmutableList.of(x509Cert), + "google.com", ImmutableList.of(x509Cert, anotherCert)), staticValidationContext); + assertThat(factory).isNotNull(); + tms = factory.getTrustManagers(); + assertThat(tms).isNotNull(); + assertThat(tms).hasLength(1); + myTm = tms[0]; + assertThat(myTm).isInstanceOf(XdsX509TrustManager.class); + xdsX509TrustManager = (XdsX509TrustManager) myTm; + assertThat(xdsX509TrustManager.getAcceptedIssuers()).isNotNull(); + assertThat(xdsX509TrustManager.getAcceptedIssuers()).hasLength(2); + assertThat(xdsX509TrustManager.getAcceptedIssuers()[0].getIssuerX500Principal().getName()) + .isEqualTo("CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"); + assertThat(xdsX509TrustManager.getAcceptedIssuers()[1].getIssuerX500Principal().getName()) + .isEqualTo("CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"); + } + @Test public void constructorRootCert_checkServerTrusted() throws CertificateException, IOException, CertStoreException { diff --git a/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java index 9ceb6f706f..6fa3d2e7d2 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java @@ -20,7 +20,9 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_SPIFFE_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_SPIFFE_PEM_FILE; import static org.junit.Assert.fail; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doReturn; @@ -30,6 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; @@ -38,6 +41,7 @@ import java.io.IOException; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.net.ssl.SSLEngine; @@ -537,6 +541,71 @@ public class XdsX509TrustManagerTest { assertThat(sslEngine.getSSLParameters().getEndpointIdentificationAlgorithm()).isEmpty(); } + @Test + public void checkServerTrustedSslEngineSpiffeTrustMap() + throws CertificateException, IOException, CertStoreException { + TestSslEngine sslEngine = buildTrustManagerAndGetSslEngine(); + X509Certificate[] serverCerts = + CertificateUtils.toX509Certificates(TlsTesting.loadCert(SERVER_1_SPIFFE_PEM_FILE)); + List caCerts = Arrays.asList(CertificateUtils + .toX509Certificates(TlsTesting.loadCert(CA_PEM_FILE))); + trustManager = XdsTrustManagerFactory.createX509TrustManager( + ImmutableMap.of("example.com", caCerts), null); + trustManager.checkServerTrusted(serverCerts, "ECDHE_ECDSA", sslEngine); + verify(sslEngine, times(1)).getHandshakeSession(); + assertThat(sslEngine.getSSLParameters().getEndpointIdentificationAlgorithm()).isEmpty(); + } + + @Test + public void checkServerTrustedSslEngineSpiffeTrustMap_missing_spiffe_id() + throws CertificateException, IOException, CertStoreException { + TestSslEngine sslEngine = buildTrustManagerAndGetSslEngine(); + X509Certificate[] serverCerts = + CertificateUtils.toX509Certificates(TlsTesting.loadCert(SERVER_1_PEM_FILE)); + List caCerts = Arrays.asList(CertificateUtils + .toX509Certificates(TlsTesting.loadCert(CA_PEM_FILE))); + trustManager = XdsTrustManagerFactory.createX509TrustManager( + ImmutableMap.of("example.com", caCerts), null); + try { + trustManager.checkServerTrusted(serverCerts, "ECDHE_ECDSA", sslEngine); + fail("exception expected"); + } catch (CertificateException expected) { + assertThat(expected).hasMessageThat() + .isEqualTo("Failed to extract SPIFFE ID from peer leaf certificate"); + } + } + + @Test + public void checkServerTrustedSpiffeSslEngineTrustMap_missing_trust_domain() + throws CertificateException, IOException, CertStoreException { + TestSslEngine sslEngine = buildTrustManagerAndGetSslEngine(); + X509Certificate[] serverCerts = + CertificateUtils.toX509Certificates(TlsTesting.loadCert(SERVER_1_SPIFFE_PEM_FILE)); + List caCerts = Arrays.asList(CertificateUtils + .toX509Certificates(TlsTesting.loadCert(CA_PEM_FILE))); + trustManager = XdsTrustManagerFactory.createX509TrustManager( + ImmutableMap.of("unknown.com", caCerts), null); + try { + trustManager.checkServerTrusted(serverCerts, "ECDHE_ECDSA", sslEngine); + fail("exception expected"); + } catch (CertificateException expected) { + assertThat(expected).hasMessageThat().isEqualTo("Spiffe Trust Map doesn't contain trust" + + " domain 'example.com' from peer leaf certificate"); + } + } + + @Test + public void checkClientTrustedSpiffeTrustMap() + throws CertificateException, IOException, CertStoreException { + X509Certificate[] clientCerts = + CertificateUtils.toX509Certificates(TlsTesting.loadCert(CLIENT_SPIFFE_PEM_FILE)); + List caCerts = Arrays.asList(CertificateUtils + .toX509Certificates(TlsTesting.loadCert(CA_PEM_FILE))); + trustManager = XdsTrustManagerFactory.createX509TrustManager( + ImmutableMap.of("foo.bar.com", caCerts), null); + trustManager.checkClientTrusted(clientCerts, "RSA"); + } + @Test public void checkServerTrustedSslEngine_untrustedServer_expectException() throws CertificateException, IOException, CertStoreException { @@ -565,6 +634,22 @@ public class XdsX509TrustManagerTest { assertThat(sslSocket.getSSLParameters().getEndpointIdentificationAlgorithm()).isEmpty(); } + @Test + public void checkServerTrustedSslSocketSpiffeTrustMap() + throws CertificateException, IOException, CertStoreException { + TestSslSocket sslSocket = buildTrustManagerAndGetSslSocket(); + X509Certificate[] serverCerts = + CertificateUtils.toX509Certificates(TlsTesting.loadCert(SERVER_1_SPIFFE_PEM_FILE)); + List caCerts = Arrays.asList(CertificateUtils + .toX509Certificates(TlsTesting.loadCert(CA_PEM_FILE))); + trustManager = XdsTrustManagerFactory.createX509TrustManager( + ImmutableMap.of("example.com", caCerts), null); + trustManager.checkServerTrusted(serverCerts, "ECDHE_ECDSA", sslSocket); + verify(sslSocket, times(1)).isConnected(); + verify(sslSocket, times(1)).getHandshakeSession(); + assertThat(sslSocket.getSSLParameters().getEndpointIdentificationAlgorithm()).isEmpty(); + } + @Test public void checkServerTrustedSslSocket_untrustedServer_expectException() throws CertificateException, IOException, CertStoreException {