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  }