xds: Spiffe Trust Bundle Support (#11627)

Adds verification of SPIFFE based identities using SPIFFE trust bundles.

For in-progress gRFC A87.
This commit is contained in:
erm-g 2024-11-08 00:03:15 -05:00 committed by GitHub
parent 76705c235c
commit d6c80294a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 953 additions and 120 deletions

View File

@ -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

View File

@ -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-----

View File

@ -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-----

View File

@ -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
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

View File

@ -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": "<base64urlUint-encoded value>",
"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": "<base64urlUint-encoded value>",
"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="]
}
]
}
}
}

View File

@ -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": "<base64urlUint-encoded value>",
"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=="]
}
]
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -41,6 +41,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
@Nullable protected PrivateKey savedKey;
@Nullable protected List<X509Certificate> savedCertChain;
@Nullable protected List<X509Certificate> savedTrustedRoots;
@Nullable protected Map<String, List<X509Certificate>> savedSpiffeTrustMap;
private final boolean isUsingSystemRootCerts;
protected CertProviderSslContextProvider(
@ -152,14 +153,21 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
updateSslContextWhenReady();
}
@Override
public final void updateSpiffeTrustMap(Map<String, List<X509Certificate>> 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;
}

View File

@ -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<X509Certificate> trustedRoots);
void updateSpiffeTrustMap(Map<String, List<X509Certificate>> spiffeTrustMap);
void onError(Status errorStatus);
}
@ -53,6 +56,7 @@ public abstract class CertificateProvider implements Closeable {
private PrivateKey privateKey;
private List<X509Certificate> certChain;
private List<X509Certificate> trustedRoots;
private Map<String, List<X509Certificate>> spiffeTrustMap;
@VisibleForTesting
final Set<Watcher> 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<X509Certificate> certChain) {
checkNotNull(key, "key");
@ -103,6 +114,14 @@ public abstract class CertificateProvider implements Closeable {
}
}
@Override
public void updateSpiffeTrustMap(Map<String, List<X509Certificate>> 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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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<String, List<X509Certificate>> 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<String, List<X509Certificate>> 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<String, List<X509Certificate>> spiffeTrustMapFile,
CertificateValidationContext certContext) throws CertStoreException {
checkNotNull(spiffeTrustMapFile, "spiffeTrustMapFile");
Map<String, X509ExtendedTrustManager> delegates = new HashMap<>();
for (Map.Entry<String, List<X509Certificate>> 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

View File

@ -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<String, X509ExtendedTrustManager> 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<String, X509ExtendedTrustManager> 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<SpiffeUtil.SpiffeId> 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<X509Certificate> result = new HashSet<>();
for (X509ExtendedTrustManager tm: spiffeTrustMapDelegates.values()) {
result.addAll(Arrays.asList(tm.getAcceptedIssuers()));
}
return result.toArray(new X509Certificate[0]);
}
return delegate.getAcceptedIssuers();
}
}

View File

@ -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<String, Bootstrapper.CertificateProviderInfo> 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);

View File

@ -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<Boolean> 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,

View File

@ -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";

View File

@ -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);

View File

@ -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);

View File

@ -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<Boolean> 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<String, ?> map = (Map<String, ?>) 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<String, ?> map = (Map<String, ?>) 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<String, ?> map = (Map<String, ?>) JsonParser.parse(MISSING_ROOT_CONFIG);
Map<String, ?> map = (Map<String, ?>) 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\""

View File

@ -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.<X509Certificate>anyList());
verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.<X509Certificate>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.<X509Certificate>anyList());
}
if (spiffeFile != null) {
@SuppressWarnings("unchecked")
ArgumentCaptor<Map<String, List<X509Certificate>>> spiffeCaptor =
ArgumentCaptor.forClass(Map.class);
verify(mockWatcher, times(1)).updateSpiffeTrustMap(spiffeCaptor.capture());
Map<String, List<X509Certificate>> trustMap = spiffeCaptor.getValue();
assertThat(trustMap).hasSize(2);
verify(mockWatcher, never()).onError(any(Status.class));
} else {
verify(mockWatcher, never()).updateSpiffeTrustMap(ArgumentMatchers.anyMap());
}
}
static class TestScheduledFuture<V> implements ScheduledFuture<V> {

View File

@ -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 {

View File

@ -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<X509Certificate> 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<X509Certificate> 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<X509Certificate> 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<X509Certificate> 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<X509Certificate> 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 {