google.golang.org/grpc@v1.74.2/internal/xds/bootstrap/tlscreds/bundle.go (about) 1 /* 2 * 3 * Copyright 2023 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package tlscreds implements mTLS Credentials in xDS Bootstrap File. 20 // See gRFC A65: github.com/grpc/proposal/blob/master/A65-xds-mtls-creds-in-bootstrap.md. 21 package tlscreds 22 23 import ( 24 "context" 25 "crypto/tls" 26 "crypto/x509" 27 "encoding/json" 28 "errors" 29 "fmt" 30 "net" 31 "sync" 32 "time" 33 34 "github.com/spiffe/go-spiffe/v2/bundle/spiffebundle" 35 "google.golang.org/grpc/credentials" 36 "google.golang.org/grpc/credentials/tls/certprovider" 37 "google.golang.org/grpc/credentials/tls/certprovider/pemfile" 38 "google.golang.org/grpc/internal/credentials/spiffe" 39 "google.golang.org/grpc/internal/envconfig" 40 ) 41 42 // bundle is an implementation of credentials.Bundle which implements mTLS 43 // Credentials in xDS Bootstrap File. 44 type bundle struct { 45 transportCredentials credentials.TransportCredentials 46 } 47 48 // NewBundle returns a credentials.Bundle which implements mTLS Credentials in xDS 49 // Bootstrap File. It delegates certificate loading to a file_watcher provider 50 // if either client certificates or server root CA is specified. The second 51 // return value is a close func that should be called when the caller no longer 52 // needs this bundle. 53 // See gRFC A65: github.com/grpc/proposal/blob/master/A65-xds-mtls-creds-in-bootstrap.md 54 func NewBundle(jd json.RawMessage) (credentials.Bundle, func(), error) { 55 cfg := &struct { 56 CertificateFile string `json:"certificate_file"` 57 CACertificateFile string `json:"ca_certificate_file"` 58 PrivateKeyFile string `json:"private_key_file"` 59 SPIFFETrustBundleMapFile string `json:"spiffe_trust_bundle_map_file"` 60 }{} 61 62 if jd != nil { 63 if err := json.Unmarshal(jd, cfg); err != nil { 64 return nil, nil, fmt.Errorf("failed to unmarshal config: %v", err) 65 } 66 } // Else the config field is absent. Treat it as an empty config. 67 68 if !envconfig.XDSSPIFFEEnabled { 69 cfg.SPIFFETrustBundleMapFile = "" 70 } 71 if cfg.CACertificateFile == "" && cfg.CertificateFile == "" && cfg.PrivateKeyFile == "" && cfg.SPIFFETrustBundleMapFile == "" { 72 // We cannot use (and do not need) a file_watcher provider in this case, 73 // and can simply directly use the TLS transport credentials. 74 // Quoting A65: 75 // 76 // > The only difference between the file-watcher certificate provider 77 // > config and this one is that in the file-watcher certificate 78 // > provider, at least one of the "certificate_file" or 79 // > "ca_certificate_file" fields must be specified, whereas in this 80 // > configuration, it is acceptable to specify neither one. 81 // Further, with the introduction of SPIFFE Trust Map support, we also 82 // check for this value. 83 return &bundle{transportCredentials: credentials.NewTLS(&tls.Config{})}, func() {}, nil 84 } 85 // Otherwise we need to use a file_watcher provider to watch the CA, 86 // private and public keys. 87 88 // The pemfile plugin (file_watcher) currently ignores BuildOptions. 89 provider, err := certprovider.GetProvider(pemfile.PluginName, jd, certprovider.BuildOptions{}) 90 if err != nil { 91 return nil, nil, err 92 } 93 return &bundle{ 94 transportCredentials: &reloadingCreds{provider: provider}, 95 }, sync.OnceFunc(func() { provider.Close() }), nil 96 } 97 98 func (t *bundle) TransportCredentials() credentials.TransportCredentials { 99 return t.transportCredentials 100 } 101 102 func (t *bundle) PerRPCCredentials() credentials.PerRPCCredentials { 103 // mTLS provides transport credentials only. There are no per-RPC 104 // credentials. 105 return nil 106 } 107 108 func (t *bundle) NewWithMode(string) (credentials.Bundle, error) { 109 // This bundle has a single mode which only uses TLS transport credentials, 110 // so there is no legitimate case where callers would call NewWithMode. 111 return nil, fmt.Errorf("xDS TLS credentials only support one mode") 112 } 113 114 // reloadingCreds is a credentials.TransportCredentials for client 115 // side mTLS that reloads the server root CA certificate and the client 116 // certificates from the provider on every client handshake. This is necessary 117 // because the standard TLS credentials do not support reloading CA 118 // certificates. 119 type reloadingCreds struct { 120 provider certprovider.Provider 121 } 122 123 func (c *reloadingCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { 124 km, err := c.provider.KeyMaterial(ctx) 125 if err != nil { 126 return nil, nil, err 127 } 128 var config *tls.Config 129 if km.SPIFFEBundleMap != nil { 130 config = &tls.Config{ 131 InsecureSkipVerify: true, 132 VerifyPeerCertificate: buildSPIFFEVerifyFunc(km.SPIFFEBundleMap), 133 Certificates: km.Certs, 134 } 135 } else { 136 config = &tls.Config{ 137 RootCAs: km.Roots, 138 Certificates: km.Certs, 139 } 140 } 141 return credentials.NewTLS(config).ClientHandshake(ctx, authority, rawConn) 142 } 143 144 func (c *reloadingCreds) Info() credentials.ProtocolInfo { 145 return credentials.ProtocolInfo{SecurityProtocol: "tls"} 146 } 147 148 func (c *reloadingCreds) Clone() credentials.TransportCredentials { 149 return &reloadingCreds{provider: c.provider} 150 } 151 152 func (c *reloadingCreds) OverrideServerName(string) error { 153 return errors.New("overriding server name is not supported by xDS client TLS credentials") 154 } 155 156 func (c *reloadingCreds) ServerHandshake(net.Conn) (net.Conn, credentials.AuthInfo, error) { 157 return nil, nil, errors.New("server handshake is not supported by xDS client TLS credentials") 158 } 159 160 func buildSPIFFEVerifyFunc(spiffeBundleMap map[string]*spiffebundle.Bundle) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 161 return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { 162 rawCertList := make([]*x509.Certificate, len(rawCerts)) 163 for i, asn1Data := range rawCerts { 164 cert, err := x509.ParseCertificate(asn1Data) 165 if err != nil { 166 return fmt.Errorf("spiffe: verify function could not parse input certificate: %v", err) 167 } 168 rawCertList[i] = cert 169 } 170 if len(rawCertList) == 0 { 171 return fmt.Errorf("spiffe: verify function has no valid input certificates") 172 } 173 leafCert := rawCertList[0] 174 roots, err := spiffe.GetRootsFromSPIFFEBundleMap(spiffeBundleMap, leafCert) 175 if err != nil { 176 return err 177 } 178 179 opts := x509.VerifyOptions{ 180 Roots: roots, 181 CurrentTime: time.Now(), 182 Intermediates: x509.NewCertPool(), 183 } 184 185 for _, cert := range rawCertList[1:] { 186 opts.Intermediates.AddCert(cert) 187 } 188 // The verified chain is (surprisingly) unused. 189 if _, err = rawCertList[0].Verify(opts); err != nil { 190 return fmt.Errorf("spiffe: x509 certificate Verify failed: %v", err) 191 } 192 return nil 193 } 194 }