Skip to content

Commit 413b966

Browse files
markdrotherm-ggtcooke94
authored
A87: mTLS SPIFFE Support (#462)
* A87: mTLS SPIFFE Support * Java API * a bit of cleanup * Update Implementation section * Moved few items from Implementation to Proposal, update Implementation with Java PRs * cleanup * add mailing list link * add basic Go section * add gtcooke94 as an author * add C++ API * address comments * address pr comments * Java section * remove java function, and a bit of cleanup * review comments * go spiffe section --------- Co-authored-by: erm-g <110920239+erm-g@users.noreply.github.com> Co-authored-by: Gregory Cooke <gregorycooke@google.com>
1 parent 341672b commit 413b966

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

A87-mtls-spiffe-support.md

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
A87: mTLS SPIFFE Support
2+
----
3+
* Author(s): @markdroth, @erm-g, @gtcooke94
4+
* Approver: @ejona86, @dfawley
5+
* Status: {Draft, In Review, Ready for Implementation, Implemented}
6+
* Implemented in: <language, ...>
7+
* Last updated: 2025-03-31
8+
* Discussion at: https://groups.google.com/g/grpc-io/c/55oIW6GNabs
9+
10+
## Abstract
11+
12+
We will add support for SPIFFE certificate verification in mTLS. This
13+
will be supported both with TlsCredentials and via xDS.
14+
15+
## Background
16+
17+
[SPIFFE] is a standardized way of encoding workload identity in mTLS
18+
certificates. Specifically, we want to support using the [SPIFFE bundle
19+
format] in place of a single CA certificate for peer certificate
20+
verification.
21+
22+
### Related Proposals:
23+
* [gRFC A29: xDS-Based mTLS Security for gRPC Clients and Servers][gRFC A29]
24+
25+
[gRFC A29]: A29-xds-tls-security.md
26+
[SPIFFE]: https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE.md
27+
[SPIFFE ID format]: https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#2-spiffe-identity
28+
[SPIFFE bundle format]: https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#4-spiffe-bundle-format
29+
[Publishing SPIFFE bundle format]: https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md#61-publishing-spiffe-bundle-elements
30+
31+
## Proposal
32+
33+
There are two main parts to this proposal: support for SPIFFE
34+
certificate verification in TlsCredentials, and configuring that
35+
functionality via xDS. We will cover the xDS case first.
36+
37+
### Configuring SPIFFE Verification via xDS
38+
39+
Currently, certificate providers as defined in [gRFC A29] can return
40+
only a single CA certificate for use in peer certificate verification. We
41+
will change the certificate provider API such that the provider may
42+
choose to return either CA certificates or a SPIFFE trust bundle
43+
map, although it must choose one -- it may not return both for the same
44+
certificate name.
45+
46+
If the certificate provider returns CA certificates, then
47+
standard X509 certificate verification will be used, just as it is
48+
today. However, if the certificate provider returns a SPIFFE trust
49+
bundle map, then SPIFFE verification will be performed, and non-SPIFFE
50+
peer certificates will be rejected.
51+
52+
When performing SPIFFE verification, the following steps will be performed:
53+
54+
1. Upon receiving a peer certificate, verify that it is a well-formed SPIFFE
55+
leaf certificate. In particular, it must have a single URI SAN containing
56+
a well-formed SPIFFE ID ([SPIFFE ID format]).
57+
58+
2. Use the trust domain in the peer certificate's SPIFFE ID to lookup
59+
the SPIFFE trust bundle. If the trust domain is not contained in the
60+
configured trust map, reject the certificate.
61+
62+
3. Verify the peer certificate using the default security library using
63+
the SPIFFE trust bundle certificates as roots.
64+
65+
SAN matching will be performed as configured via xDS, just as it is with
66+
X509 verification.
67+
68+
gRPC currently supports only one certificate provider implementation,
69+
which is the `file_watcher` provider. We will add a new field to the
70+
configuration of this provider implementation configuration called
71+
`spiffe_trust_bundle_map_file`. This field will specify the path to
72+
the SPIFFE trust bundle map.
73+
74+
If the `spiffe_trust_bundle_map_file` field is unset, then the
75+
`ca_certificate_file` field will be used exactly as it is today, and
76+
the certificate provider will return CA certificates.
77+
78+
If the `spiffe_trust_bundle_map_file` field is set, the certificate
79+
provider will return the SPIFFE trust bundle map from the specified file.
80+
If the `ca_certificate_file` field is also set, it will be ignored,
81+
which is helpful for forward compatibility: bootstrap configs can start
82+
setting the new field regardless of what gRPC version is in use, which
83+
results in older versions returning CA certificates and newer versions
84+
returning the SPIFFE trust bundle map.
85+
86+
When reading the SPIFFE trust bundle map file, the certificate provider
87+
will parse the JSON to ensure its validity and store the map in memory.
88+
If an error occurs during the initial load (e.g., a failure to read
89+
the file, or the file contains invalid entries), then there is no valid
90+
map, which means new connection attempts will fail the TLS handshake.
91+
For subsequent loads, errors will not affect the existing in-memory
92+
trust bundle map. An empty map will be considered a valid (but empty)
93+
trust bundle map.
94+
95+
Note that the [SPIFFE bundle format] and [Publishing SPIFFE bundle format]
96+
are partially supported:
97+
- Only the `keys` and `spiffe_sequence` elements of the JWK set are supported.
98+
- Only the `kty`, `use`, and `x5c` elements of `keys` are supported.
99+
- Instead of ignoring individual JWK entries in case of issues, we ignore
100+
the whole trust bundle.
101+
102+
### SPIFFE Certificate Verification in TlsCredentials
103+
104+
The xDS mTLS support is built on top of existing mTLS functionality in
105+
TlsCredentials. This section describes how the SPIFFE functionality
106+
will work in TlsCredentials and how the xDS functionality builds on top
107+
of it. The details of this are different in each language.
108+
109+
#### C++
110+
111+
In C++, TlsCredentials natively supports a CertificateProvider API with
112+
essentially the same semantics as in xDS. We will add the ability for
113+
the `FileWatcherCertificateProvider` to be instantiated with a SPIFFE
114+
trust bundle map file instead of a CA certificate file.
115+
116+
With the adding of a SPIFFE trust bundle map file, the
117+
`FileWatcherCertificateProvider` constructor is beginning to have a lot of
118+
arguments. We will introduce a `FileWatcherCertificateProviderOptions` struct
119+
and associated constructor similarly to `TlsCredentialOptions`.
120+
We will not remove the old constructors in order to not break current users.
121+
122+
```
123+
class FileWatcherCertificateProviderOptions {
124+
public:
125+
FileWatcherCertificateProviderOptions();
126+
127+
FileWatcherCertificateProviderOptions& set_private_key_path(
128+
absl::string_view private_key_path);
129+
FileWatcherCertificateProviderOptions& set_identity_certificate_path(
130+
absl::string_view identity_certificate_path);
131+
FileWatcherCertificateProviderOptions& set_root_cert_path(
132+
absl::string_view root_cert_path);
133+
FileWatcherCertificateProviderOptions& set_spiffe_bundle_map_path(
134+
absl::string_view spiffe_bundle_map_path);
135+
FileWatcherCertificateProviderOptions& set_private_key_path(
136+
absl::string_view private_key_path);
137+
138+
private:
139+
std::string private_key_path_;
140+
std::string identity_certificate_path_;
141+
std::string root_cert_path_;
142+
std::string spiffe_bundle_map_path_;
143+
unsigned int refresh_interval_sec_;
144+
};
145+
146+
class FileWatcherCertificateProvider final ... {
147+
public:
148+
// ...existing class definitions...
149+
FileWatcherCertificateProvider(
150+
const FileWatcherCertificateProviderOptions& options);
151+
};
152+
```
153+
154+
#### Java
155+
156+
In Java, we need new API to work with [SPIFFE] and [SPIFFE bundle
157+
format]. A new `SpiffeUtil` will be developed:
158+
159+
```java
160+
class SpiffeUtil {
161+
Optional<SpiffeId> extractSpiffeId(X509Certificate[] certChain);
162+
SpiffeBundle loadTrustBundleFromFile(String trustBundleFile);
163+
}
164+
class SpiffeId {
165+
String getTrustDomain();
166+
String getPath();
167+
}
168+
class SpiffeBundle {
169+
Map<String, List<X509Certificates>> getBundleMap();
170+
}
171+
```
172+
173+
For the xDS functionality described above, the `XdsX509TrustManager`
174+
will gain the ability to be instantiated with a SPIFFE trust bundle
175+
map. In this case, it will use the SPIFFE trust bundle certificates as
176+
trusted roots. If the `XdsX509TrustManager` is instantiated using CA
177+
certificates (existing functionality), then it'll continue to use them
178+
exactly as it does today. The new APIs will look like this:
179+
180+
```java
181+
class XdsTrustManagerFactory {
182+
XdsTrustManagerFactory(Map<String, List<X509Certificates>>);
183+
XdsTrustManagerFactory(X509Certificates[]);
184+
}
185+
class XdsX509TrustManager {
186+
XdsX509TrustManager(Map<String, XdsX509ExtendedTrustManager>);
187+
XdsTrustManagerFactory(XdsX509ExtendedTrustManager);
188+
}
189+
```
190+
191+
We'll also adjust existing hierarchies of `Watcher` and
192+
`CertificateProvider` to support the new SPIFFE trust bundle map.
193+
194+
For the AdvancedTls case in Java, the API changes will look as follows.
195+
In AdvancedTlsX509TrustManager.java, the API must allow configuring and
196+
reloading a SPIFFE Bundle Map. There are currently three functions that
197+
configure the root trust certificate
198+
199+
```
200+
public void updateTrustCredentials(X509Certificate[] trustCerts) throws IOException,
201+
GeneralSecurityException
202+
203+
public void updateTrustCredentials(File trustCertFile) throws IOException,
204+
GeneralSecurityException
205+
206+
public Closeable updateTrustCredentials(File trustCertFile, long period, TimeUnit unit,
207+
ScheduledExecutorService executor) throws IOException, GeneralSecurityException
208+
```
209+
210+
We will add two similar new functions for updating the SPIIFE root of trust:
211+
212+
```
213+
public void updateSpiffeTrustBundle(File spiffeBundleFile) throws IOException,
214+
GeneralSecurityException
215+
216+
public Closeable updateSpiffeTrustBundle(File spiffeBundleFile, long period, TimeUnit unit,
217+
ScheduledExecutorService executor) throws IOException, GeneralSecurityException
218+
```
219+
220+
The `checkTrusted` implementations will be updated to properly use the SPIFFE Bundles as described in this gRFC.
221+
222+
#### Go
223+
224+
In Go, we can leverage [the go spiffe
225+
package](https://pkg.go.dev/github.com/spiffe/go-spiffe/v2) for parsing and
226+
object abstractions. Currently, similarly to C++ above, Go already supports
227+
[file watcher credential
228+
providers](https://github.com/grpc/grpc-go/blob/e0d191d8adcdd73aad084154769404dd2f6b0fc6/credentials/tls/certprovider/pemfile/watcher.go#L92C4-L99)
229+
with xDS. We can further modify these providers, specifically the file watcher,
230+
to allow them to be configured with and return SPIFFE bundles instead of only
231+
certificates.
232+
The Provider is designed to return a
233+
[KeyMaterial](https://github.com/grpc/grpc-go/blob/e0d191d8adcdd73aad084154769404dd2f6b0fc6/credentials/tls/certprovider/provider.go#L91-L97)
234+
struct to which we will add the field SPIFFEBundleMap.
235+
236+
```
237+
// KeyMaterial wraps the certificates and keys returned by a Provider instance.
238+
type KeyMaterial struct {
239+
// Certs contains a slice of cert/key pairs used to prove local identity.
240+
Certs []tls.Certificate
241+
// Roots contains the set of trusted roots to validate the peer's identity.
242+
// This field will only be used if the `SPIFFEBundleMap` field is unset.
243+
Roots *x509.CertPool
244+
// SPIFFEBundleMap is an in-memory representation of a spiffe trust bundle
245+
// map. If this value exists, it will be used to find the roots for a given
246+
// trust domain rather than the Roots in this struct.
247+
SPIFFEBundleMap map[string]*spiffebundle.Bundle
248+
}
249+
```
250+
251+
When using providers, we already [create our own custom verification
252+
function](https://github.com/grpc/grpc-go/blob/e0d191d8adcdd73aad084154769404dd2f6b0fc6/security/advancedtls/advancedtls.go#L513)
253+
for the `tls.Config`. Any specific needs for the SPIFFE bundles during
254+
verification can be implemented here as well.
255+
256+
### Temporary environment variable protection
257+
258+
The xDS functionality will be guarded via the
259+
`GRPC_EXPERIMENTAL_XDS_MTLS_SPIFFE` environment variable. The new
260+
bootstrap field will not be read if this env var is unset. This env var
261+
guard will be removed when the feature has passed interop tests.
262+
263+
## Rationale
264+
265+
Allowing both `spiffe_trust_bundle_map_file` and `ca_certificate_file`
266+
to be set in the `file_watcher` certificate provider config provides
267+
forward compatibility, as described above. The alternative was to make
268+
these two fields mutually exclusive, which would have required using a
269+
different bootstrap config with older and newer versions of gRPC in
270+
order to get the proper fallback behavior (using CA certificates) for
271+
older versions.
272+
273+
## Implementation
274+
275+
Java implementation:
276+
* SPIFFE ID parser https://github.com/grpc/grpc-java/pull/11490
277+
* SPIFFE Utils for extracting SPIFFE ID from leaf certificate and extracting SPIFFE trust bundle map from JSON file https://github.com/grpc/grpc-java/pull/11575
278+
* SPIFFE verification process https://github.com/grpc/grpc-java/pull/11627
279+
280+
Will be implemented in all other languages, timelines TBD.

0 commit comments

Comments
 (0)