Added flag and env var which will disable client redirection (#17352)

* Added flag and env var which will disable client redirection

* Added changelog

* Docs fix for unsaved file, and test single request made

* Updated test for case when redirect is enabled, updated docs based on suggestions
This commit is contained in:
Peter Wilson 2022-09-30 09:29:37 +01:00 committed by GitHub
parent c88d53c708
commit 42ba1384ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 41 deletions

View File

@ -33,29 +33,30 @@ import (
) )
const ( const (
EnvVaultAddress = "VAULT_ADDR" EnvVaultAddress = "VAULT_ADDR"
EnvVaultAgentAddr = "VAULT_AGENT_ADDR" EnvVaultAgentAddr = "VAULT_AGENT_ADDR"
EnvVaultCACert = "VAULT_CACERT" EnvVaultCACert = "VAULT_CACERT"
EnvVaultCACertBytes = "VAULT_CACERT_BYTES" EnvVaultCACertBytes = "VAULT_CACERT_BYTES"
EnvVaultCAPath = "VAULT_CAPATH" EnvVaultCAPath = "VAULT_CAPATH"
EnvVaultClientCert = "VAULT_CLIENT_CERT" EnvVaultClientCert = "VAULT_CLIENT_CERT"
EnvVaultClientKey = "VAULT_CLIENT_KEY" EnvVaultClientKey = "VAULT_CLIENT_KEY"
EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT" EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT"
EnvVaultSRVLookup = "VAULT_SRV_LOOKUP" EnvVaultSRVLookup = "VAULT_SRV_LOOKUP"
EnvVaultSkipVerify = "VAULT_SKIP_VERIFY" EnvVaultSkipVerify = "VAULT_SKIP_VERIFY"
EnvVaultNamespace = "VAULT_NAMESPACE" EnvVaultNamespace = "VAULT_NAMESPACE"
EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME" EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
EnvVaultWrapTTL = "VAULT_WRAP_TTL" EnvVaultWrapTTL = "VAULT_WRAP_TTL"
EnvVaultMaxRetries = "VAULT_MAX_RETRIES" EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
EnvVaultToken = "VAULT_TOKEN" EnvVaultToken = "VAULT_TOKEN"
EnvVaultMFA = "VAULT_MFA" EnvVaultMFA = "VAULT_MFA"
EnvRateLimit = "VAULT_RATE_LIMIT" EnvRateLimit = "VAULT_RATE_LIMIT"
EnvHTTPProxy = "VAULT_HTTP_PROXY" EnvHTTPProxy = "VAULT_HTTP_PROXY"
EnvVaultProxyAddr = "VAULT_PROXY_ADDR" EnvVaultProxyAddr = "VAULT_PROXY_ADDR"
HeaderIndex = "X-Vault-Index" EnvVaultDisableRedirects = "VAULT_DISABLE_REDIRECTS"
HeaderForward = "X-Vault-Forward" HeaderIndex = "X-Vault-Index"
HeaderInconsistent = "X-Vault-Inconsistent" HeaderForward = "X-Vault-Forward"
TLSErrorString = "This error usually means that the server is running with TLS disabled\n" + HeaderInconsistent = "X-Vault-Inconsistent"
TLSErrorString = "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" + "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" + "on the server or run the client with -address set to an address\n" +
"that uses the http protocol:\n\n" + "that uses the http protocol:\n\n" +
@ -176,6 +177,16 @@ type Config struct {
// since there will be a performance penalty paid upon each request. // since there will be a performance penalty paid upon each request.
// This feature requires Enterprise server-side. // This feature requires Enterprise server-side.
ReadYourWrites bool ReadYourWrites bool
// 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
} }
// TLSConfig contains the parameters needed to configure TLS on the HTTP client // TLSConfig contains the parameters needed to configure TLS on the HTTP client
@ -340,6 +351,7 @@ func (c *Config) ReadEnvironment() error {
var envSRVLookup bool var envSRVLookup bool
var limit *rate.Limiter var limit *rate.Limiter
var envVaultProxy string var envVaultProxy string
var envVaultDisableRedirects bool
// Parse the environment variables // Parse the environment variables
if v := os.Getenv(EnvVaultAddress); v != "" { if v := os.Getenv(EnvVaultAddress); v != "" {
@ -388,7 +400,7 @@ func (c *Config) ReadEnvironment() error {
var err error var err error
envInsecure, err = strconv.ParseBool(v) envInsecure, err = strconv.ParseBool(v)
if err != nil { if err != nil {
return fmt.Errorf("could not parse VAULT_SKIP_VERIFY") return fmt.Errorf("could not parse %s", EnvVaultSkipVerify)
} }
} }
if v := os.Getenv(EnvVaultSRVLookup); v != "" { if v := os.Getenv(EnvVaultSRVLookup); v != "" {
@ -412,6 +424,16 @@ func (c *Config) ReadEnvironment() error {
envVaultProxy = v envVaultProxy = v
} }
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
}
// Configure the HTTP clients TLS configuration. // Configure the HTTP clients TLS configuration.
t := &TLSConfig{ t := &TLSConfig{
CACert: envCACert, CACert: envCACert,
@ -1270,6 +1292,7 @@ func (c *Client) rawRequestWithContext(ctx context.Context, r *Request) (*Respon
outputCurlString := c.config.OutputCurlString outputCurlString := c.config.OutputCurlString
outputPolicy := c.config.OutputPolicy outputPolicy := c.config.OutputPolicy
logger := c.config.Logger logger := c.config.Logger
disableRedirects := c.config.DisableRedirects
c.config.modifyLock.RUnlock() c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock() c.modifyLock.RUnlock()
@ -1363,8 +1386,8 @@ START:
return result, err return result, err
} }
// Check for a redirect, only allowing for a single redirect // 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 { if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 && !disableRedirects {
// Parse the updated location // Parse the updated location
respLoc, err := resp.Location() respLoc, err := resp.Location()
if err != nil { if err != nil {
@ -1423,6 +1446,7 @@ func (c *Client) httpRequestWithContext(ctx context.Context, r *Request) (*Respo
httpClient := c.config.HttpClient httpClient := c.config.HttpClient
outputCurlString := c.config.OutputCurlString outputCurlString := c.config.OutputCurlString
outputPolicy := c.config.OutputPolicy outputPolicy := c.config.OutputPolicy
disableRedirects := c.config.DisableRedirects
// add headers // add headers
if c.headers != nil { if c.headers != nil {
@ -1495,8 +1519,8 @@ func (c *Client) httpRequestWithContext(ctx context.Context, r *Request) (*Respo
return result, err return result, err
} }
// Check for a redirect, only allowing for a single redirect // 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 { if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && !disableRedirects {
// Parse the updated location // Parse the updated location
respLoc, err := resp.Location() respLoc, err := resp.Location()
if err != nil { if err != nil {

View File

@ -209,6 +209,67 @@ func TestClientBadToken(t *testing.T) {
} }
} }
func TestClientDisableRedirects(t *testing.T) {
tests := map[string]struct {
statusCode int
expectedNumReqs int
disableRedirects bool
}{
"Disabled redirects: Moved permanently": {statusCode: 301, expectedNumReqs: 1, disableRedirects: true},
"Disabled redirects: Found": {statusCode: 302, expectedNumReqs: 1, disableRedirects: true},
"Disabled redirects: Temporary Redirect": {statusCode: 307, expectedNumReqs: 1, disableRedirects: true},
"Enable redirects: Moved permanently": {statusCode: 301, expectedNumReqs: 2, disableRedirects: false},
}
for name, tc := range tests {
test := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
numReqs := 0
var config *Config
respFunc := func(w http.ResponseWriter, req *http.Request) {
// Track how many requests the server has handled
numReqs++
// Send back the relevant status code and generate a location
w.Header().Set("Location", fmt.Sprintf(config.Address+"/reqs/%v", numReqs))
w.WriteHeader(test.statusCode)
}
config, ln := testHTTPServer(t, http.HandlerFunc(respFunc))
config.DisableRedirects = test.disableRedirects
defer ln.Close()
client, err := NewClient(config)
if err != nil {
t.Fatalf("%s: error %v", name, err)
}
req := client.NewRequest("GET", "/")
resp, err := client.rawRequestWithContext(context.Background(), req)
if err != nil {
t.Fatalf("%s: error %v", name, err)
}
if numReqs != test.expectedNumReqs {
t.Fatalf("%s: expected %v request(s) but got %v", name, test.expectedNumReqs, numReqs)
}
if resp.StatusCode != test.statusCode {
t.Fatalf("%s: expected status code %v got %v", name, test.statusCode, resp.StatusCode)
}
location, err := resp.Location()
if err != nil {
t.Fatalf("%s error %v", name, err)
}
if req.URL.String() == location.String() {
t.Fatalf("%s: expected request URL %v to be different from redirect URL %v", name, req.URL, resp.Request.URL)
}
})
}
}
func TestClientRedirect(t *testing.T) { func TestClientRedirect(t *testing.T) {
primary := func(w http.ResponseWriter, req *http.Request) { primary := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test")) w.Write([]byte("test"))
@ -320,6 +381,7 @@ func TestClientEnvSettings(t *testing.T) {
oldClientKey := os.Getenv(EnvVaultClientKey) oldClientKey := os.Getenv(EnvVaultClientKey)
oldSkipVerify := os.Getenv(EnvVaultSkipVerify) oldSkipVerify := os.Getenv(EnvVaultSkipVerify)
oldMaxRetries := os.Getenv(EnvVaultMaxRetries) oldMaxRetries := os.Getenv(EnvVaultMaxRetries)
oldDisableRedirects := os.Getenv(EnvVaultDisableRedirects)
os.Setenv(EnvVaultCACert, cwd+"/test-fixtures/keys/cert.pem") os.Setenv(EnvVaultCACert, cwd+"/test-fixtures/keys/cert.pem")
os.Setenv(EnvVaultCACertBytes, string(caCertBytes)) os.Setenv(EnvVaultCACertBytes, string(caCertBytes))
@ -328,6 +390,7 @@ func TestClientEnvSettings(t *testing.T) {
os.Setenv(EnvVaultClientKey, cwd+"/test-fixtures/keys/key.pem") os.Setenv(EnvVaultClientKey, cwd+"/test-fixtures/keys/key.pem")
os.Setenv(EnvVaultSkipVerify, "true") os.Setenv(EnvVaultSkipVerify, "true")
os.Setenv(EnvVaultMaxRetries, "5") os.Setenv(EnvVaultMaxRetries, "5")
os.Setenv(EnvVaultDisableRedirects, "true")
defer func() { defer func() {
os.Setenv(EnvVaultCACert, oldCACert) os.Setenv(EnvVaultCACert, oldCACert)
@ -337,6 +400,7 @@ func TestClientEnvSettings(t *testing.T) {
os.Setenv(EnvVaultClientKey, oldClientKey) os.Setenv(EnvVaultClientKey, oldClientKey)
os.Setenv(EnvVaultSkipVerify, oldSkipVerify) os.Setenv(EnvVaultSkipVerify, oldSkipVerify)
os.Setenv(EnvVaultMaxRetries, oldMaxRetries) os.Setenv(EnvVaultMaxRetries, oldMaxRetries)
os.Setenv(EnvVaultDisableRedirects, oldDisableRedirects)
}() }()
config := DefaultConfig() config := DefaultConfig()
@ -354,6 +418,9 @@ func TestClientEnvSettings(t *testing.T) {
if tlsConfig.InsecureSkipVerify != true { if tlsConfig.InsecureSkipVerify != true {
t.Fatalf("bad: %v", tlsConfig.InsecureSkipVerify) t.Fatalf("bad: %v", tlsConfig.InsecureSkipVerify)
} }
if config.DisableRedirects != true {
t.Fatalf("bad: expected disable redirects to be true: %v", config.DisableRedirects)
}
} }
func TestClientDeprecatedEnvSettings(t *testing.T) { func TestClientDeprecatedEnvSettings(t *testing.T) {

3
changelog/17352.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
api: Support VAULT_DISABLE_REDIRECTS environment variable (and --disable-redirects flag) to disable default client behavior and prevent the client following any redirection responses.
```

View File

@ -40,19 +40,20 @@ type BaseCommand struct {
flags *FlagSets flags *FlagSets
flagsOnce sync.Once flagsOnce sync.Once
flagAddress string flagAddress string
flagAgentAddress string flagAgentAddress string
flagCACert string flagCACert string
flagCAPath string flagCAPath string
flagClientCert string flagClientCert string
flagClientKey string flagClientKey string
flagNamespace string flagNamespace string
flagNS string flagNS string
flagPolicyOverride bool flagPolicyOverride bool
flagTLSServerName string flagTLSServerName string
flagTLSSkipVerify bool flagTLSSkipVerify bool
flagWrapTTL time.Duration flagDisableRedirects bool
flagUnlockKey string flagWrapTTL time.Duration
flagUnlockKey string
flagFormat string flagFormat string
flagField string flagField string
@ -427,6 +428,15 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
"transmissions to and from the Vault server.", "transmissions to and from the Vault server.",
}) })
f.BoolVar(&BoolVar{
Name: flagNameDisableRedirects,
Target: &c.flagDisableRedirects,
Default: false,
EnvVar: api.EnvVaultDisableRedirects,
Usage: "Disable the default client behavior, which honors a single " +
"redirect response from a request",
})
f.BoolVar(&BoolVar{ f.BoolVar(&BoolVar{
Name: "policy-override", Name: "policy-override",
Target: &c.flagPolicyOverride, Target: &c.flagPolicyOverride,

View File

@ -126,6 +126,8 @@ const (
flagNameAllowedManagedKeys = "allowed-managed-keys" flagNameAllowedManagedKeys = "allowed-managed-keys"
// flagNamePluginVersion selects what version of a plugin should be used. // flagNamePluginVersion selects what version of a plugin should be used.
flagNamePluginVersion = "plugin-version" flagNamePluginVersion = "plugin-version"
// flagNameDisableRedirects is used to prevent the client from honoring a single redirect as a response to a request
flagNameDisableRedirects = "disable-redirects"
) )
var ( var (

View File

@ -419,6 +419,12 @@ All requests will resolve the specified proxy; there is no way to exclude
~> Note: If both `VAULT_HTTP_PROXY` and `VAULT_PROXY_ADDR` environment ~> Note: If both `VAULT_HTTP_PROXY` and `VAULT_PROXY_ADDR` environment
variables are supplied, `VAULT_PROXY_ADDR` will be prioritized and preferred. variables are supplied, `VAULT_PROXY_ADDR` will be prioritized and preferred.
### `VAULT_DISABLE_REDIRECTS`
Prevents the Vault client from following redirects. By default, the Vault client will automatically follow a single redirect.
~> **Note:** Disabling redirect following behavior could cause issues with commands such as 'vault operator raft snapshot' as this command redirects the request to the cluster's primary node.
## Flags ## Flags
There are different CLI flags that are available depending on subcommands. Some There are different CLI flags that are available depending on subcommands. Some