open-vault/api/client.go

877 lines
24 KiB
Go
Raw Normal View History

2015-03-04 21:10:10 +00:00
package api
import (
2018-05-11 14:42:06 +00:00
"context"
"crypto/tls"
2015-04-20 18:30:35 +00:00
"fmt"
"net"
2015-03-04 21:10:10 +00:00
"net/http"
2015-03-09 18:38:50 +00:00
"net/url"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"unicode"
2017-11-06 17:06:19 +00:00
"github.com/hashicorp/errwrap"
cleanhttp "github.com/hashicorp/go-cleanhttp"
retryablehttp "github.com/hashicorp/go-retryablehttp"
rootcerts "github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/parseutil"
"golang.org/x/net/http2"
2018-05-11 14:42:06 +00:00
"golang.org/x/time/rate"
2015-03-04 21:10:10 +00:00
)
const EnvVaultAddress = "VAULT_ADDR"
const EnvVaultAgentAddr = "VAULT_AGENT_ADDR"
const EnvVaultCACert = "VAULT_CACERT"
const EnvVaultCAPath = "VAULT_CAPATH"
const EnvVaultClientCert = "VAULT_CLIENT_CERT"
const EnvVaultClientKey = "VAULT_CLIENT_KEY"
const EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT"
const EnvVaultSkipVerify = "VAULT_SKIP_VERIFY"
const EnvVaultNamespace = "VAULT_NAMESPACE"
const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
2016-05-02 05:58:58 +00:00
const EnvVaultWrapTTL = "VAULT_WRAP_TTL"
const EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
const EnvVaultToken = "VAULT_TOKEN"
2017-10-23 20:52:56 +00:00
const EnvVaultMFA = "VAULT_MFA"
2018-05-11 14:42:06 +00:00
const EnvRateLimit = "VAULT_RATE_LIMIT"
// Deprecated values
const EnvVaultAgentAddress = "VAULT_AGENT_ADDR"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
// 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
2015-03-04 21:10:10 +00:00
// Config is used to configure the creation of the client.
type Config struct {
modifyLock sync.RWMutex
2015-03-04 21:10:10 +00:00
// 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
// AgentAddress is the address of the local Vault agent. This should be a
// complete URL such as "http://vault.example.com".
AgentAddress string
// HttpClient is the HTTP client to use. Vault sets sane defaults for the
// http.Client and its associated http.Transport created in DefaultConfig.
// If you must modify Vault's defaults, it is suggested that you start with
// that client and modify as needed rather than start with an empty client
// (or http.DefaultClient).
2015-03-04 21:10:10 +00:00
HttpClient *http.Client
2018-05-09 22:24:41 +00:00
// MaxRetries controls the maximum number of times to retry when a 5xx
// error occurs. Set to 0 to disable retrying. Defaults to 2 (for a total
// of three tries).
MaxRetries int
// Timeout is for setting custom timeout parameter in the HttpClient
Timeout time.Duration
2017-11-06 17:06:19 +00:00
// If there is an error when creating the configuration, this will be the
// error
Error error
2018-05-09 21:44:53 +00:00
// The Backoff function to use; a default is used if not provided
Backoff retryablehttp.Backoff
2018-05-11 14:42:06 +00:00
// The CheckRetry function to use; a default is used if not provided
CheckRetry retryablehttp.CheckRetry
2018-05-11 14:42:06 +00:00
// Limiter is the rate limiter used by the client.
// If this pointer is nil, then there will be no limit set.
// In contrast, if this pointer is set, even to an empty struct,
// then that limiter will be used. Note that an empty Limiter
// is equivalent blocking all events.
Limiter *rate.Limiter
// OutputCurlString causes the actual request to return an error of type
// *OutputStringError. Type asserting the error message will allow
// fetching a cURL-compatible string for the operation.
//
// Note: It is not thread-safe to set this and make concurrent requests
// with the same client. Cloning a client will not clone this value.
OutputCurlString bool
2015-03-04 21:10:10 +00:00
}
2016-08-02 20:17:45 +00:00
// 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
}
2015-03-04 21:10:10 +00:00
// DefaultConfig returns a default configuration for the client. It is
// safe to modify the return value of this function.
2015-04-23 15:45:37 +00:00
//
// The default Address is https://127.0.0.1:8200, but this can be overridden by
2015-04-23 15:46:22 +00:00
// setting the `VAULT_ADDR` environment variable.
//
// If an error is encountered, this will return nil.
2015-04-23 15:13:52 +00:00
func DefaultConfig() *Config {
config := &Config{
Address: "https://127.0.0.1:8200",
HttpClient: cleanhttp.DefaultPooledClient(),
Timeout: time.Second * 60,
2015-08-22 00:36:19 +00:00
}
transport := config.HttpClient.Transport.(*http.Transport)
transport.TLSHandshakeTimeout = 10 * time.Second
transport.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
}
if err := http2.ConfigureTransport(transport); err != nil {
2017-11-06 17:06:19 +00:00
config.Error = err
return config
}
if err := config.ReadEnvironment(); err != nil {
2017-11-06 17:06:19 +00:00
config.Error = err
return config
}
2015-08-22 00:36:19 +00:00
// 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
config.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
// Returning this value causes the Go net library to not close the
2018-05-09 21:44:53 +00:00
// response body and to nil out the error. Otherwise retry clients may
// try three times on every redirect because it sees an error from this
// function (to prevent redirects) passing through to it.
return http.ErrUseLastResponse
}
2018-05-09 21:44:53 +00:00
config.Backoff = retryablehttp.LinearJitterBackoff
2018-05-09 22:24:41 +00:00
config.MaxRetries = 2
2018-05-09 21:44:53 +00:00
2015-03-04 21:10:10 +00:00
return config
}
// ConfigureTLS takes a set of TLS configurations and applies those to the the
// HTTP client.
2016-08-02 20:17:45 +00:00
func (c *Config) ConfigureTLS(t *TLSConfig) error {
if c.HttpClient == nil {
c.HttpClient = DefaultConfig().HttpClient
}
2017-11-06 17:06:19 +00:00
clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig
2016-08-02 20:17:45 +00:00
var clientCert tls.Certificate
foundClientCert := false
2017-11-06 17:06:19 +00:00
switch {
case t.ClientCert != "" && t.ClientKey != "":
var err error
clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey)
if err != nil {
return err
}
2017-11-06 17:06:19 +00:00
foundClientCert = true
case t.ClientCert != "" || t.ClientKey != "":
return fmt.Errorf("both client cert and client key must be provided")
}
2017-11-06 17:06:19 +00:00
if t.CACert != "" || t.CAPath != "" {
rootConfig := &rootcerts.Config{
CAFile: t.CACert,
CAPath: t.CAPath,
}
if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
return err
}
}
2017-11-06 17:06:19 +00:00
if t.Insecure {
clientTLSConfig.InsecureSkipVerify = true
}
if foundClientCert {
// We use this function to ignore the server's preferential list of
// CAs, otherwise any CA used for the cert auth backend must be in the
// server's CA pool
clientTLSConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return &clientCert, nil
}
}
2017-11-06 17:06:19 +00:00
2016-08-02 20:17:45 +00:00
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 envAgentAddress string
var envCACert string
var envCAPath string
var envClientCert string
var envClientKey string
var envClientTimeout time.Duration
2016-08-02 20:17:45 +00:00
var envInsecure bool
var envTLSServerName string
var envMaxRetries *uint64
2018-05-11 14:42:06 +00:00
var limit *rate.Limiter
// Parse the environment variables
if v := os.Getenv(EnvVaultAddress); v != "" {
envAddress = v
}
if v := os.Getenv(EnvVaultAgentAddr); v != "" {
envAgentAddress = v
} else if v := os.Getenv(EnvVaultAgentAddress); v != "" {
envAgentAddress = 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
}
2018-05-11 14:42:06 +00:00
if v := os.Getenv(EnvRateLimit); v != "" {
rateLimit, burstLimit, err := parseRateLimit(v)
if err != nil {
return err
}
limit = rate.NewLimiter(rate.Limit(rateLimit), burstLimit)
}
if t := os.Getenv(EnvVaultClientTimeout); t != "" {
clientTimeout, err := parseutil.ParseDurationSecond(t)
if err != nil {
return fmt.Errorf("could not parse %q", EnvVaultClientTimeout)
}
envClientTimeout = clientTimeout
}
if v := os.Getenv(EnvVaultSkipVerify); v != "" {
var err error
envInsecure, err = strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("could not parse VAULT_SKIP_VERIFY")
}
} else if v := os.Getenv(EnvVaultInsecure); v != "" {
var err error
envInsecure, err = strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("could not parse VAULT_INSECURE")
}
}
if v := os.Getenv(EnvVaultTLSServerName); v != "" {
envTLSServerName = v
}
// Configure the HTTP clients TLS configuration.
2016-08-02 20:17:45 +00:00
t := &TLSConfig{
CACert: envCACert,
CAPath: envCAPath,
ClientCert: envClientCert,
ClientKey: envClientKey,
TLSServerName: envTLSServerName,
Insecure: envInsecure,
}
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
2018-05-11 14:42:06 +00:00
c.Limiter = limit
2016-08-02 20:17:45 +00:00
if err := c.ConfigureTLS(t); err != nil {
return err
}
if envAddress != "" {
c.Address = envAddress
}
if envAgentAddress != "" {
c.AgentAddress = envAgentAddress
}
if envMaxRetries != nil {
2018-05-09 22:24:41 +00:00
c.MaxRetries = int(*envMaxRetries)
}
if envClientTimeout != 0 {
c.Timeout = envClientTimeout
}
return nil
}
2018-05-11 14:42:06 +00:00
func parseRateLimit(val string) (rate float64, burst int, err error) {
_, err = fmt.Sscanf(val, "%f:%d", &rate, &burst)
if err != nil {
rate, err = strconv.ParseFloat(val, 64)
if err != nil {
err = fmt.Errorf("%v was provided but incorrectly formatted", EnvRateLimit)
}
burst = int(rate)
}
return rate, burst, err
}
// Client is the client to the Vault API. Create a client with NewClient.
2015-03-04 21:10:10 +00:00
type Client struct {
modifyLock sync.RWMutex
addr *url.URL
config *Config
token string
headers http.Header
wrappingLookupFunc WrappingLookupFunc
2017-10-23 20:52:56 +00:00
mfaCreds []string
policyOverride bool
}
2015-03-04 21:10:10 +00:00
// NewClient returns a new client for the given configuration.
2015-04-23 15:45:37 +00:00
//
// If the configuration is nil, Vault will use configuration from
// DefaultConfig(), which is the recommended starting configuration.
//
2015-04-23 15:45:37 +00:00
// If the environment variable `VAULT_TOKEN` is present, the token will be
// automatically added to the client. Otherwise, you must manually call
// `SetToken()`.
2015-04-23 15:13:52 +00:00
func NewClient(c *Config) (*Client, error) {
def := DefaultConfig()
if def == nil {
return nil, fmt.Errorf("could not create/read default configuration")
}
2017-11-06 17:06:19 +00:00
if def.Error != nil {
return nil, errwrap.Wrapf("error encountered setting up default configuration: {{err}}", def.Error)
}
if c == nil {
c = def
}
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
2019-02-15 18:40:03 +00:00
if c.HttpClient == nil {
c.HttpClient = def.HttpClient
}
if c.HttpClient.Transport == nil {
c.HttpClient.Transport = def.HttpClient.Transport
}
address := c.Address
if c.AgentAddress != "" {
address = c.AgentAddress
}
2019-03-05 20:20:16 +00:00
u, err := url.Parse(address)
if err != nil {
return nil, err
}
if strings.HasPrefix(address, "unix://") {
socket := strings.TrimPrefix(address, "unix://")
2019-02-15 18:40:03 +00:00
transport := c.HttpClient.Transport.(*http.Transport)
transport.DialContext = func(context.Context, string, string) (net.Conn, error) {
return net.Dial("unix", socket)
}
2019-03-05 20:20:16 +00:00
// Since the address points to a unix domain socket, the scheme in the
// *URL would be set to `unix`. The *URL in the client is expected to
// be pointing to the protocol used in the application layer and not to
// the transport layer. Hence, setting the fields accordingly.
u.Scheme = "http"
u.Host = socket
u.Path = ""
2015-03-09 18:38:50 +00:00
}
client := &Client{
addr: u,
config: c,
headers: make(http.Header),
}
// Add the VaultRequest SSRF protection header
client.headers[consts.RequestHeaderName] = []string{"true"}
if token := os.Getenv(EnvVaultToken); token != "" {
client.token = token
}
if namespace := os.Getenv(EnvVaultNamespace); namespace != "" {
client.setNamespace(namespace)
}
return client, nil
2015-03-04 21:10:10 +00:00
}
2015-03-09 18:38:50 +00:00
// Sets the address of Vault in the client. The format of address should be
// "<Scheme>://<Host>:<Port>". Setting this on a client will override the
// value of VAULT_ADDR environment variable.
func (c *Client) SetAddress(addr string) error {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
parsedAddr, err := url.Parse(addr)
if err != nil {
return errwrap.Wrapf("failed to set address: {{err}}", err)
}
c.addr = parsedAddr
return nil
}
// Address returns the Vault URL the client is configured to connect to
func (c *Client) Address() string {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
return c.addr.String()
}
2018-05-11 14:42:06 +00:00
// SetLimiter will set the rate limiter for this client.
// This method is thread-safe.
// rateLimit and burst are specified according to https://godoc.org/golang.org/x/time/rate#NewLimiter
func (c *Client) SetLimiter(rateLimit float64, burst int) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
2018-05-11 14:42:06 +00:00
c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst)
}
2017-03-01 17:23:54 +00:00
// SetMaxRetries sets the number of retries that will be used in the case of certain errors
func (c *Client) SetMaxRetries(retries int) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
2017-03-01 17:23:54 +00:00
c.config.MaxRetries = retries
}
// SetCheckRetry sets the CheckRetry function to be used for future requests.
func (c *Client) SetCheckRetry(checkRetry retryablehttp.CheckRetry) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
c.config.CheckRetry = checkRetry
}
// SetClientTimeout sets the client request timeout
func (c *Client) SetClientTimeout(timeout time.Duration) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
c.config.Timeout = timeout
}
func (c *Client) OutputCurlString() bool {
c.modifyLock.RLock()
c.config.modifyLock.RLock()
defer c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock()
return c.config.OutputCurlString
}
func (c *Client) SetOutputCurlString(curl bool) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
c.config.OutputCurlString = curl
}
Fix response wrapping from K/V version 2 (#4511) This takes place in two parts, since working on this exposed an issue with response wrapping when there is a raw body set. The changes are (in diff order): * A CurrentWrappingLookupFunc has been added to return the current value. This is necessary for the lookahead call since we don't want the lookahead call to be wrapped. * Support for unwrapping < 0.6.2 tokens via the API/CLI has been removed, because we now have backends returning 404s with data and can't rely on the 404 trick. These can still be read manually via cubbyhole/response. * KV preflight version request now ensures that its calls is not wrapped, and restores any given function after. * When responding with a raw body, instead of always base64-decoding a string value and erroring on failure, on failure we assume that it simply wasn't a base64-encoded value and use it as is. * A test that fails on master and works now that ensures that raw body responses that are wrapped and then unwrapped return the expected values. * A flag for response data that indicates to the wrapping handling that the data contained therein is already JSON decoded (more later). * RespondWithStatusCode now defaults to a string so that the value is HMAC'd during audit. The function always JSON encodes the body, so before now it was always returning []byte which would skip HMACing. We don't know what's in the data, so this is a "better safe than sorry" issue. If different behavior is needed, backends can always manually populate the data instead of relying on the helper function. * We now check unwrapped data after unwrapping to see if there were raw flags. If so, we try to detect whether the value can be unbase64'd. The reason is that if it can it was probably originally a []byte and shouldn't be audit HMAC'd; if not, it was probably originally a string and should be. In either case, we then set the value as the raw body and hit the flag indicating that it's already been JSON decoded so not to try again before auditing. Doing it this way ensures the right typing. * There is now a check to see if the data coming from unwrapping is already JSON decoded and if so the decoding is skipped before setting the audit response.
2018-05-10 19:40:03 +00:00
// CurrentWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
// for a given operation and path
func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
return c.wrappingLookupFunc
}
// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
// for a given operation and path
func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.wrappingLookupFunc = lookupFunc
}
// SetMFACreds sets the MFA credentials supplied either via the environment
// variable or via the command line.
func (c *Client) SetMFACreds(creds []string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.mfaCreds = creds
}
2018-08-09 22:29:03 +00:00
// SetNamespace sets the namespace supplied either via the environment
// variable or via the command line.
func (c *Client) SetNamespace(namespace string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.setNamespace(namespace)
}
2018-08-09 22:29:03 +00:00
func (c *Client) setNamespace(namespace string) {
2018-08-09 22:29:03 +00:00
if c.headers == nil {
c.headers = make(http.Header)
}
2018-08-22 18:37:40 +00:00
c.headers.Set(consts.NamespaceHeaderName, namespace)
2018-08-09 22:29:03 +00:00
}
2015-03-11 22:42:08 +00:00
// 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 {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
2015-08-22 00:36:19 +00:00
return c.token
2015-03-11 22:42:08 +00:00
}
2015-03-31 04:20:23 +00:00
// SetToken sets the token directly. This won't perform any auth
2015-09-03 14:36:59 +00:00
// verification, it simply sets the token properly for future requests.
2015-03-31 04:20:23 +00:00
func (c *Client) SetToken(v string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
2015-08-22 00:36:19 +00:00
c.token = v
2015-03-31 04:20:23 +00:00
}
2015-09-03 14:36:59 +00:00
// ClearToken deletes the token if it is set or does nothing otherwise.
2015-03-11 22:42:08 +00:00
func (c *Client) ClearToken() {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
2015-08-22 00:36:19 +00:00
c.token = ""
2015-03-11 22:42:08 +00:00
}
2018-08-09 22:29:03 +00:00
// Headers gets the current set of headers used for requests. This returns a
// copy; to modify it make modifications locally and use SetHeaders.
func (c *Client) Headers() http.Header {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
if c.headers == nil {
return nil
}
ret := make(http.Header)
for k, v := range c.headers {
for _, val := range v {
ret[k] = append(ret[k], val)
}
}
return ret
}
// SetHeaders sets the headers to be used for future requests.
func (c *Client) SetHeaders(headers http.Header) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.headers = headers
}
2018-05-09 21:44:53 +00:00
// SetBackoff sets the backoff function to be used for future requests.
func (c *Client) SetBackoff(backoff retryablehttp.Backoff) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
c.config.Backoff = backoff
}
// Clone creates a new client with the same configuration. Note that the same
// underlying http.Client is used; modifying the client from more than one
// goroutine at once may not be safe, so modify the client as needed and then
// clone.
//
// Also, only the client's config is currently copied; this means items not in
// the api.Config struct, such as policy override and wrapping function
// behavior, must currently then be set as desired on the new client.
func (c *Client) Clone() (*Client, error) {
c.modifyLock.RLock()
c.config.modifyLock.RLock()
config := c.config
c.modifyLock.RUnlock()
newConfig := &Config{
Address: config.Address,
HttpClient: config.HttpClient,
MaxRetries: config.MaxRetries,
Timeout: config.Timeout,
2018-05-09 21:44:53 +00:00
Backoff: config.Backoff,
CheckRetry: config.CheckRetry,
2018-05-11 15:50:10 +00:00
Limiter: config.Limiter,
}
config.modifyLock.RUnlock()
return NewClient(newConfig)
}
2017-10-23 20:52:56 +00:00
// SetPolicyOverride sets whether requests should be sent with the policy
// override flag to request overriding soft-mandatory Sentinel policies (both
// RGPs and EGPs)
func (c *Client) SetPolicyOverride(override bool) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
2017-10-23 20:52:56 +00:00
c.policyOverride = override
}
2015-03-09 18:38:50 +00:00
// 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, requestPath string) *Request {
c.modifyLock.RLock()
addr := c.addr
token := c.token
mfaCreds := c.mfaCreds
wrappingLookupFunc := c.wrappingLookupFunc
policyOverride := c.policyOverride
c.modifyLock.RUnlock()
// if SRV records exist (see https://tools.ietf.org/html/draft-andrews-http-srv-02), lookup the SRV
// record and take the highest match; this is not designed for high-availability, just discovery
var host string = addr.Host
if addr.Port() == "" {
// Internet Draft specifies that the SRV record is ignored if a port is given
_, addrs, err := net.LookupSRV("http", "tcp", addr.Hostname())
if err == nil && len(addrs) > 0 {
2017-08-02 23:38:35 +00:00
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
}
}
2015-08-22 00:36:19 +00:00
req := &Request{
2015-03-09 18:38:50 +00:00
Method: method,
URL: &url.URL{
User: addr.User,
Scheme: addr.Scheme,
Host: host,
Path: path.Join(addr.Path, requestPath),
2015-03-09 18:38:50 +00:00
},
ClientToken: token,
2015-08-22 00:36:19 +00:00
Params: make(map[string][]string),
2015-03-09 18:38:50 +00:00
}
2015-08-22 00:36:19 +00:00
2016-09-29 04:01:28 +00:00
var lookupPath string
switch {
case strings.HasPrefix(requestPath, "/v1/"):
lookupPath = strings.TrimPrefix(requestPath, "/v1/")
case strings.HasPrefix(requestPath, "v1/"):
lookupPath = strings.TrimPrefix(requestPath, "v1/")
2016-09-29 04:01:28 +00:00
default:
lookupPath = requestPath
2016-09-29 04:01:28 +00:00
}
2017-10-23 20:52:56 +00:00
req.MFAHeaderVals = mfaCreds
2017-10-23 20:52:56 +00:00
if wrappingLookupFunc != nil {
req.WrapTTL = wrappingLookupFunc(method, lookupPath)
2016-09-29 04:01:28 +00:00
} else {
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
}
req.Headers = c.Headers()
req.PolicyOverride = policyOverride
2017-10-23 20:52:56 +00:00
2015-08-22 00:36:19 +00:00
return req
2015-03-09 18:38:50 +00:00
}
// 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.
2015-03-11 18:33:20 +00:00
func (c *Client) RawRequest(r *Request) (*Response, error) {
return c.RawRequestWithContext(context.Background(), r)
}
// RawRequestWithContext 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) RawRequestWithContext(ctx context.Context, r *Request) (*Response, error) {
c.modifyLock.RLock()
token := c.token
2018-05-11 14:42:06 +00:00
c.config.modifyLock.RLock()
limiter := c.config.Limiter
maxRetries := c.config.MaxRetries
checkRetry := c.config.CheckRetry
backoff := c.config.Backoff
httpClient := c.config.HttpClient
timeout := c.config.Timeout
outputCurlString := c.config.OutputCurlString
c.config.modifyLock.RUnlock()
2018-05-11 14:42:06 +00:00
c.modifyLock.RUnlock()
if limiter != nil {
limiter.Wait(ctx)
}
// Sanity check the token before potentially erroring from the API
idx := strings.IndexFunc(token, func(c rune) bool {
return !unicode.IsPrint(c)
})
if idx != -1 {
return nil, fmt.Errorf("configured Vault token contains non-printable characters and cannot be used")
}
2015-04-20 18:30:35 +00:00
redirectCount := 0
START:
2019-02-15 18:40:03 +00:00
req, err := r.toRetryableHTTP()
2015-03-09 18:38:50 +00:00
if err != nil {
return nil, err
}
2018-05-09 21:44:53 +00:00
if req == nil {
return nil, fmt.Errorf("nil request created")
}
if outputCurlString {
LastOutputStringError = &OutputStringError{Request: req}
return nil, LastOutputStringError
}
if timeout != 0 {
// NOTE: this leaks a timer. But when we defer a cancel call here for
// the returned function we see errors in tests with contxt canceled.
// Although the request is done by the time we exit this function it is
// still causing something else to go wrong. Maybe it ends up being
// tied to the response somehow and reading the response body ends up
// checking it, or something. I don't know, but until we can chase this
// down, keep it not-canceled even though vet complains.
ctx, _ = context.WithTimeout(ctx, timeout)
}
req.Request = req.Request.WithContext(ctx)
2018-05-09 21:44:53 +00:00
if backoff == nil {
backoff = retryablehttp.LinearJitterBackoff
}
2015-03-09 18:38:50 +00:00
if checkRetry == nil {
checkRetry = retryablehttp.DefaultRetryPolicy
}
2018-05-09 21:44:53 +00:00
client := &retryablehttp.Client{
HTTPClient: httpClient,
2018-05-09 22:24:41 +00:00
RetryWaitMin: 1000 * time.Millisecond,
RetryWaitMax: 1500 * time.Millisecond,
RetryMax: maxRetries,
2018-05-09 21:44:53 +00:00
Backoff: backoff,
CheckRetry: checkRetry,
ErrorHandler: retryablehttp.PassthroughErrorHandler,
2018-05-09 21:44:53 +00:00
}
2015-04-07 18:15:20 +00:00
var result *Response
resp, err := client.Do(req)
2015-04-07 18:15:20 +00:00
if resp != nil {
result = &Response{Response: resp}
}
2015-04-20 18:30:35 +00:00
if err != nil {
2016-08-12 19:13:42 +00:00
if strings.Contains(err.Error(), "tls: oversized") {
err = errwrap.Wrapf(
"{{err}}\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 <command> -address http://<address>\n\n"+
"You can also set the VAULT_ADDR environment variable:\n\n\n"+
" VAULT_ADDR=http://<address> vault <command>\n\n"+
"where <address> is replaced by the actual address to the server.",
err)
2015-04-20 18:30:35 +00:00
}
2015-04-07 18:15:20 +00:00
return result, err
2015-03-09 18:38:50 +00:00
}
2015-04-20 18:30:35 +00:00
// Check for a redirect, only allowing for a single redirect
if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 {
2015-04-20 18:30:35 +00:00
// 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 {
2015-04-07 18:15:20 +00:00
return result, err
}
return result, nil
2015-03-09 18:38:50 +00:00
}