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