Add rate-limiting options

This commit is contained in:
Louis Dureuil 2022-12-28 15:47:38 +01:00
parent ab655a85e8
commit 5cf71c6014
No known key found for this signature in database
4 changed files with 295 additions and 2 deletions

View File

@ -73,6 +73,69 @@ ignore_dump_if_db_exists = false
# https://docs.meilisearch.com/learn/configuration/instance_options.html#ignore-dump-if-db-exists
#####################
### RATE LIMITING ###
#####################
rate_limiting_disable_all = false
# Prevents a Meilisearch instance from performing any rate limiting.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-disable-all
rate_limiting_disable_global = false
# Prevents a Meilisearch instance from performing rate limiting global to all queries.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-disable-global
rate_limiting_global_pool = 100000
# The maximum pool of search requests that can be performed before they are rejected.
#
# The pool starts full at the provided value, then each search request diminishes the pool by 1.
# When the pool is empty the search request is rejected.
# The pool is replenished by 1 depending on the cooldown period.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-global-pool
rate_limiting_global_cooldown_ns = 50000
# The amount of time, in nanoseconds, before the pool of available search requests is replenished by 1 again.
#
# The maximum number of available search requests is given by `rate_limiting_global_pool`.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-global-cooldown-ns
rate_limiting_disable_ip = false
# Prevents a Meilisearch instance from performing rate limiting per IP address.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-disable-ip
rate_limiting_ip_pool = 200
# The maximum pool of search requests that can be performed from a specific IP before they are rejected.
#
# The pool starts full at the provided value, then each search request from the same IP address diminishes the pool by 1.
# When the pool is empty the search request is rejected.
# The pool is replenished by 1 depending on the cooldown period.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-ip-pool
rate_limiting_ip_cooldown_ns = 50000000
# The amount of time, in nanoseconds, before the pool of available search requests for a specific IP address is replenished by 1 again.
#
# The maximum number of available search requests for a specific IP address is given by `rate_limiting_ip_pool`.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-ip-cooldown-ns
rate_limiting_disable_api_key = false
# Prevents a Meilisearch instance from performing rate limiting per API key.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-disable-api-key
rate_limiting_api_key_pool = 10000
# The maximum pool of search requests that can be performed using a specific API key before they are rejected.
#
# The pool starts full at the provided value, then each search request using the same API key diminishes the pool by 1.
# When the pool is empty the search request is rejected.
# The pool is replenished by 1 depending on the cooldown period.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-api-key-pool
rate_limiting_api_key_cooldown_ns = 500000
# The amount of time, in nanoseconds, before the pool of available search requests using a specific API key is replenished by 1 again.
#
# The maximum number of available search requests using a specific API key is given by `rate_limiting_api_key_pool`.
# https://docs.meilisearch.com/learn/configuration/instance_options.html#rate-limiting-api-key-cooldown-ns
#################
### SNAPSHOTS ###
#################

View File

@ -25,7 +25,9 @@ use uuid::Uuid;
use super::{config_user_id_path, DocumentDeletionKind, MEILISEARCH_CONFIG_PATH};
use crate::analytics::Analytics;
use crate::option::{default_http_addr, IndexerOpts, MaxMemory, MaxThreads, SchedulerConfig};
use crate::option::{
default_http_addr, IndexerOpts, MaxMemory, MaxThreads, RateLimiterConfig, SchedulerConfig,
};
use crate::routes::indexes::documents::UpdateDocumentsQuery;
use crate::routes::tasks::TasksFilterQueryRaw;
use crate::routes::{create_all_stats, Stats};
@ -241,6 +243,16 @@ struct Infos {
ssl_require_auth: bool,
ssl_resumption: bool,
ssl_tickets: bool,
rate_limiting_disable_all: bool,
rate_limiting_disable_global: bool,
rate_limiting_global_pool: u32,
rate_limiting_global_cooldown_ns: u64,
rate_limiting_disable_ip: bool,
rate_limiting_ip_pool: u32,
rate_limiting_ip_cooldown_ns: u64,
rate_limiting_disable_api_key: bool,
rate_limiting_api_key_pool: u32,
rate_limiting_api_key_cooldown_ns: u64,
}
impl From<Opt> for Infos {
@ -278,6 +290,7 @@ impl From<Opt> for Infos {
scheduler_options,
config_file_path,
generate_master_key: _,
rate_limiter_options,
#[cfg(all(not(debug_assertions), feature = "analytics"))]
no_analytics: _,
} = options;
@ -289,6 +302,18 @@ impl From<Opt> for Infos {
max_indexing_memory,
max_indexing_threads,
} = indexer_options;
let RateLimiterConfig {
rate_limiting_disable_all,
rate_limiting_disable_global,
rate_limiting_global_pool,
rate_limiting_global_cooldown_ns,
rate_limiting_disable_ip,
rate_limiting_ip_pool,
rate_limiting_ip_cooldown_ns,
rate_limiting_disable_api_key,
rate_limiting_api_key_pool,
rate_limiting_api_key_cooldown_ns,
} = rate_limiter_options;
// We're going to override every sensible information.
// We consider information sensible if it contains a path, an address, or a key.
@ -321,6 +346,16 @@ impl From<Opt> for Infos {
ssl_require_auth,
ssl_resumption,
ssl_tickets,
rate_limiting_disable_all,
rate_limiting_disable_global,
rate_limiting_global_pool,
rate_limiting_global_cooldown_ns,
rate_limiting_disable_ip,
rate_limiting_ip_pool,
rate_limiting_ip_cooldown_ns,
rate_limiting_disable_api_key,
rate_limiting_api_key_pool,
rate_limiting_api_key_cooldown_ns,
}
}
}

View File

@ -50,6 +50,22 @@ const MEILI_IGNORE_DUMP_IF_DB_EXISTS: &str = "MEILI_IGNORE_DUMP_IF_DB_EXISTS";
const MEILI_DUMP_DIR: &str = "MEILI_DUMP_DIR";
const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL";
const MEILI_GENERATE_MASTER_KEY: &str = "MEILI_GENERATE_MASTER_KEY";
// rate limiting
const MEILI_RATE_LIMITING_DISABLE_ALL: &str = "MEILI_RATE_LIMITING_DISABLE_ALL";
const MEILI_RATE_LIMITING_DISABLE_GLOBAL: &str = "MEILI_RATE_LIMITING_DISABLE_GLOBAL";
const MEILI_RATE_LIMITING_DISABLE_IP: &str = "MEILI_RATE_LIMITING_DISABLE_IP";
const MEILI_RATE_LIMITING_DISABLE_API_KEY: &str = "MEILI_RATE_LIMITING_DISABLE_API_KEY";
const MEILI_RATE_LIMITING_GLOBAL_POOL: &str = "MEILI_RATE_LIMITING_GLOBAL_POOL";
const MEILI_RATE_LIMITING_IP_POOL: &str = "MEILI_RATE_LIMITING_IP_POOL";
const MEILI_RATE_LIMITING_API_KEY_POOL: &str = "MEILI_RATE_LIMITING_API_KEY_POOL";
const MEILI_RATE_LIMITING_GLOBAL_COOLDOWN_NS: &str = "MEILI_RATE_LIMITING_GLOBAL_COOLDOWN_NS";
const MEILI_RATE_LIMITING_IP_COOLDOWN_NS: &str = "MEILI_RATE_LIMITING_IP_COOLDOWN_NS";
const MEILI_RATE_LIMITING_API_KEY_COOLDOWN_NS: &str = "MEILI_RATE_LIMITING_API_KEY_COOLDOWN_NS";
#[cfg(feature = "metrics")]
const MEILI_ENABLE_METRICS_ROUTE: &str = "MEILI_ENABLE_METRICS_ROUTE";
@ -70,6 +86,15 @@ const MEILI_MAX_INDEXING_THREADS: &str = "MEILI_MAX_INDEXING_THREADS";
const DISABLE_AUTO_BATCHING: &str = "DISABLE_AUTO_BATCHING";
const DEFAULT_LOG_EVERY_N: usize = 100000;
const DEFAULT_GLOBAL_RATE_LIMITING_POOL: u32 = 100_000;
const DEFAULT_GLOBAL_RATE_LIMITING_COOLDOWN_NS: u64 = 50_000; // pool replenishes in 5s
const DEFAULT_IP_RATE_LIMITING_POOL: u32 = 200;
const DEFAULT_IP_RATE_LIMITING_COOLDOWN_NS: u64 = 50_000_000; // pool replenishes in 10s
const DEFAULT_API_KEY_RATE_LIMITING_POOL: u32 = 10_000;
const DEFAULT_API_KEY_RATE_LIMITING_COOLDOWN_NS: u64 = 500_000; // pool replenishes in 10s
#[derive(Debug, Clone, Parser, Deserialize)]
#[clap(version, next_display_order = None)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
@ -252,6 +277,10 @@ pub struct Opt {
#[clap(flatten)]
pub scheduler_options: SchedulerConfig,
#[serde(flatten)]
#[clap(flatten)]
pub rate_limiter_options: RateLimiterConfig,
/// Set the path to a configuration file that should be used to setup the engine.
/// Format must be TOML.
#[clap(long)]
@ -340,6 +369,7 @@ impl Opt {
ignore_missing_dump: _,
ignore_dump_if_db_exists: _,
config_file_path: _,
rate_limiter_options,
#[cfg(all(not(debug_assertions), feature = "analytics"))]
no_analytics,
#[cfg(feature = "metrics")]
@ -393,6 +423,7 @@ impl Opt {
}
indexer_options.export_to_env();
scheduler_options.export_to_env();
rate_limiter_options.export_to_env();
}
pub fn get_ssl_config(&self) -> anyhow::Result<Option<rustls::ServerConfig>> {
@ -537,6 +568,142 @@ impl Default for IndexerOpts {
}
}
/// Options related to the configuration of the rate limiters.
#[derive(Debug, Clone, Parser, Default, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct RateLimiterConfig {
/// When provided, completely disables all rate limiting.
#[clap(long, env = MEILI_RATE_LIMITING_DISABLE_ALL)]
#[serde(default)]
pub rate_limiting_disable_all: bool,
/// When provided, disables the global rate limiting that applies to all search requests.
///
/// Disabling the global rate limiting does not disable IP-based and API-key-based rate limitings.
/// To disable all rate limiting regardless of the origin use `--rate-limiting-disable-all`.
#[clap(long, env = MEILI_RATE_LIMITING_DISABLE_GLOBAL)]
#[serde(default)]
pub rate_limiting_disable_global: bool,
/// The maximum pool of search requests that can be performed before they are rejected.
///
/// The pool starts full at the provided value, then each search request diminishes the pool by 1.
/// When the pool is empty the search request is rejected.
/// The pool is replenished by 1 depending on the cooldown period.
#[clap(long, env = MEILI_RATE_LIMITING_GLOBAL_POOL, default_value_t = default_rate_limiting_global_pool())]
#[serde(default = "default_rate_limiting_global_pool")]
pub rate_limiting_global_pool: u32,
/// The amount of time, in nanoseconds, before the pool of available search requests is replenished by 1 again.
///
/// The maximum number of available search requests is given by `--rate-limiting-global-pool`.
#[clap(long, env = MEILI_RATE_LIMITING_GLOBAL_COOLDOWN_NS, default_value_t = default_rate_limiting_global_cooldown_ns())]
#[serde(default = "default_rate_limiting_global_cooldown_ns")]
pub rate_limiting_global_cooldown_ns: u64,
/// When provided, disables the rate limiting that applies to all search requests originating with a specific IP address.
///
/// Disabling the IP rate limiting does not disable the rate limiting that applies to all requests ("global") nor the API-key-based rate limiting.
/// To disable all rate limiting regardless of the origin use `--rate-limiting-disable-all`.
#[clap(long, env = MEILI_RATE_LIMITING_DISABLE_IP)]
#[serde(default)]
pub rate_limiting_disable_ip: bool,
/// The maximum pool of search requests that can be performed from a specific IP before they are rejected.
///
/// The pool starts full at the provided value, then each search request from the same IP address diminishes the pool by 1.
/// When the pool is empty the search request is rejected.
/// The pool is replenished by 1 depending on the cooldown period.
#[clap(long, env = MEILI_RATE_LIMITING_IP_POOL, default_value_t = default_rate_limiting_ip_pool())]
#[serde(default = "default_rate_limiting_ip_pool")]
pub rate_limiting_ip_pool: u32,
/// The amount of time, in nanoseconds, before the pool of available search requests for a specific IP address is replenished by 1 again.
///
/// The maximum number of available search requests for a specific IP address is given by `--rate-limiting-ip-pool`.
#[clap(long, env = MEILI_RATE_LIMITING_IP_COOLDOWN_NS, default_value_t = default_rate_limiting_ip_cooldown_ns())]
#[serde(default = "default_rate_limiting_ip_cooldown_ns")]
pub rate_limiting_ip_cooldown_ns: u64,
/// When provided, disables the rate limiting that applies to all search requests originating with a specific API key.
///
/// Disabling the API key limiting does not disable the rate limiting that applies to all requests ("global") nor the IP-based rate limiting.
/// To disable all rate limiting regardless of the origin use `--rate-limiting-disable-all`.
#[clap(long, env = MEILI_RATE_LIMITING_DISABLE_API_KEY)]
#[serde(default)]
pub rate_limiting_disable_api_key: bool,
/// The maximum pool of search requests that can be performed using a specific API key before they are rejected.
///
/// The pool starts full at the provided value, then each search request using the same API key diminishes the pool by 1.
/// When the pool is empty the search request is rejected.
/// The pool is replenished by 1 depending on the cooldown period.
#[clap(long, env = MEILI_RATE_LIMITING_API_KEY_POOL, default_value_t = default_rate_limiting_api_key_pool())]
#[serde(default = "default_rate_limiting_api_key_pool")]
pub rate_limiting_api_key_pool: u32,
/// The amount of time, in nanoseconds, before the pool of available search requests using a specific API key is replenished by 1 again.
///
/// The maximum number of available search requests using a specific API key is given by `--rate-limiting-api-key-pool`.
#[clap(long, env = MEILI_RATE_LIMITING_API_KEY_COOLDOWN_NS, default_value_t = default_rate_limiting_api_key_cooldown_ns())]
#[serde(default = "default_rate_limiting_api_key_cooldown_ns")]
pub rate_limiting_api_key_cooldown_ns: u64,
}
impl RateLimiterConfig {
/// Exports the values to their corresponding env vars if they are not set.
pub fn export_to_env(self) {
let RateLimiterConfig {
rate_limiting_disable_all: disable_rate_limiting,
rate_limiting_disable_global: disable_global_rate_limiting,
rate_limiting_global_pool: global_rate_limiting_pool,
rate_limiting_global_cooldown_ns: global_rate_limiting_cooldown_ns,
rate_limiting_disable_ip: disable_ip_rate_limiting,
rate_limiting_ip_pool: ip_rate_limiting_pool,
rate_limiting_ip_cooldown_ns: ip_rate_limiting_cooldown_ns,
rate_limiting_disable_api_key: disable_api_key_rate_limiting,
rate_limiting_api_key_pool: api_key_rate_limiting_pool,
rate_limiting_api_key_cooldown_ns: api_key_rate_limiting_cooldown_ns,
} = self;
export_to_env_if_not_present(
MEILI_RATE_LIMITING_DISABLE_ALL,
disable_rate_limiting.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_DISABLE_GLOBAL,
disable_global_rate_limiting.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_DISABLE_IP,
disable_ip_rate_limiting.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_DISABLE_API_KEY,
disable_api_key_rate_limiting.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_GLOBAL_POOL,
global_rate_limiting_pool.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_IP_POOL,
ip_rate_limiting_pool.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_API_KEY_POOL,
api_key_rate_limiting_pool.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_GLOBAL_COOLDOWN_NS,
global_rate_limiting_cooldown_ns.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_IP_COOLDOWN_NS,
ip_rate_limiting_cooldown_ns.to_string(),
);
export_to_env_if_not_present(
MEILI_RATE_LIMITING_API_KEY_COOLDOWN_NS,
api_key_rate_limiting_cooldown_ns.to_string(),
);
}
}
/// A type used to detect the max memory available and use 2/3 of it.
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct MaxMemory(Option<Byte>);
@ -729,6 +896,30 @@ fn default_log_every_n() -> usize {
DEFAULT_LOG_EVERY_N
}
fn default_rate_limiting_global_pool() -> u32 {
DEFAULT_GLOBAL_RATE_LIMITING_POOL
}
fn default_rate_limiting_ip_pool() -> u32 {
DEFAULT_IP_RATE_LIMITING_POOL
}
fn default_rate_limiting_api_key_pool() -> u32 {
DEFAULT_API_KEY_RATE_LIMITING_POOL
}
fn default_rate_limiting_global_cooldown_ns() -> u64 {
DEFAULT_GLOBAL_RATE_LIMITING_COOLDOWN_NS
}
fn default_rate_limiting_ip_cooldown_ns() -> u64 {
DEFAULT_IP_RATE_LIMITING_COOLDOWN_NS
}
fn default_rate_limiting_api_key_cooldown_ns() -> u64 {
DEFAULT_API_KEY_RATE_LIMITING_COOLDOWN_NS
}
#[cfg(test)]
mod test {

View File

@ -8,7 +8,7 @@ use actix_web::dev::ServiceResponse;
use actix_web::http::StatusCode;
use byte_unit::{Byte, ByteUnit};
use clap::Parser;
use meilisearch::option::{IndexerOpts, MaxMemory, Opt};
use meilisearch::option::{IndexerOpts, MaxMemory, Opt, RateLimiterConfig};
use meilisearch::{analytics, create_app, setup_meilisearch};
use once_cell::sync::Lazy;
use serde_json::{json, Value};
@ -192,6 +192,10 @@ pub fn default_settings(dir: impl AsRef<Path>) -> Opt {
max_task_db_size: Byte::from_unit(1.0, ByteUnit::GiB).unwrap(),
http_payload_size_limit: Byte::from_unit(10.0, ByteUnit::MiB).unwrap(),
snapshot_dir: ".".into(),
rate_limiter_options: RateLimiterConfig {
rate_limiting_disable_all: true,
..Parser::parse_from(None as Option<&str>)
},
indexer_options: IndexerOpts {
// memory has to be unlimited because several meilisearch are running in test context.
max_indexing_memory: MaxMemory::unlimited(),