google.golang.org/grpc@v1.74.2/credentials/xds/xds.go (about)

     1  /*
     2   *
     3   * Copyright 2020 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 xds provides a transport credentials implementation where the
    20  // security configuration is pushed by a management server using xDS APIs.
    21  package xds
    22  
    23  import (
    24  	"context"
    25  	"crypto/tls"
    26  	"errors"
    27  	"net"
    28  	"sync/atomic"
    29  	"time"
    30  
    31  	"google.golang.org/grpc/credentials"
    32  	credinternal "google.golang.org/grpc/internal/credentials"
    33  	xdsinternal "google.golang.org/grpc/internal/credentials/xds"
    34  )
    35  
    36  // ClientOptions contains parameters to configure a new client-side xDS
    37  // credentials implementation.
    38  type ClientOptions struct {
    39  	// FallbackCreds specifies the fallback credentials to be used when either
    40  	// the `xds` scheme is not used in the user's dial target or when the
    41  	// management server does not return any security configuration. Attempts to
    42  	// create client credentials without fallback credentials will fail.
    43  	FallbackCreds credentials.TransportCredentials
    44  }
    45  
    46  // NewClientCredentials returns a new client-side transport credentials
    47  // implementation which uses xDS APIs to fetch its security configuration.
    48  func NewClientCredentials(opts ClientOptions) (credentials.TransportCredentials, error) {
    49  	if opts.FallbackCreds == nil {
    50  		return nil, errors.New("missing fallback credentials")
    51  	}
    52  	return &credsImpl{
    53  		isClient: true,
    54  		fallback: opts.FallbackCreds,
    55  	}, nil
    56  }
    57  
    58  // ServerOptions contains parameters to configure a new server-side xDS
    59  // credentials implementation.
    60  type ServerOptions struct {
    61  	// FallbackCreds specifies the fallback credentials to be used when the
    62  	// management server does not return any security configuration. Attempts to
    63  	// create server credentials without fallback credentials will fail.
    64  	FallbackCreds credentials.TransportCredentials
    65  }
    66  
    67  // NewServerCredentials returns a new server-side transport credentials
    68  // implementation which uses xDS APIs to fetch its security configuration.
    69  func NewServerCredentials(opts ServerOptions) (credentials.TransportCredentials, error) {
    70  	if opts.FallbackCreds == nil {
    71  		return nil, errors.New("missing fallback credentials")
    72  	}
    73  	return &credsImpl{
    74  		isClient: false,
    75  		fallback: opts.FallbackCreds,
    76  	}, nil
    77  }
    78  
    79  // credsImpl is an implementation of the credentials.TransportCredentials
    80  // interface which uses xDS APIs to fetch its security configuration.
    81  type credsImpl struct {
    82  	isClient bool
    83  	fallback credentials.TransportCredentials
    84  }
    85  
    86  // ClientHandshake performs the TLS handshake on the client-side.
    87  //
    88  // It looks for the presence of a HandshakeInfo value in the passed in context
    89  // (added using a call to NewContextWithHandshakeInfo()), and retrieves identity
    90  // and root certificates from there. It also retrieves a list of acceptable SANs
    91  // and uses a custom verification function to validate the certificate presented
    92  // by the peer. It uses fallback credentials if no HandshakeInfo is present in
    93  // the passed in context.
    94  func (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
    95  	if !c.isClient {
    96  		return nil, nil, errors.New("ClientHandshake() is not supported for server credentials")
    97  	}
    98  
    99  	// The CDS balancer constructs a new HandshakeInfo using a call to
   100  	// NewHandshakeInfo(), and then adds it to the attributes field of the
   101  	// resolver.Address when handling calls to NewSubConn(). The transport layer
   102  	// takes care of shipping these attributes in the context to this handshake
   103  	// function. We first read the credentials.ClientHandshakeInfo type from the
   104  	// context, which contains the attributes added by the CDS balancer. We then
   105  	// read the HandshakeInfo from the attributes to get to the actual data that
   106  	// we need here for the handshake.
   107  	chi := credentials.ClientHandshakeInfoFromContext(ctx)
   108  	// If there are no attributes in the received context or the attributes does
   109  	// not contain a HandshakeInfo, it could either mean that the user did not
   110  	// specify an `xds` scheme in their dial target or that the xDS server did
   111  	// not provide any security configuration. In both of these cases, we use
   112  	// the fallback credentials specified by the user.
   113  	if chi.Attributes == nil {
   114  		return c.fallback.ClientHandshake(ctx, authority, rawConn)
   115  	}
   116  
   117  	uPtr := xdsinternal.GetHandshakeInfo(chi.Attributes)
   118  	hi := (*xdsinternal.HandshakeInfo)(atomic.LoadPointer(uPtr))
   119  	if hi.UseFallbackCreds() {
   120  		return c.fallback.ClientHandshake(ctx, authority, rawConn)
   121  	}
   122  
   123  	// We build the tls.Config with the following values
   124  	// 1. Root certificate as returned by the root provider.
   125  	// 2. Identity certificate as returned by the identity provider. This may be
   126  	//    empty on the client side, if the client is not doing mTLS.
   127  	// 3. InsecureSkipVerify to true. Certificates used in Mesh environments
   128  	//    usually contains the identity of the workload presenting the
   129  	//    certificate as a SAN (instead of a hostname in the CommonName field).
   130  	//    This means that normal certificate verification as done by the
   131  	//    standard library will fail.
   132  	// 4. Key usage to match whether client/server usage.
   133  	// 5. A `VerifyPeerCertificate` function which performs normal peer
   134  	// 	  cert verification using configured roots, and the custom SAN checks.
   135  	cfg, err := hi.ClientSideTLSConfig(ctx)
   136  	if err != nil {
   137  		return nil, nil, err
   138  	}
   139  
   140  	// Perform the TLS handshake with the tls.Config that we have. We run the
   141  	// actual Handshake() function in a goroutine because we need to respect the
   142  	// deadline specified on the passed in context, and we need a way to cancel
   143  	// the handshake if the context is cancelled.
   144  	conn := tls.Client(rawConn, cfg)
   145  	errCh := make(chan error, 1)
   146  	go func() {
   147  		errCh <- conn.Handshake()
   148  		close(errCh)
   149  	}()
   150  	select {
   151  	case err := <-errCh:
   152  		if err != nil {
   153  			conn.Close()
   154  			return nil, nil, err
   155  		}
   156  	case <-ctx.Done():
   157  		conn.Close()
   158  		return nil, nil, ctx.Err()
   159  	}
   160  	info := credentials.TLSInfo{
   161  		State: conn.ConnectionState(),
   162  		CommonAuthInfo: credentials.CommonAuthInfo{
   163  			SecurityLevel: credentials.PrivacyAndIntegrity,
   164  		},
   165  		SPIFFEID: credinternal.SPIFFEIDFromState(conn.ConnectionState()),
   166  	}
   167  	return credinternal.WrapSyscallConn(rawConn, conn), info, nil
   168  }
   169  
   170  // ServerHandshake performs the TLS handshake on the server-side.
   171  func (c *credsImpl) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
   172  	if c.isClient {
   173  		return nil, nil, errors.New("ServerHandshake is not supported for client credentials")
   174  	}
   175  
   176  	// An xds-enabled gRPC server wraps the underlying raw net.Conn in a type
   177  	// that provides a way to retrieve `HandshakeInfo`, which contains the
   178  	// certificate providers to be used during the handshake. If the net.Conn
   179  	// passed to this function does not implement this interface, or if the
   180  	// `HandshakeInfo` does not contain the information we are looking for, we
   181  	// delegate the handshake to the fallback credentials.
   182  	hiConn, ok := rawConn.(interface {
   183  		XDSHandshakeInfo() (*xdsinternal.HandshakeInfo, error)
   184  	})
   185  	if !ok {
   186  		return c.fallback.ServerHandshake(rawConn)
   187  	}
   188  	hi, err := hiConn.XDSHandshakeInfo()
   189  	if err != nil {
   190  		return nil, nil, err
   191  	}
   192  	if hi.UseFallbackCreds() {
   193  		return c.fallback.ServerHandshake(rawConn)
   194  	}
   195  
   196  	// An xds-enabled gRPC server is expected to wrap the underlying raw
   197  	// net.Conn in a type which provides a way to retrieve the deadline set on
   198  	// it. If we cannot retrieve the deadline here, we fail (by setting deadline
   199  	// to time.Now()), instead of using a default deadline and possibly taking
   200  	// longer to eventually fail.
   201  	deadline := time.Now()
   202  	if dConn, ok := rawConn.(interface{ GetDeadline() time.Time }); ok {
   203  		deadline = dConn.GetDeadline()
   204  	}
   205  	ctx, cancel := context.WithDeadline(context.Background(), deadline)
   206  	defer cancel()
   207  	cfg, err := hi.ServerSideTLSConfig(ctx)
   208  	if err != nil {
   209  		return nil, nil, err
   210  	}
   211  
   212  	conn := tls.Server(rawConn, cfg)
   213  	if err := conn.Handshake(); err != nil {
   214  		conn.Close()
   215  		return nil, nil, err
   216  	}
   217  	info := credentials.TLSInfo{
   218  		State: conn.ConnectionState(),
   219  		CommonAuthInfo: credentials.CommonAuthInfo{
   220  			SecurityLevel: credentials.PrivacyAndIntegrity,
   221  		},
   222  	}
   223  	info.SPIFFEID = credinternal.SPIFFEIDFromState(conn.ConnectionState())
   224  	return credinternal.WrapSyscallConn(rawConn, conn), info, nil
   225  }
   226  
   227  // Info provides the ProtocolInfo of this TransportCredentials.
   228  func (c *credsImpl) Info() credentials.ProtocolInfo {
   229  	return credentials.ProtocolInfo{SecurityProtocol: "tls"}
   230  }
   231  
   232  // Clone makes a copy of this TransportCredentials.
   233  func (c *credsImpl) Clone() credentials.TransportCredentials {
   234  	clone := *c
   235  	return &clone
   236  }
   237  
   238  func (c *credsImpl) OverrideServerName(_ string) error {
   239  	return errors.New("serverName for peer validation must be configured as a list of acceptable SANs")
   240  }
   241  
   242  // UsesXDS returns true if c uses xDS to fetch security configuration
   243  // used at handshake time, and false otherwise.
   244  func (c *credsImpl) UsesXDS() bool {
   245  	return true
   246  }