From cbc63cf4840e6e4e85860d2a413ab9a098c53098 Mon Sep 17 00:00:00 2001 From: Jonathan Sokolowski Date: Wed, 22 Mar 2017 20:14:23 +1100 Subject: [PATCH 1/2] etcd: Add discovery_srv option --- physical/etcd.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ physical/etcd2.go | 23 ++++------------------- physical/etcd3.go | 7 +++---- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/physical/etcd.go b/physical/etcd.go index 3a9b1d3f0f37..01a928d40733 100644 --- a/physical/etcd.go +++ b/physical/etcd.go @@ -3,7 +3,10 @@ package physical import ( "context" "errors" + "fmt" + "net/url" "os" + "strings" "github.com/coreos/etcd/client" "github.com/coreos/go-semver/semver" @@ -13,6 +16,7 @@ import ( var ( EtcdSyncConfigError = errors.New("client setup failed: unable to parse etcd sync field in config") EtcdSyncClusterError = errors.New("client setup failed: unable to sync etcd cluster") + EtcdMultipleBootstrapError = errors.New("client setup failed: multiple discovery or bootstrap flags specified, use either \"address\" or \"discovery_srv\"") EtcdAddressError = errors.New("client setup failed: address must be valid URL (ex. 'scheme://host:port')") EtcdSemaphoreKeysEmptyError = errors.New("lock queue is empty") EtcdLockHeldError = errors.New("lock already held") @@ -95,3 +99,47 @@ func getEtcdAPIVersion(c client.Client) (string, error) { return "3", nil } + +// Retrieves the config option in order of priority: +// 1. The named environment variable if it exist +// 2. The key in the config map +func getEtcdOption(conf map[string]string, confKey, envVar string) (string, bool) { + confVal, inConf := conf[confKey] + envVal, inEnv := os.LookupEnv(envVar) + if inEnv { + return envVal, true + } + return confVal, inConf +} + +func getEtcdEndpoints(conf map[string]string) ([]string, error) { + address, staticBootstrap := getEtcdOption(conf, "address", "ETCD_ADDR") + domain, useSrv := getEtcdOption(conf, "discovery_srv", "ETCD_DISCOVERY_SRV") + if useSrv && staticBootstrap { + return nil, EtcdMultipleBootstrapError + } + + if staticBootstrap { + endpoints := strings.Split(address, Etcd2MachineDelimiter) + // Verify that the machines are valid URLs + for _, e := range endpoints { + u, urlErr := url.Parse(e) + if urlErr != nil || u.Scheme == "" { + return nil, EtcdAddressError + } + } + return endpoints, nil + } + + if useSrv { + discoverer := client.NewSRVDiscover() + endpoints, err := discoverer.Discover(domain) + if err != nil { + return nil, fmt.Errorf("failed to discover etcd endpoints through SRV discovery: %v", err) + } + return endpoints, nil + } + + // Set a default endpoints list if no option was set + return []string{"http://127.0.0.1:2379"}, nil +} diff --git a/physical/etcd2.go b/physical/etcd2.go index 2076ca361370..4ef4b08c7913 100644 --- a/physical/etcd2.go +++ b/physical/etcd2.go @@ -4,7 +4,6 @@ import ( "context" "encoding/base64" "fmt" - "net/url" "os" "path/filepath" "strconv" @@ -118,23 +117,9 @@ func newEtcd2Backend(conf map[string]string, logger log.Logger) (Backend, error) } func newEtcdV2Client(conf map[string]string) (client.Client, error) { - // Set a default machines list and check for an overriding address value. - machines := "http://127.0.0.1:2379" - if address, ok := conf["address"]; ok { - machines = address - } - machinesEnv := os.Getenv("ETCD_ADDR") - if machinesEnv != "" { - machines = machinesEnv - } - machinesParsed := strings.Split(machines, Etcd2MachineDelimiter) - - // Verify that the machines are valid URLs - for _, machine := range machinesParsed { - u, urlErr := url.Parse(machine) - if urlErr != nil || u.Scheme == "" { - return nil, EtcdAddressError - } + endpoints, err := getEtcdEndpoints(conf) + if err != nil { + return nil, err } // Create a new client from the supplied address and attempt to sync with the @@ -160,7 +145,7 @@ func newEtcdV2Client(conf map[string]string) (client.Client, error) { } cfg := client.Config{ - Endpoints: machinesParsed, + Endpoints: endpoints, Transport: cTransport, } diff --git a/physical/etcd3.go b/physical/etcd3.go index 420d8028bcd9..94da9a5b0795 100644 --- a/physical/etcd3.go +++ b/physical/etcd3.go @@ -45,10 +45,9 @@ func newEtcd3Backend(conf map[string]string, logger log.Logger) (Backend, error) path = "/" + path } - // Set a default machines list and check for an overriding address value. - endpoints := []string{"http://127.0.0.1:2379"} - if address, ok := conf["address"]; ok { - endpoints = strings.Split(address, ",") + endpoints, err := getEtcdEndpoints(conf) + if err != nil { + return nil, err } cfg := clientv3.Config{ From 5a985b274fccf12290fa72e12c66b0c9c6dfea87 Mon Sep 17 00:00:00 2001 From: Jonathan Sokolowski Date: Wed, 22 Mar 2017 20:22:05 +1100 Subject: [PATCH 2/2] website: Update etcd docs to include discovery_srv option --- .../docs/configuration/storage/etcd.html.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/website/source/docs/configuration/storage/etcd.html.md b/website/source/docs/configuration/storage/etcd.html.md index fd136a5dad11..70895e3919da 100644 --- a/website/source/docs/configuration/storage/etcd.html.md +++ b/website/source/docs/configuration/storage/etcd.html.md @@ -36,6 +36,10 @@ storage "etcd" { Etcd instances as a comma-separated list. This can also be provided via the environment variable `ETCD_ADDR`. +- `discovery_srv` `(string: "example.com")` - Specifies the domain name to + query for SRV records describing cluster endpoints. This can also be provided + via the environment variable `ETCD_DISCOVERY_SRV`. + - `etcd_api` `(string: "")` – Specifies the version of the API to communicate with. By default, this is derived automatically. If the cluster version is 3.1+ and there has been no data written using the v2 API, the @@ -89,6 +93,18 @@ discussed in more detail in the [HA concepts page](/docs/concepts/ha.html). ## `etcd` Examples +### DNS Discovery of cluster members + +This example configures vault to discover the Etcd cluster members via SRV +records as outlined in the +[DNS Discovery protocol documentation][dns discovery]. + +```hcl +storage "etcd" { + discovery_srv = "example.com" +} +``` + ### Custom Authentication This example shows connecting to the Etcd cluster using a username and password. @@ -100,7 +116,7 @@ storage "etcd" { } ``` -### Custon Path +### Custom Path This example shows storing data in a custom path. @@ -122,3 +138,4 @@ storage "etcd" { ``` [etcd]: https://coreos.com/etcd "Etcd by CoreOS" +[dns discovery]: https://coreos.com/etcd/docs/latest/op-guide/clustering.html#dns-discovery "Etcd cluster DNS Discovery"