github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/etcd/etcd.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package etcd
    19  
    20  import (
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/minio/minio/internal/config"
    27  	"github.com/minio/pkg/v2/env"
    28  	xnet "github.com/minio/pkg/v2/net"
    29  	clientv3 "go.etcd.io/etcd/client/v3"
    30  	"go.etcd.io/etcd/client/v3/namespace"
    31  	"go.uber.org/zap"
    32  )
    33  
    34  const (
    35  	// Default values used while communicating with etcd.
    36  	defaultDialTimeout   = 5 * time.Second
    37  	defaultDialKeepAlive = 30 * time.Second
    38  )
    39  
    40  // etcd environment values
    41  const (
    42  	Endpoints     = "endpoints"
    43  	PathPrefix    = "path_prefix"
    44  	CoreDNSPath   = "coredns_path"
    45  	ClientCert    = "client_cert"
    46  	ClientCertKey = "client_cert_key"
    47  
    48  	EnvEtcdEndpoints     = "MINIO_ETCD_ENDPOINTS"
    49  	EnvEtcdPathPrefix    = "MINIO_ETCD_PATH_PREFIX"
    50  	EnvEtcdCoreDNSPath   = "MINIO_ETCD_COREDNS_PATH"
    51  	EnvEtcdClientCert    = "MINIO_ETCD_CLIENT_CERT"
    52  	EnvEtcdClientCertKey = "MINIO_ETCD_CLIENT_CERT_KEY"
    53  )
    54  
    55  // DefaultKVS - default KV settings for etcd.
    56  var (
    57  	DefaultKVS = config.KVS{
    58  		config.KV{
    59  			Key:   Endpoints,
    60  			Value: "",
    61  		},
    62  		config.KV{
    63  			Key:   PathPrefix,
    64  			Value: "",
    65  		},
    66  		config.KV{
    67  			Key:   CoreDNSPath,
    68  			Value: "/skydns",
    69  		},
    70  		config.KV{
    71  			Key:   ClientCert,
    72  			Value: "",
    73  		},
    74  		config.KV{
    75  			Key:   ClientCertKey,
    76  			Value: "",
    77  		},
    78  	}
    79  )
    80  
    81  // Config - server etcd config.
    82  type Config struct {
    83  	Enabled     bool   `json:"enabled"`
    84  	PathPrefix  string `json:"pathPrefix"`
    85  	CoreDNSPath string `json:"coreDNSPath"`
    86  	clientv3.Config
    87  }
    88  
    89  // New - initialize new etcd client.
    90  func New(cfg Config) (*clientv3.Client, error) {
    91  	if !cfg.Enabled {
    92  		return nil, nil
    93  	}
    94  	cli, err := clientv3.New(cfg.Config)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	cli.KV = namespace.NewKV(cli.KV, cfg.PathPrefix)
    99  	cli.Watcher = namespace.NewWatcher(cli.Watcher, cfg.PathPrefix)
   100  	cli.Lease = namespace.NewLease(cli.Lease, cfg.PathPrefix)
   101  	return cli, nil
   102  }
   103  
   104  func parseEndpoints(endpoints string) ([]string, bool, error) {
   105  	etcdEndpoints := strings.Split(endpoints, config.ValueSeparator)
   106  
   107  	var etcdSecure bool
   108  	for _, endpoint := range etcdEndpoints {
   109  		u, err := xnet.ParseHTTPURL(endpoint)
   110  		if err != nil {
   111  			return nil, false, err
   112  		}
   113  		if etcdSecure && u.Scheme == "http" {
   114  			return nil, false, config.Errorf("all endpoints should be https or http: %s", endpoint)
   115  		}
   116  		// If one of the endpoint is https, we will use https directly.
   117  		etcdSecure = etcdSecure || u.Scheme == "https"
   118  	}
   119  
   120  	return etcdEndpoints, etcdSecure, nil
   121  }
   122  
   123  // Enabled returns if etcd is enabled.
   124  func Enabled(kvs config.KVS) bool {
   125  	endpoints := kvs.Get(Endpoints)
   126  	return endpoints != ""
   127  }
   128  
   129  // LookupConfig - Initialize new etcd config.
   130  func LookupConfig(kvs config.KVS, rootCAs *x509.CertPool) (Config, error) {
   131  	cfg := Config{}
   132  	if err := config.CheckValidKeys(config.EtcdSubSys, kvs, DefaultKVS); err != nil {
   133  		return cfg, err
   134  	}
   135  
   136  	endpoints := env.Get(EnvEtcdEndpoints, kvs.Get(Endpoints))
   137  	if endpoints == "" {
   138  		return cfg, nil
   139  	}
   140  
   141  	etcdEndpoints, etcdSecure, err := parseEndpoints(endpoints)
   142  	if err != nil {
   143  		return cfg, err
   144  	}
   145  
   146  	cfg.Enabled = true
   147  	cfg.DialTimeout = defaultDialTimeout
   148  	cfg.DialKeepAliveTime = defaultDialKeepAlive
   149  	// Disable etcd client SDK logging, etcd client
   150  	// incorrectly starts logging in unexpected data
   151  	// format.
   152  	cfg.LogConfig = &zap.Config{
   153  		Level:    zap.NewAtomicLevelAt(zap.FatalLevel),
   154  		Encoding: "console",
   155  	}
   156  	cfg.Endpoints = etcdEndpoints
   157  	cfg.CoreDNSPath = env.Get(EnvEtcdCoreDNSPath, kvs.Get(CoreDNSPath))
   158  	// Default path prefix for all keys on etcd, other than CoreDNSPath.
   159  	cfg.PathPrefix = env.Get(EnvEtcdPathPrefix, kvs.Get(PathPrefix))
   160  	if etcdSecure {
   161  		cfg.TLS = &tls.Config{
   162  			RootCAs: rootCAs,
   163  		}
   164  		// This is only to support client side certificate authentication
   165  		// https://coreos.com/etcd/docs/latest/op-guide/security.html
   166  		etcdClientCertFile := env.Get(EnvEtcdClientCert, kvs.Get(ClientCert))
   167  		etcdClientCertKey := env.Get(EnvEtcdClientCertKey, kvs.Get(ClientCertKey))
   168  		if etcdClientCertFile != "" && etcdClientCertKey != "" {
   169  			cfg.TLS.GetClientCertificate = func(unused *tls.CertificateRequestInfo) (*tls.Certificate, error) {
   170  				cert, err := tls.LoadX509KeyPair(etcdClientCertFile, etcdClientCertKey)
   171  				return &cert, err
   172  			}
   173  		}
   174  	}
   175  	return cfg, nil
   176  }