github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/peer/config.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  // The 'viper' package for configuration handling is very flexible, but has
     8  // been found to have extremely poor performance when configuration values are
     9  // accessed repeatedly. The function CacheConfiguration() defined here caches
    10  // all configuration values that are accessed frequently.  These parameters
    11  // are now presented as function calls that access local configuration
    12  // variables.  This seems to be the most robust way to represent these
    13  // parameters in the face of the numerous ways that configuration files are
    14  // loaded and used (e.g, normal usage vs. test cases).
    15  
    16  // The CacheConfiguration() function is allowed to be called globally to
    17  // ensure that the correct values are always cached; See for example how
    18  // certain parameters are forced in 'ChaincodeDevMode' in main.go.
    19  
    20  package peer
    21  
    22  import (
    23  	"crypto/tls"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net"
    27  	"path/filepath"
    28  	"runtime"
    29  	"time"
    30  
    31  	"github.com/hechain20/hechain/common/viperutil"
    32  	"github.com/hechain20/hechain/core/config"
    33  	"github.com/hechain20/hechain/internal/pkg/comm"
    34  	gatewayconfig "github.com/hechain20/hechain/internal/pkg/gateway/config"
    35  	"github.com/pkg/errors"
    36  	"github.com/spf13/viper"
    37  )
    38  
    39  // ExternalBuilder represents the configuration structure of
    40  // a chaincode external builder
    41  type ExternalBuilder struct {
    42  	// TODO: Remove Environment in 3.0
    43  	// Deprecated: Environment is retained for backwards compatibility.
    44  	// New deployments should use the new PropagateEnvironment field
    45  	Environment          []string `yaml:"environmentWhitelist"`
    46  	PropagateEnvironment []string `yaml:"propagateEnvironment"`
    47  	Name                 string   `yaml:"name"`
    48  	Path                 string   `yaml:"path"`
    49  }
    50  
    51  // Config is the struct that defines the Peer configurations.
    52  type Config struct {
    53  	// LocalMSPID is the identifier of the local MSP.
    54  	LocalMSPID string
    55  	// ListenAddress is the local address the peer will listen on. It must be
    56  	// formatted as [host | ipaddr]:port.
    57  	ListenAddress string
    58  	// PeerID provides a name for this peer instance. It is used when naming
    59  	// docker resources to segregate fabric networks and peers.
    60  	PeerID string
    61  	// PeerAddress is the address other peers and clients should use to
    62  	// communicate with the peer. It must be formatted as [host | ipaddr]:port.
    63  	// When used by the CLI, it represents the target peer endpoint.
    64  	PeerAddress string
    65  	// NetworkID specifies a name to use for logical separation of networks. It
    66  	// is used when naming docker resources to segregate fabric networks and
    67  	// peers.
    68  	NetworkID string
    69  	// ChaincodeListenAddress is the endpoint on which this peer will listen for
    70  	// chaincode connections. If omitted, it defaults to the host portion of
    71  	// PeerAddress and port 7052.
    72  	ChaincodeListenAddress string
    73  	// ChaincodeAddress specifies the endpoint chaincode launched by the peer
    74  	// should use to connect to the peer. If omitted, it defaults to
    75  	// ChaincodeListenAddress and falls back to ListenAddress.
    76  	ChaincodeAddress string
    77  	// ValidatorPoolSize indicates the number of goroutines that will execute
    78  	// transaction validation in parallel. If omitted, it defaults to number of
    79  	// hardware threads on the machine.
    80  	ValidatorPoolSize int
    81  
    82  	// ----- Peer Delivery Client Keepalive -----
    83  	// DeliveryClient Keepalive settings for communication with ordering nodes.
    84  	DeliverClientKeepaliveOptions comm.KeepaliveOptions
    85  
    86  	// ----- Profile -----
    87  	// TODO: create separate sub-struct for Profile config.
    88  
    89  	// ProfileEnabled determines if the go pprof endpoint is enabled in the peer.
    90  	ProfileEnabled bool
    91  	// ProfileListenAddress is the address the pprof server should accept
    92  	// connections on.
    93  	ProfileListenAddress string
    94  
    95  	// ----- Discovery -----
    96  
    97  	// The discovery service is used by clients to query information about peers,
    98  	// such as - which peers have joined a certain channel, what is the latest
    99  	// channel config, and most importantly - given a chaincode and a channel, what
   100  	// possible sets of peers satisfy the endorsement policy.
   101  	// TODO: create separate sub-struct for Discovery config.
   102  
   103  	// DiscoveryEnabled is used to enable the discovery service.
   104  	DiscoveryEnabled bool
   105  	// DiscoveryOrgMembersAllowed allows non-admins to perform non channel-scoped queries.
   106  	DiscoveryOrgMembersAllowed bool
   107  	// DiscoveryAuthCacheEnabled is used to enable the authentication cache.
   108  	DiscoveryAuthCacheEnabled bool
   109  	// DiscoveryAuthCacheMaxSize sets the maximum size of authentication cache.
   110  	DiscoveryAuthCacheMaxSize int
   111  	// DiscoveryAuthCachePurgeRetentionRatio set the proportion of entries remains in cache
   112  	// after overpopulation purge.
   113  	DiscoveryAuthCachePurgeRetentionRatio float64
   114  
   115  	// ----- Limits -----
   116  	// Limits is used to configure some internal resource limits.
   117  	// TODO: create separate sub-struct for Limits config.
   118  
   119  	// LimitsConcurrencyEndorserService sets the limits for concurrent requests sent to
   120  	// endorser service that handles chaincode deployment, query and invocation,
   121  	// including both user chaincodes and system chaincodes.
   122  	LimitsConcurrencyEndorserService int
   123  
   124  	// LimitsConcurrencyDeliverService sets the limits for concurrent event listeners
   125  	// registered to deliver service for blocks and transaction events.
   126  	LimitsConcurrencyDeliverService int
   127  
   128  	// LimitsConcurrencyGatewayService sets the limits for concurrent requests to
   129  	// gateway service that handles the submission and evaluation of transactions.
   130  	LimitsConcurrencyGatewayService int
   131  
   132  	// ----- TLS -----
   133  	// Require server-side TLS.
   134  	// TODO: create separate sub-struct for PeerTLS config.
   135  
   136  	// PeerTLSEnabled enables/disables Peer TLS.
   137  	PeerTLSEnabled bool
   138  
   139  	// ----- Authentication -----
   140  	// Authentication contains configuration parameters related to authenticating
   141  	// client messages.
   142  	// TODO: create separate sub-struct for Authentication config.
   143  
   144  	// AuthenticationTimeWindow sets the acceptable time duration for current
   145  	// server time and client's time as specified in a client request message.
   146  	AuthenticationTimeWindow time.Duration
   147  
   148  	// Endpoint of the vm management system. For docker can be one of the following in general
   149  	// unix:///var/run/docker.sock
   150  	// http://localhost:2375
   151  	// https://localhost:2376
   152  	VMEndpoint string
   153  
   154  	// ----- vm.docker.tls -----
   155  	// TODO: create separate sub-struct for VM.Docker.TLS config.
   156  
   157  	// VMDockerTLSEnabled enables/disables TLS for dockers.
   158  	VMDockerTLSEnabled   bool
   159  	VMDockerAttachStdout bool
   160  	// VMNetworkMode sets the networking mode for the container.
   161  	VMNetworkMode string
   162  
   163  	// ChaincodePull enables/disables force pulling of the base docker image.
   164  	ChaincodePull bool
   165  	// ExternalBuilders represents the builders and launchers for
   166  	// chaincode. The external builder detection processing will iterate over the
   167  	// builders in the order specified below.
   168  	ExternalBuilders []ExternalBuilder
   169  
   170  	// ----- Operations config -----
   171  	// TODO: create separate sub-struct for Operations config.
   172  
   173  	// OperationsListenAddress provides the host and port for the operations server
   174  	OperationsListenAddress string
   175  	// OperationsTLSEnabled enables/disables TLS for operations.
   176  	OperationsTLSEnabled bool
   177  	// OperationsTLSCertFile provides the path to PEM encoded server certificate for
   178  	// the operations server.
   179  	OperationsTLSCertFile string
   180  	// OperationsTLSKeyFile provides the path to PEM encoded server key for the
   181  	// operations server.
   182  	OperationsTLSKeyFile string
   183  	// OperationsTLSClientAuthRequired enables/disables the requirements for client
   184  	// certificate authentication at the TLS layer to access all resource.
   185  	OperationsTLSClientAuthRequired bool
   186  	// OperationsTLSClientRootCAs provides the path to PEM encoded ca certiricates to
   187  	// trust for client authentication.
   188  	OperationsTLSClientRootCAs []string
   189  
   190  	// ----- Metrics config -----
   191  	// TODO: create separate sub-struct for Metrics config.
   192  
   193  	// MetricsProvider provides the categories of metrics providers, which is one of
   194  	// statsd, prometheus, or disabled.
   195  	MetricsProvider string
   196  	// StatsdNetwork indicate the network type used by statsd metrics. (tcp or udp).
   197  	StatsdNetwork string
   198  	// StatsdAaddress provides the address for statsd server.
   199  	StatsdAaddress string
   200  	// StatsdWriteInterval set the time interval at which locally cached counters and
   201  	// gauges are pushed.
   202  	StatsdWriteInterval time.Duration
   203  	// StatsdPrefix provides the prefix that prepended to all emitted statsd metrics.
   204  	StatsdPrefix string
   205  
   206  	// ----- Docker config ------
   207  
   208  	// DockerCert is the path to the PEM encoded TLS client certificate required to access
   209  	// the docker daemon.
   210  	DockerCert string
   211  	// DockerKey is the path to the PEM encoded key required to access the docker daemon.
   212  	DockerKey string
   213  	// DockerCA is the path to the PEM encoded CA certificate for the docker daemon.
   214  	DockerCA string
   215  
   216  	// ----- Gateway config -----
   217  
   218  	// The gateway service is used by client SDKs to
   219  	// interact with fabric networks
   220  
   221  	GatewayOptions gatewayconfig.Options
   222  }
   223  
   224  // GlobalConfig obtains a set of configuration from viper, build and returns
   225  // the config struct.
   226  func GlobalConfig() (*Config, error) {
   227  	c := &Config{}
   228  	if err := c.load(); err != nil {
   229  		return nil, err
   230  	}
   231  	return c, nil
   232  }
   233  
   234  func (c *Config) load() error {
   235  	peerAddress, err := getLocalAddress()
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	configDir := filepath.Dir(viper.ConfigFileUsed())
   241  
   242  	c.PeerAddress = peerAddress
   243  	c.PeerID = viper.GetString("peer.id")
   244  	c.LocalMSPID = viper.GetString("peer.localMspId")
   245  	c.ListenAddress = viper.GetString("peer.listenAddress")
   246  
   247  	c.AuthenticationTimeWindow = viper.GetDuration("peer.authentication.timewindow")
   248  	if c.AuthenticationTimeWindow == 0 {
   249  		defaultTimeWindow := 15 * time.Minute
   250  		logger.Warningf("`peer.authentication.timewindow` not set; defaulting to %s", defaultTimeWindow)
   251  		c.AuthenticationTimeWindow = defaultTimeWindow
   252  	}
   253  
   254  	c.PeerTLSEnabled = viper.GetBool("peer.tls.enabled")
   255  	c.NetworkID = viper.GetString("peer.networkId")
   256  	c.LimitsConcurrencyEndorserService = viper.GetInt("peer.limits.concurrency.endorserService")
   257  	c.LimitsConcurrencyDeliverService = viper.GetInt("peer.limits.concurrency.deliverService")
   258  	c.LimitsConcurrencyGatewayService = viper.GetInt("peer.limits.concurrency.gatewayService")
   259  	c.DiscoveryEnabled = viper.GetBool("peer.discovery.enabled")
   260  	c.ProfileEnabled = viper.GetBool("peer.profile.enabled")
   261  	c.ProfileListenAddress = viper.GetString("peer.profile.listenAddress")
   262  	c.DiscoveryOrgMembersAllowed = viper.GetBool("peer.discovery.orgMembersAllowedAccess")
   263  	c.DiscoveryAuthCacheEnabled = viper.GetBool("peer.discovery.authCacheEnabled")
   264  	c.DiscoveryAuthCacheMaxSize = viper.GetInt("peer.discovery.authCacheMaxSize")
   265  	c.DiscoveryAuthCachePurgeRetentionRatio = viper.GetFloat64("peer.discovery.authCachePurgeRetentionRatio")
   266  	c.ChaincodeListenAddress = viper.GetString("peer.chaincodeListenAddress")
   267  	c.ChaincodeAddress = viper.GetString("peer.chaincodeAddress")
   268  
   269  	c.ValidatorPoolSize = viper.GetInt("peer.validatorPoolSize")
   270  	if c.ValidatorPoolSize <= 0 {
   271  		c.ValidatorPoolSize = runtime.NumCPU()
   272  	}
   273  
   274  	c.DeliverClientKeepaliveOptions = comm.DefaultKeepaliveOptions
   275  	if viper.IsSet("peer.keepalive.deliveryClient.interval") {
   276  		c.DeliverClientKeepaliveOptions.ClientInterval = viper.GetDuration("peer.keepalive.deliveryClient.interval")
   277  	}
   278  	if viper.IsSet("peer.keepalive.deliveryClient.timeout") {
   279  		c.DeliverClientKeepaliveOptions.ClientTimeout = viper.GetDuration("peer.keepalive.deliveryClient.timeout")
   280  	}
   281  
   282  	c.GatewayOptions = gatewayconfig.GetOptions(viper.GetViper())
   283  
   284  	c.VMEndpoint = viper.GetString("vm.endpoint")
   285  	c.VMDockerTLSEnabled = viper.GetBool("vm.docker.tls.enabled")
   286  	c.VMDockerAttachStdout = viper.GetBool("vm.docker.attachStdout")
   287  
   288  	c.VMNetworkMode = viper.GetString("vm.docker.hostConfig.NetworkMode")
   289  	if c.VMNetworkMode == "" {
   290  		c.VMNetworkMode = "host"
   291  	}
   292  
   293  	c.ChaincodePull = viper.GetBool("chaincode.pull")
   294  	var externalBuilders []ExternalBuilder
   295  
   296  	err = viper.UnmarshalKey("chaincode.externalBuilders", &externalBuilders, viper.DecodeHook(viperutil.YamlStringToStructHook(externalBuilders)))
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	c.ExternalBuilders = externalBuilders
   302  	for builderIndex, builder := range c.ExternalBuilders {
   303  		if builder.Path == "" {
   304  			return fmt.Errorf("invalid external builder configuration, path attribute missing in one or more builders")
   305  		}
   306  		if builder.Name == "" {
   307  			return fmt.Errorf("external builder at path %s has no name attribute", builder.Path)
   308  		}
   309  		if builder.Environment != nil && builder.PropagateEnvironment == nil {
   310  			c.ExternalBuilders[builderIndex].PropagateEnvironment = builder.Environment
   311  		}
   312  	}
   313  
   314  	c.OperationsListenAddress = viper.GetString("operations.listenAddress")
   315  	c.OperationsTLSEnabled = viper.GetBool("operations.tls.enabled")
   316  	c.OperationsTLSCertFile = config.GetPath("operations.tls.cert.file")
   317  	c.OperationsTLSKeyFile = config.GetPath("operations.tls.key.file")
   318  	c.OperationsTLSClientAuthRequired = viper.GetBool("operations.tls.clientAuthRequired")
   319  
   320  	for _, rca := range viper.GetStringSlice("operations.tls.clientRootCAs.files") {
   321  		c.OperationsTLSClientRootCAs = append(c.OperationsTLSClientRootCAs, config.TranslatePath(configDir, rca))
   322  	}
   323  
   324  	c.MetricsProvider = viper.GetString("metrics.provider")
   325  	c.StatsdNetwork = viper.GetString("metrics.statsd.network")
   326  	c.StatsdAaddress = viper.GetString("metrics.statsd.address")
   327  	c.StatsdWriteInterval = viper.GetDuration("metrics.statsd.writeInterval")
   328  	c.StatsdPrefix = viper.GetString("metrics.statsd.prefix")
   329  
   330  	c.DockerCert = config.GetPath("vm.docker.tls.cert.file")
   331  	c.DockerKey = config.GetPath("vm.docker.tls.key.file")
   332  	c.DockerCA = config.GetPath("vm.docker.tls.ca.file")
   333  
   334  	return nil
   335  }
   336  
   337  // getLocalAddress returns the address:port the local peer is operating on.  Affected by env:peer.addressAutoDetect
   338  func getLocalAddress() (string, error) {
   339  	peerAddress := viper.GetString("peer.address")
   340  	if peerAddress == "" {
   341  		return "", fmt.Errorf("peer.address isn't set")
   342  	}
   343  	host, port, err := net.SplitHostPort(peerAddress)
   344  	if err != nil {
   345  		return "", errors.Errorf("peer.address isn't in host:port format: %s", peerAddress)
   346  	}
   347  
   348  	localIP, err := getLocalIP()
   349  	if err != nil {
   350  		peerLogger.Errorf("local IP address not auto-detectable: %s", err)
   351  		return "", err
   352  	}
   353  	autoDetectedIPAndPort := net.JoinHostPort(localIP, port)
   354  	peerLogger.Info("Auto-detected peer address:", autoDetectedIPAndPort)
   355  	// If host is the IPv4 address "0.0.0.0" or the IPv6 address "::",
   356  	// then fallback to auto-detected address
   357  	if ip := net.ParseIP(host); ip != nil && ip.IsUnspecified() {
   358  		peerLogger.Info("Host is", host, ", falling back to auto-detected address:", autoDetectedIPAndPort)
   359  		return autoDetectedIPAndPort, nil
   360  	}
   361  
   362  	if viper.GetBool("peer.addressAutoDetect") {
   363  		peerLogger.Info("Auto-detect flag is set, returning", autoDetectedIPAndPort)
   364  		return autoDetectedIPAndPort, nil
   365  	}
   366  	peerLogger.Info("Returning", peerAddress)
   367  	return peerAddress, nil
   368  }
   369  
   370  // getLocalIP returns the a loopback local IP of the host.
   371  func getLocalIP() (string, error) {
   372  	addrs, err := net.InterfaceAddrs()
   373  	if err != nil {
   374  		return "", err
   375  	}
   376  	for _, address := range addrs {
   377  		// check the address type and if it is not a loopback then display it
   378  		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
   379  			if ipnet.IP.To4() != nil {
   380  				return ipnet.IP.String(), nil
   381  			}
   382  		}
   383  	}
   384  	return "", errors.Errorf("no non-loopback, IPv4 interface detected")
   385  }
   386  
   387  // GetServerConfig returns the gRPC server configuration for the peer
   388  func GetServerConfig() (comm.ServerConfig, error) {
   389  	serverConfig := comm.ServerConfig{
   390  		ConnectionTimeout: viper.GetDuration("peer.connectiontimeout"),
   391  		SecOpts: comm.SecureOptions{
   392  			UseTLS: viper.GetBool("peer.tls.enabled"),
   393  		},
   394  	}
   395  	if serverConfig.SecOpts.UseTLS {
   396  		// get the certs from the file system
   397  		serverKey, err := ioutil.ReadFile(config.GetPath("peer.tls.key.file"))
   398  		if err != nil {
   399  			return serverConfig, fmt.Errorf("error loading TLS key (%s)", err)
   400  		}
   401  		serverCert, err := ioutil.ReadFile(config.GetPath("peer.tls.cert.file"))
   402  		if err != nil {
   403  			return serverConfig, fmt.Errorf("error loading TLS certificate (%s)", err)
   404  		}
   405  		serverConfig.SecOpts.Certificate = serverCert
   406  		serverConfig.SecOpts.Key = serverKey
   407  		serverConfig.SecOpts.RequireClientCert = viper.GetBool("peer.tls.clientAuthRequired")
   408  		if serverConfig.SecOpts.RequireClientCert {
   409  			var clientRoots [][]byte
   410  			for _, file := range viper.GetStringSlice("peer.tls.clientRootCAs.files") {
   411  				clientRoot, err := ioutil.ReadFile(
   412  					config.TranslatePath(filepath.Dir(viper.ConfigFileUsed()), file))
   413  				if err != nil {
   414  					return serverConfig,
   415  						fmt.Errorf("error loading client root CAs (%s)", err)
   416  				}
   417  				clientRoots = append(clientRoots, clientRoot)
   418  			}
   419  			serverConfig.SecOpts.ClientRootCAs = clientRoots
   420  		}
   421  		// check for root cert
   422  		if config.GetPath("peer.tls.rootcert.file") != "" {
   423  			rootCert, err := ioutil.ReadFile(config.GetPath("peer.tls.rootcert.file"))
   424  			if err != nil {
   425  				return serverConfig, fmt.Errorf("error loading TLS root certificate (%s)", err)
   426  			}
   427  			serverConfig.SecOpts.ServerRootCAs = [][]byte{rootCert}
   428  		}
   429  	}
   430  	// get the default keepalive options
   431  	serverConfig.KaOpts = comm.DefaultKeepaliveOptions
   432  	// check to see if interval is set for the env
   433  	if viper.IsSet("peer.keepalive.interval") {
   434  		serverConfig.KaOpts.ServerInterval = viper.GetDuration("peer.keepalive.interval")
   435  	}
   436  	// check to see if timeout is set for the env
   437  	if viper.IsSet("peer.keepalive.timeout") {
   438  		serverConfig.KaOpts.ServerTimeout = viper.GetDuration("peer.keepalive.timeout")
   439  	}
   440  	// check to see if minInterval is set for the env
   441  	if viper.IsSet("peer.keepalive.minInterval") {
   442  		serverConfig.KaOpts.ServerMinInterval = viper.GetDuration("peer.keepalive.minInterval")
   443  	}
   444  
   445  	serverConfig.MaxRecvMsgSize = comm.DefaultMaxRecvMsgSize
   446  	serverConfig.MaxSendMsgSize = comm.DefaultMaxSendMsgSize
   447  
   448  	if viper.IsSet("peer.maxRecvMsgSize") {
   449  		serverConfig.MaxRecvMsgSize = int(viper.GetInt32("peer.maxRecvMsgSize"))
   450  	}
   451  	if viper.IsSet("peer.maxSendMsgSize") {
   452  		serverConfig.MaxSendMsgSize = int(viper.GetInt32("peer.maxSendMsgSize"))
   453  	}
   454  	return serverConfig, nil
   455  }
   456  
   457  // GetClientCertificate returns the TLS certificate to use for gRPC client
   458  // connections
   459  func GetClientCertificate() (tls.Certificate, error) {
   460  	cert := tls.Certificate{}
   461  
   462  	keyPath := viper.GetString("peer.tls.clientKey.file")
   463  	certPath := viper.GetString("peer.tls.clientCert.file")
   464  
   465  	if keyPath != "" || certPath != "" {
   466  		// need both keyPath and certPath to be set
   467  		if keyPath == "" || certPath == "" {
   468  			return cert, errors.New("peer.tls.clientKey.file and " +
   469  				"peer.tls.clientCert.file must both be set or must both be empty")
   470  		}
   471  		keyPath = config.GetPath("peer.tls.clientKey.file")
   472  		certPath = config.GetPath("peer.tls.clientCert.file")
   473  
   474  	} else {
   475  		// use the TLS server keypair
   476  		keyPath = viper.GetString("peer.tls.key.file")
   477  		certPath = viper.GetString("peer.tls.cert.file")
   478  
   479  		if keyPath != "" || certPath != "" {
   480  			// need both keyPath and certPath to be set
   481  			if keyPath == "" || certPath == "" {
   482  				return cert, errors.New("peer.tls.key.file and " +
   483  					"peer.tls.cert.file must both be set or must both be empty")
   484  			}
   485  			keyPath = config.GetPath("peer.tls.key.file")
   486  			certPath = config.GetPath("peer.tls.cert.file")
   487  		} else {
   488  			return cert, errors.New("must set either " +
   489  				"[peer.tls.key.file and peer.tls.cert.file] or " +
   490  				"[peer.tls.clientKey.file and peer.tls.clientCert.file]" +
   491  				"when peer.tls.clientAuthEnabled is set to true")
   492  		}
   493  	}
   494  	// get the keypair from the file system
   495  	clientKey, err := ioutil.ReadFile(keyPath)
   496  	if err != nil {
   497  		return cert, errors.WithMessage(err,
   498  			"error loading client TLS key")
   499  	}
   500  	clientCert, err := ioutil.ReadFile(certPath)
   501  	if err != nil {
   502  		return cert, errors.WithMessage(err,
   503  			"error loading client TLS certificate")
   504  	}
   505  	cert, err = tls.X509KeyPair(clientCert, clientKey)
   506  	if err != nil {
   507  		return cert, errors.WithMessage(err,
   508  			"error parsing client TLS key pair")
   509  	}
   510  	return cert, nil
   511  }