You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
Add configuration for rate-limiting of logins, replacing hardcoded limits (#3090)
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3303,6 +3303,7 @@ dependencies = [
|
|||||||
"camino",
|
"camino",
|
||||||
"chrono",
|
"chrono",
|
||||||
"figment",
|
"figment",
|
||||||
|
"governor",
|
||||||
"indoc",
|
"indoc",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
"mas-iana",
|
"mas-iana",
|
||||||
@@ -3380,6 +3381,7 @@ dependencies = [
|
|||||||
"insta",
|
"insta",
|
||||||
"lettre",
|
"lettre",
|
||||||
"mas-axum-utils",
|
"mas-axum-utils",
|
||||||
|
"mas-config",
|
||||||
"mas-data-model",
|
"mas-data-model",
|
||||||
"mas-http",
|
"mas-http",
|
||||||
"mas-i18n",
|
"mas-i18n",
|
||||||
|
@@ -197,14 +197,18 @@ impl Options {
|
|||||||
let activity_tracker = ActivityTracker::new(pool.clone(), Duration::from_secs(60));
|
let activity_tracker = ActivityTracker::new(pool.clone(), Duration::from_secs(60));
|
||||||
let trusted_proxies = config.http.trusted_proxies.clone();
|
let trusted_proxies = config.http.trusted_proxies.clone();
|
||||||
|
|
||||||
|
// Build a rate limiter.
|
||||||
|
// This should not raise an error here as the config should already have been
|
||||||
|
// validated.
|
||||||
|
let limiter = Limiter::new(&config.rate_limiting)
|
||||||
|
.context("rate-limiting configuration is not valid")?;
|
||||||
|
|
||||||
// Explicitly the config to properly zeroize secret keys
|
// Explicitly the config to properly zeroize secret keys
|
||||||
drop(config);
|
drop(config);
|
||||||
|
|
||||||
// Listen for SIGHUP
|
// Listen for SIGHUP
|
||||||
register_sighup(&templates, &activity_tracker)?;
|
register_sighup(&templates, &activity_tracker)?;
|
||||||
|
|
||||||
let limiter = Limiter::default();
|
|
||||||
|
|
||||||
limiter.start();
|
limiter.start();
|
||||||
|
|
||||||
let graphql_schema = mas_handlers::graphql_schema(
|
let graphql_schema = mas_handlers::graphql_schema(
|
||||||
|
@@ -38,6 +38,8 @@ rand_chacha = "0.3.1"
|
|||||||
|
|
||||||
indoc = "2.0.5"
|
indoc = "2.0.5"
|
||||||
|
|
||||||
|
governor.workspace = true
|
||||||
|
|
||||||
mas-jose.workspace = true
|
mas-jose.workspace = true
|
||||||
mas-keystore.workspace = true
|
mas-keystore.workspace = true
|
||||||
mas-iana.workspace = true
|
mas-iana.workspace = true
|
||||||
|
@@ -27,6 +27,7 @@ mod http;
|
|||||||
mod matrix;
|
mod matrix;
|
||||||
mod passwords;
|
mod passwords;
|
||||||
mod policy;
|
mod policy;
|
||||||
|
mod rate_limiting;
|
||||||
mod secrets;
|
mod secrets;
|
||||||
mod telemetry;
|
mod telemetry;
|
||||||
mod templates;
|
mod templates;
|
||||||
@@ -47,6 +48,7 @@ pub use self::{
|
|||||||
matrix::MatrixConfig,
|
matrix::MatrixConfig,
|
||||||
passwords::{Algorithm as PasswordAlgorithm, PasswordsConfig},
|
passwords::{Algorithm as PasswordAlgorithm, PasswordsConfig},
|
||||||
policy::PolicyConfig,
|
policy::PolicyConfig,
|
||||||
|
rate_limiting::RateLimitingConfig,
|
||||||
secrets::SecretsConfig,
|
secrets::SecretsConfig,
|
||||||
telemetry::{
|
telemetry::{
|
||||||
MetricsConfig, MetricsExporterKind, Propagator, TelemetryConfig, TracingConfig,
|
MetricsConfig, MetricsExporterKind, Propagator, TelemetryConfig, TracingConfig,
|
||||||
@@ -103,6 +105,11 @@ pub struct RootConfig {
|
|||||||
#[serde(default, skip_serializing_if = "PolicyConfig::is_default")]
|
#[serde(default, skip_serializing_if = "PolicyConfig::is_default")]
|
||||||
pub policy: PolicyConfig,
|
pub policy: PolicyConfig,
|
||||||
|
|
||||||
|
/// Configuration related to limiting the rate of user actions to prevent
|
||||||
|
/// abuse
|
||||||
|
#[serde(default, skip_serializing_if = "RateLimitingConfig::is_default")]
|
||||||
|
pub rate_limiting: RateLimitingConfig,
|
||||||
|
|
||||||
/// Configuration related to upstream OAuth providers
|
/// Configuration related to upstream OAuth providers
|
||||||
#[serde(default, skip_serializing_if = "UpstreamOAuth2Config::is_default")]
|
#[serde(default, skip_serializing_if = "UpstreamOAuth2Config::is_default")]
|
||||||
pub upstream_oauth2: UpstreamOAuth2Config,
|
pub upstream_oauth2: UpstreamOAuth2Config,
|
||||||
@@ -137,6 +144,7 @@ impl ConfigurationSection for RootConfig {
|
|||||||
self.secrets.validate(figment)?;
|
self.secrets.validate(figment)?;
|
||||||
self.matrix.validate(figment)?;
|
self.matrix.validate(figment)?;
|
||||||
self.policy.validate(figment)?;
|
self.policy.validate(figment)?;
|
||||||
|
self.rate_limiting.validate(figment)?;
|
||||||
self.upstream_oauth2.validate(figment)?;
|
self.upstream_oauth2.validate(figment)?;
|
||||||
self.branding.validate(figment)?;
|
self.branding.validate(figment)?;
|
||||||
self.captcha.validate(figment)?;
|
self.captcha.validate(figment)?;
|
||||||
@@ -168,6 +176,7 @@ impl RootConfig {
|
|||||||
secrets: SecretsConfig::generate(&mut rng).await?,
|
secrets: SecretsConfig::generate(&mut rng).await?,
|
||||||
matrix: MatrixConfig::generate(&mut rng),
|
matrix: MatrixConfig::generate(&mut rng),
|
||||||
policy: PolicyConfig::default(),
|
policy: PolicyConfig::default(),
|
||||||
|
rate_limiting: RateLimitingConfig::default(),
|
||||||
upstream_oauth2: UpstreamOAuth2Config::default(),
|
upstream_oauth2: UpstreamOAuth2Config::default(),
|
||||||
branding: BrandingConfig::default(),
|
branding: BrandingConfig::default(),
|
||||||
captcha: CaptchaConfig::default(),
|
captcha: CaptchaConfig::default(),
|
||||||
@@ -190,6 +199,7 @@ impl RootConfig {
|
|||||||
secrets: SecretsConfig::test(),
|
secrets: SecretsConfig::test(),
|
||||||
matrix: MatrixConfig::test(),
|
matrix: MatrixConfig::test(),
|
||||||
policy: PolicyConfig::default(),
|
policy: PolicyConfig::default(),
|
||||||
|
rate_limiting: RateLimitingConfig::default(),
|
||||||
upstream_oauth2: UpstreamOAuth2Config::default(),
|
upstream_oauth2: UpstreamOAuth2Config::default(),
|
||||||
branding: BrandingConfig::default(),
|
branding: BrandingConfig::default(),
|
||||||
captcha: CaptchaConfig::default(),
|
captcha: CaptchaConfig::default(),
|
||||||
@@ -225,6 +235,9 @@ pub struct AppConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub policy: PolicyConfig,
|
pub policy: PolicyConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub rate_limiting: RateLimitingConfig,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub branding: BrandingConfig,
|
pub branding: BrandingConfig,
|
||||||
|
|
||||||
@@ -248,6 +261,7 @@ impl ConfigurationSection for AppConfig {
|
|||||||
self.secrets.validate(figment)?;
|
self.secrets.validate(figment)?;
|
||||||
self.matrix.validate(figment)?;
|
self.matrix.validate(figment)?;
|
||||||
self.policy.validate(figment)?;
|
self.policy.validate(figment)?;
|
||||||
|
self.rate_limiting.validate(figment)?;
|
||||||
self.branding.validate(figment)?;
|
self.branding.validate(figment)?;
|
||||||
self.captcha.validate(figment)?;
|
self.captcha.validate(figment)?;
|
||||||
self.account.validate(figment)?;
|
self.account.validate(figment)?;
|
||||||
|
152
crates/config/src/sections/rate_limiting.rs
Normal file
152
crates/config/src/sections/rate_limiting.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::{num::NonZeroU32, time::Duration};
|
||||||
|
|
||||||
|
use governor::Quota;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{de::Error as _, Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::ConfigurationSection;
|
||||||
|
|
||||||
|
/// Configuration related to sending emails
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
|
pub struct RateLimitingConfig {
|
||||||
|
/// Login-specific rate limits
|
||||||
|
#[serde(default)]
|
||||||
|
pub login: LoginRateLimitingConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
|
pub struct LoginRateLimitingConfig {
|
||||||
|
/// Controls how many login attempts are permitted
|
||||||
|
/// based on source address.
|
||||||
|
/// This can protect against brute force login attempts.
|
||||||
|
///
|
||||||
|
/// Note: this limit also applies to password checks when a user attempts to
|
||||||
|
/// change their own password.
|
||||||
|
#[serde(default = "default_login_per_address")]
|
||||||
|
pub per_address: RateLimiterConfiguration,
|
||||||
|
/// Controls how many login attempts are permitted
|
||||||
|
/// based on the account that is being attempted to be logged into.
|
||||||
|
/// This can protect against a distributed brute force attack
|
||||||
|
/// but should be set high enough to prevent someone's account being
|
||||||
|
/// casually locked out.
|
||||||
|
///
|
||||||
|
/// Note: this limit also applies to password checks when a user attempts to
|
||||||
|
/// change their own password.
|
||||||
|
#[serde(default = "default_login_per_account")]
|
||||||
|
pub per_account: RateLimiterConfiguration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
|
pub struct RateLimiterConfiguration {
|
||||||
|
/// A one-off burst of actions that the user can perform
|
||||||
|
/// in one go without waiting.
|
||||||
|
pub burst: NonZeroU32,
|
||||||
|
/// How quickly the allowance replenishes, in number of actions per second.
|
||||||
|
/// Can be fractional to replenish slower.
|
||||||
|
pub per_second: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigurationSection for RateLimitingConfig {
|
||||||
|
const PATH: Option<&'static str> = Some("rate_limiting");
|
||||||
|
|
||||||
|
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
|
||||||
|
let metadata = figment.find_metadata(Self::PATH.unwrap());
|
||||||
|
|
||||||
|
let error_on_nested_field =
|
||||||
|
|mut error: figment::error::Error, container: &'static str, field: &'static str| {
|
||||||
|
error.metadata = metadata.cloned();
|
||||||
|
error.profile = Some(figment::Profile::Default);
|
||||||
|
error.path = vec![
|
||||||
|
Self::PATH.unwrap().to_owned(),
|
||||||
|
container.to_owned(),
|
||||||
|
field.to_owned(),
|
||||||
|
];
|
||||||
|
error
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check one limiter's configuration for errors
|
||||||
|
let error_on_limiter =
|
||||||
|
|limiter: &RateLimiterConfiguration| -> Option<figment::error::Error> {
|
||||||
|
let recip = limiter.per_second.recip();
|
||||||
|
// period must be at least 1 nanosecond according to the governor library
|
||||||
|
if recip < 1.0e-9 || !recip.is_finite() {
|
||||||
|
return Some(figment::error::Error::custom(
|
||||||
|
"`per_second` must be a number that is more than zero and less than 1_000_000_000 (1e9)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(error) = error_on_limiter(&self.login.per_address) {
|
||||||
|
return Err(error_on_nested_field(error, "login", "per_address"));
|
||||||
|
}
|
||||||
|
if let Some(error) = error_on_limiter(&self.login.per_account) {
|
||||||
|
return Err(error_on_nested_field(error, "login", "per_account"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateLimitingConfig {
|
||||||
|
pub(crate) fn is_default(config: &RateLimitingConfig) -> bool {
|
||||||
|
config == &RateLimitingConfig::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateLimiterConfiguration {
|
||||||
|
pub fn to_quota(self) -> Option<Quota> {
|
||||||
|
let reciprocal = self.per_second.recip();
|
||||||
|
if !reciprocal.is_finite() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Quota::with_period(Duration::from_secs_f64(reciprocal))?.allow_burst(self.burst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_login_per_address() -> RateLimiterConfiguration {
|
||||||
|
RateLimiterConfiguration {
|
||||||
|
burst: NonZeroU32::new(3).unwrap(),
|
||||||
|
per_second: 3.0 / 60.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_login_per_account() -> RateLimiterConfiguration {
|
||||||
|
RateLimiterConfiguration {
|
||||||
|
burst: NonZeroU32::new(1800).unwrap(),
|
||||||
|
per_second: 1800.0 / 3600.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::derivable_impls)] // when we add some top-level ratelimiters this will not be derivable anymore
|
||||||
|
impl Default for RateLimitingConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
RateLimitingConfig {
|
||||||
|
login: LoginRateLimitingConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LoginRateLimitingConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
LoginRateLimitingConfig {
|
||||||
|
per_address: default_login_per_address(),
|
||||||
|
per_account: default_login_per_account(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -81,6 +81,7 @@ headers.workspace = true
|
|||||||
ulid.workspace = true
|
ulid.workspace = true
|
||||||
|
|
||||||
mas-axum-utils.workspace = true
|
mas-axum-utils.workspace = true
|
||||||
|
mas-config.workspace = true
|
||||||
mas-data-model.workspace = true
|
mas-data-model.workspace = true
|
||||||
mas-http.workspace = true
|
mas-http.workspace = true
|
||||||
mas-i18n.workspace = true
|
mas-i18n.workspace = true
|
||||||
|
@@ -14,14 +14,11 @@
|
|||||||
|
|
||||||
use std::{net::IpAddr, sync::Arc, time::Duration};
|
use std::{net::IpAddr, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use governor::{clock::QuantaClock, state::keyed::DashMapStateStore, Quota, RateLimiter};
|
use governor::{clock::QuantaClock, state::keyed::DashMapStateStore, RateLimiter};
|
||||||
|
use mas_config::RateLimitingConfig;
|
||||||
use mas_data_model::User;
|
use mas_data_model::User;
|
||||||
use nonzero_ext::nonzero;
|
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
const PASSWORD_CHECK_FOR_REQUESTER_QUOTA: Quota = Quota::per_minute(nonzero!(3u32));
|
|
||||||
const PASSWORD_CHECK_FOR_USER_QUOTA: Quota = Quota::per_hour(nonzero!(1800u32));
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||||
pub enum PasswordCheckLimitedError {
|
pub enum PasswordCheckLimitedError {
|
||||||
#[error("Too many password checks for requester {0}")]
|
#[error("Too many password checks for requester {0}")]
|
||||||
@@ -60,7 +57,7 @@ impl RequesterFingerprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Rate limiters for the different operations
|
/// Rate limiters for the different operations
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Limiter {
|
pub struct Limiter {
|
||||||
inner: Arc<LimiterInner>,
|
inner: Arc<LimiterInner>,
|
||||||
}
|
}
|
||||||
@@ -73,16 +70,27 @@ struct LimiterInner {
|
|||||||
password_check_for_user: KeyedRateLimiter<Ulid>,
|
password_check_for_user: KeyedRateLimiter<Ulid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LimiterInner {
|
impl LimiterInner {
|
||||||
fn default() -> Self {
|
fn new(config: &RateLimitingConfig) -> Option<Self> {
|
||||||
Self {
|
Some(Self {
|
||||||
password_check_for_requester: RateLimiter::keyed(PASSWORD_CHECK_FOR_REQUESTER_QUOTA),
|
password_check_for_requester: RateLimiter::keyed(config.login.per_address.to_quota()?),
|
||||||
password_check_for_user: RateLimiter::keyed(PASSWORD_CHECK_FOR_USER_QUOTA),
|
password_check_for_user: RateLimiter::keyed(config.login.per_account.to_quota()?),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Limiter {
|
impl Limiter {
|
||||||
|
/// Creates a new `Limiter` based on a `RateLimitingConfig`.
|
||||||
|
///
|
||||||
|
/// If the config is not valid, returns `None`.
|
||||||
|
/// (This should not happen if the config was validated, though.)
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(config: &RateLimitingConfig) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
inner: Arc::new(LimiterInner::new(config)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Start the rate limiter housekeeping task
|
/// Start the rate limiter housekeeping task
|
||||||
///
|
///
|
||||||
/// This task will periodically remove old entries from the rate limiters,
|
/// This task will periodically remove old entries from the rate limiters,
|
||||||
@@ -142,7 +150,7 @@ mod tests {
|
|||||||
let now = MockClock::default().now();
|
let now = MockClock::default().now();
|
||||||
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
|
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
|
||||||
|
|
||||||
let limiter = Limiter::default();
|
let limiter = Limiter::new(&RateLimitingConfig::default()).unwrap();
|
||||||
|
|
||||||
// Let's create a lot of requesters to test account-level rate limiting
|
// Let's create a lot of requesters to test account-level rate limiting
|
||||||
let requesters: [_; 768] = (0..=255)
|
let requesters: [_; 768] = (0..=255)
|
||||||
|
@@ -37,6 +37,7 @@ use mas_axum_utils::{
|
|||||||
http_client_factory::HttpClientFactory,
|
http_client_factory::HttpClientFactory,
|
||||||
ErrorWrapper,
|
ErrorWrapper,
|
||||||
};
|
};
|
||||||
|
use mas_config::RateLimitingConfig;
|
||||||
use mas_data_model::SiteConfig;
|
use mas_data_model::SiteConfig;
|
||||||
use mas_i18n::Translator;
|
use mas_i18n::Translator;
|
||||||
use mas_keystore::{Encrypter, JsonWebKey, JsonWebKeySet, Keystore, PrivateKey};
|
use mas_keystore::{Encrypter, JsonWebKey, JsonWebKeySet, Keystore, PrivateKey};
|
||||||
@@ -214,7 +215,7 @@ impl TestState {
|
|||||||
let activity_tracker =
|
let activity_tracker =
|
||||||
ActivityTracker::new(pool.clone(), std::time::Duration::from_secs(1));
|
ActivityTracker::new(pool.clone(), std::time::Duration::from_secs(1));
|
||||||
|
|
||||||
let limiter = Limiter::default();
|
let limiter = Limiter::new(&RateLimitingConfig::default()).unwrap();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
pool,
|
pool,
|
||||||
|
@@ -168,6 +168,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"rate_limiting": {
|
||||||
|
"description": "Configuration related to limiting the rate of user actions to prevent abuse",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RateLimitingConfig"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"upstream_oauth2": {
|
"upstream_oauth2": {
|
||||||
"description": "Configuration related to upstream OAuth providers",
|
"description": "Configuration related to upstream OAuth providers",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -1656,6 +1664,79 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"RateLimitingConfig": {
|
||||||
|
"description": "Configuration related to sending emails",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"login": {
|
||||||
|
"description": "Login-specific rate limits",
|
||||||
|
"default": {
|
||||||
|
"per_address": {
|
||||||
|
"burst": 3,
|
||||||
|
"per_second": 0.05
|
||||||
|
},
|
||||||
|
"per_account": {
|
||||||
|
"burst": 1800,
|
||||||
|
"per_second": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/LoginRateLimitingConfig"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LoginRateLimitingConfig": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"per_address": {
|
||||||
|
"description": "Controls how many login attempts are permitted based on source address. This can protect against brute force login attempts.\n\nNote: this limit also applies to password checks when a user attempts to change their own password.",
|
||||||
|
"default": {
|
||||||
|
"burst": 3,
|
||||||
|
"per_second": 0.05
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RateLimiterConfiguration"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"per_account": {
|
||||||
|
"description": "Controls how many login attempts are permitted based on the account that is being attempted to be logged into. This can protect against a distributed brute force attack but should be set high enough to prevent someone's account being casually locked out.\n\nNote: this limit also applies to password checks when a user attempts to change their own password.",
|
||||||
|
"default": {
|
||||||
|
"burst": 1800,
|
||||||
|
"per_second": 0.5
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RateLimiterConfiguration"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RateLimiterConfiguration": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"burst",
|
||||||
|
"per_second"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"burst": {
|
||||||
|
"description": "A one-off burst of actions that the user can perform in one go without waiting.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 1.0
|
||||||
|
},
|
||||||
|
"per_second": {
|
||||||
|
"description": "How quickly the allowance replenishes, in number of actions per second. Can be fractional to replenish slower.",
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"UpstreamOAuth2Config": {
|
"UpstreamOAuth2Config": {
|
||||||
"description": "Upstream OAuth 2.0 providers configuration",
|
"description": "Upstream OAuth 2.0 providers configuration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@@ -361,6 +361,38 @@ policy:
|
|||||||
require_number: true
|
require_number: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `rate_limiting`
|
||||||
|
|
||||||
|
Settings for limiting the rate of user actions to prevent abuse.
|
||||||
|
|
||||||
|
Each rate limiter consists of two options:
|
||||||
|
- `burst`: a base amount of how many actions are allowed in one go.
|
||||||
|
- `per_second`: how many units of the allowance replenish per second.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rate_limiting:
|
||||||
|
# Limits how many login attempts are allowed.
|
||||||
|
#
|
||||||
|
# Note: these limit also applies to password checks when a user attempts to
|
||||||
|
# change their own password.
|
||||||
|
login:
|
||||||
|
# Controls how many login attempts are permitted
|
||||||
|
# based on source address.
|
||||||
|
# This can protect against brute force login attempts.
|
||||||
|
per_address:
|
||||||
|
burst: 3
|
||||||
|
per_second: 0.05
|
||||||
|
|
||||||
|
# Controls how many login attempts are permitted
|
||||||
|
# based on the account that is being attempted to be logged into.
|
||||||
|
# This can protect against a distributed brute force attack
|
||||||
|
# but should be set high enough to prevent someone's account being
|
||||||
|
# casually locked out.
|
||||||
|
per_account:
|
||||||
|
burst: 1800
|
||||||
|
per_second: 0.5
|
||||||
|
```
|
||||||
|
|
||||||
## `telemetry`
|
## `telemetry`
|
||||||
|
|
||||||
Settings related to metrics and traces
|
Settings related to metrics and traces
|
||||||
|
Reference in New Issue
Block a user