From 17cd70cea546e287713a3d4c086528a85abefa2e Mon Sep 17 00:00:00 2001 From: Niall Sheridan Date: Tue, 4 Oct 2016 14:37:01 -0700 Subject: Add support for Hashicorp Vault Vault is supported for the following: As a well-known filesystem for TLS cert, TLS key and SSH signing key. For configuration secrets for cookie_secret, csrf_secret, oauth_client_id and oauth_client_secret options. --- vendor/github.com/hashicorp/vault/api/client.go | 416 ++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 vendor/github.com/hashicorp/vault/api/client.go (limited to 'vendor/github.com/hashicorp/vault/api/client.go') diff --git a/vendor/github.com/hashicorp/vault/api/client.go b/vendor/github.com/hashicorp/vault/api/client.go new file mode 100644 index 0000000..4aee40c --- /dev/null +++ b/vendor/github.com/hashicorp/vault/api/client.go @@ -0,0 +1,416 @@ +package api + +import ( + "crypto/tls" + "fmt" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-rootcerts" + "github.com/sethgrid/pester" +) + +const EnvVaultAddress = "VAULT_ADDR" +const EnvVaultCACert = "VAULT_CACERT" +const EnvVaultCAPath = "VAULT_CAPATH" +const EnvVaultClientCert = "VAULT_CLIENT_CERT" +const EnvVaultClientKey = "VAULT_CLIENT_KEY" +const EnvVaultInsecure = "VAULT_SKIP_VERIFY" +const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME" +const EnvVaultWrapTTL = "VAULT_WRAP_TTL" +const EnvVaultMaxRetries = "VAULT_MAX_RETRIES" + +// WrappingLookupFunc is a function that, given an HTTP verb and a path, +// returns an optional string duration to be used for response wrapping (e.g. +// "15s", or simply "15"). The path will not begin with "/v1/" or "v1/" or "/", +// however, end-of-path forward slashes are not trimmed, so must match your +// called path precisely. +type WrappingLookupFunc func(operation, path string) string + +// Config is used to configure the creation of the client. +type Config struct { + // Address is the address of the Vault server. This should be a complete + // URL such as "http://vault.example.com". If you need a custom SSL + // cert or want to enable insecure mode, you need to specify a custom + // HttpClient. + Address string + + // HttpClient is the HTTP client to use, which will currently always have the + // same values as http.DefaultClient. This is used to control redirect behavior. + HttpClient *http.Client + + redirectSetup sync.Once + + // MaxRetries controls the maximum number of times to retry when a 5xx error + // occurs. Set to 0 or less to disable retrying. + MaxRetries int +} + +// TLSConfig contains the parameters needed to configure TLS on the HTTP client +// used to communicate with Vault. +type TLSConfig struct { + // CACert is the path to a PEM-encoded CA cert file to use to verify the + // Vault server SSL certificate. + CACert string + + // CAPath is the path to a directory of PEM-encoded CA cert files to verify + // the Vault server SSL certificate. + CAPath string + + // ClientCert is the path to the certificate for Vault communication + ClientCert string + + // ClientKey is the path to the private key for Vault communication + ClientKey string + + // TLSServerName, if set, is used to set the SNI host when connecting via + // TLS. + TLSServerName string + + // Insecure enables or disables SSL verification + Insecure bool +} + +// DefaultConfig returns a default configuration for the client. It is +// safe to modify the return value of this function. +// +// The default Address is https://127.0.0.1:8200, but this can be overridden by +// setting the `VAULT_ADDR` environment variable. +func DefaultConfig() *Config { + config := &Config{ + Address: "https://127.0.0.1:8200", + + HttpClient: cleanhttp.DefaultClient(), + } + config.HttpClient.Timeout = time.Second * 60 + transport := config.HttpClient.Transport.(*http.Transport) + transport.TLSHandshakeTimeout = 10 * time.Second + transport.TLSClientConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } + + if v := os.Getenv(EnvVaultAddress); v != "" { + config.Address = v + } + + config.MaxRetries = pester.DefaultClient.MaxRetries + + return config +} + +// ConfigureTLS takes a set of TLS configurations and applies those to the the HTTP client. +func (c *Config) ConfigureTLS(t *TLSConfig) error { + + if c.HttpClient == nil { + return fmt.Errorf("config HTTP Client must be set") + } + + var clientCert tls.Certificate + foundClientCert := false + if t.CACert != "" || t.CAPath != "" || t.ClientCert != "" || t.ClientKey != "" || t.Insecure { + if t.ClientCert != "" && t.ClientKey != "" { + var err error + clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey) + if err != nil { + return err + } + foundClientCert = true + } else if t.ClientCert != "" || t.ClientKey != "" { + return fmt.Errorf("Both client cert and client key must be provided") + } + } + + clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig + rootConfig := &rootcerts.Config{ + CAFile: t.CACert, + CAPath: t.CAPath, + } + if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil { + return err + } + + clientTLSConfig.InsecureSkipVerify = t.Insecure + + if foundClientCert { + clientTLSConfig.Certificates = []tls.Certificate{clientCert} + } + if t.TLSServerName != "" { + clientTLSConfig.ServerName = t.TLSServerName + } + + return nil +} + +// ReadEnvironment reads configuration information from the +// environment. If there is an error, no configuration value +// is updated. +func (c *Config) ReadEnvironment() error { + var envAddress string + var envCACert string + var envCAPath string + var envClientCert string + var envClientKey string + var envInsecure bool + var envTLSServerName string + var envMaxRetries *uint64 + + // Parse the environment variables + if v := os.Getenv(EnvVaultAddress); v != "" { + envAddress = v + } + if v := os.Getenv(EnvVaultMaxRetries); v != "" { + maxRetries, err := strconv.ParseUint(v, 10, 32) + if err != nil { + return err + } + envMaxRetries = &maxRetries + } + if v := os.Getenv(EnvVaultCACert); v != "" { + envCACert = v + } + if v := os.Getenv(EnvVaultCAPath); v != "" { + envCAPath = v + } + if v := os.Getenv(EnvVaultClientCert); v != "" { + envClientCert = v + } + if v := os.Getenv(EnvVaultClientKey); v != "" { + envClientKey = v + } + if v := os.Getenv(EnvVaultInsecure); v != "" { + var err error + envInsecure, err = strconv.ParseBool(v) + if err != nil { + return fmt.Errorf("Could not parse VAULT_SKIP_VERIFY") + } + } + if v := os.Getenv(EnvVaultTLSServerName); v != "" { + envTLSServerName = v + } + + // Configure the HTTP clients TLS configuration. + t := &TLSConfig{ + CACert: envCACert, + CAPath: envCAPath, + ClientCert: envClientCert, + ClientKey: envClientKey, + TLSServerName: envTLSServerName, + Insecure: envInsecure, + } + if err := c.ConfigureTLS(t); err != nil { + return err + } + + if envAddress != "" { + c.Address = envAddress + } + + if envMaxRetries != nil { + c.MaxRetries = int(*envMaxRetries) + 1 + } + + return nil +} + +// Client is the client to the Vault API. Create a client with +// NewClient. +type Client struct { + addr *url.URL + config *Config + token string + wrappingLookupFunc WrappingLookupFunc +} + +// NewClient returns a new client for the given configuration. +// +// If the environment variable `VAULT_TOKEN` is present, the token will be +// automatically added to the client. Otherwise, you must manually call +// `SetToken()`. +func NewClient(c *Config) (*Client, error) { + if c == nil { + c = DefaultConfig() + if err := c.ReadEnvironment(); err != nil { + return nil, fmt.Errorf("error reading environment: %v", err) + } + } + + u, err := url.Parse(c.Address) + if err != nil { + return nil, err + } + + if c.HttpClient == nil { + c.HttpClient = DefaultConfig().HttpClient + } + + redirFunc := func() { + // Ensure redirects are not automatically followed + // Note that this is sane for the API client as it has its own + // redirect handling logic (and thus also for command/meta), + // but in e.g. http_test actual redirect handling is necessary + c.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + // Returning this value causes the Go net library to not close the + // response body and nil out the error. Otherwise pester tries + // three times on every redirect because it sees an error from this + // function being passed through. + return http.ErrUseLastResponse + } + } + + c.redirectSetup.Do(redirFunc) + + client := &Client{ + addr: u, + config: c, + } + + if token := os.Getenv("VAULT_TOKEN"); token != "" { + client.SetToken(token) + } + + return client, nil +} + +// Sets the address of Vault in the client. The format of address should be +// "://:". Setting this on a client will override the +// value of VAULT_ADDR environment variable. +func (c *Client) SetAddress(addr string) error { + var err error + if c.addr, err = url.Parse(addr); err != nil { + return fmt.Errorf("failed to set address: %v", err) + } + + return nil +} + +// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs +// for a given operation and path +func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) { + c.wrappingLookupFunc = lookupFunc +} + +// Token returns the access token being used by this client. It will +// return the empty string if there is no token set. +func (c *Client) Token() string { + return c.token +} + +// SetToken sets the token directly. This won't perform any auth +// verification, it simply sets the token properly for future requests. +func (c *Client) SetToken(v string) { + c.token = v +} + +// ClearToken deletes the token if it is set or does nothing otherwise. +func (c *Client) ClearToken() { + c.token = "" +} + +// NewRequest creates a new raw request object to query the Vault server +// configured for this client. This is an advanced method and generally +// doesn't need to be called externally. +func (c *Client) NewRequest(method, path string) *Request { + req := &Request{ + Method: method, + URL: &url.URL{ + Scheme: c.addr.Scheme, + Host: c.addr.Host, + Path: path, + }, + ClientToken: c.token, + Params: make(map[string][]string), + } + + var lookupPath string + switch { + case strings.HasPrefix(path, "/v1/"): + lookupPath = strings.TrimPrefix(path, "/v1/") + case strings.HasPrefix(path, "v1/"): + lookupPath = strings.TrimPrefix(path, "v1/") + default: + lookupPath = path + } + if c.wrappingLookupFunc != nil { + req.WrapTTL = c.wrappingLookupFunc(method, lookupPath) + } else { + req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath) + } + + return req +} + +// RawRequest performs the raw request given. This request may be against +// a Vault server not configured with this client. This is an advanced operation +// that generally won't need to be called externally. +func (c *Client) RawRequest(r *Request) (*Response, error) { + redirectCount := 0 +START: + req, err := r.ToHTTP() + if err != nil { + return nil, err + } + + client := pester.NewExtendedClient(c.config.HttpClient) + client.Backoff = pester.LinearJitterBackoff + client.MaxRetries = c.config.MaxRetries + + var result *Response + resp, err := client.Do(req) + if resp != nil { + result = &Response{Response: resp} + } + if err != nil { + if strings.Contains(err.Error(), "tls: oversized") { + err = fmt.Errorf( + "%s\n\n"+ + "This error usually means that the server is running with TLS disabled\n"+ + "but the client is configured to use TLS. Please either enable TLS\n"+ + "on the server or run the client with -address set to an address\n"+ + "that uses the http protocol:\n\n"+ + " vault -address http://
\n\n"+ + "You can also set the VAULT_ADDR environment variable:\n\n\n"+ + " VAULT_ADDR=http://
vault \n\n"+ + "where
is replaced by the actual address to the server.", + err) + } + return result, err + } + + // Check for a redirect, only allowing for a single redirect + if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 { + // Parse the updated location + respLoc, err := resp.Location() + if err != nil { + return result, err + } + + // Ensure a protocol downgrade doesn't happen + if req.URL.Scheme == "https" && respLoc.Scheme != "https" { + return result, fmt.Errorf("redirect would cause protocol downgrade") + } + + // Update the request + r.URL = respLoc + + // Reset the request body if any + if err := r.ResetJSONBody(); err != nil { + return result, err + } + + // Retry the request + redirectCount++ + goto START + } + + if err := result.Error(); err != nil { + return result, err + } + + return result, nil +} -- cgit v1.2.3