forked from kelseyhightower/confd
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request kelseyhightower#383 from odedlaz/vault
Added support for Vault backend
- Loading branch information
Showing
189 changed files
with
17,295 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/bin/bash | ||
set -e | ||
ROOT_TOKEN=$(vault read -field id auth/token/lookup-self) | ||
|
||
vault mount -path database generic | ||
vault mount -path key generic | ||
vault mount -path upstream generic | ||
|
||
vault write key value=foobar | ||
vault write database/host value=127.0.0.1 | ||
vault write database/port value=3306 | ||
vault write database/username value=confd | ||
vault write database/password value=p@sSw0rd | ||
vault write upstream app1=10.0.1.10:8080 app2=10.0.1.11:8080 | ||
|
||
|
||
# Run confd | ||
confd --onetime --log-level debug \ | ||
--confdir ./integration/confdir \ | ||
--backend vault \ | ||
--auth-type token \ | ||
--auth-token $ROOT_TOKEN \ | ||
--node http://127.0.0.1:8200 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
209 changes: 209 additions & 0 deletions
209
src/github.com/kelseyhightower/confd/backends/vault/client.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
package vault | ||
|
||
import ( | ||
"crypto/tls" | ||
"crypto/x509" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"path" | ||
|
||
vaultapi "github.com/hashicorp/vault/api" | ||
"github.com/kelseyhightower/confd/log" | ||
) | ||
|
||
// Client is a wrapper around the vault client | ||
type Client struct { | ||
client *vaultapi.Client | ||
} | ||
|
||
// get a | ||
func getParameter(key string, parameters map[string]string) string { | ||
value := parameters[key] | ||
if value == "" { | ||
// panic if a configuration is missing | ||
panic(fmt.Sprintf("%s is missing from configuration", key)) | ||
} | ||
return value | ||
} | ||
|
||
// panicToError converts a panic to an error | ||
func panicToError(err *error) { | ||
if r := recover(); r != nil { | ||
switch t := r.(type) { | ||
case string: | ||
*err = errors.New(t) | ||
case error: | ||
*err = t | ||
default: // panic again if we don't know how to handle | ||
panic(r) | ||
} | ||
} | ||
} | ||
|
||
// authenticate with the remote client | ||
func authenticate(c *vaultapi.Client, authType string, params map[string]string) (err error) { | ||
var secret *vaultapi.Secret | ||
|
||
// handle panics gracefully by creating an error | ||
// this would happen when we get a parameter that is missing | ||
defer panicToError(&err) | ||
|
||
switch authType { | ||
case "app-id": | ||
secret, err = c.Logical().Write("/auth/app-id/login", map[string]interface{}{ | ||
"app_id": getParameter("app-id", params), | ||
"user_id": getParameter("user-id", params), | ||
}) | ||
case "github": | ||
secret, err = c.Logical().Write("/auth/github/login", map[string]interface{}{ | ||
"token": getParameter("token", params), | ||
}) | ||
case "token": | ||
c.SetToken(getParameter("token", params)) | ||
secret, err = c.Logical().Read("/auth/token/lookup-self") | ||
case "userpass": | ||
username, password := getParameter("username", params), getParameter("password", params) | ||
secret, err = c.Logical().Write(fmt.Sprintf("/auth/userpass/login/%s", username), map[string]interface{}{ | ||
"password": password, | ||
}) | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
// if the token has already been set | ||
if c.Token() != "" { | ||
return nil | ||
} | ||
|
||
log.Debug("client authenticated with auth backend: %s", authType) | ||
// the default place for a token is in the auth section | ||
// otherwise, the backend will set the token itself | ||
c.SetToken(secret.Auth.ClientToken) | ||
return nil | ||
} | ||
|
||
func getConfig(address, cert, key, caCert string) (*vaultapi.Config, error) { | ||
conf := vaultapi.DefaultConfig() | ||
conf.Address = address | ||
|
||
tlsConfig := &tls.Config{} | ||
if cert != "" && key != "" { | ||
clientCert, err := tls.LoadX509KeyPair(cert, key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tlsConfig.Certificates = []tls.Certificate{clientCert} | ||
tlsConfig.BuildNameToCertificate() | ||
} | ||
|
||
if caCert != "" { | ||
ca, err := ioutil.ReadFile(caCert) | ||
if err != nil { | ||
return nil, err | ||
} | ||
caCertPool := x509.NewCertPool() | ||
caCertPool.AppendCertsFromPEM(ca) | ||
tlsConfig.RootCAs = caCertPool | ||
} | ||
|
||
conf.HttpClient.Transport = &http.Transport{ | ||
TLSClientConfig: tlsConfig, | ||
} | ||
|
||
return conf, nil | ||
} | ||
|
||
// New returns an *vault.Client with a connection to named machines. | ||
// It returns an error if a connection to the cluster cannot be made. | ||
func New(address, authType string, params map[string]string) (*Client, error) { | ||
if authType == "" { | ||
return nil, errors.New("you have to set the auth type when using the vault backend") | ||
} | ||
log.Info("Vault authentication backend set to %s", authType) | ||
conf, err := getConfig(address, params["cert"], params["key"], params["caCert"]) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
c, err := vaultapi.NewClient(conf) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := authenticate(c, authType, params); err != nil { | ||
return nil, err | ||
} | ||
return &Client{c}, nil | ||
} | ||
|
||
// GetValues queries etcd for keys prefixed by prefix. | ||
func (c *Client) GetValues(keys []string) (map[string]string, error) { | ||
vars := make(map[string]string) | ||
for _, key := range keys { | ||
log.Debug("getting %s from vault", key) | ||
resp, err := c.client.Logical().Read(key) | ||
|
||
if err != nil { | ||
log.Debug("there was an error extracting %s", key) | ||
return nil, err | ||
} | ||
if resp == nil || resp.Data == nil { | ||
continue | ||
} | ||
|
||
// if the key has only one string value | ||
// treat it as a string and not a map of values | ||
if val, ok := isKV(resp.Data); ok { | ||
vars[key] = val | ||
} else { | ||
// save the json encoded response | ||
// and flatten it to allow usage of gets & getvs | ||
js, _ := json.Marshal(resp.Data) | ||
vars[key] = string(js) | ||
flatten(key, resp.Data, vars) | ||
} | ||
} | ||
return vars, nil | ||
} | ||
|
||
// isKV checks if a given map has only one key of type string | ||
// if so, returns the value of that key | ||
func isKV(data map[string]interface{}) (string, bool) { | ||
if len(data) == 1 { | ||
if value, ok := data["value"]; ok { | ||
if text, ok := value.(string); ok { | ||
return text, true | ||
} | ||
} | ||
} | ||
return "", false | ||
} | ||
|
||
// recursively walks on all the values of a specific key and set them in the variables map | ||
func flatten(key string, value interface{}, vars map[string]string) { | ||
switch value.(type) { | ||
case string: | ||
log.Debug("setting key %s to: %s", key, value) | ||
vars[key] = value.(string) | ||
case map[string]interface{}: | ||
inner := value.(map[string]interface{}) | ||
for innerKey, innerValue := range inner { | ||
innerKey = path.Join(key, "/", innerKey) | ||
flatten(innerKey, innerValue, vars) | ||
} | ||
default: // we don't know how to handle non string or maps of strings | ||
log.Warning("type of '%s' is not supported (%T)", key, value) | ||
} | ||
} | ||
|
||
// WatchPrefix - not implemented at the moment | ||
func (c *Client) WatchPrefix(prefix string, waitIndex uint64, stopChan chan bool) (uint64, error) { | ||
<-stopChan | ||
return 0, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.