github.com/true-sqn/fabric@v2.1.1+incompatible/internal/pkg/comm/client.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package comm
     8  
     9  import (
    10  	"context"
    11  	"crypto/tls"
    12  	"crypto/x509"
    13  	"time"
    14  
    15  	"github.com/pkg/errors"
    16  	"google.golang.org/grpc"
    17  	"google.golang.org/grpc/keepalive"
    18  )
    19  
    20  type GRPCClient struct {
    21  	// TLS configuration used by the grpc.ClientConn
    22  	tlsConfig *tls.Config
    23  	// Options for setting up new connections
    24  	dialOpts []grpc.DialOption
    25  	// Duration for which to block while established a new connection
    26  	timeout time.Duration
    27  	// Maximum message size the client can receive
    28  	maxRecvMsgSize int
    29  	// Maximum message size the client can send
    30  	maxSendMsgSize int
    31  }
    32  
    33  // NewGRPCClient creates a new implementation of GRPCClient given an address
    34  // and client configuration
    35  func NewGRPCClient(config ClientConfig) (*GRPCClient, error) {
    36  	client := &GRPCClient{}
    37  
    38  	// parse secure options
    39  	err := client.parseSecureOptions(config.SecOpts)
    40  	if err != nil {
    41  		return client, err
    42  	}
    43  
    44  	// keepalive options
    45  
    46  	kap := keepalive.ClientParameters{
    47  		Time:                config.KaOpts.ClientInterval,
    48  		Timeout:             config.KaOpts.ClientTimeout,
    49  		PermitWithoutStream: true,
    50  	}
    51  	// set keepalive
    52  	client.dialOpts = append(client.dialOpts, grpc.WithKeepaliveParams(kap))
    53  	// Unless asynchronous connect is set, make connection establishment blocking.
    54  	if !config.AsyncConnect {
    55  		client.dialOpts = append(client.dialOpts, grpc.WithBlock())
    56  		client.dialOpts = append(client.dialOpts, grpc.FailOnNonTempDialError(true))
    57  	}
    58  	client.timeout = config.Timeout
    59  	// set send/recv message size to package defaults
    60  	client.maxRecvMsgSize = MaxRecvMsgSize
    61  	client.maxSendMsgSize = MaxSendMsgSize
    62  
    63  	return client, nil
    64  }
    65  
    66  func (client *GRPCClient) parseSecureOptions(opts SecureOptions) error {
    67  	// if TLS is not enabled, return
    68  	if !opts.UseTLS {
    69  		return nil
    70  	}
    71  
    72  	client.tlsConfig = &tls.Config{
    73  		VerifyPeerCertificate: opts.VerifyCertificate,
    74  		MinVersion:            tls.VersionTLS12} // TLS 1.2 only
    75  	if len(opts.ServerRootCAs) > 0 {
    76  		client.tlsConfig.RootCAs = x509.NewCertPool()
    77  		for _, certBytes := range opts.ServerRootCAs {
    78  			err := AddPemToCertPool(certBytes, client.tlsConfig.RootCAs)
    79  			if err != nil {
    80  				commLogger.Debugf("error adding root certificate: %v", err)
    81  				return errors.WithMessage(err,
    82  					"error adding root certificate")
    83  			}
    84  		}
    85  	}
    86  	if opts.RequireClientCert {
    87  		// make sure we have both Key and Certificate
    88  		if opts.Key != nil &&
    89  			opts.Certificate != nil {
    90  			cert, err := tls.X509KeyPair(opts.Certificate,
    91  				opts.Key)
    92  			if err != nil {
    93  				return errors.WithMessage(err, "failed to "+
    94  					"load client certificate")
    95  			}
    96  			client.tlsConfig.Certificates = append(
    97  				client.tlsConfig.Certificates, cert)
    98  		} else {
    99  			return errors.New("both Key and Certificate " +
   100  				"are required when using mutual TLS")
   101  		}
   102  	}
   103  
   104  	if opts.TimeShift > 0 {
   105  		client.tlsConfig.Time = func() time.Time {
   106  			return time.Now().Add((-1) * opts.TimeShift)
   107  		}
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  // Certificate returns the tls.Certificate used to make TLS connections
   114  // when client certificates are required by the server
   115  func (client *GRPCClient) Certificate() tls.Certificate {
   116  	cert := tls.Certificate{}
   117  	if client.tlsConfig != nil && len(client.tlsConfig.Certificates) > 0 {
   118  		cert = client.tlsConfig.Certificates[0]
   119  	}
   120  	return cert
   121  }
   122  
   123  // TLSEnabled is a flag indicating whether to use TLS for client
   124  // connections
   125  func (client *GRPCClient) TLSEnabled() bool {
   126  	return client.tlsConfig != nil
   127  }
   128  
   129  // MutualTLSRequired is a flag indicating whether the client
   130  // must send a certificate when making TLS connections
   131  func (client *GRPCClient) MutualTLSRequired() bool {
   132  	return client.tlsConfig != nil &&
   133  		len(client.tlsConfig.Certificates) > 0
   134  }
   135  
   136  // SetMaxRecvMsgSize sets the maximum message size the client can receive
   137  func (client *GRPCClient) SetMaxRecvMsgSize(size int) {
   138  	client.maxRecvMsgSize = size
   139  }
   140  
   141  // SetMaxSendMsgSize sets the maximum message size the client can send
   142  func (client *GRPCClient) SetMaxSendMsgSize(size int) {
   143  	client.maxSendMsgSize = size
   144  }
   145  
   146  // SetServerRootCAs sets the list of authorities used to verify server
   147  // certificates based on a list of PEM-encoded X509 certificate authorities
   148  func (client *GRPCClient) SetServerRootCAs(serverRoots [][]byte) error {
   149  
   150  	// NOTE: if no serverRoots are specified, the current cert pool will be
   151  	// replaced with an empty one
   152  	certPool := x509.NewCertPool()
   153  	for _, root := range serverRoots {
   154  		err := AddPemToCertPool(root, certPool)
   155  		if err != nil {
   156  			return errors.WithMessage(err, "error adding root certificate")
   157  		}
   158  	}
   159  	client.tlsConfig.RootCAs = certPool
   160  	return nil
   161  }
   162  
   163  type TLSOption func(tlsConfig *tls.Config)
   164  
   165  func ServerNameOverride(name string) TLSOption {
   166  	return func(tlsConfig *tls.Config) {
   167  		tlsConfig.ServerName = name
   168  	}
   169  }
   170  
   171  func CertPoolOverride(pool *x509.CertPool) TLSOption {
   172  	return func(tlsConfig *tls.Config) {
   173  		tlsConfig.RootCAs = pool
   174  	}
   175  }
   176  
   177  // NewConnection returns a grpc.ClientConn for the target address and
   178  // overrides the server name used to verify the hostname on the
   179  // certificate returned by a server when using TLS
   180  func (client *GRPCClient) NewConnection(address string, tlsOptions ...TLSOption) (*grpc.ClientConn, error) {
   181  
   182  	var dialOpts []grpc.DialOption
   183  	dialOpts = append(dialOpts, client.dialOpts...)
   184  
   185  	// set transport credentials and max send/recv message sizes
   186  	// immediately before creating a connection in order to allow
   187  	// SetServerRootCAs / SetMaxRecvMsgSize / SetMaxSendMsgSize
   188  	//  to take effect on a per connection basis
   189  	if client.tlsConfig != nil {
   190  		dialOpts = append(dialOpts, grpc.WithTransportCredentials(
   191  			&DynamicClientCredentials{
   192  				TLSConfig:  client.tlsConfig,
   193  				TLSOptions: tlsOptions,
   194  			},
   195  		))
   196  	} else {
   197  		dialOpts = append(dialOpts, grpc.WithInsecure())
   198  	}
   199  
   200  	dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(
   201  		grpc.MaxCallRecvMsgSize(client.maxRecvMsgSize),
   202  		grpc.MaxCallSendMsgSize(client.maxSendMsgSize),
   203  	))
   204  
   205  	ctx, cancel := context.WithTimeout(context.Background(), client.timeout)
   206  	defer cancel()
   207  	conn, err := grpc.DialContext(ctx, address, dialOpts...)
   208  	if err != nil {
   209  		return nil, errors.WithMessage(errors.WithStack(err),
   210  			"failed to create new connection")
   211  	}
   212  	return conn, nil
   213  }