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 }