123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*
- *
- * Copyright 2020 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- // Package xds provides a transport credentials implementation where the
- // security configuration is pushed by a management server using xDS APIs.
- package xds
- import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "errors"
- "fmt"
- "net"
- "time"
- "google.golang.org/grpc/credentials"
- credinternal "google.golang.org/grpc/internal/credentials"
- xdsinternal "google.golang.org/grpc/internal/credentials/xds"
- )
- // ClientOptions contains parameters to configure a new client-side xDS
- // credentials implementation.
- type ClientOptions struct {
- // FallbackCreds specifies the fallback credentials to be used when either
- // the `xds` scheme is not used in the user's dial target or when the
- // management server does not return any security configuration. Attempts to
- // create client credentials without fallback credentials will fail.
- FallbackCreds credentials.TransportCredentials
- }
- // NewClientCredentials returns a new client-side transport credentials
- // implementation which uses xDS APIs to fetch its security configuration.
- func NewClientCredentials(opts ClientOptions) (credentials.TransportCredentials, error) {
- if opts.FallbackCreds == nil {
- return nil, errors.New("missing fallback credentials")
- }
- return &credsImpl{
- isClient: true,
- fallback: opts.FallbackCreds,
- }, nil
- }
- // ServerOptions contains parameters to configure a new server-side xDS
- // credentials implementation.
- type ServerOptions struct {
- // FallbackCreds specifies the fallback credentials to be used when the
- // management server does not return any security configuration. Attempts to
- // create server credentials without fallback credentials will fail.
- FallbackCreds credentials.TransportCredentials
- }
- // NewServerCredentials returns a new server-side transport credentials
- // implementation which uses xDS APIs to fetch its security configuration.
- func NewServerCredentials(opts ServerOptions) (credentials.TransportCredentials, error) {
- if opts.FallbackCreds == nil {
- return nil, errors.New("missing fallback credentials")
- }
- return &credsImpl{
- isClient: false,
- fallback: opts.FallbackCreds,
- }, nil
- }
- // credsImpl is an implementation of the credentials.TransportCredentials
- // interface which uses xDS APIs to fetch its security configuration.
- type credsImpl struct {
- isClient bool
- fallback credentials.TransportCredentials
- }
- // ClientHandshake performs the TLS handshake on the client-side.
- //
- // It looks for the presence of a HandshakeInfo value in the passed in context
- // (added using a call to NewContextWithHandshakeInfo()), and retrieves identity
- // and root certificates from there. It also retrieves a list of acceptable SANs
- // and uses a custom verification function to validate the certificate presented
- // by the peer. It uses fallback credentials if no HandshakeInfo is present in
- // the passed in context.
- func (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
- if !c.isClient {
- return nil, nil, errors.New("ClientHandshake() is not supported for server credentials")
- }
- // The CDS balancer constructs a new HandshakeInfo using a call to
- // NewHandshakeInfo(), and then adds it to the attributes field of the
- // resolver.Address when handling calls to NewSubConn(). The transport layer
- // takes care of shipping these attributes in the context to this handshake
- // function. We first read the credentials.ClientHandshakeInfo type from the
- // context, which contains the attributes added by the CDS balancer. We then
- // read the HandshakeInfo from the attributes to get to the actual data that
- // we need here for the handshake.
- chi := credentials.ClientHandshakeInfoFromContext(ctx)
- // If there are no attributes in the received context or the attributes does
- // not contain a HandshakeInfo, it could either mean that the user did not
- // specify an `xds` scheme in their dial target or that the xDS server did
- // not provide any security configuration. In both of these cases, we use
- // the fallback credentials specified by the user.
- if chi.Attributes == nil {
- return c.fallback.ClientHandshake(ctx, authority, rawConn)
- }
- hi := xdsinternal.GetHandshakeInfo(chi.Attributes)
- if hi.UseFallbackCreds() {
- return c.fallback.ClientHandshake(ctx, authority, rawConn)
- }
- // We build the tls.Config with the following values
- // 1. Root certificate as returned by the root provider.
- // 2. Identity certificate as returned by the identity provider. This may be
- // empty on the client side, if the client is not doing mTLS.
- // 3. InsecureSkipVerify to true. Certificates used in Mesh environments
- // usually contains the identity of the workload presenting the
- // certificate as a SAN (instead of a hostname in the CommonName field).
- // This means that normal certificate verification as done by the
- // standard library will fail.
- // 4. Key usage to match whether client/server usage.
- // 5. A `VerifyPeerCertificate` function which performs normal peer
- // cert verification using configured roots, and the custom SAN checks.
- cfg, err := hi.ClientSideTLSConfig(ctx)
- if err != nil {
- return nil, nil, err
- }
- cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
- // Parse all raw certificates presented by the peer.
- var certs []*x509.Certificate
- for _, rc := range rawCerts {
- cert, err := x509.ParseCertificate(rc)
- if err != nil {
- return err
- }
- certs = append(certs, cert)
- }
- // Build the intermediates list and verify that the leaf certificate
- // is signed by one of the root certificates.
- intermediates := x509.NewCertPool()
- for _, cert := range certs[1:] {
- intermediates.AddCert(cert)
- }
- opts := x509.VerifyOptions{
- Roots: cfg.RootCAs,
- Intermediates: intermediates,
- KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- }
- if _, err := certs[0].Verify(opts); err != nil {
- return err
- }
- // The SANs sent by the MeshCA are encoded as SPIFFE IDs. We need to
- // only look at the SANs on the leaf cert.
- if cert := certs[0]; !hi.MatchingSANExists(cert) {
- // TODO: Print the complete certificate once the x509 package
- // supports a String() method on the Certificate type.
- return fmt.Errorf("xds: received SANs {DNSNames: %v, EmailAddresses: %v, IPAddresses: %v, URIs: %v} do not match any of the accepted SANs", cert.DNSNames, cert.EmailAddresses, cert.IPAddresses, cert.URIs)
- }
- return nil
- }
- // Perform the TLS handshake with the tls.Config that we have. We run the
- // actual Handshake() function in a goroutine because we need to respect the
- // deadline specified on the passed in context, and we need a way to cancel
- // the handshake if the context is cancelled.
- conn := tls.Client(rawConn, cfg)
- errCh := make(chan error, 1)
- go func() {
- errCh <- conn.Handshake()
- close(errCh)
- }()
- select {
- case err := <-errCh:
- if err != nil {
- conn.Close()
- return nil, nil, err
- }
- case <-ctx.Done():
- conn.Close()
- return nil, nil, ctx.Err()
- }
- info := credentials.TLSInfo{
- State: conn.ConnectionState(),
- CommonAuthInfo: credentials.CommonAuthInfo{
- SecurityLevel: credentials.PrivacyAndIntegrity,
- },
- SPIFFEID: credinternal.SPIFFEIDFromState(conn.ConnectionState()),
- }
- return credinternal.WrapSyscallConn(rawConn, conn), info, nil
- }
- // ServerHandshake performs the TLS handshake on the server-side.
- func (c *credsImpl) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
- if c.isClient {
- return nil, nil, errors.New("ServerHandshake is not supported for client credentials")
- }
- // An xds-enabled gRPC server wraps the underlying raw net.Conn in a type
- // that provides a way to retrieve `HandshakeInfo`, which contains the
- // certificate providers to be used during the handshake. If the net.Conn
- // passed to this function does not implement this interface, or if the
- // `HandshakeInfo` does not contain the information we are looking for, we
- // delegate the handshake to the fallback credentials.
- hiConn, ok := rawConn.(interface {
- XDSHandshakeInfo() (*xdsinternal.HandshakeInfo, error)
- })
- if !ok {
- return c.fallback.ServerHandshake(rawConn)
- }
- hi, err := hiConn.XDSHandshakeInfo()
- if err != nil {
- return nil, nil, err
- }
- if hi.UseFallbackCreds() {
- return c.fallback.ServerHandshake(rawConn)
- }
- // An xds-enabled gRPC server is expected to wrap the underlying raw
- // net.Conn in a type which provides a way to retrieve the deadline set on
- // it. If we cannot retrieve the deadline here, we fail (by setting deadline
- // to time.Now()), instead of using a default deadline and possibly taking
- // longer to eventually fail.
- deadline := time.Now()
- if dConn, ok := rawConn.(interface{ GetDeadline() time.Time }); ok {
- deadline = dConn.GetDeadline()
- }
- ctx, cancel := context.WithDeadline(context.Background(), deadline)
- defer cancel()
- cfg, err := hi.ServerSideTLSConfig(ctx)
- if err != nil {
- return nil, nil, err
- }
- conn := tls.Server(rawConn, cfg)
- if err := conn.Handshake(); err != nil {
- conn.Close()
- return nil, nil, err
- }
- info := credentials.TLSInfo{
- State: conn.ConnectionState(),
- CommonAuthInfo: credentials.CommonAuthInfo{
- SecurityLevel: credentials.PrivacyAndIntegrity,
- },
- }
- info.SPIFFEID = credinternal.SPIFFEIDFromState(conn.ConnectionState())
- return credinternal.WrapSyscallConn(rawConn, conn), info, nil
- }
- // Info provides the ProtocolInfo of this TransportCredentials.
- func (c *credsImpl) Info() credentials.ProtocolInfo {
- return credentials.ProtocolInfo{SecurityProtocol: "tls"}
- }
- // Clone makes a copy of this TransportCredentials.
- func (c *credsImpl) Clone() credentials.TransportCredentials {
- clone := *c
- return &clone
- }
- func (c *credsImpl) OverrideServerName(_ string) error {
- return errors.New("serverName for peer validation must be configured as a list of acceptable SANs")
- }
- // UsesXDS returns true if c uses xDS to fetch security configuration
- // used at handshake time, and false otherwise.
- func (c *credsImpl) UsesXDS() bool {
- return true
- }
|