feat: add api-rate-limit flag (#5013)

This commit is contained in:
Dean Sheather
2022-11-11 07:53:48 +10:00
committed by GitHub
parent 2042b575dc
commit 8e5af82275
7 changed files with 110 additions and 1 deletions

View File

@ -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.",

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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.

View File

@ -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"`
}

View File

@ -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>
}