512 lines
20 KiB
Go
512 lines
20 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// The 'viper' package for configuration handling is very flexible, but has
|
|
// been found to have extremely poor performance when configuration values are
|
|
// accessed repeatedly. The function CacheConfiguration() defined here caches
|
|
// all configuration values that are accessed frequently. These parameters
|
|
// are now presented as function calls that access local configuration
|
|
// variables. This seems to be the most robust way to represent these
|
|
// parameters in the face of the numerous ways that configuration files are
|
|
// loaded and used (e.g, normal usage vs. test cases).
|
|
|
|
// The CacheConfiguration() function is allowed to be called globally to
|
|
// ensure that the correct values are always cached; See for example how
|
|
// certain parameters are forced in 'ChaincodeDevMode' in main.go.
|
|
|
|
package peer
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/hyperledger/fabric/common/viperutil"
|
|
"github.com/hyperledger/fabric/core/config"
|
|
"github.com/hyperledger/fabric/internal/pkg/comm"
|
|
gatewayconfig "github.com/hyperledger/fabric/internal/pkg/gateway/config"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// ExternalBuilder represents the configuration structure of
|
|
// a chaincode external builder
|
|
type ExternalBuilder struct {
|
|
// TODO: Remove Environment in 3.0
|
|
// Deprecated: Environment is retained for backwards compatibility.
|
|
// New deployments should use the new PropagateEnvironment field
|
|
Environment []string `yaml:"environmentWhitelist"`
|
|
PropagateEnvironment []string `yaml:"propagateEnvironment"`
|
|
Name string `yaml:"name"`
|
|
Path string `yaml:"path"`
|
|
}
|
|
|
|
// Config is the struct that defines the Peer configurations.
|
|
type Config struct {
|
|
// LocalMSPID is the identifier of the local MSP.
|
|
LocalMSPID string
|
|
// ListenAddress is the local address the peer will listen on. It must be
|
|
// formatted as [host | ipaddr]:port.
|
|
ListenAddress string
|
|
// PeerID provides a name for this peer instance. It is used when naming
|
|
// docker resources to segregate fabric networks and peers.
|
|
PeerID string
|
|
// PeerAddress is the address other peers and clients should use to
|
|
// communicate with the peer. It must be formatted as [host | ipaddr]:port.
|
|
// When used by the CLI, it represents the target peer endpoint.
|
|
PeerAddress string
|
|
// NetworkID specifies a name to use for logical separation of networks. It
|
|
// is used when naming docker resources to segregate fabric networks and
|
|
// peers.
|
|
NetworkID string
|
|
// ChaincodeListenAddress is the endpoint on which this peer will listen for
|
|
// chaincode connections. If omitted, it defaults to the host portion of
|
|
// PeerAddress and port 7052.
|
|
ChaincodeListenAddress string
|
|
// ChaincodeAddress specifies the endpoint chaincode launched by the peer
|
|
// should use to connect to the peer. If omitted, it defaults to
|
|
// ChaincodeListenAddress and falls back to ListenAddress.
|
|
ChaincodeAddress string
|
|
// ValidatorPoolSize indicates the number of goroutines that will execute
|
|
// transaction validation in parallel. If omitted, it defaults to number of
|
|
// hardware threads on the machine.
|
|
ValidatorPoolSize int
|
|
|
|
// ----- Peer Delivery Client Keepalive -----
|
|
// DeliveryClient Keepalive settings for communication with ordering nodes.
|
|
DeliverClientKeepaliveOptions comm.KeepaliveOptions
|
|
|
|
// ----- Profile -----
|
|
// TODO: create separate sub-struct for Profile config.
|
|
|
|
// ProfileEnabled determines if the go pprof endpoint is enabled in the peer.
|
|
ProfileEnabled bool
|
|
// ProfileListenAddress is the address the pprof server should accept
|
|
// connections on.
|
|
ProfileListenAddress string
|
|
|
|
// ----- Discovery -----
|
|
|
|
// The discovery service is used by clients to query information about peers,
|
|
// such as - which peers have joined a certain channel, what is the latest
|
|
// channel config, and most importantly - given a chaincode and a channel, what
|
|
// possible sets of peers satisfy the endorsement policy.
|
|
// TODO: create separate sub-struct for Discovery config.
|
|
|
|
// DiscoveryEnabled is used to enable the discovery service.
|
|
DiscoveryEnabled bool
|
|
// DiscoveryOrgMembersAllowed allows non-admins to perform non channel-scoped queries.
|
|
DiscoveryOrgMembersAllowed bool
|
|
// DiscoveryAuthCacheEnabled is used to enable the authentication cache.
|
|
DiscoveryAuthCacheEnabled bool
|
|
// DiscoveryAuthCacheMaxSize sets the maximum size of authentication cache.
|
|
DiscoveryAuthCacheMaxSize int
|
|
// DiscoveryAuthCachePurgeRetentionRatio set the proportion of entries remains in cache
|
|
// after overpopulation purge.
|
|
DiscoveryAuthCachePurgeRetentionRatio float64
|
|
|
|
// ----- Limits -----
|
|
// Limits is used to configure some internal resource limits.
|
|
// TODO: create separate sub-struct for Limits config.
|
|
|
|
// LimitsConcurrencyEndorserService sets the limits for concurrent requests sent to
|
|
// endorser service that handles chaincode deployment, query and invocation,
|
|
// including both user chaincodes and system chaincodes.
|
|
LimitsConcurrencyEndorserService int
|
|
|
|
// LimitsConcurrencyDeliverService sets the limits for concurrent event listeners
|
|
// registered to deliver service for blocks and transaction events.
|
|
LimitsConcurrencyDeliverService int
|
|
|
|
// LimitsConcurrencyGatewayService sets the limits for concurrent requests to
|
|
// gateway service that handles the submission and evaluation of transactions.
|
|
LimitsConcurrencyGatewayService int
|
|
|
|
// ----- TLS -----
|
|
// Require server-side TLS.
|
|
// TODO: create separate sub-struct for PeerTLS config.
|
|
|
|
// PeerTLSEnabled enables/disables Peer TLS.
|
|
PeerTLSEnabled bool
|
|
|
|
// ----- Authentication -----
|
|
// Authentication contains configuration parameters related to authenticating
|
|
// client messages.
|
|
// TODO: create separate sub-struct for Authentication config.
|
|
|
|
// AuthenticationTimeWindow sets the acceptable time duration for current
|
|
// server time and client's time as specified in a client request message.
|
|
AuthenticationTimeWindow time.Duration
|
|
|
|
// Endpoint of the vm management system. For docker can be one of the following in general
|
|
// unix:///var/run/docker.sock
|
|
// http://localhost:2375
|
|
// https://localhost:2376
|
|
VMEndpoint string
|
|
|
|
// ----- vm.docker.tls -----
|
|
// TODO: create separate sub-struct for VM.Docker.TLS config.
|
|
|
|
// VMDockerTLSEnabled enables/disables TLS for dockers.
|
|
VMDockerTLSEnabled bool
|
|
VMDockerAttachStdout bool
|
|
// VMNetworkMode sets the networking mode for the container.
|
|
VMNetworkMode string
|
|
|
|
// ChaincodePull enables/disables force pulling of the base docker image.
|
|
ChaincodePull bool
|
|
// ExternalBuilders represents the builders and launchers for
|
|
// chaincode. The external builder detection processing will iterate over the
|
|
// builders in the order specified below.
|
|
ExternalBuilders []ExternalBuilder
|
|
|
|
// ----- Operations config -----
|
|
// TODO: create separate sub-struct for Operations config.
|
|
|
|
// OperationsListenAddress provides the host and port for the operations server
|
|
OperationsListenAddress string
|
|
// OperationsTLSEnabled enables/disables TLS for operations.
|
|
OperationsTLSEnabled bool
|
|
// OperationsTLSCertFile provides the path to PEM encoded server certificate for
|
|
// the operations server.
|
|
OperationsTLSCertFile string
|
|
// OperationsTLSKeyFile provides the path to PEM encoded server key for the
|
|
// operations server.
|
|
OperationsTLSKeyFile string
|
|
// OperationsTLSClientAuthRequired enables/disables the requirements for client
|
|
// certificate authentication at the TLS layer to access all resource.
|
|
OperationsTLSClientAuthRequired bool
|
|
// OperationsTLSClientRootCAs provides the path to PEM encoded ca certiricates to
|
|
// trust for client authentication.
|
|
OperationsTLSClientRootCAs []string
|
|
|
|
// ----- Metrics config -----
|
|
// TODO: create separate sub-struct for Metrics config.
|
|
|
|
// MetricsProvider provides the categories of metrics providers, which is one of
|
|
// statsd, prometheus, or disabled.
|
|
MetricsProvider string
|
|
// StatsdNetwork indicate the network type used by statsd metrics. (tcp or udp).
|
|
StatsdNetwork string
|
|
// StatsdAaddress provides the address for statsd server.
|
|
StatsdAaddress string
|
|
// StatsdWriteInterval set the time interval at which locally cached counters and
|
|
// gauges are pushed.
|
|
StatsdWriteInterval time.Duration
|
|
// StatsdPrefix provides the prefix that prepended to all emitted statsd metrics.
|
|
StatsdPrefix string
|
|
|
|
// ----- Docker config ------
|
|
|
|
// DockerCert is the path to the PEM encoded TLS client certificate required to access
|
|
// the docker daemon.
|
|
DockerCert string
|
|
// DockerKey is the path to the PEM encoded key required to access the docker daemon.
|
|
DockerKey string
|
|
// DockerCA is the path to the PEM encoded CA certificate for the docker daemon.
|
|
DockerCA string
|
|
|
|
// ----- Gateway config -----
|
|
|
|
// The gateway service is used by client SDKs to
|
|
// interact with fabric networks
|
|
|
|
GatewayOptions gatewayconfig.Options
|
|
}
|
|
|
|
// GlobalConfig obtains a set of configuration from viper, build and returns
|
|
// the config struct.
|
|
func GlobalConfig() (*Config, error) {
|
|
c := &Config{}
|
|
if err := c.load(); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Config) load() error {
|
|
peerAddress, err := getLocalAddress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
configDir := filepath.Dir(viper.ConfigFileUsed())
|
|
|
|
c.PeerAddress = peerAddress
|
|
c.PeerID = viper.GetString("peer.id")
|
|
c.LocalMSPID = viper.GetString("peer.localMspId")
|
|
c.ListenAddress = viper.GetString("peer.listenAddress")
|
|
|
|
c.AuthenticationTimeWindow = viper.GetDuration("peer.authentication.timewindow")
|
|
if c.AuthenticationTimeWindow == 0 {
|
|
defaultTimeWindow := 15 * time.Minute
|
|
logger.Warningf("`peer.authentication.timewindow` not set; defaulting to %s", defaultTimeWindow)
|
|
c.AuthenticationTimeWindow = defaultTimeWindow
|
|
}
|
|
|
|
c.PeerTLSEnabled = viper.GetBool("peer.tls.enabled")
|
|
c.NetworkID = viper.GetString("peer.networkId")
|
|
c.LimitsConcurrencyEndorserService = viper.GetInt("peer.limits.concurrency.endorserService")
|
|
c.LimitsConcurrencyDeliverService = viper.GetInt("peer.limits.concurrency.deliverService")
|
|
c.LimitsConcurrencyGatewayService = viper.GetInt("peer.limits.concurrency.gatewayService")
|
|
c.DiscoveryEnabled = viper.GetBool("peer.discovery.enabled")
|
|
c.ProfileEnabled = viper.GetBool("peer.profile.enabled")
|
|
c.ProfileListenAddress = viper.GetString("peer.profile.listenAddress")
|
|
c.DiscoveryOrgMembersAllowed = viper.GetBool("peer.discovery.orgMembersAllowedAccess")
|
|
c.DiscoveryAuthCacheEnabled = viper.GetBool("peer.discovery.authCacheEnabled")
|
|
c.DiscoveryAuthCacheMaxSize = viper.GetInt("peer.discovery.authCacheMaxSize")
|
|
c.DiscoveryAuthCachePurgeRetentionRatio = viper.GetFloat64("peer.discovery.authCachePurgeRetentionRatio")
|
|
c.ChaincodeListenAddress = viper.GetString("peer.chaincodeListenAddress")
|
|
c.ChaincodeAddress = viper.GetString("peer.chaincodeAddress")
|
|
|
|
c.ValidatorPoolSize = viper.GetInt("peer.validatorPoolSize")
|
|
if c.ValidatorPoolSize <= 0 {
|
|
c.ValidatorPoolSize = runtime.NumCPU()
|
|
}
|
|
|
|
c.DeliverClientKeepaliveOptions = comm.DefaultKeepaliveOptions
|
|
if viper.IsSet("peer.keepalive.deliveryClient.interval") {
|
|
c.DeliverClientKeepaliveOptions.ClientInterval = viper.GetDuration("peer.keepalive.deliveryClient.interval")
|
|
}
|
|
if viper.IsSet("peer.keepalive.deliveryClient.timeout") {
|
|
c.DeliverClientKeepaliveOptions.ClientTimeout = viper.GetDuration("peer.keepalive.deliveryClient.timeout")
|
|
}
|
|
|
|
c.GatewayOptions = gatewayconfig.GetOptions(viper.GetViper())
|
|
|
|
c.VMEndpoint = viper.GetString("vm.endpoint")
|
|
c.VMDockerTLSEnabled = viper.GetBool("vm.docker.tls.enabled")
|
|
c.VMDockerAttachStdout = viper.GetBool("vm.docker.attachStdout")
|
|
|
|
c.VMNetworkMode = viper.GetString("vm.docker.hostConfig.NetworkMode")
|
|
if c.VMNetworkMode == "" {
|
|
c.VMNetworkMode = "host"
|
|
}
|
|
|
|
c.ChaincodePull = viper.GetBool("chaincode.pull")
|
|
var externalBuilders []ExternalBuilder
|
|
|
|
err = viper.UnmarshalKey("chaincode.externalBuilders", &externalBuilders, viper.DecodeHook(viperutil.YamlStringToStructHook(externalBuilders)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.ExternalBuilders = externalBuilders
|
|
for builderIndex, builder := range c.ExternalBuilders {
|
|
if builder.Path == "" {
|
|
return fmt.Errorf("invalid external builder configuration, path attribute missing in one or more builders")
|
|
}
|
|
if builder.Name == "" {
|
|
return fmt.Errorf("external builder at path %s has no name attribute", builder.Path)
|
|
}
|
|
if builder.Environment != nil && len(builder.PropagateEnvironment) == 0 {
|
|
c.ExternalBuilders[builderIndex].PropagateEnvironment = builder.Environment
|
|
}
|
|
}
|
|
|
|
c.OperationsListenAddress = viper.GetString("operations.listenAddress")
|
|
c.OperationsTLSEnabled = viper.GetBool("operations.tls.enabled")
|
|
c.OperationsTLSCertFile = config.GetPath("operations.tls.cert.file")
|
|
c.OperationsTLSKeyFile = config.GetPath("operations.tls.key.file")
|
|
c.OperationsTLSClientAuthRequired = viper.GetBool("operations.tls.clientAuthRequired")
|
|
|
|
for _, rca := range viper.GetStringSlice("operations.tls.clientRootCAs.files") {
|
|
c.OperationsTLSClientRootCAs = append(c.OperationsTLSClientRootCAs, config.TranslatePath(configDir, rca))
|
|
}
|
|
|
|
c.MetricsProvider = viper.GetString("metrics.provider")
|
|
c.StatsdNetwork = viper.GetString("metrics.statsd.network")
|
|
c.StatsdAaddress = viper.GetString("metrics.statsd.address")
|
|
c.StatsdWriteInterval = viper.GetDuration("metrics.statsd.writeInterval")
|
|
c.StatsdPrefix = viper.GetString("metrics.statsd.prefix")
|
|
|
|
c.DockerCert = config.GetPath("vm.docker.tls.cert.file")
|
|
c.DockerKey = config.GetPath("vm.docker.tls.key.file")
|
|
c.DockerCA = config.GetPath("vm.docker.tls.ca.file")
|
|
|
|
return nil
|
|
}
|
|
|
|
// getLocalAddress returns the address:port the local peer is operating on. Affected by env:peer.addressAutoDetect
|
|
func getLocalAddress() (string, error) {
|
|
peerAddress := viper.GetString("peer.address")
|
|
if peerAddress == "" {
|
|
return "", fmt.Errorf("peer.address isn't set")
|
|
}
|
|
host, port, err := net.SplitHostPort(peerAddress)
|
|
if err != nil {
|
|
return "", errors.Errorf("peer.address isn't in host:port format: %s", peerAddress)
|
|
}
|
|
|
|
localIP, err := getLocalIP()
|
|
if err != nil {
|
|
peerLogger.Errorf("local IP address not auto-detectable: %s", err)
|
|
return "", err
|
|
}
|
|
autoDetectedIPAndPort := net.JoinHostPort(localIP, port)
|
|
peerLogger.Info("Auto-detected peer address:", autoDetectedIPAndPort)
|
|
// If host is the IPv4 address "0.0.0.0" or the IPv6 address "::",
|
|
// then fallback to auto-detected address
|
|
if ip := net.ParseIP(host); ip != nil && ip.IsUnspecified() {
|
|
peerLogger.Info("Host is", host, ", falling back to auto-detected address:", autoDetectedIPAndPort)
|
|
return autoDetectedIPAndPort, nil
|
|
}
|
|
|
|
if viper.GetBool("peer.addressAutoDetect") {
|
|
peerLogger.Info("Auto-detect flag is set, returning", autoDetectedIPAndPort)
|
|
return autoDetectedIPAndPort, nil
|
|
}
|
|
peerLogger.Info("Returning", peerAddress)
|
|
return peerAddress, nil
|
|
}
|
|
|
|
// getLocalIP returns the a loopback local IP of the host.
|
|
func getLocalIP() (string, error) {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, address := range addrs {
|
|
// check the address type and if it is not a loopback then display it
|
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
if ipnet.IP.To4() != nil {
|
|
return ipnet.IP.String(), nil
|
|
}
|
|
}
|
|
}
|
|
return "", errors.Errorf("no non-loopback, IPv4 interface detected")
|
|
}
|
|
|
|
// GetServerConfig returns the gRPC server configuration for the peer
|
|
func GetServerConfig() (comm.ServerConfig, error) {
|
|
serverConfig := comm.ServerConfig{
|
|
ConnectionTimeout: viper.GetDuration("peer.connectiontimeout"),
|
|
SecOpts: comm.SecureOptions{
|
|
UseTLS: viper.GetBool("peer.tls.enabled"),
|
|
},
|
|
}
|
|
if serverConfig.SecOpts.UseTLS {
|
|
// get the certs from the file system
|
|
serverKey, err := ioutil.ReadFile(config.GetPath("peer.tls.key.file"))
|
|
if err != nil {
|
|
return serverConfig, fmt.Errorf("error loading TLS key (%s)", err)
|
|
}
|
|
serverCert, err := ioutil.ReadFile(config.GetPath("peer.tls.cert.file"))
|
|
if err != nil {
|
|
return serverConfig, fmt.Errorf("error loading TLS certificate (%s)", err)
|
|
}
|
|
serverConfig.SecOpts.Certificate = serverCert
|
|
serverConfig.SecOpts.Key = serverKey
|
|
serverConfig.SecOpts.RequireClientCert = viper.GetBool("peer.tls.clientAuthRequired")
|
|
if serverConfig.SecOpts.RequireClientCert {
|
|
var clientRoots [][]byte
|
|
for _, file := range viper.GetStringSlice("peer.tls.clientRootCAs.files") {
|
|
clientRoot, err := ioutil.ReadFile(
|
|
config.TranslatePath(filepath.Dir(viper.ConfigFileUsed()), file))
|
|
if err != nil {
|
|
return serverConfig,
|
|
fmt.Errorf("error loading client root CAs (%s)", err)
|
|
}
|
|
clientRoots = append(clientRoots, clientRoot)
|
|
}
|
|
serverConfig.SecOpts.ClientRootCAs = clientRoots
|
|
}
|
|
// check for root cert
|
|
if config.GetPath("peer.tls.rootcert.file") != "" {
|
|
rootCert, err := ioutil.ReadFile(config.GetPath("peer.tls.rootcert.file"))
|
|
if err != nil {
|
|
return serverConfig, fmt.Errorf("error loading TLS root certificate (%s)", err)
|
|
}
|
|
serverConfig.SecOpts.ServerRootCAs = [][]byte{rootCert}
|
|
}
|
|
}
|
|
// get the default keepalive options
|
|
serverConfig.KaOpts = comm.DefaultKeepaliveOptions
|
|
// check to see if interval is set for the env
|
|
if viper.IsSet("peer.keepalive.interval") {
|
|
serverConfig.KaOpts.ServerInterval = viper.GetDuration("peer.keepalive.interval")
|
|
}
|
|
// check to see if timeout is set for the env
|
|
if viper.IsSet("peer.keepalive.timeout") {
|
|
serverConfig.KaOpts.ServerTimeout = viper.GetDuration("peer.keepalive.timeout")
|
|
}
|
|
// check to see if minInterval is set for the env
|
|
if viper.IsSet("peer.keepalive.minInterval") {
|
|
serverConfig.KaOpts.ServerMinInterval = viper.GetDuration("peer.keepalive.minInterval")
|
|
}
|
|
|
|
serverConfig.MaxRecvMsgSize = comm.DefaultMaxRecvMsgSize
|
|
serverConfig.MaxSendMsgSize = comm.DefaultMaxSendMsgSize
|
|
|
|
if viper.IsSet("peer.maxRecvMsgSize") {
|
|
serverConfig.MaxRecvMsgSize = int(viper.GetInt32("peer.maxRecvMsgSize"))
|
|
}
|
|
if viper.IsSet("peer.maxSendMsgSize") {
|
|
serverConfig.MaxSendMsgSize = int(viper.GetInt32("peer.maxSendMsgSize"))
|
|
}
|
|
return serverConfig, nil
|
|
}
|
|
|
|
// GetClientCertificate returns the TLS certificate to use for gRPC client
|
|
// connections
|
|
func GetClientCertificate() (tls.Certificate, error) {
|
|
cert := tls.Certificate{}
|
|
|
|
keyPath := viper.GetString("peer.tls.clientKey.file")
|
|
certPath := viper.GetString("peer.tls.clientCert.file")
|
|
|
|
if keyPath != "" || certPath != "" {
|
|
// need both keyPath and certPath to be set
|
|
if keyPath == "" || certPath == "" {
|
|
return cert, errors.New("peer.tls.clientKey.file and " +
|
|
"peer.tls.clientCert.file must both be set or must both be empty")
|
|
}
|
|
keyPath = config.GetPath("peer.tls.clientKey.file")
|
|
certPath = config.GetPath("peer.tls.clientCert.file")
|
|
|
|
} else {
|
|
// use the TLS server keypair
|
|
keyPath = viper.GetString("peer.tls.key.file")
|
|
certPath = viper.GetString("peer.tls.cert.file")
|
|
|
|
if keyPath != "" || certPath != "" {
|
|
// need both keyPath and certPath to be set
|
|
if keyPath == "" || certPath == "" {
|
|
return cert, errors.New("peer.tls.key.file and " +
|
|
"peer.tls.cert.file must both be set or must both be empty")
|
|
}
|
|
keyPath = config.GetPath("peer.tls.key.file")
|
|
certPath = config.GetPath("peer.tls.cert.file")
|
|
} else {
|
|
return cert, errors.New("must set either " +
|
|
"[peer.tls.key.file and peer.tls.cert.file] or " +
|
|
"[peer.tls.clientKey.file and peer.tls.clientCert.file]" +
|
|
"when peer.tls.clientAuthEnabled is set to true")
|
|
}
|
|
}
|
|
// get the keypair from the file system
|
|
clientKey, err := ioutil.ReadFile(keyPath)
|
|
if err != nil {
|
|
return cert, errors.WithMessage(err,
|
|
"error loading client TLS key")
|
|
}
|
|
clientCert, err := ioutil.ReadFile(certPath)
|
|
if err != nil {
|
|
return cert, errors.WithMessage(err,
|
|
"error loading client TLS certificate")
|
|
}
|
|
cert, err = tls.X509KeyPair(clientCert, clientKey)
|
|
if err != nil {
|
|
return cert, errors.WithMessage(err,
|
|
"error parsing client TLS key pair")
|
|
}
|
|
return cert, nil
|
|
}
|