vault/vault/logical_system_limits_testonly.go
Mike Palmiotto e4a11ae7cd
Request Limiter Reload tests (#25126)
This PR introduces a new testonly endpoint for introspecting the
RequestLimiter state. It makes use of the endpoint to verify that changes to
the request_limiter config are honored across reload.

In the future, we may choose to make the sys/internal/request-limiter/status
endpoint available in normal binaries, but this is an expedient way to expose
the status for testing without having to rush the design.

In order to re-use as much of the existing command package utility funcionality
as possible without introducing sprawling code changes, I introduced a new
server_util.go and exported some fields via accessors.

The tests shook out a couple of bugs (including a deadlock and lack of
locking around the core limiterRegistry state).
2024-02-01 09:11:08 -05:00

89 lines
2.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build testonly
package vault
import (
"context"
"net/http"
"github.com/hashicorp/vault/limits"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
// RequestLimiterResponse is a struct for marshalling Request Limiter status responses.
type RequestLimiterResponse struct {
GlobalDisabled bool `json:"global_disabled" mapstructure:"global_disabled"`
ListenerDisabled bool `json:"listener_disabled" mapstructure:"listener_disabled"`
Limiters map[string]*LimiterStatus `json:"types" mapstructure:"types"`
}
// LimiterStatus holds the per-limiter status and flags for testing.
type LimiterStatus struct {
Enabled bool `json:"enabled" mapstructure:"enabled"`
Flags limits.LimiterFlags `json:"flags,omitempty" mapstructure:"flags,omitempty"`
}
const readRequestLimiterHelpText = `
Read the current status of the request limiter.
`
func (b *SystemBackend) requestLimiterReadPath() *framework.Path {
return &framework.Path{
Pattern: "internal/request-limiter/status$",
HelpDescription: readRequestLimiterHelpText,
HelpSynopsis: readRequestLimiterHelpText,
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.handleReadRequestLimiter,
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "read",
OperationSuffix: "verbosity-level-for",
},
Responses: map[int][]framework.Response{
http.StatusOK: {{
Description: "OK",
}},
},
Summary: "Read the current status of the request limiter.",
},
},
}
}
// handleReadRequestLimiter returns the enabled Request Limiter status for this node.
func (b *SystemBackend) handleReadRequestLimiter(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
resp := &RequestLimiterResponse{
Limiters: make(map[string]*LimiterStatus),
}
b.Core.limiterRegistryLock.Lock()
registry := b.Core.limiterRegistry
b.Core.limiterRegistryLock.Unlock()
resp.GlobalDisabled = !registry.Enabled
resp.ListenerDisabled = req.RequestLimiterDisabled
enabled := !(resp.GlobalDisabled || resp.ListenerDisabled)
for name := range limits.DefaultLimiterFlags {
var flags limits.LimiterFlags
if requestLimiter := b.Core.GetRequestLimiter(name); requestLimiter != nil && enabled {
flags = requestLimiter.Flags
}
resp.Limiters[name] = &LimiterStatus{
Enabled: enabled,
Flags: flags,
}
}
return &logical.Response{
Data: map[string]interface{}{
"request_limiter": resp,
},
}, nil
}