Support trimming trailing slashes via a mount tuneable to support CMPv2 (#28752)

* Support trimming trailing slashes via a mount tuneable to support CMPv2

* changelog/

* Perform trimming in handleLoginRequest too

* Eagerly fetch the mount entry so we only test this once

* Add a mount match function that gets path and entry

* Update vault/request_handling.go

Co-authored-by: Steven Clark <steven.clark@hashicorp.com>

* more docs

* Some patches (from ENT) didnt apply

* patch fail

* Update vault/router.go

Co-authored-by: Steven Clark <steven.clark@hashicorp.com>

* PR feedback

* dupe

* another dupe

* Add support for enabling trim_request_trailing_slashes on mount creation

* Fix read mount api returning configuration for trim_request_trailing_slashes

* Fix test assertion

* Switch enable and tune arguments to BoolPtrVal to allow end-users to specify false flag

* Add trim-request-trailing-slashes to the auth enable API and CLI

---------

Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
Scott Miller
2024-10-24 10:47:17 -05:00
committed by GitHub
parent 314874c2b1
commit 415d260995
21 changed files with 292 additions and 140 deletions

View File

@@ -23,24 +23,25 @@ var (
type AuthEnableCommand struct {
*BaseCommand
flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPluginName string
flagPassthroughRequestHeaders []string
flagAllowedResponseHeaders []string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
flagExternalEntropyAccess bool
flagTokenType string
flagVersion int
flagPluginVersion string
flagIdentityTokenKey string
flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPluginName string
flagPassthroughRequestHeaders []string
flagAllowedResponseHeaders []string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
flagExternalEntropyAccess bool
flagTokenType string
flagVersion int
flagPluginVersion string
flagIdentityTokenKey string
flagTrimRequestTrailingSlashes BoolPtr
}
func (c *AuthEnableCommand) Synopsis() string {
@@ -217,6 +218,12 @@ func (c *AuthEnableCommand) Flags() *FlagSets {
Usage: "Select the key used to sign plugin identity tokens.",
})
f.BoolPtrVar(&BoolPtrVar{
Name: flagNameTrimRequestTrailingSlashes,
Target: &c.flagTrimRequestTrailingSlashes,
Usage: "Whether to trim trailing slashes for incoming requests to this mount",
})
return set
}
@@ -324,6 +331,11 @@ func (c *AuthEnableCommand) Run(args []string) int {
if fl.Name == flagNameIdentityTokenKey {
authOpts.Config.IdentityTokenKey = c.flagIdentityTokenKey
}
if fl.Name == flagNameTrimRequestTrailingSlashes && c.flagTrimRequestTrailingSlashes.IsSet() {
val := c.flagTrimRequestTrailingSlashes.Get()
authOpts.Config.TrimRequestTrailingSlashes = &val
}
})
if err := client.Sys().EnableAuthWithOptions(authPath, authOpts); err != nil {

View File

@@ -100,6 +100,7 @@ func TestAuthEnableCommand_Run(t *testing.T) {
"-allowed-response-headers", "authorization",
"-listing-visibility", "unauth",
"-identity-token-key", "default",
"-trim-request-trailing-slashes=true",
"userpass",
})
if exp := 0; code != exp {
@@ -127,6 +128,9 @@ func TestAuthEnableCommand_Run(t *testing.T) {
if exp := "The best kind of test"; authInfo.Description != exp {
t.Errorf("expected %q to be %q", authInfo.Description, exp)
}
if !authInfo.Config.TrimRequestTrailingSlashes {
t.Errorf("expected trim_request_trailing_slashes to be enabled")
}
if diff := deep.Equal([]string{"authorization,authentication", "www-authentication"}, authInfo.Config.PassthroughRequestHeaders); len(diff) > 0 {
t.Errorf("Failed to find expected values in PassthroughRequestHeaders. Difference is: %v", diff)
}

View File

@@ -40,6 +40,7 @@ type AuthTuneCommand struct {
flagUserLockoutCounterResetDuration time.Duration
flagUserLockoutDisable bool
flagIdentityTokenKey string
flagTrimRequestTrailingSlashes BoolPtr
}
func (c *AuthTuneCommand) Synopsis() string {
@@ -195,6 +196,11 @@ func (c *AuthTuneCommand) Flags() *FlagSets {
Usage: "Select the semantic version of the plugin to run. The new version must be registered in " +
"the plugin catalog, and will not start running until the plugin is reloaded.",
})
f.BoolPtrVar(&BoolPtrVar{
Name: flagNameTrimRequestTrailingSlashes,
Target: &c.flagTrimRequestTrailingSlashes,
Usage: "Whether to trim trailing slashes for incoming requests to this mount",
})
f.StringVar(&StringVar{
Name: flagNameIdentityTokenKey,
@@ -306,6 +312,11 @@ func (c *AuthTuneCommand) Run(args []string) int {
if fl.Name == flagNameIdentityTokenKey {
mountConfigInput.IdentityTokenKey = c.flagIdentityTokenKey
}
if fl.Name == flagNameTrimRequestTrailingSlashes && c.flagTrimRequestTrailingSlashes.IsSet() {
val := c.flagTrimRequestTrailingSlashes.Get()
mountConfigInput.TrimRequestTrailingSlashes = &val
}
})
// Append /auth (since that's where auths live) and a trailing slash to

View File

@@ -120,6 +120,7 @@ func TestAuthTuneCommand_Run(t *testing.T) {
"-listing-visibility", "unauth",
"-plugin-version", version,
"-identity-token-key", "default",
"-trim-request-trailing-slashes=true",
"my-auth/",
})
if exp := 0; code != exp {
@@ -156,6 +157,9 @@ func TestAuthTuneCommand_Run(t *testing.T) {
if exp := 3600; mountInfo.Config.MaxLeaseTTL != exp {
t.Errorf("expected %d to be %d", mountInfo.Config.MaxLeaseTTL, exp)
}
if !mountInfo.Config.TrimRequestTrailingSlashes {
t.Errorf("expected trim_request_trailing_slashes to be enabled")
}
if diff := deep.Equal([]string{"authorization", "www-authentication"}, mountInfo.Config.PassthroughRequestHeaders); len(diff) > 0 {
t.Errorf("Failed to find expected values in PassthroughRequestHeaders. Difference is: %v", diff)
}

View File

@@ -97,6 +97,8 @@ const (
flagNamePluginVersion = "plugin-version"
// flagNameIdentityTokenKey selects the key used to sign plugin identity tokens
flagNameIdentityTokenKey = "identity-token-key"
// flagNameTrimRequestTrailingSlashes selects the key used to determine whether to trim trailing slashes
flagNameTrimRequestTrailingSlashes = "trim-request-trailing-slashes"
// flagNameUserLockoutThreshold is the flag name used for tuning the auth mount lockout threshold parameter
flagNameUserLockoutThreshold = "user-lockout-threshold"
// flagNameUserLockoutDuration is the flag name used for tuning the auth mount lockout duration parameter

View File

@@ -23,26 +23,27 @@ var (
type SecretsEnableCommand struct {
*BaseCommand
flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPassthroughRequestHeaders []string
flagAllowedResponseHeaders []string
flagForceNoCache bool
flagPluginName string
flagPluginVersion string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
flagExternalEntropyAccess bool
flagVersion int
flagAllowedManagedKeys []string
flagDelegatedAuthAccessors []string
flagIdentityTokenKey string
flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPassthroughRequestHeaders []string
flagAllowedResponseHeaders []string
flagForceNoCache bool
flagPluginName string
flagPluginVersion string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
flagExternalEntropyAccess bool
flagVersion int
flagAllowedManagedKeys []string
flagDelegatedAuthAccessors []string
flagIdentityTokenKey string
flagTrimRequestTrailingSlashes BoolPtr
}
func (c *SecretsEnableCommand) Synopsis() string {
@@ -245,6 +246,12 @@ func (c *SecretsEnableCommand) Flags() *FlagSets {
Usage: "Select the key used to sign plugin identity tokens.",
})
f.BoolPtrVar(&BoolPtrVar{
Name: flagNameTrimRequestTrailingSlashes,
Target: &c.flagTrimRequestTrailingSlashes,
Usage: "Whether to trim trailing slashes for incoming requests to this mount",
})
return set
}
@@ -359,6 +366,11 @@ func (c *SecretsEnableCommand) Run(args []string) int {
if fl.Name == flagNameIdentityTokenKey {
mountInput.Config.IdentityTokenKey = c.flagIdentityTokenKey
}
if fl.Name == flagNameTrimRequestTrailingSlashes && c.flagTrimRequestTrailingSlashes.IsSet() {
val := c.flagTrimRequestTrailingSlashes.Get()
mountInput.Config.TrimRequestTrailingSlashes = &val
}
})
if err := client.Sys().Mount(mountPath, mountInput); err != nil {

View File

@@ -120,6 +120,7 @@ func TestSecretsEnableCommand_Run(t *testing.T) {
"-allowed-managed-keys", "key1,key2",
"-identity-token-key", "default",
"-delegated-auth-accessors", "authAcc1,authAcc2",
"-trim-request-trailing-slashes=true",
"-force-no-cache",
"pki",
})
@@ -157,6 +158,9 @@ func TestSecretsEnableCommand_Run(t *testing.T) {
if exp := true; mountInfo.Config.ForceNoCache != exp {
t.Errorf("expected %t to be %t", mountInfo.Config.ForceNoCache, exp)
}
if !mountInfo.Config.TrimRequestTrailingSlashes {
t.Errorf("expected trim_request_trailing_slashes to be enabled")
}
if diff := deep.Equal([]string{"authorization,authentication", "www-authentication"}, mountInfo.Config.PassthroughRequestHeaders); len(diff) > 0 {
t.Errorf("Failed to find expected values in PassthroughRequestHeaders. Difference is: %v", diff)
}

View File

@@ -23,20 +23,21 @@ var (
type SecretsTuneCommand struct {
*BaseCommand
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagDefaultLeaseTTL time.Duration
flagDescription string
flagListingVisibility string
flagMaxLeaseTTL time.Duration
flagPassthroughRequestHeaders []string
flagAllowedResponseHeaders []string
flagOptions map[string]string
flagVersion int
flagPluginVersion string
flagAllowedManagedKeys []string
flagDelegatedAuthAccessors []string
flagIdentityTokenKey string
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagDefaultLeaseTTL time.Duration
flagDescription string
flagListingVisibility string
flagMaxLeaseTTL time.Duration
flagPassthroughRequestHeaders []string
flagAllowedResponseHeaders []string
flagOptions map[string]string
flagVersion int
flagPluginVersion string
flagAllowedManagedKeys []string
flagDelegatedAuthAccessors []string
flagIdentityTokenKey string
flagTrimRequestTrailingSlashes BoolPtr
}
func (c *SecretsTuneCommand) Synopsis() string {
@@ -175,6 +176,12 @@ func (c *SecretsTuneCommand) Flags() *FlagSets {
Usage: "Select the key used to sign plugin identity tokens.",
})
f.BoolPtrVar(&BoolPtrVar{
Name: flagNameTrimRequestTrailingSlashes,
Target: &c.flagTrimRequestTrailingSlashes,
Usage: "Whether to trim trailing slashes for incoming requests to this mount",
})
return set
}
@@ -267,6 +274,10 @@ func (c *SecretsTuneCommand) Run(args []string) int {
if fl.Name == flagNameIdentityTokenKey {
mountConfigInput.IdentityTokenKey = c.flagIdentityTokenKey
}
if fl.Name == flagNameTrimRequestTrailingSlashes && c.flagTrimRequestTrailingSlashes.IsSet() {
val := c.flagTrimRequestTrailingSlashes.Get()
mountConfigInput.TrimRequestTrailingSlashes = &val
}
})
if err := client.Sys().TuneMount(mountPath, mountConfigInput); err != nil {

View File

@@ -196,6 +196,7 @@ func TestSecretsTuneCommand_Run(t *testing.T) {
"-listing-visibility", "unauth",
"-plugin-version", version,
"-delegated-auth-accessors", "authAcc1,authAcc2",
"-trim-request-trailing-slashes=true",
"mount_tune_integration/",
})
if exp := 0; code != exp {
@@ -232,6 +233,9 @@ func TestSecretsTuneCommand_Run(t *testing.T) {
if exp := 3600; mountInfo.Config.MaxLeaseTTL != exp {
t.Errorf("expected %d to be %d", mountInfo.Config.MaxLeaseTTL, exp)
}
if !mountInfo.Config.TrimRequestTrailingSlashes {
t.Errorf("expected trim_request_trailing_slashes to be enabled")
}
if diff := deep.Equal([]string{"authorization", "www-authentication"}, mountInfo.Config.PassthroughRequestHeaders); len(diff) > 0 {
t.Errorf("Failed to find expected values for PassthroughRequestHeaders. Difference is: %v", diff)
}