mirror of https://github.com/coder/coder.git
feat: add flag to disaable all rate limits (#5570)
This commit is contained in:
parent
ab7e676b54
commit
5a968e2f93
|
@ -429,11 +429,22 @@ 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,
|
||||
RateLimit: &codersdk.RateLimitConfig{
|
||||
DisableAll: &codersdk.DeploymentConfigField[bool]{
|
||||
Name: "Disable All Rate Limits",
|
||||
Usage: "Disables all rate limits. This is not recommended in production.",
|
||||
Flag: "dangerous-disable-rate-limits",
|
||||
Default: false,
|
||||
},
|
||||
API: &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 have separate strict rate limits regardless of this value to prevent denial-of-service or brute force attacks.",
|
||||
// Change the env from the auto-generated CODER_RATE_LIMIT_API to the
|
||||
// old value to avoid breaking existing deployments.
|
||||
EnvOverride: "CODER_API_RATE_LIMIT",
|
||||
Flag: "api-rate-limit",
|
||||
Default: 512,
|
||||
},
|
||||
},
|
||||
Experimental: &codersdk.DeploymentConfigField[bool]{
|
||||
Name: "Experimental",
|
||||
|
@ -498,21 +509,30 @@ func setConfig(prefix string, vip *viper.Viper, target interface{}) {
|
|||
// assigned a value.
|
||||
if strings.HasPrefix(typ.Name(), "DeploymentConfigField[") {
|
||||
value := val.FieldByName("Value").Interface()
|
||||
|
||||
env, ok := val.FieldByName("EnvOverride").Interface().(string)
|
||||
if !ok {
|
||||
panic("DeploymentConfigField[].EnvOverride must be a string")
|
||||
}
|
||||
if env == "" {
|
||||
env = formatEnv(prefix)
|
||||
}
|
||||
|
||||
switch value.(type) {
|
||||
case string:
|
||||
vip.MustBindEnv(prefix, formatEnv(prefix))
|
||||
vip.MustBindEnv(prefix, env)
|
||||
val.FieldByName("Value").SetString(vip.GetString(prefix))
|
||||
case bool:
|
||||
vip.MustBindEnv(prefix, formatEnv(prefix))
|
||||
vip.MustBindEnv(prefix, env)
|
||||
val.FieldByName("Value").SetBool(vip.GetBool(prefix))
|
||||
case int:
|
||||
vip.MustBindEnv(prefix, formatEnv(prefix))
|
||||
vip.MustBindEnv(prefix, env)
|
||||
val.FieldByName("Value").SetInt(int64(vip.GetInt(prefix)))
|
||||
case time.Duration:
|
||||
vip.MustBindEnv(prefix, formatEnv(prefix))
|
||||
vip.MustBindEnv(prefix, env)
|
||||
val.FieldByName("Value").SetInt(int64(vip.GetDuration(prefix)))
|
||||
case []string:
|
||||
vip.MustBindEnv(prefix, formatEnv(prefix))
|
||||
vip.MustBindEnv(prefix, env)
|
||||
// As of October 21st, 2022 we supported delimiting a string
|
||||
// with a comma, but Viper only supports with a space. This
|
||||
// is a small hack around it!
|
||||
|
@ -580,6 +600,9 @@ func readSliceFromViper[T any](vip *viper.Viper, key string, value any) []T {
|
|||
|
||||
// Ensure the env entry for this key is registered
|
||||
// before checking value.
|
||||
//
|
||||
// We don't support DeploymentConfigField[].EnvOverride for array flags so
|
||||
// this is fine to just use `formatEnv` here.
|
||||
vip.MustBindEnv(configKey, formatEnv(configKey))
|
||||
|
||||
value := vip.Get(configKey)
|
||||
|
@ -626,7 +649,7 @@ func setViperDefaults(prefix string, vip *viper.Viper, target interface{}) {
|
|||
val := reflect.ValueOf(target).Elem()
|
||||
val = reflect.Indirect(val)
|
||||
typ := val.Type()
|
||||
if strings.HasPrefix(typ.Name(), "DeploymentConfigField") {
|
||||
if strings.HasPrefix(typ.Name(), "DeploymentConfigField[") {
|
||||
value := val.FieldByName("Default").Interface()
|
||||
vip.SetDefault(prefix, value)
|
||||
return
|
||||
|
@ -663,7 +686,7 @@ func AttachFlags(flagset *pflag.FlagSet, vip *viper.Viper, enterprise bool) {
|
|||
func setFlags(prefix string, flagset *pflag.FlagSet, vip *viper.Viper, target interface{}, enterprise bool) {
|
||||
val := reflect.Indirect(reflect.ValueOf(target))
|
||||
typ := val.Type()
|
||||
if strings.HasPrefix(typ.Name(), "DeploymentConfigField") {
|
||||
if strings.HasPrefix(typ.Name(), "DeploymentConfigField[") {
|
||||
isEnt := val.FieldByName("Enterprise").Bool()
|
||||
if enterprise != isEnt {
|
||||
return
|
||||
|
@ -672,15 +695,24 @@ func setFlags(prefix string, flagset *pflag.FlagSet, vip *viper.Viper, target in
|
|||
if flg == "" {
|
||||
return
|
||||
}
|
||||
|
||||
env, ok := val.FieldByName("EnvOverride").Interface().(string)
|
||||
if !ok {
|
||||
panic("DeploymentConfigField[].EnvOverride must be a string")
|
||||
}
|
||||
if env == "" {
|
||||
env = formatEnv(prefix)
|
||||
}
|
||||
|
||||
usage := val.FieldByName("Usage").String()
|
||||
usage = fmt.Sprintf("%s\n%s", usage, cliui.Styles.Placeholder.Render("Consumes $"+formatEnv(prefix)))
|
||||
usage = fmt.Sprintf("%s\n%s", usage, cliui.Styles.Placeholder.Render("Consumes $"+env))
|
||||
shorthand := val.FieldByName("Shorthand").String()
|
||||
hidden := val.FieldByName("Hidden").Bool()
|
||||
value := val.FieldByName("Default").Interface()
|
||||
|
||||
// Allow currently set environment variables
|
||||
// to override default values in help output.
|
||||
vip.MustBindEnv(prefix, formatEnv(prefix))
|
||||
vip.MustBindEnv(prefix, env)
|
||||
|
||||
switch value.(type) {
|
||||
case string:
|
||||
|
|
|
@ -265,7 +265,7 @@ func requireAdmin(ctx context.Context, client *codersdk.Client) (codersdk.User,
|
|||
// Only owners can do scaletests. This isn't a very strong check but there's
|
||||
// not much else we can do. Ratelimits are enforced for non-owners so
|
||||
// hopefully that limits the damage if someone disables this check and runs
|
||||
// it against a non-owner account.
|
||||
// it against a non-owner account on a production deployment.
|
||||
ok := false
|
||||
for _, role := range me.Roles {
|
||||
if role.Name == "owner" {
|
||||
|
@ -488,7 +488,9 @@ func scaletestCreateWorkspaces() *cobra.Command {
|
|||
cmd := &cobra.Command{
|
||||
Use: "create-workspaces",
|
||||
Short: "Creates many workspaces and waits for them to be ready",
|
||||
Long: "Creates many users, then creates a workspace for each user and waits for them finish building and fully come online. Optionally runs a command inside each workspace, and connects to the workspace over WireGuard.",
|
||||
Long: `Creates many users, then creates a workspace for each user and waits for them finish building and fully come online. Optionally runs a command inside each workspace, and connects to the workspace over WireGuard.
|
||||
|
||||
It is recommended that all rate limits are disabled on the server before running this scaletest. This test generates many login events which will be rate limited against the (most likely single) IP.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
client, err := CreateClient(cmd)
|
||||
|
|
|
@ -104,6 +104,16 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||
return xerrors.Errorf("either HTTP or TLS must be enabled")
|
||||
}
|
||||
|
||||
// Disable rate limits if the `--dangerous-disable-rate-limits` flag
|
||||
// was specified.
|
||||
loginRateLimit := 60
|
||||
filesRateLimit := 12
|
||||
if cfg.RateLimit.DisableAll.Value {
|
||||
cfg.RateLimit.API.Value = -1
|
||||
loginRateLimit = -1
|
||||
filesRateLimit = -1
|
||||
}
|
||||
|
||||
printLogo(cmd)
|
||||
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
|
||||
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
|
||||
|
@ -432,7 +442,9 @@ 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,
|
||||
APIRateLimit: cfg.RateLimit.API.Value,
|
||||
LoginRateLimit: loginRateLimit,
|
||||
FilesRateLimit: filesRateLimit,
|
||||
HTTPClient: httpClient,
|
||||
}
|
||||
if tlsConfig != nil {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
Creates many users, then creates a workspace for each user and waits for them finish building and fully come online. Optionally runs a command inside each workspace, and connects to the workspace over WireGuard.
|
||||
|
||||
It is recommended that all rate limits are disabled on the server before running this scaletest. This test generates many login events which will be rate limited against the (most likely single) IP.
|
||||
|
||||
Usage:
|
||||
coder scaletest create-workspaces [flags]
|
||||
|
||||
|
|
|
@ -18,9 +18,10 @@ Flags:
|
|||
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.
|
||||
API endpoints have separate strict rate
|
||||
limits regardless of this value to
|
||||
prevent denial-of-service or brute force
|
||||
attacks.
|
||||
Consumes $CODER_API_RATE_LIMIT (default 512)
|
||||
--cache-dir string The directory to cache temporary files.
|
||||
If unspecified and $CACHE_DIRECTORY is
|
||||
|
@ -28,6 +29,9 @@ Flags:
|
|||
with systemd.
|
||||
Consumes $CODER_CACHE_DIRECTORY (default
|
||||
"/tmp/coder-cli-test-cache")
|
||||
--dangerous-disable-rate-limits Disables all rate limits. This is not
|
||||
recommended in production.
|
||||
Consumes $CODER_RATE_LIMIT_DISABLE_ALL
|
||||
--derp-config-path string Path to read a DERP mapping from. See:
|
||||
https://tailscale.com/kb/1118/custom-derp-servers/
|
||||
Consumes $CODER_DERP_CONFIG_PATH
|
||||
|
|
|
@ -2325,9 +2325,6 @@ const docTemplate = `{
|
|||
"agent_stat_refresh_interval": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration"
|
||||
},
|
||||
"api_rate_limit": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-int"
|
||||
},
|
||||
"audit_logging": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-bool"
|
||||
},
|
||||
|
@ -2385,6 +2382,9 @@ const docTemplate = `{
|
|||
"proxy_trusted_origins": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-array_string"
|
||||
},
|
||||
"rate_limit": {
|
||||
"$ref": "#/definitions/codersdk.RateLimitConfig"
|
||||
},
|
||||
"scim_api_key": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-string"
|
||||
},
|
||||
|
@ -2950,6 +2950,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.RateLimitConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-int"
|
||||
},
|
||||
"disable_all": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-bool"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -2058,9 +2058,6 @@
|
|||
"agent_stat_refresh_interval": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration"
|
||||
},
|
||||
"api_rate_limit": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-int"
|
||||
},
|
||||
"audit_logging": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-bool"
|
||||
},
|
||||
|
@ -2118,6 +2115,9 @@
|
|||
"proxy_trusted_origins": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-array_string"
|
||||
},
|
||||
"rate_limit": {
|
||||
"$ref": "#/definitions/codersdk.RateLimitConfig"
|
||||
},
|
||||
"scim_api_key": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-string"
|
||||
},
|
||||
|
@ -2662,6 +2662,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.RateLimitConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-int"
|
||||
},
|
||||
"disable_all": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-bool"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -82,24 +82,20 @@ type Options struct {
|
|||
Auditor audit.Auditor
|
||||
AgentConnectionUpdateFrequency time.Duration
|
||||
AgentInactiveDisconnectTimeout time.Duration
|
||||
// APIRateLimit is the minutely throughput rate limit per user or ip.
|
||||
// Setting a rate limit <0 will disable the rate limiter across the entire
|
||||
// app. Specific routes may have their own limiters.
|
||||
APIRateLimit int
|
||||
AWSCertificates awsidentity.Certificates
|
||||
Authorizer rbac.Authorizer
|
||||
AzureCertificates x509.VerifyOptions
|
||||
GoogleTokenValidator *idtoken.Validator
|
||||
GithubOAuth2Config *GithubOAuth2Config
|
||||
OIDCConfig *OIDCConfig
|
||||
PrometheusRegistry *prometheus.Registry
|
||||
SecureAuthCookie bool
|
||||
SSHKeygenAlgorithm gitsshkey.Algorithm
|
||||
Telemetry telemetry.Reporter
|
||||
TracerProvider trace.TracerProvider
|
||||
GitAuthConfigs []*gitauth.Config
|
||||
RealIPConfig *httpmw.RealIPConfig
|
||||
TrialGenerator func(ctx context.Context, email string) error
|
||||
AWSCertificates awsidentity.Certificates
|
||||
Authorizer rbac.Authorizer
|
||||
AzureCertificates x509.VerifyOptions
|
||||
GoogleTokenValidator *idtoken.Validator
|
||||
GithubOAuth2Config *GithubOAuth2Config
|
||||
OIDCConfig *OIDCConfig
|
||||
PrometheusRegistry *prometheus.Registry
|
||||
SecureAuthCookie bool
|
||||
SSHKeygenAlgorithm gitsshkey.Algorithm
|
||||
Telemetry telemetry.Reporter
|
||||
TracerProvider trace.TracerProvider
|
||||
GitAuthConfigs []*gitauth.Config
|
||||
RealIPConfig *httpmw.RealIPConfig
|
||||
TrialGenerator func(ctx context.Context, email string) error
|
||||
// TLSCertificates is used to mesh DERP servers securely.
|
||||
TLSCertificates []tls.Certificate
|
||||
TailnetCoordinator tailnet.Coordinator
|
||||
|
@ -107,6 +103,13 @@ type Options struct {
|
|||
DERPMap *tailcfg.DERPMap
|
||||
SwaggerEndpoint bool
|
||||
|
||||
// APIRateLimit is the minutely throughput rate limit per user or ip.
|
||||
// Setting a rate limit <0 will disable the rate limiter across the entire
|
||||
// app. Some specific routes have their own configurable rate limits.
|
||||
APIRateLimit int
|
||||
LoginRateLimit int
|
||||
FilesRateLimit int
|
||||
|
||||
MetricsCacheRefreshInterval time.Duration
|
||||
AgentStatsRefreshInterval time.Duration
|
||||
Experimental bool
|
||||
|
@ -157,6 +160,12 @@ func New(options *Options) *API {
|
|||
if options.APIRateLimit == 0 {
|
||||
options.APIRateLimit = 512
|
||||
}
|
||||
if options.LoginRateLimit == 0 {
|
||||
options.LoginRateLimit = 60
|
||||
}
|
||||
if options.FilesRateLimit == 0 {
|
||||
options.FilesRateLimit = 12
|
||||
}
|
||||
if options.Authorizer == nil {
|
||||
options.Authorizer = rbac.NewAuthorizer()
|
||||
}
|
||||
|
@ -230,6 +239,10 @@ func New(options *Options) *API {
|
|||
Optional: false,
|
||||
})
|
||||
|
||||
// API rate limit middleware. The counter is local and not shared between
|
||||
// replicas or instances of this middleware.
|
||||
apiRateLimiter := httpmw.RateLimit(options.APIRateLimit, time.Minute)
|
||||
|
||||
r.Use(
|
||||
httpmw.Recover(api.Logger),
|
||||
tracing.StatusWriterMiddleware,
|
||||
|
@ -241,8 +254,8 @@ func New(options *Options) *API {
|
|||
// handleSubdomainApplications checks if the first subdomain is a valid
|
||||
// app URL. If it is, it will serve that application.
|
||||
api.handleSubdomainApplications(
|
||||
apiRateLimiter,
|
||||
// Middleware to impose on the served application.
|
||||
httpmw.RateLimit(options.APIRateLimit, time.Minute),
|
||||
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
|
||||
DB: options.Database,
|
||||
OAuth2Configs: oauthConfigs,
|
||||
|
@ -268,7 +281,7 @@ func New(options *Options) *API {
|
|||
|
||||
apps := func(r chi.Router) {
|
||||
r.Use(
|
||||
httpmw.RateLimit(options.APIRateLimit, time.Minute),
|
||||
apiRateLimiter,
|
||||
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
|
||||
DB: options.Database,
|
||||
OAuth2Configs: oauthConfigs,
|
||||
|
@ -315,8 +328,9 @@ func New(options *Options) *API {
|
|||
|
||||
r.NotFound(func(rw http.ResponseWriter, r *http.Request) { httpapi.RouteNotFound(rw) })
|
||||
r.Use(
|
||||
// Specific routes can specify smaller limits.
|
||||
httpmw.RateLimit(options.APIRateLimit, time.Minute),
|
||||
// Specific routes can specify different limits, but every rate
|
||||
// limit must be configurable by the admin.
|
||||
apiRateLimiter,
|
||||
)
|
||||
r.Get("/", apiRoot)
|
||||
// All CSP errors will be logged
|
||||
|
@ -339,9 +353,7 @@ func New(options *Options) *API {
|
|||
r.Route("/files", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
// This number is arbitrary, but reading/writing
|
||||
// file content is expensive so it should be small.
|
||||
httpmw.RateLimit(12, time.Minute),
|
||||
httpmw.RateLimit(options.FilesRateLimit, time.Minute),
|
||||
)
|
||||
r.Get("/{fileID}", api.fileByID)
|
||||
r.Post("/", api.postFile)
|
||||
|
@ -426,25 +438,25 @@ func New(options *Options) *API {
|
|||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/first", api.firstUser)
|
||||
r.Post("/first", api.postFirstUser)
|
||||
r.Group(func(r chi.Router) {
|
||||
// We use a tight limit for password login to protect
|
||||
// against audit-log write DoS, pbkdf2 DoS, and simple
|
||||
// brute-force attacks.
|
||||
//
|
||||
// Making this too small can break tests.
|
||||
r.Use(httpmw.RateLimit(60, time.Minute))
|
||||
r.Post("/login", api.postLogin)
|
||||
})
|
||||
r.Get("/authmethods", api.userAuthMethods)
|
||||
r.Route("/oauth2", func(r chi.Router) {
|
||||
r.Route("/github", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2(options.GithubOAuth2Config, options.HTTPClient))
|
||||
r.Get("/callback", api.userOAuth2Github)
|
||||
r.Group(func(r chi.Router) {
|
||||
// We use a tight limit for password login to protect against
|
||||
// audit-log write DoS, pbkdf2 DoS, and simple brute-force
|
||||
// attacks.
|
||||
//
|
||||
// This value is intentionally increased during tests.
|
||||
r.Use(httpmw.RateLimit(options.LoginRateLimit, time.Minute))
|
||||
r.Post("/login", api.postLogin)
|
||||
r.Route("/oauth2", func(r chi.Router) {
|
||||
r.Route("/github", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2(options.GithubOAuth2Config, options.HTTPClient))
|
||||
r.Get("/callback", api.userOAuth2Github)
|
||||
})
|
||||
})
|
||||
r.Route("/oidc/callback", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2(options.OIDCConfig, options.HTTPClient))
|
||||
r.Get("/", api.userOIDC)
|
||||
})
|
||||
})
|
||||
r.Route("/oidc/callback", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2(options.OIDCConfig, options.HTTPClient))
|
||||
r.Get("/", api.userOIDC)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(
|
||||
|
|
|
@ -92,7 +92,6 @@ type Options struct {
|
|||
OIDCConfig *coderd.OIDCConfig
|
||||
GoogleTokenValidator *idtoken.Validator
|
||||
SSHKeygenAlgorithm gitsshkey.Algorithm
|
||||
APIRateLimit int
|
||||
AutobuildTicker <-chan time.Time
|
||||
AutobuildStats chan<- executor.Stats
|
||||
Auditor audit.Auditor
|
||||
|
@ -100,6 +99,11 @@ type Options struct {
|
|||
GitAuthConfigs []*gitauth.Config
|
||||
TrialGenerator func(context.Context, string) error
|
||||
|
||||
// All rate limits default to -1 (unlimited) in tests if not set.
|
||||
APIRateLimit int
|
||||
LoginRateLimit int
|
||||
FilesRateLimit int
|
||||
|
||||
// IncludeProvisionerDaemon when true means to start an in-memory provisionerD
|
||||
IncludeProvisionerDaemon bool
|
||||
MetricsCacheRefreshInterval time.Duration
|
||||
|
@ -177,6 +181,17 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
|
|||
options.DeploymentConfig = DeploymentConfig(t)
|
||||
}
|
||||
|
||||
// If no ratelimits are set, disable all rate limiting for tests.
|
||||
if options.APIRateLimit == 0 {
|
||||
options.APIRateLimit = -1
|
||||
}
|
||||
if options.LoginRateLimit == 0 {
|
||||
options.LoginRateLimit = -1
|
||||
}
|
||||
if options.FilesRateLimit == 0 {
|
||||
options.FilesRateLimit = -1
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
lifecycleExecutor := executor.New(
|
||||
ctx,
|
||||
|
@ -270,6 +285,8 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
|
|||
SSHKeygenAlgorithm: options.SSHKeygenAlgorithm,
|
||||
DERPServer: derpServer,
|
||||
APIRateLimit: options.APIRateLimit,
|
||||
LoginRateLimit: options.LoginRateLimit,
|
||||
FilesRateLimit: options.FilesRateLimit,
|
||||
Authorizer: options.Authorizer,
|
||||
Telemetry: telemetry.NewNoop(),
|
||||
TLSCertificates: options.TLSCertificates,
|
||||
|
|
|
@ -661,7 +661,7 @@ func TestTemplateVersionDryRun(t *testing.T) {
|
|||
Type: "cool_resource_type",
|
||||
}
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1, IncludeProvisionerDaemon: true})
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
|
@ -882,7 +882,7 @@ func TestTemplateVersionDryRun(t *testing.T) {
|
|||
func TestPaginatedTemplateVersions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1})
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
|
|
@ -1325,7 +1325,7 @@ func TestWorkspacesByUser(t *testing.T) {
|
|||
func TestSuspendedPagination(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Skip("This fails when two users are created at the exact same time. The reason is unknown... See: https://github.com/coder/coder/actions/runs/3057047622/jobs/4931863163")
|
||||
client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1})
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
|
@ -1370,7 +1370,7 @@ func TestSuspendedPagination(t *testing.T) {
|
|||
// them using different page sizes.
|
||||
func TestPaginatedUsers(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1})
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
// This test takes longer than a long time.
|
||||
|
|
|
@ -40,7 +40,7 @@ type DeploymentConfig struct {
|
|||
BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"`
|
||||
SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"`
|
||||
Provisioner *ProvisionerConfig `json:"provisioner" typescript:",notnull"`
|
||||
APIRateLimit *DeploymentConfigField[int] `json:"api_rate_limit" typescript:",notnull"`
|
||||
RateLimit *RateLimitConfig `json:"rate_limit" typescript:",notnull"`
|
||||
Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
|
||||
UpdateCheck *DeploymentConfigField[bool] `json:"update_check" typescript:",notnull"`
|
||||
MaxTokenLifetime *DeploymentConfigField[time.Duration] `json:"max_token_lifetime" typescript:",notnull"`
|
||||
|
@ -146,6 +146,11 @@ type ProvisionerConfig struct {
|
|||
ForceCancelInterval *DeploymentConfigField[time.Duration] `json:"force_cancel_interval" typescript:",notnull"`
|
||||
}
|
||||
|
||||
type RateLimitConfig struct {
|
||||
DisableAll *DeploymentConfigField[bool] `json:"disable_all" typescript:",notnull"`
|
||||
API *DeploymentConfigField[int] `json:"api" typescript:",notnull"`
|
||||
}
|
||||
|
||||
type SwaggerConfig struct {
|
||||
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
|
||||
}
|
||||
|
@ -155,15 +160,21 @@ type Flaggable interface {
|
|||
}
|
||||
|
||||
type DeploymentConfigField[T Flaggable] struct {
|
||||
Name string `json:"name"`
|
||||
Usage string `json:"usage"`
|
||||
Flag string `json:"flag"`
|
||||
Shorthand string `json:"shorthand"`
|
||||
Enterprise bool `json:"enterprise"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Secret bool `json:"secret"`
|
||||
Default T `json:"default"`
|
||||
Value T `json:"value"`
|
||||
Name string `json:"name"`
|
||||
Usage string `json:"usage"`
|
||||
Flag string `json:"flag"`
|
||||
// EnvOverride will override the automatically generated environment
|
||||
// variable name. Useful if you're moving values around but need to keep
|
||||
// backwards compatibility with old environment variable names.
|
||||
//
|
||||
// NOTE: this is not supported for array flags.
|
||||
EnvOverride string `json:"-"`
|
||||
Shorthand string `json:"shorthand"`
|
||||
Enterprise bool `json:"enterprise"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Secret bool `json:"secret"`
|
||||
Default T `json:"default"`
|
||||
Value T `json:"value"`
|
||||
}
|
||||
|
||||
// MarshalJSON removes the Value field from the JSON output of any fields marked Secret.
|
||||
|
|
|
@ -129,17 +129,6 @@ curl -X GET http://coder-server:8080/api/v2/config/deployment \
|
|||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"api_rate_limit": {
|
||||
"default": 0,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"audit_logging": {
|
||||
"default": true,
|
||||
"enterprise": true,
|
||||
|
@ -664,6 +653,30 @@ curl -X GET http://coder-server:8080/api/v2/config/deployment \
|
|||
"usage": "string",
|
||||
"value": "string"
|
||||
},
|
||||
"rate_limit": {
|
||||
"api": {
|
||||
"default": 0,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"disable_all": {
|
||||
"default": true,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"scim_api_key": {
|
||||
"default": "string",
|
||||
"enterprise": true,
|
||||
|
|
|
@ -844,17 +844,6 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"api_rate_limit": {
|
||||
"default": 0,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"audit_logging": {
|
||||
"default": true,
|
||||
"enterprise": true,
|
||||
|
@ -1379,6 +1368,30 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
"usage": "string",
|
||||
"value": "string"
|
||||
},
|
||||
"rate_limit": {
|
||||
"api": {
|
||||
"default": 0,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"disable_all": {
|
||||
"default": true,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"scim_api_key": {
|
||||
"default": "string",
|
||||
"enterprise": true,
|
||||
|
@ -1640,7 +1653,6 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| `address` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | |
|
||||
| `agent_fallback_troubleshooting_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | |
|
||||
| `agent_stat_refresh_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | |
|
||||
| `api_rate_limit` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | |
|
||||
| `audit_logging` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | |
|
||||
| `autobuild_poll_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | |
|
||||
| `browser_only` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | |
|
||||
|
@ -1660,6 +1672,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| `provisioner` | [codersdk.ProvisionerConfig](#codersdkprovisionerconfig) | false | | |
|
||||
| `proxy_trusted_headers` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | |
|
||||
| `proxy_trusted_origins` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | |
|
||||
| `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | |
|
||||
| `scim_api_key` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | |
|
||||
| `secure_auth_cookie` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | |
|
||||
| `ssh_keygen_algorithm` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | |
|
||||
|
@ -2532,6 +2545,42 @@ Parameter represents a set value for the scope.
|
|||
| ---------- | ------ | -------- | ------------ | ----------- |
|
||||
| `deadline` | string | true | | |
|
||||
|
||||
## codersdk.RateLimitConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"api": {
|
||||
"default": 0,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": 0
|
||||
},
|
||||
"disable_all": {
|
||||
"default": true,
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------- | -------------------------------------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `api` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | |
|
||||
| `disable_all` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | |
|
||||
|
||||
## codersdk.Response
|
||||
|
||||
```json
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
return "";
|
||||
}
|
||||
|
||||
let description = p.description.replaceAll("<br><br>", "\n").replaceAll("<br>", " ");
|
||||
let description = p.description.replace(/<br><br>/g, "\n").replace(/<br>/g, " ");
|
||||
const words = description.split(' ');
|
||||
if (words.length == 0) {
|
||||
return "";
|
||||
|
@ -53,8 +53,8 @@
|
|||
|
||||
const countUppercase = words[0].length - words[0].replace(/[A-Z]/g, '').length;
|
||||
if (countUppercase > 1) {
|
||||
let displayName = p.displayName.replaceAll("» **additionalProperties**", "It");
|
||||
displayName = displayName.charAt(0).toUpperCase() + displayName.replaceAll("_", " ").toLowerCase().slice(1);
|
||||
let displayName = p.displayName.replace(/» \*\*additionalProperties\*\*/g, "It");
|
||||
displayName = displayName.charAt(0).toUpperCase() + displayName.replace(/_/g, " ").toLowerCase().slice(1);
|
||||
description = displayName + " " + words.slice(1).join(' ');
|
||||
}
|
||||
return correctLetterCase(description);
|
||||
|
|
|
@ -308,7 +308,7 @@ export interface DeploymentConfig {
|
|||
readonly browser_only: DeploymentConfigField<boolean>
|
||||
readonly scim_api_key: DeploymentConfigField<string>
|
||||
readonly provisioner: ProvisionerConfig
|
||||
readonly api_rate_limit: DeploymentConfigField<number>
|
||||
readonly rate_limit: RateLimitConfig
|
||||
readonly experimental: DeploymentConfigField<boolean>
|
||||
readonly update_check: DeploymentConfigField<boolean>
|
||||
readonly max_token_lifetime: DeploymentConfigField<number>
|
||||
|
@ -585,6 +585,12 @@ export interface PutExtendWorkspaceRequest {
|
|||
readonly deadline: string
|
||||
}
|
||||
|
||||
// From codersdk/deploymentconfig.go
|
||||
export interface RateLimitConfig {
|
||||
readonly disable_all: DeploymentConfigField<boolean>
|
||||
readonly api: DeploymentConfigField<number>
|
||||
}
|
||||
|
||||
// From codersdk/replicas.go
|
||||
export interface Replica {
|
||||
readonly id: string
|
||||
|
|
Loading…
Reference in New Issue