From 983424e17b11754efa8381015656678fafb07b58 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 22:03:52 +1000 Subject: [PATCH] Add rate limiting functionality --- backend/Cargo.lock | 173 +++++++++++++++++++++++++++++++++++++++++++- backend/Cargo.toml | 4 +- backend/src/main.rs | 27 ++++++- 3 files changed, 197 insertions(+), 7 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f1f695a..768aa05 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -604,7 +604,9 @@ dependencies = [ "serde_json", "sql-adaptor", "tokio", + "tower", "tower-http", + "tower_governor", "tracing", "tracing-subscriber", ] @@ -702,6 +704,19 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if", + "hashbrown 0.12.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.7", +] + [[package]] name = "der" version = "0.5.1" @@ -840,6 +855,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror", +] + [[package]] name = "futures" version = "0.3.28" @@ -848,6 +873,7 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -889,7 +915,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -913,6 +939,17 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -925,6 +962,12 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.28" @@ -934,6 +977,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -975,6 +1019,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "governor" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c390a940a5d157878dd057c78680a33ce3415bcd05b4799509ea44210914b4d5" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot 0.12.1", + "quanta", + "rand", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1295,6 +1357,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1367,6 +1438,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nom" version = "7.1.3" @@ -1377,6 +1454,18 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1555,7 +1644,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.7", ] [[package]] @@ -1572,6 +1671,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + [[package]] name = "paste" version = "1.0.12" @@ -1743,6 +1855,22 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" +[[package]] +name = "quanta" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8" +dependencies = [ + "crossbeam-utils", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi 0.10.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.27" @@ -1782,6 +1910,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2237,6 +2374,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -2586,7 +2732,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -2665,6 +2813,7 @@ dependencies = [ "pin-project-lite", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2679,6 +2828,26 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "tower_governor" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6be418f6d18863291f0a7fa1da1de71495a19a54b5fb44969136f731a47e86" +dependencies = [ + "axum", + "forwarded-header-value", + "futures", + "futures-core", + "governor", + "http", + "pin-project", + "thiserror", + "tokio", + "tower", + "tower-layer", + "tracing", +] + [[package]] name = "tracing" version = "0.1.37" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d123fac..2e0e2a7 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -21,4 +21,6 @@ tracing = "0.1.37" tracing-subscriber = "0.3.17" chrono = "0.4.24" bcrypt = "0.14.0" -tower-http = { version = "0.4.0", features = ["cors"] } +tower-http = { version = "0.4.0", features = ["cors", "trace"] } +tower_governor = "0.0.4" +tower = "0.4.13" diff --git a/backend/src/main.rs b/backend/src/main.rs index 644a879..a875d68 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,15 +1,18 @@ use std::{env, net::SocketAddr, sync::Arc}; use axum::{ + error_handling::HandleErrorLayer, extract, http::{HeaderValue, Method}, routing::{get, patch, post}, - Router, Server, + BoxError, Router, Server, }; use routes::*; use sql_adaptor::SqlAdaptor; use tokio::sync::Mutex; -use tower_http::cors::CorsLayer; +use tower::ServiceBuilder; +use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer}; +use tower_http::{cors::CorsLayer, trace::TraceLayer}; mod errors; mod payloads; @@ -32,6 +35,7 @@ async fn main() { adaptor: SqlAdaptor::new().await, })); + // CORS configuration let cors = CorsLayer::new() .allow_methods([Method::GET, Method::POST, Method::PATCH]) .allow_origin( @@ -44,6 +48,19 @@ async fn main() { .unwrap(), ); + // Rate limiting configuration (using tower_governor) + // From the docs: Allows bursts with up to eight requests and replenishes + // one element after 500ms, based on peer IP. + let governor_config = Box::new(GovernorConfigBuilder::default().finish().unwrap()); + let rate_limit = ServiceBuilder::new() + // Handle errors from governor and convert into HTTP responses + .layer(HandleErrorLayer::new(|e: BoxError| async move { + display_error(e) + })) + .layer(GovernorLayer { + config: Box::leak(governor_config), + }); + let app = Router::new() .route("/", get(get_root)) .route("/stats", get(get_stats)) @@ -53,7 +70,9 @@ async fn main() { .route("/event/:event_id/people/:person_name", get(get_person)) .route("/event/:event_id/people/:person_name", patch(update_person)) .with_state(shared_state) - .layer(cors); + .layer(cors) + .layer(rate_limit) + .layer(TraceLayer::new_for_http()); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -67,7 +86,7 @@ async fn main() { } ); Server::bind(&addr) - .serve(app.into_make_service()) + .serve(app.into_make_service_with_connect_info::()) .await .unwrap(); }