Request Limiter listener config opt-out (#25098)

This commit introduces a new listener config option to allow disabling the request limiter per-listener.
This commit is contained in:
Mike Palmiotto
2024-01-26 15:24:32 -05:00
committed by GitHub
parent dc9d1e275d
commit 12f69a8ce5
10 changed files with 94 additions and 13 deletions

4
changelog/25098.txt Normal file
View File

@@ -0,0 +1,4 @@
```release-note:improvement
limits: Add a listener configuration option `disable_request_limiter` to allow
disabling the request limiter per-listener.
```

View File

@@ -350,11 +350,13 @@ listener "tcp" {
address = "%s" address = "%s"
tls_disable = true tls_disable = true
require_request_header = false require_request_header = false
disable_request_limiter = false
} }
listener "tcp" { listener "tcp" {
address = "%s" address = "%s"
tls_disable = true tls_disable = true
require_request_header = true require_request_header = true
disable_request_limiter = true
} }
` `
listenAddr1 := generateListenerAddress(t) listenAddr1 := generateListenerAddress(t)

View File

@@ -901,6 +901,8 @@ func (c *ServerCommand) InitListeners(config *server.Config, disableClustering b
} }
props["max_request_duration"] = lnConfig.MaxRequestDuration.String() props["max_request_duration"] = lnConfig.MaxRequestDuration.String()
props["disable_request_limiter"] = strconv.FormatBool(lnConfig.DisableRequestLimiter)
if lnConfig.ChrootNamespace != "" { if lnConfig.ChrootNamespace != "" {
props["chroot_namespace"] = lnConfig.ChrootNamespace props["chroot_namespace"] = lnConfig.ChrootNamespace
} }

View File

@@ -611,6 +611,7 @@ func testLoadConfigFile_json(t *testing.T) {
Type: "tcp", Type: "tcp",
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
CustomResponseHeaders: DefaultCustomHeaders, CustomResponseHeaders: DefaultCustomHeaders,
DisableRequestLimiter: false,
}, },
}, },
@@ -789,8 +790,9 @@ func testConfig_Sanitized(t *testing.T) {
"listeners": []interface{}{ "listeners": []interface{}{
map[string]interface{}{ map[string]interface{}{
"config": map[string]interface{}{ "config": map[string]interface{}{
"address": "127.0.0.1:443", "address": "127.0.0.1:443",
"chroot_namespace": "admin/", "chroot_namespace": "admin/",
"disable_request_limiter": false,
}, },
"type": configutil.TCP, "type": configutil.TCP,
}, },
@@ -889,6 +891,7 @@ listener "tcp" {
redact_addresses = true redact_addresses = true
redact_cluster_name = true redact_cluster_name = true
redact_version = true redact_version = true
disable_request_limiter = true
} }
listener "unix" { listener "unix" {
address = "/var/run/vault.sock" address = "/var/run/vault.sock"
@@ -951,6 +954,7 @@ listener "unix" {
RedactAddresses: true, RedactAddresses: true,
RedactClusterName: true, RedactClusterName: true,
RedactVersion: true, RedactVersion: true,
DisableRequestLimiter: true,
}, },
{ {
Type: "unix", Type: "unix",

View File

@@ -13,6 +13,7 @@ cluster_addr = "top_level_cluster_addr"
listener "tcp" { listener "tcp" {
address = "127.0.0.1:443" address = "127.0.0.1:443"
chroot_namespace="admin/" chroot_namespace="admin/"
disable_request_limiter = false
} }
backend "consul" { backend "consul" {

View File

@@ -263,6 +263,10 @@ func handler(props *vault.HandlerProperties) http.Handler {
wrappedHandler = disableReplicationStatusEndpointWrapping(wrappedHandler) wrappedHandler = disableReplicationStatusEndpointWrapping(wrappedHandler)
} }
if props.ListenerConfig != nil && props.ListenerConfig.DisableRequestLimiter {
wrappedHandler = wrapRequestLimiterHandler(wrappedHandler, props)
}
return wrappedHandler return wrappedHandler
} }
@@ -910,6 +914,15 @@ func forwardRequest(core *vault.Core, w http.ResponseWriter, r *http.Request) {
} }
func acquireLimiterListener(core *vault.Core, rawReq *http.Request, r *logical.Request) (*limits.RequestListener, bool) { func acquireLimiterListener(core *vault.Core, rawReq *http.Request, r *logical.Request) (*limits.RequestListener, bool) {
var disable bool
disableRequestLimiter := rawReq.Context().Value(logical.CtxKeyDisableRequestLimiter{})
if disableRequestLimiter != nil {
disable = disableRequestLimiter.(bool)
}
if disable {
return &limits.RequestListener{}, true
}
lim := &limits.RequestLimiter{} lim := &limits.RequestLimiter{}
if r.PathLimited { if r.PathLimited {
lim = core.GetRequestLimiter(limits.SpecialPathLimiter) lim = core.GetRequestLimiter(limits.SpecialPathLimiter)

View File

@@ -43,6 +43,19 @@ func wrapMaxRequestSizeHandler(handler http.Handler, props *vault.HandlerPropert
}) })
} }
func wrapRequestLimiterHandler(handler http.Handler, props *vault.HandlerProperties) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
request := r.WithContext(
context.WithValue(
r.Context(),
logical.CtxKeyDisableRequestLimiter{},
props.ListenerConfig.DisableRequestLimiter,
),
)
handler.ServeHTTP(w, request)
})
}
func rateLimitQuotaWrapping(handler http.Handler, core *vault.Core) http.Handler { func rateLimitQuotaWrapping(handler http.Handler, core *vault.Core) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ns, err := namespace.FromContext(r.Context()) ns, err := namespace.FromContext(r.Context())

View File

@@ -143,6 +143,10 @@ type Listener struct {
// DisableReplicationStatusEndpoint disables the unauthenticated replication status endpoints // DisableReplicationStatusEndpoint disables the unauthenticated replication status endpoints
DisableReplicationStatusEndpointsRaw interface{} `hcl:"disable_replication_status_endpoints"` DisableReplicationStatusEndpointsRaw interface{} `hcl:"disable_replication_status_endpoints"`
DisableReplicationStatusEndpoints bool `hcl:"-"` DisableReplicationStatusEndpoints bool `hcl:"-"`
// DisableRequestLimiter allows per-listener disabling of the Request Limiter.
DisableRequestLimiterRaw any `hcl:"disable_request_limiter"`
DisableRequestLimiter bool `hcl:"-"`
} }
// AgentAPI allows users to select which parts of the Agent API they want enabled. // AgentAPI allows users to select which parts of the Agent API they want enabled.
@@ -257,6 +261,7 @@ func parseListener(item *ast.ObjectItem) (*Listener, error) {
l.parseChrootNamespaceSettings, l.parseChrootNamespaceSettings,
l.parseRedactionSettings, l.parseRedactionSettings,
l.parseDisableReplicationStatusEndpointSettings, l.parseDisableReplicationStatusEndpointSettings,
l.parseDisableRequestLimiter,
} { } {
err := parser() err := parser()
if err != nil { if err != nil {
@@ -370,6 +375,17 @@ func (l *Listener) parseDisableReplicationStatusEndpointSettings() error {
return nil return nil
} }
// parseDisableRequestLimiter attempts to parse the raw disable_request_limiter
// setting. The receiving Listener's DisableRequestLimiter field will be set
// with the successfully parsed value or return an error
func (l *Listener) parseDisableRequestLimiter() error {
if err := parseAndClearBool(&l.DisableRequestLimiterRaw, &l.DisableRequestLimiter); err != nil {
return fmt.Errorf("invalid value for disable_request_limiter: %w", err)
}
return nil
}
// parseChrootNamespace attempts to parse the raw listener chroot namespace settings. // parseChrootNamespace attempts to parse the raw listener chroot namespace settings.
// The state of the listener will be modified, raw data will be cleared upon // The state of the listener will be modified, raw data will be cleared upon
// successful parsing. // successful parsing.
@@ -446,6 +462,10 @@ func (l *Listener) parseRequestSettings() error {
return fmt.Errorf("invalid value for require_request_header: %w", err) return fmt.Errorf("invalid value for require_request_header: %w", err)
} }
if err := parseAndClearBool(&l.DisableRequestLimiterRaw, &l.DisableRequestLimiter); err != nil {
return fmt.Errorf("invalid value for disable_request_limiter: %w", err)
}
return nil return nil
} }

View File

@@ -181,14 +181,16 @@ func TestListener_parseRequestSettings(t *testing.T) {
t.Parallel() t.Parallel()
tests := map[string]struct { tests := map[string]struct {
rawMaxRequestSize any rawMaxRequestSize any
expectedMaxRequestSize int64 expectedMaxRequestSize int64
rawMaxRequestDuration any rawMaxRequestDuration any
expectedDuration time.Duration expectedDuration time.Duration
rawRequireRequestHeader any rawRequireRequestHeader any
expectedRequireRequestHeader bool expectedRequireRequestHeader bool
isErrorExpected bool rawDisableRequestLimiter any
errorMessage string expectedDisableRequestLimiter bool
isErrorExpected bool
errorMessage string
}{ }{
"nil": { "nil": {
isErrorExpected: false, isErrorExpected: false,
@@ -224,6 +226,17 @@ func TestListener_parseRequestSettings(t *testing.T) {
expectedRequireRequestHeader: true, expectedRequireRequestHeader: true,
isErrorExpected: false, isErrorExpected: false,
}, },
"disable-request-limiter-bad": {
rawDisableRequestLimiter: "badvalue",
expectedDisableRequestLimiter: false,
isErrorExpected: true,
errorMessage: "invalid value for disable_request_limiter",
},
"disable-request-limiter-good": {
rawDisableRequestLimiter: "true",
expectedDisableRequestLimiter: true,
isErrorExpected: false,
},
} }
for name, tc := range tests { for name, tc := range tests {
@@ -234,9 +247,10 @@ func TestListener_parseRequestSettings(t *testing.T) {
// Configure listener with raw values // Configure listener with raw values
l := &Listener{ l := &Listener{
MaxRequestSizeRaw: tc.rawMaxRequestSize, MaxRequestSizeRaw: tc.rawMaxRequestSize,
MaxRequestDurationRaw: tc.rawMaxRequestDuration, MaxRequestDurationRaw: tc.rawMaxRequestDuration,
RequireRequestHeaderRaw: tc.rawRequireRequestHeader, RequireRequestHeaderRaw: tc.rawRequireRequestHeader,
DisableRequestLimiterRaw: tc.rawDisableRequestLimiter,
} }
err := l.parseRequestSettings() err := l.parseRequestSettings()
@@ -251,11 +265,13 @@ func TestListener_parseRequestSettings(t *testing.T) {
require.Equal(t, tc.expectedMaxRequestSize, l.MaxRequestSize) require.Equal(t, tc.expectedMaxRequestSize, l.MaxRequestSize)
require.Equal(t, tc.expectedDuration, l.MaxRequestDuration) require.Equal(t, tc.expectedDuration, l.MaxRequestDuration)
require.Equal(t, tc.expectedRequireRequestHeader, l.RequireRequestHeader) require.Equal(t, tc.expectedRequireRequestHeader, l.RequireRequestHeader)
require.Equal(t, tc.expectedDisableRequestLimiter, l.DisableRequestLimiter)
// Ensure the state was modified for the raw values. // Ensure the state was modified for the raw values.
require.Nil(t, l.MaxRequestSizeRaw) require.Nil(t, l.MaxRequestSizeRaw)
require.Nil(t, l.MaxRequestDurationRaw) require.Nil(t, l.MaxRequestDurationRaw)
require.Nil(t, l.RequireRequestHeaderRaw) require.Nil(t, l.RequireRequestHeaderRaw)
require.Nil(t, l.DisableRequestLimiterRaw)
} }
}) })
} }

View File

@@ -543,3 +543,9 @@ func ContextOriginalBodyValue(ctx context.Context) (io.ReadCloser, bool) {
func CreateContextOriginalBody(parent context.Context, body io.ReadCloser) context.Context { func CreateContextOriginalBody(parent context.Context, body io.ReadCloser) context.Context {
return context.WithValue(parent, ctxKeyOriginalBody{}, body) return context.WithValue(parent, ctxKeyOriginalBody{}, body)
} }
type CtxKeyDisableRequestLimiter struct{}
func (c CtxKeyDisableRequestLimiter) String() string {
return "disable_request_limiter"
}