github.com/mailgun/holster/v4@v4.20.0/etcdutil/config.go (about)

     1  package etcdutil
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/mailgun/holster/v4/errors"
    11  	"github.com/mailgun/holster/v4/setter"
    12  	etcd "go.etcd.io/etcd/client/v3"
    13  	"google.golang.org/grpc/grpclog"
    14  )
    15  
    16  const (
    17  	localEtcdEndpoint = "127.0.0.1:2379"
    18  )
    19  
    20  func init() {
    21  	// We check this here to avoid data race with GRPC go routines writing to the logger
    22  	if os.Getenv("ETCD3_DEBUG") != "" {
    23  		grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(os.Stderr, os.Stderr, os.Stderr, 4))
    24  	}
    25  }
    26  
    27  // NewClient creates a new etcd.Client with the specified config where blanks
    28  // are filled from environment variables by NewConfig.
    29  //
    30  // If the provided config is nil and no environment variables are set, it will
    31  // return a client connecting without TLS via localhost:2379.
    32  func NewClient(cfg *etcd.Config) (*etcd.Client, error) {
    33  	var err error
    34  	if cfg, err = NewConfig(cfg); err != nil {
    35  		return nil, errors.Wrap(err, "failed to build etcd config")
    36  	}
    37  
    38  	etcdClt, err := etcd.New(*cfg)
    39  	if err != nil {
    40  		return nil, errors.Wrap(err, "failed to create etcd client")
    41  	}
    42  	return etcdClt, nil
    43  }
    44  
    45  // NewConfig creates a new etcd.Config using environment variables. If an
    46  // existing config is passed, it will fill in missing configuration using
    47  // environment variables or defaults if they exists on the local system.
    48  //
    49  // If no environment variables are set, it will return a config set to
    50  // connect without TLS via localhost:2379.
    51  func NewConfig(cfg *etcd.Config) (*etcd.Config, error) {
    52  	var envEndpoint, tlsCertFile, tlsKeyFile, tlsCAFile string
    53  
    54  	setter.SetDefault(&cfg, &etcd.Config{})
    55  	setter.SetDefault(&cfg.Username, os.Getenv("ETCD3_USER"))
    56  	setter.SetDefault(&cfg.Password, os.Getenv("ETCD3_PASSWORD"))
    57  	setter.SetDefault(&tlsCertFile, os.Getenv("ETCD3_TLS_CERT"))
    58  	setter.SetDefault(&tlsKeyFile, os.Getenv("ETCD3_TLS_KEY"))
    59  	setter.SetDefault(&tlsCAFile, os.Getenv("ETCD3_CA"))
    60  
    61  	// Default to 5 second timeout, else connections hang indefinitely
    62  	setter.SetDefault(&cfg.DialTimeout, time.Second*5)
    63  	// Or if the user provided a timeout
    64  	if timeout := os.Getenv("ETCD3_DIAL_TIMEOUT"); timeout != "" {
    65  		duration, err := time.ParseDuration(timeout)
    66  		if err != nil {
    67  			return nil, errors.Errorf(
    68  				"ETCD3_DIAL_TIMEOUT='%s' is not a duration (1m|15s|24h): %s", timeout, err)
    69  		}
    70  		cfg.DialTimeout = duration
    71  	}
    72  
    73  	defaultCfg := &tls.Config{
    74  		MinVersion: tls.VersionTLS12,
    75  	}
    76  
    77  	// If the CA file was provided
    78  	if tlsCAFile != "" {
    79  		setter.SetDefault(&cfg.TLS, defaultCfg)
    80  
    81  		var certPool *x509.CertPool = nil
    82  		if pemBytes, err := os.ReadFile(tlsCAFile); err == nil {
    83  			certPool = x509.NewCertPool()
    84  			certPool.AppendCertsFromPEM(pemBytes)
    85  		} else {
    86  			return nil, errors.Errorf("while loading cert CA file '%s': %s", tlsCAFile, err)
    87  		}
    88  		setter.SetDefault(&cfg.TLS.RootCAs, certPool)
    89  		cfg.TLS.InsecureSkipVerify = false
    90  	}
    91  
    92  	// If the cert and key files are provided attempt to load them
    93  	if tlsCertFile != "" && tlsKeyFile != "" {
    94  		setter.SetDefault(&cfg.TLS, defaultCfg)
    95  		tlsCert, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile)
    96  		if err != nil {
    97  			return nil, errors.Errorf("while loading cert '%s' and key file '%s': %s",
    98  				tlsCertFile, tlsKeyFile, err)
    99  		}
   100  		setter.SetDefault(&cfg.TLS.Certificates, []tls.Certificate{tlsCert})
   101  	}
   102  
   103  	setter.SetDefault(&envEndpoint, os.Getenv("ETCD3_ENDPOINT"), localEtcdEndpoint)
   104  	setter.SetDefault(&cfg.Endpoints, strings.Split(envEndpoint, ","))
   105  
   106  	// If no other TLS config is provided this will force connecting with TLS,
   107  	// without cert verification
   108  	if os.Getenv("ETCD3_SKIP_VERIFY") != "" {
   109  		setter.SetDefault(&cfg.TLS, defaultCfg)
   110  		cfg.TLS.InsecureSkipVerify = true
   111  	}
   112  
   113  	// Enable TLS with no additional configuration
   114  	if os.Getenv("ETCD3_ENABLE_TLS") != "" {
   115  		setter.SetDefault(&cfg.TLS, defaultCfg)
   116  	}
   117  
   118  	return cfg, nil
   119  }