feat(registry): add ratelimits

This commit is contained in:
daimond113 2024-03-06 21:40:11 +01:00
parent d6dcad739f
commit 21d841fcee
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
7 changed files with 173 additions and 3234 deletions

116
Cargo.lock generated
View file

@ -34,6 +34,18 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "actix-governor"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2e7b88f3804e01bd4191fdb08650430bbfcb43d3d9b2890064df3551ec7d25b"
dependencies = [
"actix-http",
"actix-web",
"futures",
"governor",
]
[[package]] [[package]]
name = "actix-http" name = "actix-http"
version = "3.6.0" version = "3.6.0"
@ -1116,6 +1128,19 @@ dependencies = [
"syn 2.0.52", "syn 2.0.52",
] ]
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.14.3",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.5.0" version = "2.5.0"
@ -1541,6 +1566,21 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.30" version = "0.3.30"
@ -1548,6 +1588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@ -1624,12 +1665,19 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.30" version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro", "futures-macro",
@ -1732,6 +1780,26 @@ dependencies = [
"regex-syntax 0.8.2", "regex-syntax 0.8.2",
] ]
[[package]]
name = "governor"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b"
dependencies = [
"cfg-if",
"dashmap",
"futures",
"futures-timer",
"no-std-compat",
"nonzero_ext",
"parking_lot",
"portable-atomic",
"quanta",
"rand",
"smallvec",
"spinning_top",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.24" version = "0.3.24"
@ -2628,6 +2696,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "no-std-compat"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -2638,6 +2712,12 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nonzero_ext"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -2973,9 +3053,10 @@ dependencies = [
[[package]] [[package]]
name = "pesde-registry" name = "pesde-registry"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"actix-cors", "actix-cors",
"actix-governor",
"actix-multipart", "actix-multipart",
"actix-multipart-derive", "actix-multipart-derive",
"actix-web", "actix-web",
@ -3174,6 +3255,21 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "quanta"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c"
dependencies = [
"crossbeam-utils",
"libc",
"once_cell",
"raw-cpuid",
"wasi 0.11.0+wasi-snapshot-preview1",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.30.0" version = "0.30.0"
@ -3242,6 +3338,15 @@ dependencies = [
"getrandom 0.2.12", "getrandom 0.2.12",
] ]
[[package]]
name = "raw-cpuid"
version = "11.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1"
dependencies = [
"bitflags 2.4.2",
]
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.9.0" version = "1.9.0"
@ -4146,6 +4251,15 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spinning_top"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
dependencies = [
"lock_api",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"

3201
registry/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pesde-registry" name = "pesde-registry"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
@ -9,6 +9,7 @@ actix-cors = "0.7.0"
actix-web-httpauth = "0.8.1" actix-web-httpauth = "0.8.1"
actix-multipart = "0.6.1" actix-multipart = "0.6.1"
actix-multipart-derive = "0.6.1" actix-multipart-derive = "0.6.1"
actix-governor = "0.5.0"
dotenvy = "0.15.7" dotenvy = "0.15.7"
reqwest = { version = "0.11.24", features = ["json", "blocking"] } reqwest = { version = "0.11.24", features = ["json", "blocking"] }
rusty-s3 = "0.5.0" rusty-s3 = "0.5.0"

View file

@ -1,8 +1,2 @@
use actix_web::web;
pub mod packages; pub mod packages;
pub mod search; pub mod search;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.configure(packages::configure);
}

View file

@ -1,5 +1,5 @@
use actix_multipart::form::{bytes::Bytes, MultipartForm}; use actix_multipart::form::{bytes::Bytes, MultipartForm};
use actix_web::{get, post, web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use reqwest::StatusCode; use reqwest::StatusCode;
use rusty_s3::S3Action; use rusty_s3::S3Action;
@ -14,13 +14,12 @@ use pesde::{
use crate::{commit_signature, errors, AppState, UserId, S3_EXPIRY}; use crate::{commit_signature, errors, AppState, UserId, S3_EXPIRY};
#[derive(MultipartForm)] #[derive(MultipartForm)]
struct CreateForm { pub struct CreateForm {
#[multipart(limit = "4 MiB")] #[multipart(limit = "4 MiB")]
tarball: Bytes, tarball: Bytes,
} }
#[post("/packages")] pub async fn create_package(
async fn create(
form: MultipartForm<CreateForm>, form: MultipartForm<CreateForm>,
app_state: web::Data<AppState>, app_state: web::Data<AppState>,
user_id: web::ReqData<UserId>, user_id: web::ReqData<UserId>,
@ -156,8 +155,7 @@ async fn create(
))) )))
} }
#[get("/packages/{author_name}/{package_name}/{version}")] pub async fn get_package_version(
async fn get(
app_state: web::Data<AppState>, app_state: web::Data<AppState>,
path: web::Path<(String, String, String)>, path: web::Path<(String, String, String)>,
) -> Result<impl Responder, errors::Errors> { ) -> Result<impl Responder, errors::Errors> {
@ -202,7 +200,3 @@ async fn get(
Ok(HttpResponse::Ok().body(response.bytes().await?)) Ok(HttpResponse::Ok().body(response.bytes().await?))
} }
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(create).service(get);
}

View file

@ -1,16 +1,15 @@
use actix_web::{get, web, Responder}; use actix_web::{web, Responder};
use serde::Deserialize; use serde::Deserialize;
use serde_json::{json, Value}; use serde_json::{json, Value};
use crate::{errors, AppState}; use crate::{errors, AppState};
#[derive(Deserialize)] #[derive(Deserialize)]
struct Query { pub struct Query {
query: String, query: String,
} }
#[get("/search")] pub async fn search_packages(
async fn search(
app_state: web::Data<AppState>, app_state: web::Data<AppState>,
query: web::Query<Query>, query: web::Query<Query>,
) -> Result<impl Responder, errors::Errors> { ) -> Result<impl Responder, errors::Errors> {
@ -50,7 +49,3 @@ async fn search(
.collect::<Vec<Value>>(), .collect::<Vec<Value>>(),
)) ))
} }
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(search);
}

View file

@ -1,10 +1,11 @@
use std::{fs::read_dir, sync::Mutex, time::Duration}; use std::{fs::read_dir, sync::Mutex, time::Duration};
use actix_cors::Cors; use actix_cors::Cors;
use actix_governor::{Governor, GovernorConfigBuilder, KeyExtractor, SimpleKeyExtractionError};
use actix_web::{ use actix_web::{
dev::ServiceRequest, dev::ServiceRequest,
error::ErrorUnauthorized, error::ErrorUnauthorized,
middleware::{Compress, Condition}, middleware::{Compress, Condition, Logger},
rt::System, rt::System,
web, App, Error, HttpMessage, HttpServer, web, App, Error, HttpMessage, HttpServer,
}; };
@ -73,7 +74,7 @@ pub fn commit_signature<'a>() -> Signature<'a> {
.unwrap() .unwrap()
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct UserId(pub u64); pub struct UserId(pub u64);
async fn validator( async fn validator(
@ -107,6 +108,18 @@ async fn validator(
Ok(req) Ok(req)
} }
#[derive(Debug, Clone)]
struct UserIdKey;
impl KeyExtractor for UserIdKey {
type Key = UserId;
type KeyExtractionError = SimpleKeyExtractionError<&'static str>;
fn extract(&self, req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> {
Ok(*req.extensions().get::<UserId>().unwrap())
}
}
fn search_index(index: &GitIndex) -> (IndexReader, IndexWriter) { fn search_index(index: &GitIndex) -> (IndexReader, IndexWriter) {
let mut schema_builder = tantivy::schema::SchemaBuilder::new(); let mut schema_builder = tantivy::schema::SchemaBuilder::new();
let name = let name =
@ -245,23 +258,52 @@ fn main() -> std::io::Result<()> {
search_writer: Mutex::new(search_writer), search_writer: Mutex::new(search_writer),
}); });
let upload_governor_config = GovernorConfigBuilder::default()
.burst_size(10)
.per_second(600)
.key_extractor(UserIdKey)
.use_headers()
.finish()
.unwrap();
let generic_governor_config = GovernorConfigBuilder::default()
.burst_size(10)
.per_second(10)
.use_headers()
.finish()
.unwrap();
info!("listening on {address}:{port}"); info!("listening on {address}:{port}");
System::new().block_on(async move { System::new().block_on(async move {
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(Condition::new(with_sentry, sentry_actix::Sentry::new())) .wrap(Condition::new(with_sentry, sentry_actix::Sentry::new()))
.wrap(Logger::default())
.wrap(Cors::permissive()) .wrap(Cors::permissive())
.wrap(Compress::default()) .wrap(Compress::default())
.app_data(app_data.clone()) .app_data(app_data.clone())
.route("/", web::get().to(|| async { env!("CARGO_PKG_VERSION") })) .route("/", web::get().to(|| async { env!("CARGO_PKG_VERSION") }))
.service( .service(
web::scope("/v0") web::scope("/v0")
.configure(endpoints::search::configure) .route(
.service( "/search",
web::scope("") web::get()
.wrap(HttpAuthentication::with_fn(validator)) .to(endpoints::search::search_packages)
.configure(endpoints::configure), .wrap(Governor::new(&generic_governor_config)),
)
.route(
"/packages/{scope}/{name}/{version}",
web::get()
.to(endpoints::packages::get_package_version)
.wrap(Governor::new(&generic_governor_config)),
)
.route(
"/packages",
web::post()
.to(endpoints::packages::create_package)
.wrap(Governor::new(&upload_governor_config))
.wrap(HttpAuthentication::bearer(validator)),
), ),
) )
}) })