mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: add api-rate-limit flag (#5013)
This commit is contained in:
@ -372,6 +372,12 @@ func newConfig() *codersdk.DeploymentConfig {
|
||||
Default: 10 * time.Minute,
|
||||
},
|
||||
},
|
||||
APIRateLimit: &codersdk.DeploymentConfigField[int]{
|
||||
Name: "API Rate Limit",
|
||||
Usage: "Maximum number of requests per minute allowed to the API per user, or per IP address for unauthenticated users. Negative values mean no rate limit. Some API endpoints are always rate limited regardless of this value to prevent denial-of-service attacks.",
|
||||
Flag: "api-rate-limit",
|
||||
Default: 512,
|
||||
},
|
||||
Experimental: &codersdk.DeploymentConfigField[bool]{
|
||||
Name: "Experimental",
|
||||
Usage: "Enable experimental features. Experimental features are not ready for production.",
|
||||
|
@ -363,6 +363,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
||||
AgentStatsRefreshInterval: cfg.AgentStatRefreshInterval.Value,
|
||||
DeploymentConfig: cfg,
|
||||
PrometheusRegistry: prometheus.NewRegistry(),
|
||||
APIRateLimit: cfg.APIRateLimit.Value,
|
||||
}
|
||||
if tlsConfig != nil {
|
||||
options.TLSCertificates = tlsConfig.Certificates
|
||||
|
@ -633,6 +633,94 @@ func TestServer(t *testing.T) {
|
||||
cancelFunc()
|
||||
<-serverErr
|
||||
})
|
||||
|
||||
t.Run("RateLimit", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
)
|
||||
serverErr := make(chan error, 1)
|
||||
go func() {
|
||||
serverErr <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
accessURL := waitAccessURL(t, cfg)
|
||||
client := codersdk.New(accessURL)
|
||||
|
||||
resp, err := client.Request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, "512", resp.Header.Get("X-Ratelimit-Limit"))
|
||||
cancelFunc()
|
||||
<-serverErr
|
||||
})
|
||||
|
||||
t.Run("Changed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
val := "100"
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--api-rate-limit", val,
|
||||
)
|
||||
serverErr := make(chan error, 1)
|
||||
go func() {
|
||||
serverErr <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
accessURL := waitAccessURL(t, cfg)
|
||||
client := codersdk.New(accessURL)
|
||||
|
||||
resp, err := client.Request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, val, resp.Header.Get("X-Ratelimit-Limit"))
|
||||
cancelFunc()
|
||||
<-serverErr
|
||||
})
|
||||
|
||||
t.Run("Disabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--api-rate-limit", "-1",
|
||||
)
|
||||
serverErr := make(chan error, 1)
|
||||
go func() {
|
||||
serverErr <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
accessURL := waitAccessURL(t, cfg)
|
||||
client := codersdk.New(accessURL)
|
||||
|
||||
resp, err := client.Request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, "", resp.Header.Get("X-Ratelimit-Limit"))
|
||||
cancelFunc()
|
||||
<-serverErr
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func generateTLSCertificate(t testing.TB, commonName ...string) (certPath, keyPath string) {
|
||||
|
8
cli/testdata/coder_server_--help.golden
vendored
8
cli/testdata/coder_server_--help.golden
vendored
@ -17,6 +17,14 @@ Flags:
|
||||
-a, --address string Bind address of the server.
|
||||
Consumes $CODER_ADDRESS (default
|
||||
"127.0.0.1:3000")
|
||||
--api-rate-limit int Maximum number of requests per minute
|
||||
allowed to the API per user, or per IP
|
||||
address for unauthenticated users.
|
||||
Negative values mean no rate limit. Some
|
||||
API endpoints are always rate limited
|
||||
regardless of this value to prevent
|
||||
denial-of-service attacks.
|
||||
Consumes $CODER_API_RATE_LIMIT (default 512)
|
||||
--cache-dir string The directory to cache temporary files.
|
||||
If unspecified and $CACHE_DIRECTORY is
|
||||
set, it will be used for compatibility
|
||||
|
@ -22,8 +22,12 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
|
||||
first=true
|
||||
for fi in queries/*.sql.go; do
|
||||
# Find the last line from the imports section and add 1.
|
||||
# Find the last line from the imports section and add 1. We have to
|
||||
# disable pipefail temporarily to avoid ERRPIPE errors when piping into
|
||||
# `head -n1`.
|
||||
set +o pipefail
|
||||
cut=$(grep -n ')' "$fi" | head -n 1 | cut -d: -f1)
|
||||
set -o pipefail
|
||||
cut=$((cut + 1))
|
||||
|
||||
# Copy the header from the first file only, ignoring the source comment.
|
||||
|
@ -39,6 +39,7 @@ type DeploymentConfig struct {
|
||||
SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"`
|
||||
UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"`
|
||||
Provisioner *ProvisionerConfig `json:"provisioner" typescript:",notnull"`
|
||||
APIRateLimit *DeploymentConfigField[int] `json:"api_rate_limit" typescript:",notnull"`
|
||||
Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
|
||||
}
|
||||
|
||||
|
@ -303,6 +303,7 @@ export interface DeploymentConfig {
|
||||
readonly scim_api_key: DeploymentConfigField<string>
|
||||
readonly user_workspace_quota: DeploymentConfigField<number>
|
||||
readonly provisioner: ProvisionerConfig
|
||||
readonly api_rate_limit: DeploymentConfigField<number>
|
||||
readonly experimental: DeploymentConfigField<boolean>
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user