2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2015-03-04 21:10:10 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2018-05-11 14:42:06 +00:00
|
|
|
"context"
|
2021-10-06 17:57:06 +00:00
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha256"
|
2015-11-03 19:21:14 +00:00
|
|
|
"crypto/tls"
|
2021-10-06 17:57:06 +00:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/hex"
|
2015-04-20 18:30:35 +00:00
|
|
|
"fmt"
|
2017-08-02 23:19:06 +00:00
|
|
|
"net"
|
2015-03-04 21:10:10 +00:00
|
|
|
"net/http"
|
2015-03-09 18:38:50 +00:00
|
|
|
"net/url"
|
2015-04-23 15:43:20 +00:00
|
|
|
"os"
|
2017-05-19 12:34:17 +00:00
|
|
|
"path"
|
2015-11-03 19:21:14 +00:00
|
|
|
"strconv"
|
2015-05-02 20:08:35 +00:00
|
|
|
"strings"
|
2015-09-03 17:34:45 +00:00
|
|
|
"sync"
|
2015-10-08 07:44:00 +00:00
|
|
|
"time"
|
2018-01-25 00:57:49 +00:00
|
|
|
"unicode"
|
2015-10-22 18:37:12 +00:00
|
|
|
|
2017-11-06 17:06:19 +00:00
|
|
|
"github.com/hashicorp/errwrap"
|
2021-12-21 21:14:39 +00:00
|
|
|
"github.com/hashicorp/go-cleanhttp"
|
|
|
|
"github.com/hashicorp/go-retryablehttp"
|
|
|
|
"github.com/hashicorp/go-rootcerts"
|
2021-07-16 00:17:31 +00:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
2023-02-06 14:41:56 +00:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
2021-10-14 18:51:31 +00:00
|
|
|
"golang.org/x/net/http2"
|
|
|
|
"golang.org/x/time/rate"
|
2015-03-04 21:10:10 +00:00
|
|
|
)
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
const (
|
2022-09-30 08:29:37 +00:00
|
|
|
EnvVaultAddress = "VAULT_ADDR"
|
|
|
|
EnvVaultAgentAddr = "VAULT_AGENT_ADDR"
|
|
|
|
EnvVaultCACert = "VAULT_CACERT"
|
|
|
|
EnvVaultCACertBytes = "VAULT_CACERT_BYTES"
|
|
|
|
EnvVaultCAPath = "VAULT_CAPATH"
|
|
|
|
EnvVaultClientCert = "VAULT_CLIENT_CERT"
|
|
|
|
EnvVaultClientKey = "VAULT_CLIENT_KEY"
|
|
|
|
EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT"
|
|
|
|
EnvVaultSRVLookup = "VAULT_SRV_LOOKUP"
|
|
|
|
EnvVaultSkipVerify = "VAULT_SKIP_VERIFY"
|
|
|
|
EnvVaultNamespace = "VAULT_NAMESPACE"
|
|
|
|
EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
|
|
|
|
EnvVaultWrapTTL = "VAULT_WRAP_TTL"
|
|
|
|
EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
|
|
|
|
EnvVaultToken = "VAULT_TOKEN"
|
|
|
|
EnvVaultMFA = "VAULT_MFA"
|
|
|
|
EnvRateLimit = "VAULT_RATE_LIMIT"
|
|
|
|
EnvHTTPProxy = "VAULT_HTTP_PROXY"
|
|
|
|
EnvVaultProxyAddr = "VAULT_PROXY_ADDR"
|
|
|
|
EnvVaultDisableRedirects = "VAULT_DISABLE_REDIRECTS"
|
|
|
|
HeaderIndex = "X-Vault-Index"
|
|
|
|
HeaderForward = "X-Vault-Forward"
|
|
|
|
HeaderInconsistent = "X-Vault-Inconsistent"
|
2023-02-06 14:41:56 +00:00
|
|
|
|
|
|
|
// NamespaceHeaderName is the header set to specify which namespace the
|
|
|
|
// request is indented for.
|
|
|
|
NamespaceHeaderName = "X-Vault-Namespace"
|
|
|
|
|
|
|
|
// AuthHeaderName is the name of the header containing the token.
|
|
|
|
AuthHeaderName = "X-Vault-Token"
|
|
|
|
|
|
|
|
// RequestHeaderName is the name of the header used by the Agent for
|
|
|
|
// SSRF protection.
|
|
|
|
RequestHeaderName = "X-Vault-Request"
|
|
|
|
|
|
|
|
TLSErrorString = "This error usually means that the server is running with TLS disabled\n" +
|
2022-03-14 17:13:33 +00:00
|
|
|
"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."
|
2021-04-08 16:43:39 +00:00
|
|
|
)
|
2015-11-03 19:21:14 +00:00
|
|
|
|
2019-07-17 10:29:25 +00:00
|
|
|
// Deprecated values
|
2021-04-08 16:43:39 +00:00
|
|
|
const (
|
|
|
|
EnvVaultAgentAddress = "VAULT_AGENT_ADDR"
|
|
|
|
EnvVaultInsecure = "VAULT_SKIP_VERIFY"
|
|
|
|
)
|
2019-07-17 10:29:25 +00:00
|
|
|
|
2016-05-19 15:47:18 +00:00
|
|
|
// 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
|
2020-06-08 14:50:48 +00:00
|
|
|
// called path precisely. Response wrapping will only be used when the return
|
|
|
|
// value is not the empty string.
|
2016-05-16 20:11:33 +00:00
|
|
|
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 {
|
2017-11-02 14:30:04 +00:00
|
|
|
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
|
|
|
|
|
2019-02-19 21:53:29 +00:00
|
|
|
// AgentAddress is the address of the local Vault agent. This should be a
|
|
|
|
// complete URL such as "http://vault.example.com".
|
|
|
|
AgentAddress string
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
// 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
|
2015-10-15 20:09:45 +00:00
|
|
|
|
2021-06-11 19:15:21 +00:00
|
|
|
// MinRetryWait controls the minimum time to wait before retrying when a 5xx
|
|
|
|
// error occurs. Defaults to 1000 milliseconds.
|
|
|
|
MinRetryWait time.Duration
|
|
|
|
|
|
|
|
// MaxRetryWait controls the maximum time to wait before retrying when a 5xx
|
|
|
|
// error occurs. Defaults to 1500 milliseconds.
|
|
|
|
MaxRetryWait time.Duration
|
|
|
|
|
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).
|
2016-07-11 21:37:46 +00:00
|
|
|
MaxRetries int
|
2017-07-18 13:48:31 +00:00
|
|
|
|
2023-01-17 20:41:59 +00:00
|
|
|
// Timeout, given a non-negative value, will apply the request timeout
|
|
|
|
// to each request function unless an earlier deadline is passed to the
|
|
|
|
// request function through context.Context. Note that this timeout is
|
|
|
|
// not applicable to Logical().ReadRaw* (raw response) functions.
|
|
|
|
// Defaults to 60 seconds.
|
2017-07-18 13:48:31 +00:00
|
|
|
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
|
|
|
|
2019-10-28 19:54:59 +00:00
|
|
|
// The CheckRetry function to use; a default is used if not provided
|
|
|
|
CheckRetry retryablehttp.CheckRetry
|
|
|
|
|
2021-05-27 17:25:25 +00:00
|
|
|
// Logger is the leveled logger to provide to the retryable HTTP client.
|
|
|
|
Logger retryablehttp.LeveledLogger
|
|
|
|
|
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
|
2019-02-01 22:13:51 +00:00
|
|
|
|
|
|
|
// 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
|
2020-03-11 13:22:58 +00:00
|
|
|
|
2022-04-27 23:35:18 +00:00
|
|
|
// OutputPolicy causes the actual request to return an error of type
|
|
|
|
// *OutputPolicyError. Type asserting the error message will display
|
|
|
|
// an example of the required policy HCL needed 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.
|
|
|
|
OutputPolicy bool
|
|
|
|
|
2022-01-20 18:25:26 +00:00
|
|
|
// curlCACert, curlCAPath, curlClientCert and curlClientKey are used to keep
|
|
|
|
// track of the name of the TLS certs and keys when OutputCurlString is set.
|
|
|
|
// Cloning a client will also not clone those values.
|
|
|
|
curlCACert, curlCAPath string
|
|
|
|
curlClientCert, curlClientKey string
|
|
|
|
|
2020-03-11 13:22:58 +00:00
|
|
|
// SRVLookup enables the client to lookup the host through DNS SRV lookup
|
|
|
|
SRVLookup bool
|
2021-07-19 21:15:31 +00:00
|
|
|
|
2021-10-14 18:51:31 +00:00
|
|
|
// CloneHeaders ensures that the source client's headers are copied to
|
|
|
|
// its clone.
|
2021-07-19 21:15:31 +00:00
|
|
|
CloneHeaders bool
|
2021-10-14 18:51:31 +00:00
|
|
|
|
2021-12-22 22:07:26 +00:00
|
|
|
// CloneToken from parent.
|
|
|
|
CloneToken bool
|
|
|
|
|
2021-10-14 18:51:31 +00:00
|
|
|
// ReadYourWrites ensures isolated read-after-write semantics by
|
|
|
|
// providing discovered cluster replication states in each request.
|
|
|
|
// The shared state is automatically propagated to all Client clones.
|
|
|
|
//
|
|
|
|
// Note: Careful consideration should be made prior to enabling this setting
|
|
|
|
// since there will be a performance penalty paid upon each request.
|
|
|
|
// This feature requires Enterprise server-side.
|
|
|
|
ReadYourWrites bool
|
2022-09-30 08:29:37 +00:00
|
|
|
|
|
|
|
// DisableRedirects when set to true, will prevent the client from
|
|
|
|
// automatically following a (single) redirect response to its initial
|
|
|
|
// request. This behavior may be desirable if using Vault CLI on the server
|
|
|
|
// side.
|
|
|
|
//
|
|
|
|
// Note: Disabling redirect following behavior could cause issues with
|
|
|
|
// commands such as 'vault operator raft snapshot' as this redirects to the
|
|
|
|
// primary node.
|
|
|
|
DisableRedirects 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
|
2022-04-06 15:21:46 +00:00
|
|
|
// Vault server SSL certificate. It takes precedence over CACertBytes
|
|
|
|
// and CAPath.
|
2016-08-02 20:17:45 +00:00
|
|
|
CACert string
|
|
|
|
|
2022-04-06 15:21:46 +00:00
|
|
|
// CACertBytes is a PEM-encoded certificate or bundle. It takes precedence
|
|
|
|
// over CAPath.
|
|
|
|
CACertBytes []byte
|
|
|
|
|
2016-08-02 20:17:45 +00:00
|
|
|
// 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.
|
2017-11-02 14:30:04 +00:00
|
|
|
//
|
2022-02-08 17:54:59 +00:00
|
|
|
// If an error is encountered, the Error field on the returned *Config will be populated with the specific error.
|
2015-04-23 15:13:52 +00:00
|
|
|
func DefaultConfig() *Config {
|
|
|
|
config := &Config{
|
2021-06-11 19:15:21 +00:00
|
|
|
Address: "https://127.0.0.1:8200",
|
|
|
|
HttpClient: cleanhttp.DefaultPooledClient(),
|
|
|
|
Timeout: time.Second * 60,
|
|
|
|
MinRetryWait: time.Millisecond * 1000,
|
|
|
|
MaxRetryWait: time.Millisecond * 1500,
|
|
|
|
MaxRetries: 2,
|
|
|
|
Backoff: retryablehttp.LinearJitterBackoff,
|
2015-08-22 00:36:19 +00:00
|
|
|
}
|
2017-11-02 14:30:04 +00:00
|
|
|
|
2015-11-03 19:21:14 +00:00
|
|
|
transport := config.HttpClient.Transport.(*http.Transport)
|
|
|
|
transport.TLSHandshakeTimeout = 10 * time.Second
|
|
|
|
transport.TLSClientConfig = &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
}
|
2017-11-02 14:30:04 +00:00
|
|
|
if err := http2.ConfigureTransport(transport); err != nil {
|
2017-11-06 17:06:19 +00:00
|
|
|
config.Error = err
|
2017-11-10 22:44:25 +00:00
|
|
|
return config
|
2017-11-02 14:30:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := config.ReadEnvironment(); err != nil {
|
2017-11-06 17:06:19 +00:00
|
|
|
config.Error = err
|
2017-11-10 22:44:25 +00:00
|
|
|
return config
|
2017-11-02 14:30:04 +00:00
|
|
|
}
|
2015-08-22 00:36:19 +00:00
|
|
|
|
2017-11-02 14:30:04 +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
|
2017-11-02 14:30:04 +00:00
|
|
|
// function (to prevent redirects) passing through to it.
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
}
|
|
|
|
|
2015-03-04 21:10:10 +00:00
|
|
|
return config
|
|
|
|
}
|
|
|
|
|
2022-01-20 18:25:26 +00:00
|
|
|
// configureTLS is a lock free version of ConfigureTLS that can be used in
|
|
|
|
// ReadEnvironment where the lock is already hold
|
|
|
|
func (c *Config) configureTLS(t *TLSConfig) error {
|
2016-07-28 00:26:26 +00:00
|
|
|
if c.HttpClient == nil {
|
2017-02-06 22:47:56 +00:00
|
|
|
c.HttpClient = DefaultConfig().HttpClient
|
2016-07-28 00:26:26 +00:00
|
|
|
}
|
2017-11-06 17:06:19 +00:00
|
|
|
clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig
|
2016-07-28 00:26:26 +00:00
|
|
|
|
2016-08-02 20:17:45 +00:00
|
|
|
var clientCert tls.Certificate
|
2016-07-28 00:26:26 +00:00
|
|
|
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
|
2016-07-28 00:26:26 +00:00
|
|
|
}
|
2017-11-06 17:06:19 +00:00
|
|
|
foundClientCert = true
|
2022-01-20 18:25:26 +00:00
|
|
|
c.curlClientCert = t.ClientCert
|
|
|
|
c.curlClientKey = t.ClientKey
|
2017-11-06 17:06:19 +00:00
|
|
|
case t.ClientCert != "" || t.ClientKey != "":
|
2018-04-05 15:49:21 +00:00
|
|
|
return fmt.Errorf("both client cert and client key must be provided")
|
2016-07-28 00:26:26 +00:00
|
|
|
}
|
|
|
|
|
2022-04-06 15:21:46 +00:00
|
|
|
if t.CACert != "" || len(t.CACertBytes) != 0 || t.CAPath != "" {
|
2022-01-20 18:25:26 +00:00
|
|
|
c.curlCACert = t.CACert
|
|
|
|
c.curlCAPath = t.CAPath
|
2017-11-06 17:06:19 +00:00
|
|
|
rootConfig := &rootcerts.Config{
|
2022-04-06 15:21:46 +00:00
|
|
|
CAFile: t.CACert,
|
|
|
|
CACertificate: t.CACertBytes,
|
|
|
|
CAPath: t.CAPath,
|
2017-11-06 17:06:19 +00:00
|
|
|
}
|
|
|
|
if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-28 00:26:26 +00:00
|
|
|
}
|
|
|
|
|
2017-11-06 17:06:19 +00:00
|
|
|
if t.Insecure {
|
|
|
|
clientTLSConfig.InsecureSkipVerify = true
|
|
|
|
}
|
2016-07-28 00:26:26 +00:00
|
|
|
|
|
|
|
if foundClientCert {
|
2017-11-10 23:16:50 +00:00
|
|
|
// 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
|
|
|
|
}
|
2016-07-28 00:26:26 +00:00
|
|
|
}
|
2017-11-06 17:06:19 +00:00
|
|
|
|
2016-08-02 20:17:45 +00:00
|
|
|
if t.TLSServerName != "" {
|
|
|
|
clientTLSConfig.ServerName = t.TLSServerName
|
2016-07-28 00:26:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-20 18:25:26 +00:00
|
|
|
// ConfigureTLS takes a set of TLS configurations and applies those to the
|
|
|
|
// HTTP client.
|
|
|
|
func (c *Config) ConfigureTLS(t *TLSConfig) error {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
|
|
|
|
return c.configureTLS(t)
|
|
|
|
}
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
// ReadEnvironment reads configuration information from the environment. If
|
|
|
|
// there is an error, no configuration value is updated.
|
2015-11-03 19:21:14 +00:00
|
|
|
func (c *Config) ReadEnvironment() error {
|
|
|
|
var envAddress string
|
2019-02-19 21:53:29 +00:00
|
|
|
var envAgentAddress string
|
2015-11-03 19:21:14 +00:00
|
|
|
var envCACert string
|
2022-04-06 15:21:46 +00:00
|
|
|
var envCACertBytes []byte
|
2015-11-03 19:21:14 +00:00
|
|
|
var envCAPath string
|
|
|
|
var envClientCert string
|
|
|
|
var envClientKey string
|
2017-07-18 13:48:31 +00:00
|
|
|
var envClientTimeout time.Duration
|
2016-08-02 20:17:45 +00:00
|
|
|
var envInsecure bool
|
2016-02-24 15:50:10 +00:00
|
|
|
var envTLSServerName string
|
2016-07-11 21:37:46 +00:00
|
|
|
var envMaxRetries *uint64
|
2020-03-11 13:22:58 +00:00
|
|
|
var envSRVLookup bool
|
2018-05-11 14:42:06 +00:00
|
|
|
var limit *rate.Limiter
|
2022-05-24 17:38:51 +00:00
|
|
|
var envVaultProxy string
|
2022-09-30 08:29:37 +00:00
|
|
|
var envVaultDisableRedirects bool
|
2016-07-06 20:42:34 +00:00
|
|
|
|
2016-07-28 00:26:26 +00:00
|
|
|
// Parse the environment variables
|
2015-11-03 19:21:14 +00:00
|
|
|
if v := os.Getenv(EnvVaultAddress); v != "" {
|
|
|
|
envAddress = v
|
|
|
|
}
|
2019-02-28 22:29:28 +00:00
|
|
|
if v := os.Getenv(EnvVaultAgentAddr); v != "" {
|
2019-02-19 21:53:29 +00:00
|
|
|
envAgentAddress = v
|
2019-02-15 01:10:36 +00:00
|
|
|
}
|
2016-07-11 21:37:46 +00:00
|
|
|
if v := os.Getenv(EnvVaultMaxRetries); v != "" {
|
|
|
|
maxRetries, err := strconv.ParseUint(v, 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-07-06 20:42:34 +00:00
|
|
|
}
|
2016-07-11 21:37:46 +00:00
|
|
|
envMaxRetries = &maxRetries
|
2016-07-06 20:42:34 +00:00
|
|
|
}
|
2015-11-03 19:21:14 +00:00
|
|
|
if v := os.Getenv(EnvVaultCACert); v != "" {
|
|
|
|
envCACert = v
|
|
|
|
}
|
2022-04-06 15:21:46 +00:00
|
|
|
if v := os.Getenv(EnvVaultCACertBytes); v != "" {
|
|
|
|
envCACertBytes = []byte(v)
|
|
|
|
}
|
2015-11-03 19:21:14 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-07-18 13:48:31 +00:00
|
|
|
if t := os.Getenv(EnvVaultClientTimeout); t != "" {
|
|
|
|
clientTimeout, err := parseutil.ParseDurationSecond(t)
|
|
|
|
if err != nil {
|
2018-04-05 15:49:21 +00:00
|
|
|
return fmt.Errorf("could not parse %q", EnvVaultClientTimeout)
|
2017-07-18 13:48:31 +00:00
|
|
|
}
|
|
|
|
envClientTimeout = clientTimeout
|
|
|
|
}
|
2019-02-28 22:29:28 +00:00
|
|
|
if v := os.Getenv(EnvVaultSkipVerify); v != "" {
|
2015-11-03 19:21:14 +00:00
|
|
|
var err error
|
|
|
|
envInsecure, err = strconv.ParseBool(v)
|
|
|
|
if err != nil {
|
2022-09-30 08:29:37 +00:00
|
|
|
return fmt.Errorf("could not parse %s", EnvVaultSkipVerify)
|
2015-11-03 19:21:14 +00:00
|
|
|
}
|
|
|
|
}
|
2020-03-11 13:22:58 +00:00
|
|
|
if v := os.Getenv(EnvVaultSRVLookup); v != "" {
|
|
|
|
var err error
|
|
|
|
envSRVLookup, err = strconv.ParseBool(v)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not parse %s", EnvVaultSRVLookup)
|
|
|
|
}
|
|
|
|
}
|
2019-07-17 10:29:25 +00:00
|
|
|
|
2016-02-24 15:50:10 +00:00
|
|
|
if v := os.Getenv(EnvVaultTLSServerName); v != "" {
|
|
|
|
envTLSServerName = v
|
|
|
|
}
|
2015-11-03 19:21:14 +00:00
|
|
|
|
2021-10-06 16:40:31 +00:00
|
|
|
if v := os.Getenv(EnvHTTPProxy); v != "" {
|
2022-05-24 17:38:51 +00:00
|
|
|
envVaultProxy = v
|
|
|
|
}
|
|
|
|
|
|
|
|
// VAULT_PROXY_ADDR supersedes VAULT_HTTP_PROXY
|
|
|
|
if v := os.Getenv(EnvVaultProxyAddr); v != "" {
|
|
|
|
envVaultProxy = v
|
2021-10-06 16:40:31 +00:00
|
|
|
}
|
|
|
|
|
2022-09-30 08:29:37 +00:00
|
|
|
if v := os.Getenv(EnvVaultDisableRedirects); v != "" {
|
|
|
|
var err error
|
|
|
|
envVaultDisableRedirects, err = strconv.ParseBool(v)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not parse %s", EnvVaultDisableRedirects)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.DisableRedirects = envVaultDisableRedirects
|
|
|
|
}
|
|
|
|
|
2016-07-28 00:26:26 +00:00
|
|
|
// Configure the HTTP clients TLS configuration.
|
2016-08-02 20:17:45 +00:00
|
|
|
t := &TLSConfig{
|
|
|
|
CACert: envCACert,
|
2022-04-06 15:21:46 +00:00
|
|
|
CACertBytes: envCACertBytes,
|
2016-08-02 20:17:45 +00:00
|
|
|
CAPath: envCAPath,
|
|
|
|
ClientCert: envClientCert,
|
|
|
|
ClientKey: envClientKey,
|
|
|
|
TLSServerName: envTLSServerName,
|
|
|
|
Insecure: envInsecure,
|
|
|
|
}
|
2017-11-02 14:30:04 +00:00
|
|
|
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
|
2020-03-11 13:22:58 +00:00
|
|
|
c.SRVLookup = envSRVLookup
|
2018-05-11 14:42:06 +00:00
|
|
|
c.Limiter = limit
|
|
|
|
|
2022-01-20 18:25:26 +00:00
|
|
|
if err := c.configureTLS(t); err != nil {
|
2016-07-06 20:42:34 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-11-03 19:21:14 +00:00
|
|
|
if envAddress != "" {
|
|
|
|
c.Address = envAddress
|
|
|
|
}
|
|
|
|
|
2019-02-19 21:53:29 +00:00
|
|
|
if envAgentAddress != "" {
|
|
|
|
c.AgentAddress = envAgentAddress
|
|
|
|
}
|
|
|
|
|
2016-07-11 21:37:46 +00:00
|
|
|
if envMaxRetries != nil {
|
2018-05-09 22:24:41 +00:00
|
|
|
c.MaxRetries = int(*envMaxRetries)
|
2016-07-06 20:42:34 +00:00
|
|
|
}
|
|
|
|
|
2017-07-18 13:48:31 +00:00
|
|
|
if envClientTimeout != 0 {
|
|
|
|
c.Timeout = envClientTimeout
|
|
|
|
}
|
|
|
|
|
2022-05-24 17:38:51 +00:00
|
|
|
if envVaultProxy != "" {
|
|
|
|
u, err := url.Parse(envVaultProxy)
|
2021-10-06 16:40:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
transport := c.HttpClient.Transport.(*http.Transport)
|
2022-05-24 17:38:51 +00:00
|
|
|
transport.Proxy = http.ProxyURL(u)
|
2021-10-06 16:40:31 +00:00
|
|
|
}
|
|
|
|
|
2015-11-03 19:21:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-21 22:16:58 +00:00
|
|
|
// ParseAddress transforms the provided address into a url.URL and handles
|
|
|
|
// the case of Unix domain sockets by setting the DialContext in the
|
|
|
|
// configuration's HttpClient.Transport. This function must be called with
|
|
|
|
// c.modifyLock held for write access.
|
|
|
|
func (c *Config) ParseAddress(address string) (*url.URL, error) {
|
|
|
|
u, err := url.Parse(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Address = address
|
|
|
|
|
|
|
|
if strings.HasPrefix(address, "unix://") {
|
|
|
|
// When the address begins with unix://, always change the transport's
|
|
|
|
// DialContext (to match previous behaviour)
|
|
|
|
socket := strings.TrimPrefix(address, "unix://")
|
|
|
|
|
|
|
|
if transport, ok := c.HttpClient.Transport.(*http.Transport); ok {
|
|
|
|
transport.DialContext = func(context.Context, string, string) (net.Conn, error) {
|
|
|
|
return net.Dial("unix", socket)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 = ""
|
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("attempting to specify unix:// address with non-transport transport")
|
|
|
|
}
|
|
|
|
} else if strings.HasPrefix(c.Address, "unix://") {
|
|
|
|
// When the address being set does not begin with unix:// but the previous
|
|
|
|
// address in the Config did, change the transport's DialContext back to
|
|
|
|
// use the default configuration that cleanhttp uses.
|
|
|
|
|
|
|
|
if transport, ok := c.HttpClient.Transport.(*http.Transport); ok {
|
|
|
|
transport.DialContext = cleanhttp.DefaultPooledTransport().DialContext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return u, 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
|
|
|
|
}
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
// Client is the client to the Vault API. Create a client with NewClient.
|
2015-03-04 21:10:10 +00:00
|
|
|
type Client struct {
|
2021-10-14 18:51:31 +00:00
|
|
|
modifyLock sync.RWMutex
|
|
|
|
addr *url.URL
|
|
|
|
config *Config
|
|
|
|
token string
|
|
|
|
headers http.Header
|
|
|
|
wrappingLookupFunc WrappingLookupFunc
|
|
|
|
mfaCreds []string
|
|
|
|
policyOverride bool
|
|
|
|
requestCallbacks []RequestCallback
|
|
|
|
responseCallbacks []ResponseCallback
|
|
|
|
replicationStateStore *replicationStateStore
|
2017-10-23 20:52:56 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
//
|
2017-11-02 14:30:04 +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) {
|
2017-11-02 14:30:04 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-11-02 14:30:04 +00:00
|
|
|
|
2016-08-12 15:37:13 +00:00
|
|
|
if c == nil {
|
2017-11-02 14:30:04 +00:00
|
|
|
c = def
|
2016-08-12 15:37:13 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
|
2021-06-11 19:15:21 +00:00
|
|
|
if c.MinRetryWait == 0 {
|
|
|
|
c.MinRetryWait = def.MinRetryWait
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.MaxRetryWait == 0 {
|
|
|
|
c.MaxRetryWait = def.MaxRetryWait
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-02-19 21:53:29 +00:00
|
|
|
address := c.Address
|
|
|
|
if c.AgentAddress != "" {
|
|
|
|
address = c.AgentAddress
|
|
|
|
}
|
|
|
|
|
2022-06-21 22:16:58 +00:00
|
|
|
u, err := c.ParseAddress(address)
|
2019-03-05 20:20:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-04-23 15:43:20 +00:00
|
|
|
client := &Client{
|
2019-10-11 22:56:07 +00:00
|
|
|
addr: u,
|
|
|
|
config: c,
|
|
|
|
headers: make(http.Header),
|
2015-04-23 15:43:20 +00:00
|
|
|
}
|
|
|
|
|
2021-10-14 18:51:31 +00:00
|
|
|
if c.ReadYourWrites {
|
|
|
|
client.replicationStateStore = &replicationStateStore{}
|
|
|
|
}
|
|
|
|
|
2019-10-11 22:56:07 +00:00
|
|
|
// Add the VaultRequest SSRF protection header
|
2023-02-06 14:41:56 +00:00
|
|
|
client.headers[RequestHeaderName] = []string{"true"}
|
2019-10-11 22:56:07 +00:00
|
|
|
|
2017-02-27 23:36:21 +00:00
|
|
|
if token := os.Getenv(EnvVaultToken); token != "" {
|
2017-11-02 14:30:04 +00:00
|
|
|
client.token = token
|
2015-04-23 15:43:20 +00:00
|
|
|
}
|
|
|
|
|
2019-03-25 18:23:59 +00:00
|
|
|
if namespace := os.Getenv(EnvVaultNamespace); namespace != "" {
|
2019-04-08 16:45:28 +00:00
|
|
|
client.setNamespace(namespace)
|
2019-03-25 18:23:59 +00:00
|
|
|
}
|
|
|
|
|
2015-04-23 15:43:20 +00:00
|
|
|
return client, nil
|
2015-03-04 21:10:10 +00:00
|
|
|
}
|
2015-03-09 18:38:50 +00:00
|
|
|
|
2020-12-14 19:40:48 +00:00
|
|
|
func (c *Client) CloneConfig() *Config {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
newConfig := DefaultConfig()
|
|
|
|
newConfig.Address = c.config.Address
|
|
|
|
newConfig.AgentAddress = c.config.AgentAddress
|
2021-06-11 19:15:21 +00:00
|
|
|
newConfig.MinRetryWait = c.config.MinRetryWait
|
|
|
|
newConfig.MaxRetryWait = c.config.MaxRetryWait
|
2020-12-14 19:40:48 +00:00
|
|
|
newConfig.MaxRetries = c.config.MaxRetries
|
|
|
|
newConfig.Timeout = c.config.Timeout
|
|
|
|
newConfig.Backoff = c.config.Backoff
|
|
|
|
newConfig.CheckRetry = c.config.CheckRetry
|
2021-05-27 17:25:25 +00:00
|
|
|
newConfig.Logger = c.config.Logger
|
2020-12-14 19:40:48 +00:00
|
|
|
newConfig.Limiter = c.config.Limiter
|
|
|
|
newConfig.SRVLookup = c.config.SRVLookup
|
2021-07-19 21:15:31 +00:00
|
|
|
newConfig.CloneHeaders = c.config.CloneHeaders
|
2021-12-22 22:07:26 +00:00
|
|
|
newConfig.CloneToken = c.config.CloneToken
|
2021-10-14 18:51:31 +00:00
|
|
|
newConfig.ReadYourWrites = c.config.ReadYourWrites
|
2020-12-14 19:40:48 +00:00
|
|
|
|
|
|
|
// we specifically want a _copy_ of the client here, not a pointer to the original one
|
|
|
|
newClient := *c.config.HttpClient
|
|
|
|
newConfig.HttpClient = &newClient
|
|
|
|
|
|
|
|
return newConfig
|
|
|
|
}
|
|
|
|
|
2022-05-20 20:49:11 +00:00
|
|
|
// SetAddress sets the address of Vault in the client. The format of address should be
|
2016-08-12 15:37:13 +00:00
|
|
|
// "<Scheme>://<Host>:<Port>". Setting this on a client will override the
|
|
|
|
// value of VAULT_ADDR environment variable.
|
|
|
|
func (c *Client) SetAddress(addr string) error {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
|
2022-06-21 22:16:58 +00:00
|
|
|
parsedAddr, err := c.config.ParseAddress(addr)
|
2018-05-25 18:38:06 +00:00
|
|
|
if err != nil {
|
2018-04-05 15:49:21 +00:00
|
|
|
return errwrap.Wrapf("failed to set address: {{err}}", err)
|
2016-08-12 15:37:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-25 18:38:06 +00:00
|
|
|
c.addr = parsedAddr
|
2016-08-12 15:37:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-02 16:24:57 +00:00
|
|
|
// Address returns the Vault URL the client is configured to connect to
|
|
|
|
func (c *Client) Address() string {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
|
2016-12-02 16:24:57 +00:00
|
|
|
return c.addr.String()
|
|
|
|
}
|
|
|
|
|
2022-05-20 20:49:11 +00:00
|
|
|
func (c *Client) SetCheckRedirect(f func(*http.Request, []*http.Request) error) {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.HttpClient.CheckRedirect = f
|
|
|
|
}
|
|
|
|
|
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()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2018-05-11 14:42:06 +00:00
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
2018-05-25 18:38:06 +00:00
|
|
|
|
2018-05-11 14:42:06 +00:00
|
|
|
c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst)
|
|
|
|
}
|
|
|
|
|
2020-11-06 19:27:35 +00:00
|
|
|
func (c *Client) Limiter() *rate.Limiter {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.Limiter
|
|
|
|
}
|
|
|
|
|
2021-06-11 19:15:21 +00:00
|
|
|
// SetMinRetryWait sets the minimum time to wait before retrying in the case of certain errors.
|
|
|
|
func (c *Client) SetMinRetryWait(retryWait time.Duration) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.MinRetryWait = retryWait
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) MinRetryWait() time.Duration {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.MinRetryWait
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetMaxRetryWait sets the maximum time to wait before retrying in the case of certain errors.
|
|
|
|
func (c *Client) SetMaxRetryWait(retryWait time.Duration) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.MaxRetryWait = retryWait
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) MaxRetryWait() time.Duration {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.MaxRetryWait
|
|
|
|
}
|
|
|
|
|
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) {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RLock()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2017-11-02 14:30:04 +00:00
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
2017-03-01 17:23:54 +00:00
|
|
|
c.config.MaxRetries = retries
|
|
|
|
}
|
|
|
|
|
2022-06-16 22:06:22 +00:00
|
|
|
func (c *Client) SetMaxIdleConnections(idle int) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.HttpClient.Transport.(*http.Transport).MaxIdleConns = idle
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) MaxIdleConnections() int {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
return c.config.HttpClient.Transport.(*http.Transport).MaxIdleConns
|
|
|
|
}
|
|
|
|
|
2022-07-28 19:59:49 +00:00
|
|
|
func (c *Client) SetDisableKeepAlives(disable bool) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.HttpClient.Transport.(*http.Transport).DisableKeepAlives = disable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) DisableKeepAlives() bool {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.HttpClient.Transport.(*http.Transport).DisableKeepAlives
|
|
|
|
}
|
|
|
|
|
2020-11-06 19:27:35 +00:00
|
|
|
func (c *Client) MaxRetries() int {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.MaxRetries
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SetSRVLookup(srv bool) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.SRVLookup = srv
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SRVLookup() bool {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.SRVLookup
|
|
|
|
}
|
|
|
|
|
2019-10-28 19:54:59 +00:00
|
|
|
// SetCheckRetry sets the CheckRetry function to be used for future requests.
|
|
|
|
func (c *Client) SetCheckRetry(checkRetry retryablehttp.CheckRetry) {
|
|
|
|
c.modifyLock.RLock()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2019-10-28 19:54:59 +00:00
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.CheckRetry = checkRetry
|
|
|
|
}
|
|
|
|
|
2020-11-06 19:27:35 +00:00
|
|
|
func (c *Client) CheckRetry() retryablehttp.CheckRetry {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.CheckRetry
|
|
|
|
}
|
|
|
|
|
2017-07-18 13:48:31 +00:00
|
|
|
// SetClientTimeout sets the client request timeout
|
|
|
|
func (c *Client) SetClientTimeout(timeout time.Duration) {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RLock()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2017-11-02 14:30:04 +00:00
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
2017-07-18 13:48:31 +00:00
|
|
|
c.config.Timeout = timeout
|
|
|
|
}
|
|
|
|
|
2020-11-06 19:27:35 +00:00
|
|
|
func (c *Client) ClientTimeout() time.Duration {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.Timeout
|
|
|
|
}
|
|
|
|
|
2019-02-01 22:13:51 +00:00
|
|
|
func (c *Client) OutputCurlString() bool {
|
|
|
|
c.modifyLock.RLock()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2019-02-01 22:13:51 +00:00
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.OutputCurlString
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SetOutputCurlString(curl bool) {
|
|
|
|
c.modifyLock.RLock()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2019-02-01 22:13:51 +00:00
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.OutputCurlString = curl
|
|
|
|
}
|
|
|
|
|
2022-04-27 23:35:18 +00:00
|
|
|
func (c *Client) OutputPolicy() bool {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.OutputPolicy
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SetOutputPolicy(isSet bool) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.OutputPolicy = isSet
|
|
|
|
}
|
|
|
|
|
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
|
2020-06-08 14:50:48 +00:00
|
|
|
// for a given operation and path.
|
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
|
|
|
func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
return c.wrappingLookupFunc
|
|
|
|
}
|
|
|
|
|
2016-05-16 20:11:33 +00:00
|
|
|
// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
|
2020-06-08 14:50:48 +00:00
|
|
|
// for a given operation and path.
|
2016-05-16 20:11:33 +00:00
|
|
|
func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
2016-05-16 20:11:33 +00:00
|
|
|
c.wrappingLookupFunc = lookupFunc
|
|
|
|
}
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
// 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()
|
2019-04-08 16:45:28 +00:00
|
|
|
c.setNamespace(namespace)
|
|
|
|
}
|
2018-08-09 22:29:03 +00:00
|
|
|
|
2019-04-08 16:45:28 +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)
|
|
|
|
}
|
|
|
|
|
2023-02-06 14:41:56 +00:00
|
|
|
c.headers.Set(NamespaceHeaderName, namespace)
|
2018-08-09 22:29:03 +00:00
|
|
|
}
|
|
|
|
|
2022-04-14 16:50:21 +00:00
|
|
|
// ClearNamespace removes the namespace header if set.
|
2022-02-17 21:08:51 +00:00
|
|
|
func (c *Client) ClearNamespace() {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
2022-04-14 16:50:21 +00:00
|
|
|
if c.headers != nil {
|
2023-02-06 14:41:56 +00:00
|
|
|
c.headers.Del(NamespaceHeaderName)
|
2022-04-14 16:50:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Namespace returns the namespace currently set in this client. It will
|
|
|
|
// return an empty string if there is no namespace set.
|
|
|
|
func (c *Client) Namespace() string {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
if c.headers == nil {
|
|
|
|
return ""
|
|
|
|
}
|
2023-02-06 14:41:56 +00:00
|
|
|
return c.headers.Get(NamespaceHeaderName)
|
2022-04-14 16:50:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// WithNamespace makes a shallow copy of Client, modifies it to use
|
|
|
|
// the given namespace, and returns it. Passing an empty string will
|
|
|
|
// temporarily unset the namespace.
|
|
|
|
func (c *Client) WithNamespace(namespace string) *Client {
|
|
|
|
c2 := *c
|
|
|
|
c2.modifyLock = sync.RWMutex{}
|
|
|
|
c2.headers = c.Headers()
|
|
|
|
if namespace == "" {
|
|
|
|
c2.ClearNamespace()
|
|
|
|
} else {
|
|
|
|
c2.SetNamespace(namespace)
|
|
|
|
}
|
|
|
|
return &c2
|
2022-02-17 21:08:51 +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 {
|
2017-11-02 14:30:04 +00:00
|
|
|
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) {
|
2017-11-02 14:30:04 +00:00
|
|
|
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() {
|
2017-11-02 14:30:04 +00:00
|
|
|
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
|
2020-01-09 22:56:34 +00:00
|
|
|
// copy; to modify it call AddHeader or SetHeaders.
|
2018-08-09 22:29:03 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-01-09 22:56:34 +00:00
|
|
|
// AddHeader allows a single header key/value pair to be added
|
|
|
|
// in a race-safe fashion.
|
|
|
|
func (c *Client) AddHeader(key, value string) {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
2020-01-09 22:56:34 +00:00
|
|
|
c.headers.Add(key, value)
|
|
|
|
}
|
2017-11-02 14:30:04 +00:00
|
|
|
|
2020-01-09 22:56:34 +00:00
|
|
|
// SetHeaders clears all previous headers and uses only the given
|
|
|
|
// ones going forward.
|
|
|
|
func (c *Client) SetHeaders(headers http.Header) {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
2017-10-06 18:27:58 +00:00
|
|
|
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()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
2018-05-09 21:44:53 +00:00
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.Backoff = backoff
|
|
|
|
}
|
|
|
|
|
2021-05-27 17:25:25 +00:00
|
|
|
func (c *Client) SetLogger(logger retryablehttp.LeveledLogger) {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.Logger = logger
|
|
|
|
}
|
|
|
|
|
2021-07-19 21:15:31 +00:00
|
|
|
// SetCloneHeaders to allow headers to be copied whenever the client is cloned.
|
|
|
|
func (c *Client) SetCloneHeaders(cloneHeaders bool) {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.CloneHeaders = cloneHeaders
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloneHeaders gets the configured CloneHeaders value.
|
|
|
|
func (c *Client) CloneHeaders() bool {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.CloneHeaders
|
|
|
|
}
|
|
|
|
|
2021-12-22 22:07:26 +00:00
|
|
|
// SetCloneToken from parent
|
|
|
|
func (c *Client) SetCloneToken(cloneToken bool) {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
|
|
|
c.config.CloneToken = cloneToken
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloneToken gets the configured CloneToken value.
|
|
|
|
func (c *Client) CloneToken() bool {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.CloneToken
|
|
|
|
}
|
|
|
|
|
2021-10-14 18:51:31 +00:00
|
|
|
// SetReadYourWrites to prevent reading stale cluster replication state.
|
|
|
|
func (c *Client) SetReadYourWrites(preventStaleReads bool) {
|
|
|
|
c.modifyLock.Lock()
|
|
|
|
defer c.modifyLock.Unlock()
|
|
|
|
c.config.modifyLock.Lock()
|
|
|
|
defer c.config.modifyLock.Unlock()
|
|
|
|
|
2021-12-21 21:14:39 +00:00
|
|
|
if preventStaleReads {
|
|
|
|
if c.replicationStateStore == nil {
|
|
|
|
c.replicationStateStore = &replicationStateStore{}
|
|
|
|
}
|
2021-10-14 18:51:31 +00:00
|
|
|
} else {
|
|
|
|
c.replicationStateStore = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
c.config.ReadYourWrites = preventStaleReads
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadYourWrites gets the configured value of ReadYourWrites
|
|
|
|
func (c *Client) ReadYourWrites() bool {
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
defer c.config.modifyLock.RUnlock()
|
|
|
|
|
|
|
|
return c.config.ReadYourWrites
|
|
|
|
}
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
// 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
|
2022-01-19 17:43:12 +00:00
|
|
|
// clone. The headers are cloned based on the CloneHeaders property of the
|
|
|
|
// source config
|
2018-11-01 16:26:18 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2017-06-20 03:08:15 +00:00
|
|
|
func (c *Client) Clone() (*Client, error) {
|
2022-01-19 17:43:12 +00:00
|
|
|
return c.clone(c.config.CloneHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloneWithHeaders creates a new client similar to Clone, with the difference
|
|
|
|
// being that the headers are always cloned
|
|
|
|
func (c *Client) CloneWithHeaders() (*Client, error) {
|
|
|
|
return c.clone(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// clone creates a new client, with the headers being cloned based on the
|
|
|
|
// passed in cloneheaders boolean
|
|
|
|
func (c *Client) clone(cloneHeaders bool) (*Client, error) {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RLock()
|
2020-11-06 19:27:35 +00:00
|
|
|
defer c.modifyLock.RUnlock()
|
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
config := c.config
|
2020-11-06 19:27:35 +00:00
|
|
|
config.modifyLock.RLock()
|
|
|
|
defer config.modifyLock.RUnlock()
|
2017-11-02 14:30:04 +00:00
|
|
|
|
|
|
|
newConfig := &Config{
|
2022-04-08 16:58:50 +00:00
|
|
|
Address: config.Address,
|
|
|
|
HttpClient: config.HttpClient,
|
|
|
|
MinRetryWait: config.MinRetryWait,
|
|
|
|
MaxRetryWait: config.MaxRetryWait,
|
|
|
|
MaxRetries: config.MaxRetries,
|
|
|
|
Timeout: config.Timeout,
|
|
|
|
Backoff: config.Backoff,
|
|
|
|
CheckRetry: config.CheckRetry,
|
|
|
|
Logger: config.Logger,
|
|
|
|
Limiter: config.Limiter,
|
|
|
|
AgentAddress: config.AgentAddress,
|
|
|
|
SRVLookup: config.SRVLookup,
|
|
|
|
CloneHeaders: config.CloneHeaders,
|
|
|
|
CloneToken: config.CloneToken,
|
|
|
|
ReadYourWrites: config.ReadYourWrites,
|
2020-11-06 19:27:35 +00:00
|
|
|
}
|
|
|
|
client, err := NewClient(newConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-11-02 14:30:04 +00:00
|
|
|
}
|
|
|
|
|
2022-01-19 17:43:12 +00:00
|
|
|
if cloneHeaders {
|
2021-07-19 21:15:31 +00:00
|
|
|
client.SetHeaders(c.Headers().Clone())
|
|
|
|
}
|
|
|
|
|
2021-12-22 22:07:26 +00:00
|
|
|
if config.CloneToken {
|
|
|
|
client.SetToken(c.token)
|
|
|
|
}
|
|
|
|
|
2021-10-14 18:51:31 +00:00
|
|
|
client.replicationStateStore = c.replicationStateStore
|
|
|
|
|
2020-11-06 19:27:35 +00:00
|
|
|
return client, nil
|
2017-06-20 03:08:15 +00:00
|
|
|
}
|
|
|
|
|
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) {
|
2017-11-02 14:30:04 +00:00
|
|
|
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.
|
2017-04-13 18:06:38 +00:00
|
|
|
func (c *Client) NewRequest(method, requestPath string) *Request {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RLock()
|
2018-05-25 18:38:06 +00:00
|
|
|
addr := c.addr
|
|
|
|
token := c.token
|
|
|
|
mfaCreds := c.mfaCreds
|
|
|
|
wrappingLookupFunc := c.wrappingLookupFunc
|
|
|
|
policyOverride := c.policyOverride
|
|
|
|
c.modifyLock.RUnlock()
|
2017-11-02 14:30:04 +00:00
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
host := addr.Host
|
2017-08-02 23:19:06 +00:00
|
|
|
// 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
|
2020-03-11 13:22:58 +00:00
|
|
|
// Internet Draft specifies that the SRV record is ignored if a port is given
|
|
|
|
if addr.Port() == "" && c.config.SRVLookup {
|
|
|
|
_, addrs, err := net.LookupSRV("http", "tcp", addr.Hostname())
|
|
|
|
if err == nil && len(addrs) > 0 {
|
|
|
|
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
|
2017-08-02 23:19:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-22 00:36:19 +00:00
|
|
|
req := &Request{
|
2015-03-09 18:38:50 +00:00
|
|
|
Method: method,
|
|
|
|
URL: &url.URL{
|
2018-05-25 18:38:06 +00:00
|
|
|
User: addr.User,
|
|
|
|
Scheme: addr.Scheme,
|
2017-08-02 23:19:06 +00:00
|
|
|
Host: host,
|
2018-05-25 18:38:06 +00:00
|
|
|
Path: path.Join(addr.Path, requestPath),
|
2015-03-09 18:38:50 +00:00
|
|
|
},
|
2020-03-11 13:22:58 +00:00
|
|
|
Host: addr.Host,
|
2018-05-25 18:38:06 +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 {
|
2017-04-13 18:06:38 +00:00
|
|
|
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:
|
2017-04-13 18:06:38 +00:00
|
|
|
lookupPath = requestPath
|
2016-09-29 04:01:28 +00:00
|
|
|
}
|
2017-10-23 20:52:56 +00:00
|
|
|
|
2018-05-25 18:38:06 +00:00
|
|
|
req.MFAHeaderVals = mfaCreds
|
2017-10-23 20:52:56 +00:00
|
|
|
|
2018-05-25 18:38:06 +00:00
|
|
|
if wrappingLookupFunc != nil {
|
|
|
|
req.WrapTTL = wrappingLookupFunc(method, lookupPath)
|
2016-09-29 04:01:28 +00:00
|
|
|
} else {
|
|
|
|
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
|
2016-05-16 20:11:33 +00:00
|
|
|
}
|
2018-05-25 18:38:06 +00:00
|
|
|
|
2019-10-17 15:48:15 +00:00
|
|
|
req.Headers = c.Headers()
|
2018-05-25 18:38:06 +00:00
|
|
|
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.
|
2022-03-23 21:47:43 +00:00
|
|
|
//
|
|
|
|
// Deprecated: This method should not be used directly. Use higher level
|
|
|
|
// methods instead.
|
2015-03-11 18:33:20 +00:00
|
|
|
func (c *Client) RawRequest(r *Request) (*Response, error) {
|
2018-07-24 22:49:55 +00:00
|
|
|
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.
|
2022-03-23 21:47:43 +00:00
|
|
|
//
|
|
|
|
// Deprecated: This method should not be used directly. Use higher level
|
|
|
|
// methods instead.
|
2018-07-24 22:49:55 +00:00
|
|
|
func (c *Client) RawRequestWithContext(ctx context.Context, r *Request) (*Response, error) {
|
2022-03-23 21:47:43 +00:00
|
|
|
// Note: we purposefully do not call cancel manually. The reason is
|
|
|
|
// when canceled, the request.Body will EOF when reading due to the way
|
|
|
|
// it streams data in. Cancel will still be run when the timeout is
|
|
|
|
// hit, so this doesn't really harm anything.
|
|
|
|
ctx, _ = c.withConfiguredTimeout(ctx)
|
|
|
|
return c.rawRequestWithContext(ctx, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) rawRequestWithContext(ctx context.Context, r *Request) (*Response, error) {
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RLock()
|
2018-05-25 18:38:06 +00:00
|
|
|
token := c.token
|
2018-05-11 14:42:06 +00:00
|
|
|
|
2018-05-25 18:38:06 +00:00
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
limiter := c.config.Limiter
|
2021-06-11 19:15:21 +00:00
|
|
|
minRetryWait := c.config.MinRetryWait
|
|
|
|
maxRetryWait := c.config.MaxRetryWait
|
2018-05-25 18:38:06 +00:00
|
|
|
maxRetries := c.config.MaxRetries
|
2019-10-28 19:54:59 +00:00
|
|
|
checkRetry := c.config.CheckRetry
|
2018-05-25 18:38:06 +00:00
|
|
|
backoff := c.config.Backoff
|
|
|
|
httpClient := c.config.HttpClient
|
2023-02-06 14:41:56 +00:00
|
|
|
ns := c.headers.Get(NamespaceHeaderName)
|
2019-02-01 22:13:51 +00:00
|
|
|
outputCurlString := c.config.OutputCurlString
|
2022-04-27 23:35:18 +00:00
|
|
|
outputPolicy := c.config.OutputPolicy
|
2021-05-27 17:25:25 +00:00
|
|
|
logger := c.config.Logger
|
2022-09-30 08:29:37 +00:00
|
|
|
disableRedirects := c.config.DisableRedirects
|
2018-05-25 18:38:06 +00:00
|
|
|
c.config.modifyLock.RUnlock()
|
2018-05-11 14:42:06 +00:00
|
|
|
|
2017-11-02 14:30:04 +00:00
|
|
|
c.modifyLock.RUnlock()
|
|
|
|
|
2022-04-14 16:50:21 +00:00
|
|
|
// ensure that the most current namespace setting is used at the time of the call
|
|
|
|
// e.g. calls using (*Client).WithNamespace
|
|
|
|
switch ns {
|
|
|
|
case "":
|
2023-02-06 14:41:56 +00:00
|
|
|
r.Headers.Del(NamespaceHeaderName)
|
2022-04-14 16:50:21 +00:00
|
|
|
default:
|
2023-02-06 14:41:56 +00:00
|
|
|
r.Headers.Set(NamespaceHeaderName, ns)
|
2022-04-14 16:50:21 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 11:58:10 +00:00
|
|
|
for _, cb := range c.requestCallbacks {
|
|
|
|
cb(r)
|
|
|
|
}
|
|
|
|
|
2021-10-14 18:51:31 +00:00
|
|
|
if c.config.ReadYourWrites {
|
|
|
|
c.replicationStateStore.requireState(r)
|
|
|
|
}
|
|
|
|
|
2018-05-25 18:38:06 +00:00
|
|
|
if limiter != nil {
|
2018-07-24 22:49:55 +00:00
|
|
|
limiter.Wait(ctx)
|
2018-05-25 18:38:06 +00:00
|
|
|
}
|
|
|
|
|
2022-03-14 17:13:33 +00:00
|
|
|
// check the token before potentially erroring from the API
|
|
|
|
if err := validateToken(token); err != nil {
|
|
|
|
return nil, err
|
2018-01-25 00:57:49 +00:00
|
|
|
}
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2019-02-01 22:13:51 +00:00
|
|
|
if outputCurlString {
|
2021-06-14 15:09:29 +00:00
|
|
|
LastOutputStringError = &OutputStringError{
|
|
|
|
Request: req,
|
|
|
|
TLSSkipVerify: c.config.HttpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify,
|
2022-01-20 18:25:26 +00:00
|
|
|
ClientCert: c.config.curlClientCert,
|
|
|
|
ClientKey: c.config.curlClientKey,
|
|
|
|
ClientCACert: c.config.curlCACert,
|
|
|
|
ClientCAPath: c.config.curlCAPath,
|
2021-06-14 15:09:29 +00:00
|
|
|
}
|
2019-02-01 22:13:51 +00:00
|
|
|
return nil, LastOutputStringError
|
|
|
|
}
|
|
|
|
|
2022-04-27 23:35:18 +00:00
|
|
|
if outputPolicy {
|
|
|
|
LastOutputPolicyError = &OutputPolicyError{
|
|
|
|
method: req.Method,
|
|
|
|
path: strings.TrimPrefix(req.URL.Path, "/v1"),
|
2023-02-21 15:12:45 +00:00
|
|
|
params: req.URL.Query(),
|
2022-04-27 23:35:18 +00:00
|
|
|
}
|
|
|
|
return nil, LastOutputPolicyError
|
|
|
|
}
|
|
|
|
|
2018-07-24 22:49:55 +00:00
|
|
|
req.Request = req.Request.WithContext(ctx)
|
2018-05-25 18:38:06 +00:00
|
|
|
|
2018-05-09 21:44:53 +00:00
|
|
|
if backoff == nil {
|
|
|
|
backoff = retryablehttp.LinearJitterBackoff
|
|
|
|
}
|
2015-03-09 18:38:50 +00:00
|
|
|
|
2019-10-28 19:54:59 +00:00
|
|
|
if checkRetry == nil {
|
2021-02-24 11:58:10 +00:00
|
|
|
checkRetry = DefaultRetryPolicy
|
2019-10-28 19:54:59 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 21:44:53 +00:00
|
|
|
client := &retryablehttp.Client{
|
2018-05-25 18:38:06 +00:00
|
|
|
HTTPClient: httpClient,
|
2021-06-11 19:15:21 +00:00
|
|
|
RetryWaitMin: minRetryWait,
|
|
|
|
RetryWaitMax: maxRetryWait,
|
2018-05-25 18:38:06 +00:00
|
|
|
RetryMax: maxRetries,
|
2018-05-09 21:44:53 +00:00
|
|
|
Backoff: backoff,
|
2019-10-28 19:54:59 +00:00
|
|
|
CheckRetry: checkRetry,
|
2021-05-27 17:25:25 +00:00
|
|
|
Logger: logger,
|
2018-05-09 22:11:08 +00:00
|
|
|
ErrorHandler: retryablehttp.PassthroughErrorHandler,
|
2018-05-09 21:44:53 +00:00
|
|
|
}
|
2016-07-06 20:42:34 +00:00
|
|
|
|
2015-04-07 18:15:20 +00:00
|
|
|
var result *Response
|
2016-07-06 20:42:34 +00:00
|
|
|
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") {
|
2022-03-14 17:13:33 +00:00
|
|
|
err = errwrap.Wrapf("{{err}}\n\n"+TLSErrorString, 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
|
|
|
}
|
|
|
|
|
2022-09-30 08:29:37 +00:00
|
|
|
// Check for a redirect, only allowing for a single redirect (if redirects aren't disabled)
|
|
|
|
if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 && !disableRedirects {
|
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
|
|
|
|
}
|
|
|
|
|
2021-02-24 11:58:10 +00:00
|
|
|
if result != nil {
|
|
|
|
for _, cb := range c.responseCallbacks {
|
|
|
|
cb(result)
|
|
|
|
}
|
2021-10-14 18:51:31 +00:00
|
|
|
|
|
|
|
if c.config.ReadYourWrites {
|
|
|
|
c.replicationStateStore.recordState(result)
|
|
|
|
}
|
2021-02-24 11:58:10 +00:00
|
|
|
}
|
2015-03-11 18:46:07 +00:00
|
|
|
if err := result.Error(); err != nil {
|
2015-04-07 18:15:20 +00:00
|
|
|
return result, err
|
2015-03-11 18:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
2015-03-09 18:38:50 +00:00
|
|
|
}
|
2021-02-24 11:58:10 +00:00
|
|
|
|
2022-03-14 17:13:33 +00:00
|
|
|
// httpRequestWithContext avoids the use of the go-retryable library found in RawRequestWithContext and is
|
|
|
|
// useful when making calls where a net/http client is desirable. A single redirect (status code 301, 302,
|
|
|
|
// or 307) will be followed but all retry and timeout logic is the responsibility of the caller as is
|
|
|
|
// closing the Response body.
|
|
|
|
func (c *Client) httpRequestWithContext(ctx context.Context, r *Request) (*Response, error) {
|
|
|
|
req, err := http.NewRequestWithContext(ctx, r.Method, r.URL.RequestURI(), r.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.modifyLock.RLock()
|
|
|
|
token := c.token
|
|
|
|
|
|
|
|
c.config.modifyLock.RLock()
|
|
|
|
limiter := c.config.Limiter
|
|
|
|
httpClient := c.config.HttpClient
|
|
|
|
outputCurlString := c.config.OutputCurlString
|
2022-04-27 23:35:18 +00:00
|
|
|
outputPolicy := c.config.OutputPolicy
|
2022-09-30 08:29:37 +00:00
|
|
|
disableRedirects := c.config.DisableRedirects
|
2022-04-27 23:35:18 +00:00
|
|
|
|
2022-04-14 16:50:21 +00:00
|
|
|
// add headers
|
2022-03-14 17:13:33 +00:00
|
|
|
if c.headers != nil {
|
|
|
|
for header, vals := range c.headers {
|
|
|
|
for _, val := range vals {
|
|
|
|
req.Header.Add(header, val)
|
|
|
|
}
|
|
|
|
}
|
2022-04-14 16:50:21 +00:00
|
|
|
// explicitly set the namespace header to current client
|
2023-02-06 14:41:56 +00:00
|
|
|
if ns := c.headers.Get(NamespaceHeaderName); ns != "" {
|
|
|
|
r.Headers.Set(NamespaceHeaderName, ns)
|
2022-04-14 16:50:21 +00:00
|
|
|
}
|
2022-03-14 17:13:33 +00:00
|
|
|
}
|
2022-04-14 16:50:21 +00:00
|
|
|
|
2022-03-14 17:13:33 +00:00
|
|
|
c.config.modifyLock.RUnlock()
|
|
|
|
c.modifyLock.RUnlock()
|
|
|
|
|
2022-04-27 23:35:18 +00:00
|
|
|
// OutputCurlString and OutputPolicy logic rely on the request type to be retryable.Request
|
2022-03-14 17:13:33 +00:00
|
|
|
if outputCurlString {
|
|
|
|
return nil, fmt.Errorf("output-curl-string is not implemented for this request")
|
|
|
|
}
|
2022-04-27 23:35:18 +00:00
|
|
|
if outputPolicy {
|
|
|
|
return nil, fmt.Errorf("output-policy is not implemented for this request")
|
|
|
|
}
|
2022-03-14 17:13:33 +00:00
|
|
|
|
|
|
|
req.URL.User = r.URL.User
|
|
|
|
req.URL.Scheme = r.URL.Scheme
|
|
|
|
req.URL.Host = r.URL.Host
|
|
|
|
req.Host = r.URL.Host
|
|
|
|
|
|
|
|
if len(r.ClientToken) != 0 {
|
2023-02-06 14:41:56 +00:00
|
|
|
req.Header.Set(AuthHeaderName, r.ClientToken)
|
2022-03-14 17:13:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.WrapTTL) != 0 {
|
|
|
|
req.Header.Set("X-Vault-Wrap-TTL", r.WrapTTL)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.MFAHeaderVals) != 0 {
|
|
|
|
for _, mfaHeaderVal := range r.MFAHeaderVals {
|
|
|
|
req.Header.Add("X-Vault-MFA", mfaHeaderVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.PolicyOverride {
|
|
|
|
req.Header.Set("X-Vault-Policy-Override", "true")
|
|
|
|
}
|
|
|
|
|
|
|
|
if limiter != nil {
|
|
|
|
limiter.Wait(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check the token before potentially erroring from the API
|
|
|
|
if err := validateToken(token); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var result *Response
|
|
|
|
|
|
|
|
resp, err := httpClient.Do(req)
|
|
|
|
|
|
|
|
if resp != nil {
|
|
|
|
result = &Response{Response: resp}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if strings.Contains(err.Error(), "tls: oversized") {
|
|
|
|
err = errwrap.Wrapf("{{err}}\n\n"+TLSErrorString, err)
|
|
|
|
}
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
2022-09-30 08:29:37 +00:00
|
|
|
// Check for a redirect, only allowing for a single redirect, if redirects aren't disabled
|
|
|
|
if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && !disableRedirects {
|
2022-03-14 17:13:33 +00:00
|
|
|
// Parse the updated location
|
|
|
|
respLoc, err := resp.Location()
|
|
|
|
if err != nil {
|
|
|
|
return result, fmt.Errorf("redirect failed: %s", 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
|
|
|
|
req.URL = respLoc
|
|
|
|
|
|
|
|
// Reset the request body if any
|
|
|
|
if err := r.ResetJSONBody(); err != nil {
|
|
|
|
return result, fmt.Errorf("redirect failed: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retry the request
|
|
|
|
resp, err = httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return result, fmt.Errorf("redirect failed: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := result.Error(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
type (
|
|
|
|
RequestCallback func(*Request)
|
|
|
|
ResponseCallback func(*Response)
|
|
|
|
)
|
2021-02-24 11:58:10 +00:00
|
|
|
|
|
|
|
// WithRequestCallbacks makes a shallow clone of Client, modifies it to use
|
|
|
|
// the given callbacks, and returns it. Each of the callbacks will be invoked
|
|
|
|
// on every outgoing request. A client may be used to issue requests
|
|
|
|
// concurrently; any locking needed by callbacks invoked concurrently is the
|
|
|
|
// callback's responsibility.
|
|
|
|
func (c *Client) WithRequestCallbacks(callbacks ...RequestCallback) *Client {
|
|
|
|
c2 := *c
|
|
|
|
c2.modifyLock = sync.RWMutex{}
|
|
|
|
c2.requestCallbacks = callbacks
|
|
|
|
return &c2
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithResponseCallbacks makes a shallow clone of Client, modifies it to use
|
|
|
|
// the given callbacks, and returns it. Each of the callbacks will be invoked
|
|
|
|
// on every received response. A client may be used to issue requests
|
|
|
|
// concurrently; any locking needed by callbacks invoked concurrently is the
|
|
|
|
// callback's responsibility.
|
|
|
|
func (c *Client) WithResponseCallbacks(callbacks ...ResponseCallback) *Client {
|
|
|
|
c2 := *c
|
|
|
|
c2.modifyLock = sync.RWMutex{}
|
|
|
|
c2.responseCallbacks = callbacks
|
|
|
|
return &c2
|
|
|
|
}
|
|
|
|
|
2022-03-23 21:47:43 +00:00
|
|
|
// withConfiguredTimeout wraps the context with a timeout from the client configuration.
|
|
|
|
func (c *Client) withConfiguredTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
|
|
|
|
timeout := c.ClientTimeout()
|
|
|
|
|
|
|
|
if timeout > 0 {
|
|
|
|
return context.WithTimeout(ctx, timeout)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctx, func() {}
|
|
|
|
}
|
|
|
|
|
2021-02-24 11:58:10 +00:00
|
|
|
// RecordState returns a response callback that will record the state returned
|
|
|
|
// by Vault in a response header.
|
|
|
|
func RecordState(state *string) ResponseCallback {
|
|
|
|
return func(resp *Response) {
|
2021-10-14 18:51:31 +00:00
|
|
|
*state = resp.Header.Get(HeaderIndex)
|
2021-02-24 11:58:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RequireState returns a request callback that will add a request header to
|
|
|
|
// specify the state we require of Vault. This state was obtained from a
|
|
|
|
// response header seen previous, probably captured with RecordState.
|
|
|
|
func RequireState(states ...string) RequestCallback {
|
|
|
|
return func(req *Request) {
|
|
|
|
for _, s := range states {
|
2021-10-14 18:51:31 +00:00
|
|
|
req.Headers.Add(HeaderIndex, s)
|
2021-02-24 11:58:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-06 17:57:06 +00:00
|
|
|
// compareReplicationStates returns 1 if s1 is newer or identical, -1 if s1 is older, and 0
|
|
|
|
// if neither s1 or s2 is strictly greater. An error is returned if s1 or s2
|
|
|
|
// are invalid or from different clusters.
|
|
|
|
func compareReplicationStates(s1, s2 string) (int, error) {
|
|
|
|
w1, err := ParseReplicationState(s1, nil)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
w2, err := ParseReplicationState(s2, nil)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if w1.ClusterID != w2.ClusterID {
|
|
|
|
return 0, fmt.Errorf("can't compare replication states with different ClusterIDs")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case w1.LocalIndex >= w2.LocalIndex && w1.ReplicatedIndex >= w2.ReplicatedIndex:
|
|
|
|
return 1, nil
|
|
|
|
// We've already handled the case where both are equal above, so really we're
|
|
|
|
// asking here if one or both are lesser.
|
|
|
|
case w1.LocalIndex <= w2.LocalIndex && w1.ReplicatedIndex <= w2.ReplicatedIndex:
|
|
|
|
return -1, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MergeReplicationStates returns a merged array of replication states by iterating
|
|
|
|
// through all states in `old`. An iterated state is merged to the result before `new`
|
|
|
|
// based on the result of compareReplicationStates
|
|
|
|
func MergeReplicationStates(old []string, new string) []string {
|
|
|
|
if len(old) == 0 || len(old) > 2 {
|
|
|
|
return []string{new}
|
|
|
|
}
|
|
|
|
|
|
|
|
var ret []string
|
|
|
|
for _, o := range old {
|
|
|
|
c, err := compareReplicationStates(o, new)
|
|
|
|
if err != nil {
|
|
|
|
return []string{new}
|
|
|
|
}
|
|
|
|
switch c {
|
|
|
|
case 1:
|
|
|
|
ret = append(ret, o)
|
|
|
|
case -1:
|
|
|
|
ret = append(ret, new)
|
|
|
|
case 0:
|
|
|
|
ret = append(ret, o, new)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strutil.RemoveDuplicates(ret, false)
|
|
|
|
}
|
|
|
|
|
2023-02-06 14:41:56 +00:00
|
|
|
type WALState struct {
|
|
|
|
ClusterID string
|
|
|
|
LocalIndex uint64
|
|
|
|
ReplicatedIndex uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseReplicationState(raw string, hmacKey []byte) (*WALState, error) {
|
2021-10-06 17:57:06 +00:00
|
|
|
cooked, err := base64.StdEncoding.DecodeString(raw)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
s := string(cooked)
|
|
|
|
|
|
|
|
lastIndex := strings.LastIndexByte(s, ':')
|
|
|
|
if lastIndex == -1 {
|
|
|
|
return nil, fmt.Errorf("invalid full state header format")
|
|
|
|
}
|
|
|
|
state, stateHMACRaw := s[:lastIndex], s[lastIndex+1:]
|
|
|
|
stateHMAC, err := hex.DecodeString(stateHMACRaw)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid state header HMAC: %v, %w", stateHMACRaw, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(hmacKey) != 0 {
|
|
|
|
hm := hmac.New(sha256.New, hmacKey)
|
|
|
|
hm.Write([]byte(state))
|
|
|
|
if !hmac.Equal(hm.Sum(nil), stateHMAC) {
|
|
|
|
return nil, fmt.Errorf("invalid state header HMAC (mismatch)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pieces := strings.Split(state, ":")
|
|
|
|
if len(pieces) != 4 || pieces[0] != "v1" || pieces[1] == "" {
|
|
|
|
return nil, fmt.Errorf("invalid state header format")
|
|
|
|
}
|
|
|
|
localIndex, err := strconv.ParseUint(pieces[2], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid local index in state header: %w", err)
|
|
|
|
}
|
|
|
|
replicatedIndex, err := strconv.ParseUint(pieces[3], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid replicated index in state header: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-02-06 14:41:56 +00:00
|
|
|
return &WALState{
|
2021-10-06 17:57:06 +00:00
|
|
|
ClusterID: pieces[1],
|
|
|
|
LocalIndex: localIndex,
|
|
|
|
ReplicatedIndex: replicatedIndex,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-02-24 11:58:10 +00:00
|
|
|
// ForwardInconsistent returns a request callback that will add a request
|
|
|
|
// header which says: if the state required isn't present on the node receiving
|
|
|
|
// this request, forward it to the active node. This should be used in
|
|
|
|
// conjunction with RequireState.
|
|
|
|
func ForwardInconsistent() RequestCallback {
|
|
|
|
return func(req *Request) {
|
2022-02-17 20:17:59 +00:00
|
|
|
req.Headers.Set(HeaderInconsistent, "forward-active-node")
|
2021-02-24 11:58:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForwardAlways returns a request callback which adds a header telling any
|
|
|
|
// performance standbys handling the request to forward it to the active node.
|
|
|
|
// This feature must be enabled in Vault's configuration.
|
|
|
|
func ForwardAlways() RequestCallback {
|
|
|
|
return func(req *Request) {
|
2022-02-17 20:17:59 +00:00
|
|
|
req.Headers.Set(HeaderForward, "active-node")
|
2021-02-24 11:58:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultRetryPolicy is the default retry policy used by new Client objects.
|
|
|
|
// It is the same as retryablehttp.DefaultRetryPolicy except that it also retries
|
|
|
|
// 412 requests, which are returned by Vault when a X-Vault-Index header isn't
|
|
|
|
// satisfied.
|
|
|
|
func DefaultRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) {
|
|
|
|
retry, err := retryablehttp.DefaultRetryPolicy(ctx, resp, err)
|
|
|
|
if err != nil || retry {
|
|
|
|
return retry, err
|
|
|
|
}
|
2021-02-25 18:16:17 +00:00
|
|
|
if resp != nil && resp.StatusCode == 412 {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
2021-02-24 11:58:10 +00:00
|
|
|
}
|
2021-10-14 18:51:31 +00:00
|
|
|
|
|
|
|
// replicationStateStore is used to track cluster replication states
|
|
|
|
// in order to ensure proper read-after-write semantics for a Client.
|
|
|
|
type replicationStateStore struct {
|
|
|
|
m sync.RWMutex
|
|
|
|
store []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// recordState updates the store's replication states with the merger of all
|
|
|
|
// states.
|
|
|
|
func (w *replicationStateStore) recordState(resp *Response) {
|
|
|
|
w.m.Lock()
|
|
|
|
defer w.m.Unlock()
|
|
|
|
newState := resp.Header.Get(HeaderIndex)
|
|
|
|
if newState != "" {
|
|
|
|
w.store = MergeReplicationStates(w.store, newState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// requireState updates the Request with the store's current replication states.
|
|
|
|
func (w *replicationStateStore) requireState(req *Request) {
|
|
|
|
w.m.RLock()
|
|
|
|
defer w.m.RUnlock()
|
|
|
|
for _, s := range w.store {
|
|
|
|
req.Headers.Add(HeaderIndex, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// states currently stored.
|
|
|
|
func (w *replicationStateStore) states() []string {
|
|
|
|
w.m.RLock()
|
|
|
|
defer w.m.RUnlock()
|
|
|
|
c := make([]string, len(w.store))
|
|
|
|
copy(c, w.store)
|
|
|
|
return c
|
|
|
|
}
|
2022-03-14 17:13:33 +00:00
|
|
|
|
|
|
|
// validateToken will check for non-printable characters to prevent a call that will fail at the api
|
|
|
|
func validateToken(t string) error {
|
|
|
|
idx := strings.IndexFunc(t, func(c rune) bool {
|
|
|
|
return !unicode.IsPrint(c)
|
|
|
|
})
|
|
|
|
if idx != -1 {
|
|
|
|
return fmt.Errorf("configured Vault token contains non-printable characters and cannot be used")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|