Skip to content

Commit

Permalink
Request Limiter listener config opt-out (#25098)
Browse files Browse the repository at this point in the history
This commit introduces a new listener config option to allow disabling the request limiter per-listener.
  • Loading branch information
mpalmi authored Jan 26, 2024
1 parent dc9d1e2 commit 12f69a8
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 13 deletions.
4 changes: 4 additions & 0 deletions changelog/25098.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:improvement
limits: Add a listener configuration option `disable_request_limiter` to allow
disabling the request limiter per-listener.
```
2 changes: 2 additions & 0 deletions command/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,13 @@ listener "tcp" {
address = "%s"
tls_disable = true
require_request_header = false
disable_request_limiter = false
}
listener "tcp" {
address = "%s"
tls_disable = true
require_request_header = true
disable_request_limiter = true
}
`
listenAddr1 := generateListenerAddress(t)
Expand Down
2 changes: 2 additions & 0 deletions command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,8 @@ func (c *ServerCommand) InitListeners(config *server.Config, disableClustering b
}
props["max_request_duration"] = lnConfig.MaxRequestDuration.String()

props["disable_request_limiter"] = strconv.FormatBool(lnConfig.DisableRequestLimiter)

if lnConfig.ChrootNamespace != "" {
props["chroot_namespace"] = lnConfig.ChrootNamespace
}
Expand Down
8 changes: 6 additions & 2 deletions command/server/config_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ func testLoadConfigFile_json(t *testing.T) {
Type: "tcp",
Address: "127.0.0.1:443",
CustomResponseHeaders: DefaultCustomHeaders,
DisableRequestLimiter: false,
},
},

Expand Down Expand Up @@ -789,8 +790,9 @@ func testConfig_Sanitized(t *testing.T) {
"listeners": []interface{}{
map[string]interface{}{
"config": map[string]interface{}{
"address": "127.0.0.1:443",
"chroot_namespace": "admin/",
"address": "127.0.0.1:443",
"chroot_namespace": "admin/",
"disable_request_limiter": false,
},
"type": configutil.TCP,
},
Expand Down Expand Up @@ -889,6 +891,7 @@ listener "tcp" {
redact_addresses = true
redact_cluster_name = true
redact_version = true
disable_request_limiter = true
}
listener "unix" {
address = "/var/run/vault.sock"
Expand Down Expand Up @@ -951,6 +954,7 @@ listener "unix" {
RedactAddresses: true,
RedactClusterName: true,
RedactVersion: true,
DisableRequestLimiter: true,
},
{
Type: "unix",
Expand Down
1 change: 1 addition & 0 deletions command/server/test-fixtures/config3.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ cluster_addr = "top_level_cluster_addr"
listener "tcp" {
address = "127.0.0.1:443"
chroot_namespace="admin/"
disable_request_limiter = false
}

backend "consul" {
Expand Down
13 changes: 13 additions & 0 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ func handler(props *vault.HandlerProperties) http.Handler {
wrappedHandler = disableReplicationStatusEndpointWrapping(wrappedHandler)
}

if props.ListenerConfig != nil && props.ListenerConfig.DisableRequestLimiter {
wrappedHandler = wrapRequestLimiterHandler(wrappedHandler, props)
}

return wrappedHandler
}

Expand Down Expand Up @@ -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) {
var disable bool
disableRequestLimiter := rawReq.Context().Value(logical.CtxKeyDisableRequestLimiter{})
if disableRequestLimiter != nil {
disable = disableRequestLimiter.(bool)
}
if disable {
return &limits.RequestListener{}, true
}

lim := &limits.RequestLimiter{}
if r.PathLimited {
lim = core.GetRequestLimiter(limits.SpecialPathLimiter)
Expand Down
13 changes: 13 additions & 0 deletions http/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ns, err := namespace.FromContext(r.Context())
Expand Down
20 changes: 20 additions & 0 deletions internalshared/configutil/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ type Listener struct {
// DisableReplicationStatusEndpoint disables the unauthenticated replication status endpoints
DisableReplicationStatusEndpointsRaw interface{} `hcl:"disable_replication_status_endpoints"`
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.
Expand Down Expand Up @@ -257,6 +261,7 @@ func parseListener(item *ast.ObjectItem) (*Listener, error) {
l.parseChrootNamespaceSettings,
l.parseRedactionSettings,
l.parseDisableReplicationStatusEndpointSettings,
l.parseDisableRequestLimiter,
} {
err := parser()
if err != nil {
Expand Down Expand Up @@ -370,6 +375,17 @@ func (l *Listener) parseDisableReplicationStatusEndpointSettings() error {
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.
// The state of the listener will be modified, raw data will be cleared upon
// successful parsing.
Expand Down Expand Up @@ -446,6 +462,10 @@ func (l *Listener) parseRequestSettings() error {
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
}

Expand Down
38 changes: 27 additions & 11 deletions internalshared/configutil/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,16 @@ func TestListener_parseRequestSettings(t *testing.T) {
t.Parallel()

tests := map[string]struct {
rawMaxRequestSize any
expectedMaxRequestSize int64
rawMaxRequestDuration any
expectedDuration time.Duration
rawRequireRequestHeader any
expectedRequireRequestHeader bool
isErrorExpected bool
errorMessage string
rawMaxRequestSize any
expectedMaxRequestSize int64
rawMaxRequestDuration any
expectedDuration time.Duration
rawRequireRequestHeader any
expectedRequireRequestHeader bool
rawDisableRequestLimiter any
expectedDisableRequestLimiter bool
isErrorExpected bool
errorMessage string
}{
"nil": {
isErrorExpected: false,
Expand Down Expand Up @@ -224,6 +226,17 @@ func TestListener_parseRequestSettings(t *testing.T) {
expectedRequireRequestHeader: true,
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 {
Expand All @@ -234,9 +247,10 @@ func TestListener_parseRequestSettings(t *testing.T) {

// Configure listener with raw values
l := &Listener{
MaxRequestSizeRaw: tc.rawMaxRequestSize,
MaxRequestDurationRaw: tc.rawMaxRequestDuration,
RequireRequestHeaderRaw: tc.rawRequireRequestHeader,
MaxRequestSizeRaw: tc.rawMaxRequestSize,
MaxRequestDurationRaw: tc.rawMaxRequestDuration,
RequireRequestHeaderRaw: tc.rawRequireRequestHeader,
DisableRequestLimiterRaw: tc.rawDisableRequestLimiter,
}

err := l.parseRequestSettings()
Expand All @@ -251,11 +265,13 @@ func TestListener_parseRequestSettings(t *testing.T) {
require.Equal(t, tc.expectedMaxRequestSize, l.MaxRequestSize)
require.Equal(t, tc.expectedDuration, l.MaxRequestDuration)
require.Equal(t, tc.expectedRequireRequestHeader, l.RequireRequestHeader)
require.Equal(t, tc.expectedDisableRequestLimiter, l.DisableRequestLimiter)

// Ensure the state was modified for the raw values.
require.Nil(t, l.MaxRequestSizeRaw)
require.Nil(t, l.MaxRequestDurationRaw)
require.Nil(t, l.RequireRequestHeaderRaw)
require.Nil(t, l.DisableRequestLimiterRaw)
}
})
}
Expand Down
6 changes: 6 additions & 0 deletions sdk/logical/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,9 @@ func ContextOriginalBodyValue(ctx context.Context) (io.ReadCloser, bool) {
func CreateContextOriginalBody(parent context.Context, body io.ReadCloser) context.Context {
return context.WithValue(parent, ctxKeyOriginalBody{}, body)
}

type CtxKeyDisableRequestLimiter struct{}

func (c CtxKeyDisableRequestLimiter) String() string {
return "disable_request_limiter"
}

0 comments on commit 12f69a8

Please sign in to comment.