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 ecparam -name secp256k1 -genkey -noout -out ecdsa.pem
$ openssl pkcs8 -topk8 -in ecdsa.pem -out ecdsa.key -nocrypt $ 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: Clean up:
--------- ---------
$ rm *.rsa $ 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] [spiffe_client_multi]
subjectAltName = @alt_names_multi subjectAltName = @alt_names_multi
[spiffe_server_e2e]
subjectAltName = @alt_names_server_e2e
[spiffe_client_e2e]
subjectAltName = @alt_names_client_e2e
[alt_names] [alt_names]
URI = spiffe://foo.bar.com/client/workload/1 URI = spiffe://foo.bar.com/client/workload/1
[alt_names_multi] [alt_names_multi]
URI.1 = spiffe://foo.bar.com/client/workload/1 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 // Null rootCertInstance implies hasSystemRootCerts because of the check in
// CertProviderClientSslContextProviderFactory. // CertProviderClientSslContextProviderFactory.
if (rootCertInstance != null) { if (rootCertInstance != null) {
sslContextBuilder.trustManager( if (savedSpiffeTrustMap != null) {
sslContextBuilder = sslContextBuilder.trustManager(
new XdsTrustManagerFactory( new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]), savedSpiffeTrustMap,
certificateValidationContextdationContext)); certificateValidationContextdationContext));
} else {
sslContextBuilder = sslContextBuilder.trustManager(
new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext));
}
} }
if (isMtls()) { if (isMtls()) {
sslContextBuilder.keyManager(savedKey, savedCertChain); sslContextBuilder.keyManager(savedKey, savedCertChain);
} }
return sslContextBuilder; return sslContextBuilder;
} }
} }

View File

@ -59,13 +59,17 @@ final class CertProviderServerSslContextProvider extends CertProviderSslContextP
CertificateValidationContext certificateValidationContextdationContext) CertificateValidationContext certificateValidationContextdationContext)
throws CertStoreException, CertificateException, IOException { throws CertStoreException, CertificateException, IOException {
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(savedKey, savedCertChain); SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(savedKey, savedCertChain);
setClientAuthValues( XdsTrustManagerFactory trustManagerFactory = null;
sslContextBuilder, if (isMtls() && savedSpiffeTrustMap != null) {
isMtls() trustManagerFactory = new XdsTrustManagerFactory(
? new XdsTrustManagerFactory( savedSpiffeTrustMap,
savedTrustedRoots.toArray(new X509Certificate[0]), certificateValidationContextdationContext);
certificateValidationContextdationContext) } else if (isMtls()) {
: null); trustManagerFactory = new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext);
}
setClientAuthValues(sslContextBuilder, trustManagerFactory);
sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder); sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder);
return sslContextBuilder; return sslContextBuilder;
} }

View File

@ -41,6 +41,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
@Nullable protected PrivateKey savedKey; @Nullable protected PrivateKey savedKey;
@Nullable protected List<X509Certificate> savedCertChain; @Nullable protected List<X509Certificate> savedCertChain;
@Nullable protected List<X509Certificate> savedTrustedRoots; @Nullable protected List<X509Certificate> savedTrustedRoots;
@Nullable protected Map<String, List<X509Certificate>> savedSpiffeTrustMap;
private final boolean isUsingSystemRootCerts; private final boolean isUsingSystemRootCerts;
protected CertProviderSslContextProvider( protected CertProviderSslContextProvider(
@ -152,14 +153,21 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
updateSslContextWhenReady(); updateSslContextWhenReady();
} }
@Override
public final void updateSpiffeTrustMap(Map<String, List<X509Certificate>> spiffeTrustMap) {
savedSpiffeTrustMap = spiffeTrustMap;
updateSslContextWhenReady();
}
private void updateSslContextWhenReady() { private void updateSslContextWhenReady() {
if (isMtls()) { if (isMtls()) {
if (savedKey != null && (savedTrustedRoots != null || isUsingSystemRootCerts)) { if (savedKey != null
&& (savedTrustedRoots != null || isUsingSystemRootCerts || savedSpiffeTrustMap != null)) {
updateSslContext(); updateSslContext();
clearKeysAndCerts(); clearKeysAndCerts();
} }
} else if (isClientSideTls()) { } else if (isClientSideTls()) {
if (savedTrustedRoots != null) { if (savedTrustedRoots != null || savedSpiffeTrustMap != null) {
updateSslContext(); updateSslContext();
clearKeysAndCerts(); clearKeysAndCerts();
} }
@ -174,6 +182,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
private void clearKeysAndCerts() { private void clearKeysAndCerts() {
savedKey = null; savedKey = null;
savedTrustedRoots = null; savedTrustedRoots = null;
savedSpiffeTrustMap = null;
savedCertChain = null; savedCertChain = null;
} }

View File

@ -26,6 +26,7 @@ import java.security.cert.X509Certificate;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -45,6 +46,8 @@ public abstract class CertificateProvider implements Closeable {
void updateTrustedRoots(List<X509Certificate> trustedRoots); void updateTrustedRoots(List<X509Certificate> trustedRoots);
void updateSpiffeTrustMap(Map<String, List<X509Certificate>> spiffeTrustMap);
void onError(Status errorStatus); void onError(Status errorStatus);
} }
@ -53,6 +56,7 @@ public abstract class CertificateProvider implements Closeable {
private PrivateKey privateKey; private PrivateKey privateKey;
private List<X509Certificate> certChain; private List<X509Certificate> certChain;
private List<X509Certificate> trustedRoots; private List<X509Certificate> trustedRoots;
private Map<String, List<X509Certificate>> spiffeTrustMap;
@VisibleForTesting @VisibleForTesting
final Set<Watcher> downstreamWatchers = new HashSet<>(); final Set<Watcher> downstreamWatchers = new HashSet<>();
@ -65,6 +69,9 @@ public abstract class CertificateProvider implements Closeable {
if (trustedRoots != null) { if (trustedRoots != null) {
sendLastTrustedRootsUpdate(watcher); sendLastTrustedRootsUpdate(watcher);
} }
if (spiffeTrustMap != null) {
sendLastSpiffeTrustMapUpdate(watcher);
}
} }
synchronized void removeWatcher(Watcher watcher) { synchronized void removeWatcher(Watcher watcher) {
@ -83,6 +90,10 @@ public abstract class CertificateProvider implements Closeable {
watcher.updateTrustedRoots(trustedRoots); watcher.updateTrustedRoots(trustedRoots);
} }
private void sendLastSpiffeTrustMapUpdate(Watcher watcher) {
watcher.updateSpiffeTrustMap(spiffeTrustMap);
}
@Override @Override
public synchronized void updateCertificate(PrivateKey key, List<X509Certificate> certChain) { public synchronized void updateCertificate(PrivateKey key, List<X509Certificate> certChain) {
checkNotNull(key, "key"); 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 @Override
public synchronized void onError(Status errorStatus) { public synchronized void onError(Status errorStatus) {
for (Watcher watcher : downstreamWatchers) { for (Watcher watcher : downstreamWatchers) {
@ -147,7 +166,7 @@ public abstract class CertificateProvider implements Closeable {
@Override @Override
public abstract void close(); 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(); public abstract void start();
private final DistributorWatcher watcher; private final DistributorWatcher watcher;

View File

@ -16,10 +16,12 @@
package io.grpc.xds.internal.security.certprovider; package io.grpc.xds.internal.security.certprovider;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.internal.SpiffeUtil;
import io.grpc.internal.TimeProvider; import io.grpc.internal.TimeProvider;
import io.grpc.xds.internal.security.trust.CertificateUtils; import io.grpc.xds.internal.security.trust.CertificateUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -30,6 +32,7 @@ import java.nio.file.attribute.FileTime;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -47,11 +50,13 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
private final Path certFile; private final Path certFile;
private final Path keyFile; private final Path keyFile;
private final Path trustFile; private final Path trustFile;
private final Path spiffeTrustMapFile;
private final long refreshIntervalInSeconds; private final long refreshIntervalInSeconds;
@VisibleForTesting ScheduledFuture<?> scheduledFuture; @VisibleForTesting ScheduledFuture<?> scheduledFuture;
private FileTime lastModifiedTimeCert; private FileTime lastModifiedTimeCert;
private FileTime lastModifiedTimeKey; private FileTime lastModifiedTimeKey;
private FileTime lastModifiedTimeRoot; private FileTime lastModifiedTimeRoot;
private FileTime lastModifiedTimespiffeTrustMap;
private boolean shutdown; private boolean shutdown;
FileWatcherCertificateProvider( FileWatcherCertificateProvider(
@ -60,6 +65,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
String certFile, String certFile,
String keyFile, String keyFile,
String trustFile, String trustFile,
String spiffeTrustMapFile,
long refreshIntervalInSeconds, long refreshIntervalInSeconds,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
TimeProvider timeProvider) { TimeProvider timeProvider) {
@ -69,7 +75,15 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
this.timeProvider = checkNotNull(timeProvider, "timeProvider"); this.timeProvider = checkNotNull(timeProvider, "timeProvider");
this.certFile = Paths.get(checkNotNull(certFile, "certFile")); this.certFile = Paths.get(checkNotNull(certFile, "certFile"));
this.keyFile = Paths.get(checkNotNull(keyFile, "keyFile")); 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; this.refreshIntervalInSeconds = refreshIntervalInSeconds;
} }
@ -107,39 +121,48 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
byte[] keyFileContents = Files.readAllBytes(keyFile); byte[] keyFileContents = Files.readAllBytes(keyFile);
FileTime currentCertTime2 = Files.getLastModifiedTime(certFile); FileTime currentCertTime2 = Files.getLastModifiedTime(certFile);
FileTime currentKeyTime2 = Files.getLastModifiedTime(keyFile); FileTime currentKeyTime2 = Files.getLastModifiedTime(keyFile);
if (!currentCertTime2.equals(currentCertTime)) { if (currentCertTime2.equals(currentCertTime) && 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;
} }
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) { } catch (Throwable t) {
generateErrorIfCurrentCertExpired(t); generateErrorIfCurrentCertExpired(t);
} }
try { try {
FileTime currentRootTime = Files.getLastModifiedTime(trustFile); if (spiffeTrustMapFile != null) {
if (currentRootTime.equals(lastModifiedTimeRoot)) { FileTime currentSpiffeTime = Files.getLastModifiedTime(spiffeTrustMapFile);
return; if (!currentSpiffeTime.equals(lastModifiedTimespiffeTrustMap)) {
SpiffeUtil.SpiffeBundle trustBundle = SpiffeUtil
.loadTrustBundleFromFile(spiffeTrustMapFile.toString());
getWatcher().updateSpiffeTrustMap(new HashMap<>(trustBundle.getBundleMap()));
lastModifiedTimespiffeTrustMap = currentSpiffeTime;
}
} }
byte[] rootFileContents = Files.readAllBytes(trustFile); } catch (Throwable t) {
FileTime currentRootTime2 = Files.getLastModifiedTime(trustFile); getWatcher().onError(Status.fromThrowable(t));
if (!currentRootTime2.equals(currentRootTime)) { }
return; 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) { } catch (Throwable t) {
getWatcher().onError(Status.fromThrowable(t)); getWatcher().onError(Status.fromThrowable(t));
} }
@ -195,6 +218,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
String certFile, String certFile,
String keyFile, String keyFile,
String trustFile, String trustFile,
String spiffeTrustMapFile,
long refreshIntervalInSeconds, long refreshIntervalInSeconds,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
TimeProvider timeProvider) { TimeProvider timeProvider) {
@ -204,6 +228,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
certFile, certFile,
keyFile, keyFile,
trustFile, trustFile,
spiffeTrustMapFile,
refreshIntervalInSeconds, refreshIntervalInSeconds,
scheduledExecutorService, scheduledExecutorService,
timeProvider); timeProvider);
@ -220,6 +245,7 @@ final class FileWatcherCertificateProvider extends CertificateProvider implement
String certFile, String certFile,
String keyFile, String keyFile,
String trustFile, String trustFile,
String spiffeTrustMapFile,
long refreshIntervalInSeconds, long refreshIntervalInSeconds,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
TimeProvider timeProvider); TimeProvider timeProvider);

View File

@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.protobuf.Duration; import com.google.protobuf.Duration;
import com.google.protobuf.util.Durations; import com.google.protobuf.util.Durations;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.JsonUtil; import io.grpc.internal.JsonUtil;
import io.grpc.internal.TimeProvider; import io.grpc.internal.TimeProvider;
import java.text.ParseException; import java.text.ParseException;
@ -33,11 +34,15 @@ import java.util.concurrent.ScheduledExecutorService;
/** /**
* Provider of {@link FileWatcherCertificateProvider}s. * 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 CERT_FILE_KEY = "certificate_file";
private static final String KEY_FILE_KEY = "private_key_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 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"; private static final String REFRESH_INTERVAL_KEY = "refresh_interval";
@VisibleForTesting static final long REFRESH_INTERVAL_DEFAULT = 600L; @VisibleForTesting static final long REFRESH_INTERVAL_DEFAULT = 600L;
@ -82,6 +87,7 @@ final class FileWatcherCertificateProviderProvider implements CertificateProvide
configObj.certFile, configObj.certFile,
configObj.keyFile, configObj.keyFile,
configObj.rootFile, configObj.rootFile,
configObj.spiffeTrustMapFile,
configObj.refrehInterval, configObj.refrehInterval,
scheduledExecutorServiceFactory.create(), scheduledExecutorServiceFactory.create(),
timeProvider); timeProvider);
@ -98,7 +104,20 @@ final class FileWatcherCertificateProviderProvider implements CertificateProvide
Config configObj = new Config(); Config configObj = new Config();
configObj.certFile = checkForNullAndGet(map, CERT_FILE_KEY); configObj.certFile = checkForNullAndGet(map, CERT_FILE_KEY);
configObj.keyFile = checkForNullAndGet(map, KEY_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); String refreshIntervalString = JsonUtil.getString(map, REFRESH_INTERVAL_KEY);
if (refreshIntervalString != null) { if (refreshIntervalString != null) {
try { try {
@ -139,6 +158,7 @@ final class FileWatcherCertificateProviderProvider implements CertificateProvide
String certFile; String certFile;
String keyFile; String keyFile;
String rootFile; String rootFile;
String spiffeTrustMapFile;
Long refrehInterval; Long refrehInterval;
} }
} }

View File

@ -17,6 +17,7 @@
package io.grpc.xds.internal.security.trust; package io.grpc.xds.internal.security.trust;
import static com.google.common.base.Preconditions.checkArgument; 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 static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -33,6 +34,9 @@ import java.security.NoSuchAlgorithmException;
import java.security.cert.CertStoreException; import java.security.cert.CertStoreException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; 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.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.ManagerFactoryParameters;
@ -63,6 +67,11 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory {
this(certs, staticCertificateValidationContext, true); this(certs, staticCertificateValidationContext, true);
} }
public XdsTrustManagerFactory(Map<String, List<X509Certificate>> spiffeTrustMap,
CertificateValidationContext staticCertificateValidationContext) throws CertStoreException {
this(spiffeTrustMap, staticCertificateValidationContext, true);
}
private XdsTrustManagerFactory( private XdsTrustManagerFactory(
X509Certificate[] certs, X509Certificate[] certs,
CertificateValidationContext certificateValidationContext, CertificateValidationContext certificateValidationContext,
@ -76,6 +85,19 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory {
xdsX509TrustManager = createX509TrustManager(certs, certificateValidationContext); 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( private static X509Certificate[] getTrustedCaFromCertContext(
CertificateValidationContext certificateValidationContext) CertificateValidationContext certificateValidationContext)
throws CertificateException, IOException { throws CertificateException, IOException {
@ -100,6 +122,24 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory {
@VisibleForTesting @VisibleForTesting
static XdsX509TrustManager createX509TrustManager( static XdsX509TrustManager createX509TrustManager(
X509Certificate[] certs, CertificateValidationContext certContext) throws CertStoreException { 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; TrustManagerFactory tmf = null;
try { try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
@ -131,7 +171,7 @@ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory {
if (myDelegate == null) { if (myDelegate == null) {
throw new CertStoreException("Native X509 TrustManager not found."); throw new CertStoreException("Native X509 TrustManager not found.");
} }
return new XdsX509TrustManager(certContext, myDelegate); return myDelegate;
} }
@Override @Override

View File

@ -19,18 +19,25 @@ package io.grpc.xds.internal.security.trust;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.re2j.Pattern; import com.google.re2j.Pattern;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; 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.RegexMatcher;
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
import io.grpc.internal.SpiffeUtil;
import java.net.Socket; import java.net.Socket;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters; 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 static final int ALT_IPA_NAME = 7;
private final X509ExtendedTrustManager delegate; private final X509ExtendedTrustManager delegate;
private final Map<String, X509ExtendedTrustManager> spiffeTrustMapDelegates;
private final CertificateValidationContext certContext; private final CertificateValidationContext certContext;
XdsX509TrustManager(@Nullable CertificateValidationContext certContext, XdsX509TrustManager(@Nullable CertificateValidationContext certContext,
@ -58,6 +66,15 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509
checkNotNull(delegate, "delegate"); checkNotNull(delegate, "delegate");
this.certContext = certContext; this.certContext = certContext;
this.delegate = delegate; 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( private static boolean verifyDnsNameInPattern(
@ -204,21 +221,21 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509
@Override @Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException { throws CertificateException {
delegate.checkClientTrusted(chain, authType, socket); chooseDelegate(chain).checkClientTrusted(chain, authType, socket);
verifySubjectAltNameInChain(chain); verifySubjectAltNameInChain(chain);
} }
@Override @Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
throws CertificateException { throws CertificateException {
delegate.checkClientTrusted(chain, authType, sslEngine); chooseDelegate(chain).checkClientTrusted(chain, authType, sslEngine);
verifySubjectAltNameInChain(chain); verifySubjectAltNameInChain(chain);
} }
@Override @Override
public void checkClientTrusted(X509Certificate[] chain, String authType) public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException { throws CertificateException {
delegate.checkClientTrusted(chain, authType); chooseDelegate(chain).checkClientTrusted(chain, authType);
verifySubjectAltNameInChain(chain); verifySubjectAltNameInChain(chain);
} }
@ -233,7 +250,7 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509
sslSocket.setSSLParameters(sslParams); sslSocket.setSSLParameters(sslParams);
} }
} }
delegate.checkServerTrusted(chain, authType, socket); chooseDelegate(chain).checkServerTrusted(chain, authType, socket);
verifySubjectAltNameInChain(chain); verifySubjectAltNameInChain(chain);
} }
@ -245,19 +262,44 @@ final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509
sslParams.setEndpointIdentificationAlgorithm(""); sslParams.setEndpointIdentificationAlgorithm("");
sslEngine.setSSLParameters(sslParams); sslEngine.setSSLParameters(sslParams);
} }
delegate.checkServerTrusted(chain, authType, sslEngine); chooseDelegate(chain).checkServerTrusted(chain, authType, sslEngine);
verifySubjectAltNameInChain(chain); verifySubjectAltNameInChain(chain);
} }
@Override @Override
public void checkServerTrusted(X509Certificate[] chain, String authType) public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException { throws CertificateException {
delegate.checkServerTrusted(chain, authType); chooseDelegate(chain).checkServerTrusted(chain, authType);
verifySubjectAltNameInChain(chain); 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 @Override
public X509Certificate[] getAcceptedIssuers() { 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(); return delegate.getAcceptedIssuers();
} }
} }

View File

@ -88,7 +88,7 @@ public class CommonBootstrapperTestUtils {
String certInstanceName1, @Nullable String privateKey1, String certInstanceName1, @Nullable String privateKey1,
@Nullable String cert1, @Nullable String cert1,
@Nullable String trustCa1, String certInstanceName2, String privateKey2, String cert2, @Nullable String trustCa1, String certInstanceName2, String privateKey2, String cert2,
String trustCa2) { String trustCa2, @Nullable String spiffeTrustMap) {
// get temp file for each file // get temp file for each file
try { try {
if (privateKey1 != null) { if (privateKey1 != null) {
@ -109,6 +109,9 @@ public class CommonBootstrapperTestUtils {
if (trustCa2 != null) { if (trustCa2 != null) {
trustCa2 = CommonTlsContextTestsUtil.getTempFileNameForResourcesFile(trustCa2); trustCa2 = CommonTlsContextTestsUtil.getTempFileNameForResourcesFile(trustCa2);
} }
if (spiffeTrustMap != null) {
spiffeTrustMap = CommonTlsContextTestsUtil.getTempFileNameForResourcesFile(spiffeTrustMap);
}
} catch (IOException ioe) { } catch (IOException ioe) {
throw new RuntimeException(ioe); throw new RuntimeException(ioe);
} }
@ -116,6 +119,9 @@ public class CommonBootstrapperTestUtils {
config.put("certificate_file", cert1); config.put("certificate_file", cert1);
config.put("private_key_file", privateKey1); config.put("private_key_file", privateKey1);
config.put("ca_certificate_file", trustCa1); config.put("ca_certificate_file", trustCa1);
if (spiffeTrustMap != null) {
config.put("spiffe_trust_bundle_map_file", spiffeTrustMap);
}
Bootstrapper.CertificateProviderInfo certificateProviderInfo = Bootstrapper.CertificateProviderInfo certificateProviderInfo =
Bootstrapper.CertificateProviderInfo.create("file_watcher", config); Bootstrapper.CertificateProviderInfo.create("file_watcher", config);
HashMap<String, Bootstrapper.CertificateProviderInfo> certProviders = HashMap<String, Bootstrapper.CertificateProviderInfo> certProviders =
@ -126,6 +132,9 @@ public class CommonBootstrapperTestUtils {
config.put("certificate_file", cert2); config.put("certificate_file", cert2);
config.put("private_key_file", privateKey2); config.put("private_key_file", privateKey2);
config.put("ca_certificate_file", trustCa2); config.put("ca_certificate_file", trustCa2);
if (spiffeTrustMap != null) {
config.put("spiffe_trust_bundle_map_file", spiffeTrustMap);
}
certificateProviderInfo = certificateProviderInfo =
Bootstrapper.CertificateProviderInfo.create("file_watcher", config); Bootstrapper.CertificateProviderInfo.create("file_watcher", config);
certProviders.put(certInstanceName2, certificateProviderInfo); 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.CA_PEM_FILE;
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_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_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_KEY_FILE;
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_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 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 static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList; 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.CommonTlsContextTestsUtil;
import io.grpc.xds.internal.security.SslContextProviderSupplier; import io.grpc.xds.internal.security.SslContextProviderSupplier;
import io.grpc.xds.internal.security.TlsContextManagerImpl; import io.grpc.xds.internal.security.TlsContextManagerImpl;
import io.grpc.xds.internal.security.certprovider.FileWatcherCertificateProviderProvider;
import io.netty.handler.ssl.NotSslRecordException; import io.netty.handler.ssl.NotSslRecordException;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -85,6 +90,7 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -92,18 +98,25 @@ import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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 * Unit tests for {@link XdsChannelCredentials} and {@link XdsServerBuilder} for plaintext/TLS/mTLS
* modes. * modes.
*/ */
@RunWith(JUnit4.class) @RunWith(Parameterized.class)
public class XdsSecurityClientServerTest { public class XdsSecurityClientServerTest {
@Parameter
public Boolean enableSpiffe;
private Boolean originalEnableSpiffe;
@Rule public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); @Rule public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule();
private int port; private int port;
private FakeNameResolverFactory fakeNameResolverFactory; private FakeNameResolverFactory fakeNameResolverFactory;
@ -115,11 +128,27 @@ public class XdsSecurityClientServerTest {
private FakeXdsClientPoolFactory fakePoolFactory = new FakeXdsClientPoolFactory(xdsClient); private FakeXdsClientPoolFactory fakePoolFactory = new FakeXdsClientPoolFactory(xdsClient);
private static final String OVERRIDE_AUTHORITY = "foo.test.google.fr"; 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 @After
public void tearDown() throws IOException { public void tearDown() throws IOException {
if (fakeNameResolverFactory != null) { if (fakeNameResolverFactory != null) {
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverFactory); NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverFactory);
} }
FileWatcherCertificateProviderProvider.enableSpiffe = originalEnableSpiffe;
} }
@Test @Test
@ -146,13 +175,13 @@ public class XdsSecurityClientServerTest {
@Test @Test
public void tlsClientServer_noClientAuthentication() throws Exception { public void tlsClientServer_noClientAuthentication() throws Exception {
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, false, false);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
// for TLS, client only needs trustCa // for TLS, client only needs trustCa
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
CLIENT_KEY_FILE, CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, false);
CLIENT_PEM_FILE, false);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
@ -169,7 +198,8 @@ public class XdsSecurityClientServerTest {
try { try {
setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString()); setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString());
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, false, false);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext upstreamTlsContext =
@ -195,7 +225,8 @@ public class XdsSecurityClientServerTest {
try { try {
setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString()); setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString());
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, false, false);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext upstreamTlsContext =
@ -221,7 +252,8 @@ public class XdsSecurityClientServerTest {
try { try {
setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString()); setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString());
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, false, false);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext upstreamTlsContext =
@ -252,17 +284,59 @@ public class XdsSecurityClientServerTest {
return trustStoreFile.toPath(); 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 @Test
public void requireClientAuth_noClientCert_expectException() public void requireClientAuth_noClientCert_expectException()
throws Exception { throws Exception {
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, true, true); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, true, true);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
// for TLS, client only uses trustCa // for TLS, client only uses trustCa
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
CLIENT_KEY_FILE, CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, false);
CLIENT_PEM_FILE, false);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
@ -284,12 +358,12 @@ public class XdsSecurityClientServerTest {
@Test @Test
public void noClientAuth_sendBadClientCert_passes() throws Exception { public void noClientAuth_sendBadClientCert_passes() throws Exception {
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, false, false);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
BAD_CLIENT_KEY_FILE, BAD_CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, null, true);
BAD_CLIENT_PEM_FILE, true);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
@ -299,8 +373,7 @@ public class XdsSecurityClientServerTest {
@Test @Test
public void mtls_badClientCert_expectException() throws Exception { public void mtls_badClientCert_expectException() throws Exception {
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
BAD_CLIENT_KEY_FILE, BAD_CLIENT_KEY_FILE, BAD_CLIENT_PEM_FILE, null, true);
BAD_CLIENT_PEM_FILE, true);
try { try {
performMtlsTestAndGetListenerWatcher(upstreamTlsContext, null, null, null, null); performMtlsTestAndGetListenerWatcher(upstreamTlsContext, null, null, null, null);
fail("exception expected"); 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 @Test
public void mtlsClientServer_withClientAuthentication_withXdsChannelCreds() public void mtlsClientServer_withClientAuthentication_withXdsChannelCreds()
throws Exception { throws Exception {
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
CLIENT_KEY_FILE, CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, true);
CLIENT_PEM_FILE, true);
performMtlsTestAndGetListenerWatcher(upstreamTlsContext, null, null, null, null); performMtlsTestAndGetListenerWatcher(upstreamTlsContext, null, null, null, null);
} }
@Test @Test
public void tlsServer_plaintextClient_expectException() throws Exception { public void tlsServer_plaintextClient_expectException() throws Exception {
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false); setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, null, null, null, null,
null, false, false);
buildServerWithTlsContext(downstreamTlsContext); buildServerWithTlsContext(downstreamTlsContext);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
@ -349,8 +460,7 @@ public class XdsSecurityClientServerTest {
// for TLS, client only needs trustCa // for TLS, client only needs trustCa
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
CLIENT_KEY_FILE, CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, false);
CLIENT_PEM_FILE, false);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY); getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
@ -368,8 +478,7 @@ public class XdsSecurityClientServerTest {
public void mtlsClientServer_changeServerContext_expectException() public void mtlsClientServer_changeServerContext_expectException()
throws Exception { throws Exception {
UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext( UpstreamTlsContext upstreamTlsContext = setBootstrapInfoAndBuildUpstreamTlsContext(
CLIENT_KEY_FILE, CLIENT_KEY_FILE, CLIENT_PEM_FILE, null, true);
CLIENT_PEM_FILE, true);
performMtlsTestAndGetListenerWatcher(upstreamTlsContext, "cert-instance-name2", performMtlsTestAndGetListenerWatcher(upstreamTlsContext, "cert-instance-name2",
BAD_SERVER_KEY_FILE, BAD_SERVER_PEM_FILE, CA_PEM_FILE); BAD_SERVER_KEY_FILE, BAD_SERVER_PEM_FILE, CA_PEM_FILE);
@ -396,8 +505,8 @@ public class XdsSecurityClientServerTest {
String privateKey2, String cert2, String trustCa2) String privateKey2, String cert2, String trustCa2)
throws Exception { throws Exception {
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(certInstanceName2, privateKey2, cert2, setBootstrapInfoAndBuildDownstreamTlsContext(SERVER_1_PEM_FILE, certInstanceName2,
trustCa2, true, true); privateKey2, cert2, trustCa2, null, true, false);
buildServerWithFallbackServerCredentials( buildServerWithFallbackServerCredentials(
InsecureServerCredentials.create(), downstreamTlsContext); InsecureServerCredentials.create(), downstreamTlsContext);
@ -408,22 +517,21 @@ public class XdsSecurityClientServerTest {
} }
private DownstreamTlsContext setBootstrapInfoAndBuildDownstreamTlsContext( private DownstreamTlsContext setBootstrapInfoAndBuildDownstreamTlsContext(
String certInstanceName2, String cert1, String certInstanceName2, String privateKey2,
String privateKey2, String cert2, String trustCa2, String spiffeFile,
String cert2, String trustCa2, boolean hasRootCert, boolean requireClientCertificate) { boolean hasRootCert, boolean requireClientCertificate) {
bootstrapInfoForServer = CommonBootstrapperTestUtils bootstrapInfoForServer = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, .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( return CommonTlsContextTestsUtil.buildDownstreamTlsContext(
"google_cloud_private_spiffe-server", hasRootCert, requireClientCertificate); "google_cloud_private_spiffe-server", hasRootCert, requireClientCertificate);
} }
private UpstreamTlsContext setBootstrapInfoAndBuildUpstreamTlsContext(String clientKeyFile, private UpstreamTlsContext setBootstrapInfoAndBuildUpstreamTlsContext(String clientKeyFile,
String clientPemFile, String clientPemFile, String spiffeFile, boolean hasIdentityCert) {
boolean hasIdentityCert) {
bootstrapInfoForClient = CommonBootstrapperTestUtils bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", clientKeyFile, clientPemFile, .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 return CommonTlsContextTestsUtil
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", hasIdentityCert); .buildUpstreamTlsContext("google_cloud_private_spiffe-client", hasIdentityCert);
} }
@ -434,7 +542,7 @@ public class XdsSecurityClientServerTest {
boolean useCombinedValidationContext) { boolean useCombinedValidationContext) {
bootstrapInfoForClient = CommonBootstrapperTestUtils bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", clientKeyFile, clientPemFile, .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) { if (useCombinedValidationContext) {
return CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( return CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance(
"google_cloud_private_spiffe-client", "ROOT", null, "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_PEM_FILE = "server0.pem";
public static final String SERVER_0_KEY_FILE = "server0.key"; 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_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 SERVER_1_KEY_FILE = "server1.key";
public static final String CLIENT_PEM_FILE = "client.pem"; 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 CLIENT_KEY_FILE = "client.key";
public static final String CA_PEM_FILE = "ca.pem"; 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. */ /** Bad/untrusted server certs. */
public static final String BAD_SERVER_PEM_FILE = "badserver.pem"; public static final String BAD_SERVER_PEM_FILE = "badserver.pem";
public static final String BAD_SERVER_KEY_FILE = "badserver.key"; public static final String BAD_SERVER_KEY_FILE = "badserver.key";

View File

@ -149,7 +149,7 @@ public class SecurityProtocolNegotiatorsTest {
CommonCertProviderTestUtils.register(executor); CommonCertProviderTestUtils.register(executor);
Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, .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 = UpstreamTlsContext upstreamTlsContext =
CommonTlsContextTestsUtil CommonTlsContextTestsUtil
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", true); .buildUpstreamTlsContext("google_cloud_private_spiffe-client", true);
@ -216,7 +216,7 @@ public class SecurityProtocolNegotiatorsTest {
pipeline = channel.pipeline(); pipeline = channel.pipeline();
Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, .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 = DownstreamTlsContext downstreamTlsContext =
CommonTlsContextTestsUtil.buildDownstreamTlsContext( CommonTlsContextTestsUtil.buildDownstreamTlsContext(
"google_cloud_private_spiffe-server", true, true); "google_cloud_private_spiffe-server", true, true);
@ -361,7 +361,7 @@ public class SecurityProtocolNegotiatorsTest {
CommonCertProviderTestUtils.register(executor); CommonCertProviderTestUtils.register(executor);
Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, .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 = UpstreamTlsContext upstreamTlsContext =
CommonTlsContextTestsUtil CommonTlsContextTestsUtil
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", true); .buildUpstreamTlsContext("google_cloud_private_spiffe-client", true);
@ -412,7 +412,7 @@ public class SecurityProtocolNegotiatorsTest {
CommonCertProviderTestUtils.register(executor); CommonCertProviderTestUtils.register(executor);
Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, .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 = UpstreamTlsContext upstreamTlsContext =
CommonTlsContextTestsUtil CommonTlsContextTestsUtil
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", true); .buildUpstreamTlsContext("google_cloud_private_spiffe-client", true);

View File

@ -57,7 +57,7 @@ public class TlsContextManagerTest {
public void createServerSslContextProvider() { public void createServerSslContextProvider() {
Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, .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 = DownstreamTlsContext downstreamTlsContext =
CommonTlsContextTestsUtil.buildDownstreamTlsContext( CommonTlsContextTestsUtil.buildDownstreamTlsContext(
"google_cloud_private_spiffe-server", false, false); "google_cloud_private_spiffe-server", false, false);
@ -76,7 +76,7 @@ public class TlsContextManagerTest {
public void createClientSslContextProvider() { public void createClientSslContextProvider() {
Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, .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 = UpstreamTlsContext upstreamTlsContext =
CommonTlsContextTestsUtil CommonTlsContextTestsUtil
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", false); .buildUpstreamTlsContext("google_cloud_private_spiffe-client", false);
@ -96,7 +96,7 @@ public class TlsContextManagerTest {
Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForServer = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-server", SERVER_1_KEY_FILE, .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, 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 = DownstreamTlsContext downstreamTlsContext =
CommonTlsContextTestsUtil.buildDownstreamTlsContext( CommonTlsContextTestsUtil.buildDownstreamTlsContext(
"google_cloud_private_spiffe-server", false, false); "google_cloud_private_spiffe-server", false, false);
@ -120,7 +120,7 @@ public class TlsContextManagerTest {
public void createClientSslContextProvider_differentInstance() { public void createClientSslContextProvider_differentInstance() {
Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils Bootstrapper.BootstrapInfo bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", CLIENT_KEY_FILE, CLIENT_PEM_FILE, .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 = UpstreamTlsContext upstreamTlsContext =
CommonTlsContextTestsUtil CommonTlsContextTestsUtil
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", false); .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.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import io.grpc.internal.JsonParser; import io.grpc.internal.JsonParser;
import io.grpc.internal.TimeProvider; import io.grpc.internal.TimeProvider;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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.Mock;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
/** Unit tests for {@link FileWatcherCertificateProviderProvider}. */ /** Unit tests for {@link FileWatcherCertificateProviderProvider}. */
@RunWith(JUnit4.class) @RunWith(Parameterized.class)
public class FileWatcherCertificateProviderProviderTest { public class FileWatcherCertificateProviderProviderTest {
@Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Rule public final MockitoRule mocks = MockitoJUnit.rule();
@ -48,13 +54,28 @@ public class FileWatcherCertificateProviderProviderTest {
scheduledExecutorServiceFactory; scheduledExecutorServiceFactory;
@Mock private TimeProvider timeProvider; @Mock private TimeProvider timeProvider;
@Parameter
public boolean enableSpiffe;
private boolean originalEnableSpiffe;
private FileWatcherCertificateProviderProvider provider; private FileWatcherCertificateProviderProvider provider;
@Parameters(name = "enableSpiffe={0}")
public static Collection<Boolean> data() {
return ImmutableList.of(true, false);
}
@Before @Before
public void setUp() throws IOException { public void setUp() throws IOException {
provider = provider =
new FileWatcherCertificateProviderProvider( new FileWatcherCertificateProviderProvider(
fileWatcherCertificateProviderFactory, scheduledExecutorServiceFactory, timeProvider); fileWatcherCertificateProviderFactory, scheduledExecutorServiceFactory, timeProvider);
originalEnableSpiffe = FileWatcherCertificateProviderProvider.enableSpiffe;
FileWatcherCertificateProviderProvider.enableSpiffe = enableSpiffe;
}
@After
public void restoreEnvironment() {
FileWatcherCertificateProviderProvider.enableSpiffe = originalEnableSpiffe;
} }
@Test @Test
@ -85,6 +106,30 @@ public class FileWatcherCertificateProviderProviderTest {
eq("/var/run/gke-spiffe/certs/certificates.pem"), eq("/var/run/gke-spiffe/certs/certificates.pem"),
eq("/var/run/gke-spiffe/certs/private_key.pem"), eq("/var/run/gke-spiffe/certs/private_key.pem"),
eq("/var/run/gke-spiffe/certs/ca_certificates.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(600L),
eq(mockService), eq(mockService),
eq(timeProvider)); eq(timeProvider));
@ -106,6 +151,30 @@ public class FileWatcherCertificateProviderProviderTest {
eq("/var/run/gke-spiffe/certs/certificates2.pem"), eq("/var/run/gke-spiffe/certs/certificates2.pem"),
eq("/var/run/gke-spiffe/certs/private_key3.pem"), eq("/var/run/gke-spiffe/certs/private_key3.pem"),
eq("/var/run/gke-spiffe/certs/ca_certificates4.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(7890L),
eq(mockService), eq(mockService),
eq(timeProvider)); eq(timeProvider));
@ -157,15 +226,18 @@ public class FileWatcherCertificateProviderProviderTest {
@Test @Test
public void createProvider_missingRoot_expectException() throws IOException { 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 = CertificateProvider.DistributorWatcher distWatcher =
new CertificateProvider.DistributorWatcher(); new CertificateProvider.DistributorWatcher();
@SuppressWarnings("unchecked") @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 { try {
provider.createCertificateProvider(map, distWatcher, true); provider.createCertificateProvider(map, distWatcher, true);
fail("exception expected"); fail("exception expected");
} catch (NullPointerException npe) { } 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\"" + " \"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 = private static final String FULL_FILE_WATCHER_CONFIG =
"{\n" "{\n"
+ " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates2.pem\"," + " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates2.pem\","
@ -184,6 +264,16 @@ public class FileWatcherCertificateProviderProviderTest {
+ " \"refresh_interval\": \"7890s\"" + " \"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 = private static final String MISSING_CERT_CONFIG =
"{\n" "{\n"
+ " \"private_key_file\": \"/var/run/gke-spiffe/certs/private_key.pem\"," + " \"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\"" + " \"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" "{\n"
+ " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates.pem\"," + " \"certificate_file\": \"/var/run/gke-spiffe/certs/certificates.pem\","
+ " \"private_key_file\": \"/var/run/gke-spiffe/certs/private_key.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_KEY_FILE;
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_PEM_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.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 java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -47,6 +48,7 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.Delayed; import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
@ -73,6 +75,7 @@ public class FileWatcherCertificateProviderTest {
private static final String CERT_FILE = "cert.pem"; private static final String CERT_FILE = "cert.pem";
private static final String KEY_FILE = "key.pem"; private static final String KEY_FILE = "key.pem";
private static final String ROOT_FILE = "root.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 CertificateProvider.Watcher mockWatcher;
@Mock private ScheduledExecutorService timeService; @Mock private ScheduledExecutorService timeService;
@ -84,28 +87,33 @@ public class FileWatcherCertificateProviderTest {
private String certFile; private String certFile;
private String keyFile; private String keyFile;
private String rootFile; private String rootFile;
private String spiffeTrustMapFile;
private FileWatcherCertificateProvider provider; private FileWatcherCertificateProvider provider;
private DistributorWatcher watcher;
@Before @Before
public void setUp() throws IOException { public void setUp() throws IOException {
DistributorWatcher watcher = new DistributorWatcher(); watcher = new DistributorWatcher();
watcher.addWatcher(mockWatcher); watcher.addWatcher(mockWatcher);
certFile = new File(tempFolder.getRoot(), CERT_FILE).getAbsolutePath(); certFile = new File(tempFolder.getRoot(), CERT_FILE).getAbsolutePath();
keyFile = new File(tempFolder.getRoot(), KEY_FILE).getAbsolutePath(); keyFile = new File(tempFolder.getRoot(), KEY_FILE).getAbsolutePath();
rootFile = new File(tempFolder.getRoot(), ROOT_FILE).getAbsolutePath(); rootFile = new File(tempFolder.getRoot(), ROOT_FILE).getAbsolutePath();
spiffeTrustMapFile = new File(tempFolder.getRoot(), SPIFFE_TRUST_MAP_FILE).getAbsolutePath();
provider = provider =
new FileWatcherCertificateProvider( new FileWatcherCertificateProvider(watcher, true, certFile, keyFile, rootFile, null, 600L,
watcher, true, certFile, keyFile, rootFile, 600L, timeService, timeProvider); timeService, timeProvider);
} }
private void populateTarget( private void populateTarget(
String certFileSource, String certFileSource,
String keyFileSource, String keyFileSource,
String rootFileSource, String rootFileSource,
String spiffeTrustMapFileSource,
boolean deleteCurCert, boolean deleteCurCert,
boolean deleteCurKey, boolean deleteCurKey,
boolean deleteCurSpiffeTrustMap,
boolean deleteCurRoot) boolean deleteCurRoot)
throws IOException { throws IOException {
if (deleteCurCert) { if (deleteCurCert) {
@ -135,6 +143,17 @@ public class FileWatcherCertificateProviderTest {
Files.setLastModifiedTime( Files.setLastModifiedTime(
Paths.get(rootFile), FileTime.fromMillis(timeProvider.currentTimeMillis())); 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 @Test
@ -144,9 +163,9 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
verifyWatcherUpdates(CLIENT_PEM_FILE, CA_PEM_FILE); verifyWatcherUpdates(CLIENT_PEM_FILE, CA_PEM_FILE, null);
verifyTimeServiceAndScheduledFuture(); verifyTimeServiceAndScheduledFuture();
reset(mockWatcher, timeService); reset(mockWatcher, timeService);
@ -165,7 +184,7 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
reset(mockWatcher, timeService); reset(mockWatcher, timeService);
@ -173,9 +192,10 @@ public class FileWatcherCertificateProviderTest {
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS));
timeProvider.forwardTime(1, 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(); provider.checkAndReloadCertificates();
verifyWatcherUpdates(SERVER_0_PEM_FILE, SERVER_1_PEM_FILE); verifyWatcherUpdates(SERVER_0_PEM_FILE, SERVER_1_PEM_FILE, null);
verifyTimeServiceAndScheduledFuture(); verifyTimeServiceAndScheduledFuture();
} }
@ -186,12 +206,13 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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.close();
provider.checkAndReloadCertificates(); provider.checkAndReloadCertificates();
verify(mockWatcher, never()) verify(mockWatcher, never())
.updateCertificate(any(PrivateKey.class), ArgumentMatchers.<X509Certificate>anyList()); .updateCertificate(any(PrivateKey.class), ArgumentMatchers.<X509Certificate>anyList());
verify(mockWatcher, never()).updateTrustedRoots(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, never()).schedule(any(Runnable.class), any(Long.TYPE), any(TimeUnit.class));
verify(timeService, times(1)).shutdownNow(); verify(timeService, times(1)).shutdownNow();
} }
@ -204,7 +225,7 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
reset(mockWatcher, timeService); reset(mockWatcher, timeService);
@ -212,9 +233,9 @@ public class FileWatcherCertificateProviderTest {
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS));
timeProvider.forwardTime(1, 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(); provider.checkAndReloadCertificates();
verifyWatcherUpdates(null, SERVER_1_PEM_FILE); verifyWatcherUpdates(null, SERVER_1_PEM_FILE, null);
verifyTimeServiceAndScheduledFuture(); verifyTimeServiceAndScheduledFuture();
} }
@ -226,7 +247,7 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
reset(mockWatcher, timeService); reset(mockWatcher, timeService);
@ -234,9 +255,44 @@ public class FileWatcherCertificateProviderTest {
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS));
timeProvider.forwardTime(1, 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(); 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(); verifyTimeServiceAndScheduledFuture();
} }
@ -247,7 +303,7 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
verifyWatcherErrorUpdates(Status.Code.UNKNOWN, NoSuchFileException.class, 0, 1, "cert.pem"); verifyWatcherErrorUpdates(Status.Code.UNKNOWN, NoSuchFileException.class, 0, 1, "cert.pem");
} }
@ -255,13 +311,14 @@ public class FileWatcherCertificateProviderTest {
@Test @Test
public void getCertificate_missingCertFile() throws IOException, InterruptedException { public void getCertificate_missingCertFile() throws IOException, InterruptedException {
commonErrorTest( 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 @Test
public void getCertificate_missingKeyFile() throws IOException, InterruptedException { public void getCertificate_missingKeyFile() throws IOException, InterruptedException {
commonErrorTest( 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 @Test
@ -270,6 +327,7 @@ public class FileWatcherCertificateProviderTest {
CLIENT_PEM_FILE, CLIENT_PEM_FILE,
SERVER_0_PEM_FILE, SERVER_0_PEM_FILE,
CA_PEM_FILE, CA_PEM_FILE,
null,
java.security.spec.InvalidKeySpecException.class, java.security.spec.InvalidKeySpecException.class,
0, 0,
1, 1,
@ -285,12 +343,13 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
reset(mockWatcher); reset(mockWatcher);
timeProvider.forwardTime(1, TimeUnit.SECONDS); 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( timeProvider.forwardTime(
CERT0_EXPIRY_TIME_MILLIS - 610_000L - timeProvider.currentTimeMillis(), CERT0_EXPIRY_TIME_MILLIS - 610_000L - timeProvider.currentTimeMillis(),
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
@ -302,6 +361,7 @@ public class FileWatcherCertificateProviderTest {
String certFile, String certFile,
String keyFile, String keyFile,
String rootFile, String rootFile,
String spiffeFile,
Class<?> throwableType, Class<?> throwableType,
int firstUpdateCertCount, int firstUpdateCertCount,
int firstUpdateRootCount, int firstUpdateRootCount,
@ -314,13 +374,15 @@ public class FileWatcherCertificateProviderTest {
doReturn(scheduledFuture) doReturn(scheduledFuture)
.when(timeService) .when(timeService)
.schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); .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(); provider.checkAndReloadCertificates();
reset(mockWatcher); reset(mockWatcher);
timeProvider.forwardTime(1, TimeUnit.SECONDS); timeProvider.forwardTime(1, TimeUnit.SECONDS);
populateTarget( populateTarget(
certFile, keyFile, rootFile, certFile == null, keyFile == null, rootFile == null); certFile, keyFile, rootFile, spiffeFile, certFile == null, keyFile == null,
rootFile == null, spiffeFile == null);
timeProvider.forwardTime( timeProvider.forwardTime(
CERT0_EXPIRY_TIME_MILLIS - 610_000L - timeProvider.currentTimeMillis(), CERT0_EXPIRY_TIME_MILLIS - 610_000L - timeProvider.currentTimeMillis(),
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
@ -372,7 +434,7 @@ public class FileWatcherCertificateProviderTest {
assertThat(provider.scheduledFuture.isCancelled()).isFalse(); assertThat(provider.scheduledFuture.isCancelled()).isFalse();
} }
private void verifyWatcherUpdates(String certPemFile, String rootPemFile) private void verifyWatcherUpdates(String certPemFile, String rootPemFile, String spiffeFile)
throws IOException, CertificateException { throws IOException, CertificateException {
if (certPemFile != null) { if (certPemFile != null) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -399,6 +461,17 @@ public class FileWatcherCertificateProviderTest {
} else { } else {
verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.<X509Certificate>anyList()); 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> { 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.CLIENT_PEM_FILE;
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_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 com.google.protobuf.ByteString;
import io.envoyproxy.envoy.config.core.v3.DataSource; import io.envoyproxy.envoy.config.core.v3.DataSource;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; 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]); .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 @Test
public void constructorRootCert_checkServerTrusted() public void constructorRootCert_checkServerTrusted()
throws CertificateException, IOException, CertStoreException { 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.BAD_SERVER_PEM_FILE;
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_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_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_PEM_FILE;
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_SPIFFE_PEM_FILE;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
@ -30,6 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList; 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.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; 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.CertStoreException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
@ -537,6 +541,71 @@ public class XdsX509TrustManagerTest {
assertThat(sslEngine.getSSLParameters().getEndpointIdentificationAlgorithm()).isEmpty(); 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 @Test
public void checkServerTrustedSslEngine_untrustedServer_expectException() public void checkServerTrustedSslEngine_untrustedServer_expectException()
throws CertificateException, IOException, CertStoreException { throws CertificateException, IOException, CertStoreException {
@ -565,6 +634,22 @@ public class XdsX509TrustManagerTest {
assertThat(sslSocket.getSSLParameters().getEndpointIdentificationAlgorithm()).isEmpty(); 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 @Test
public void checkServerTrustedSslSocket_untrustedServer_expectException() public void checkServerTrustedSslSocket_untrustedServer_expectException()
throws CertificateException, IOException, CertStoreException { throws CertificateException, IOException, CertStoreException {