Make server limits configurable.

This commit is contained in:
Sebastian Jeltsch 2026-04-17 12:34:24 +02:00
parent e08e648a0f
commit fd7e34a8c3
3 changed files with 41 additions and 22 deletions

View file

@ -5,6 +5,7 @@ server {
site_url: "http://localhost.trailbase.io:4000"
logs_retention_sec: 604800
enable_record_transactions: true
auth_ip_rate_limit: 50
}
auth {
enable_otp_signin: true

View file

@ -137,8 +137,19 @@ message ServerConfig {
/// If present will use S3 setup over local file-system based storage.
optional S3StorageConfig s3_storage_config = 13;
/// If enabled, batches of transactions can be submitted for attomic execution
/// If enabled, batches of transactions can be submitted for atomic execution
optional bool enable_record_transactions = 14;
/// Request size limit, default: 10MB.
optional uint64 request_size_limit_bytes = 15;
/// Limits the request per IP per second to auth POST APIs for abuse
/// protection. If TrailBase is behind a proxy, make sure to set
/// "X-Forwarded-For". Default: disabled.
///
/// Note that login endpoints have additional fixed rate limits
/// on a per credentials level.
optional uint32 auth_ip_rate_limit = 16;
}
enum SystemJobId {

View file

@ -37,6 +37,7 @@ use crate::auth::util::is_admin;
use crate::auth::{self, AuthError, User};
use crate::constants::{ADMIN_API_PATH, HEADER_CSRF_TOKEN};
use crate::data_dir::DataDir;
use crate::extract::ip::RealIpKeyExtractor;
use crate::logging;
use crate::records;
@ -170,18 +171,21 @@ impl Server {
// Install an Ip-based rate limiter on auth APIs to avoid abuse.
//
// NOTE: If you run into rate-limits and are running behind a reverse proxy, please set the
// "x-forwarded-for" header correctly to ensure ip-based rate limitting and request logging
// "x-forwarded-for" header correctly to ensure ip-based rate limiting and request logging
// works correctly.
// TODO: we should probably also allow user-configured rate limits on record APIs.
let install_auth_rate_limiter = {
// NOTE: We're using a closure here because of the awkward typing.
let install_auth_rate_limiter = if !opts.dev
&& let Some(rate_limit) = state.get_config().server.auth_ip_rate_limit
&& rate_limit > 0
{
let governor_conf = Arc::new(
GovernorConfigBuilder::default()
// Quota.
.burst_size(if cfg!(debug_assertions) { 50 } else { 5 })
// Replenish one after 2 seconds.
.per_second(2)
.key_extractor(crate::extract::ip::RealIpKeyExtractor)
// Set rate limiting headers on reply
.burst_size(rate_limit)
// Replenish one after 1 seconds.
.per_second(1)
.key_extractor(RealIpKeyExtractor)
// Set rate limiting headers on reply.
.use_headers()
// Only block POST method for abuse prevention (e.g. sign-up, ...), e.g. allow unlimited
// GET auth status.
@ -201,7 +205,11 @@ impl Server {
}
});
move |router: Router<crate::AppState>| router.layer(GovernorLayer::new(governor_conf.clone()))
Some(move |router: Router<crate::AppState>| {
router.layer(GovernorLayer::new(governor_conf.clone()))
})
} else {
None
};
Ok(Self {
@ -209,22 +217,14 @@ impl Server {
main_router: Self::build_main_router(
&state,
&opts,
if opts.dev {
None
} else {
Some(&install_auth_rate_limiter)
},
install_auth_rate_limiter.as_ref(),
custom_routers,
)
.await?,
admin_router: Self::build_independent_admin_router(
&state,
&opts,
if opts.dev {
None
} else {
Some(&install_auth_rate_limiter)
},
install_auth_rate_limiter.as_ref(),
),
tls: Self::load_tls(&opts),
})
@ -549,9 +549,16 @@ impl Server {
.on_request(logging::sqlite_logger_on_request)
.on_response(logging::sqlite_logger_on_response),
)
// Default is only 2MB Increase to 10MB.
// Default request size limit is only 2MB Increase to 10MB by default if no explicit user
// limit is provided.
.layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(10 * 1024 * 1024))
.layer(RequestBodyLimitLayer::new(
state
.get_config()
.server
.request_size_limit_bytes
.map_or(10 * 1024 * 1024, |limit| limit as usize),
))
.with_state(state.clone());
}
}