diff --git a/Cargo.lock b/Cargo.lock index 96c2a93..c29a925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,15 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +dependencies = [ + "serde", +] + [[package]] name = "cc" version = "1.0.83" @@ -1282,6 +1291,7 @@ dependencies = [ "axum", "axum-extra", "base64 0.21.5", + "bytesize", "config", "futures", "getrandom", diff --git a/Cargo.toml b/Cargo.toml index 9631e64..844f399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" axum = { version = "0.7.2", features = ["json"] } axum-extra = { version = "0.9.0", features = ["typed-routing", "typed-header"] } base64 = "0.21.5" +bytesize = { version = "1.3.0", features = ["serde"] } config = "0.13.4" futures = "0.3.29" getrandom = "0.2.11" diff --git a/README.md b/README.md index 2fd3cff..2bd988f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Quartermaster is still very early in development, and these are features which a - User/owner endpoints: Currently, tokens are global, and all crates are owned by nobody. The various `owner` endpoints are not implemented. - More varied and robust auth methods (e.g. OpenID). I have no need for them yet. - Cross-platform support: While in theory nothing stops Quartermaster from running on other platforms like Windows, MacOS or BSDs, I have only tested it on x86_64 Linux and the default values for the configuration reflect this. +- CLI management utility: Instead of a Web UI, I'm planning to add a CLI utility to perform registry maintenance tasks which cannot be performed through the Cargo API (e.g. fully removing crates, managing auth) ## Installation diff --git a/examples/config.toml b/examples/config.toml index e2b37c8..e551d5c 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -16,6 +16,11 @@ root_url = "https://foo.bar" #bind = ["10.1.1.1:80"] +[crates] + +### The maximum size of a crate publish payload allowed by this registry. Defaults to 100 MiB. +## Supports human-readable prefixes (KB, MB, KiB, etc.) +#max_publish_size = "100 MiB" [auth] @@ -34,7 +39,7 @@ type = "none" #type = "auto_token" -## The file to store the token in. Optional, defaults to `/var/lib/quartermaster/token`. +## The file to store the token in. Defaults to `/var/lib/quartermaster/token`. #token_file = "/crate-token" diff --git a/src/config.rs b/src/config.rs index 9b977e1..f9b27d4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,12 +5,14 @@ use std::{ path::PathBuf, }; +use bytesize::ByteSize; use config::FileFormat; use serde::Deserialize; #[derive(Clone, Debug, Deserialize)] pub struct Config { pub server: Server, + pub crates: Crates, pub auth: Auth, pub storage: Storage, } @@ -33,6 +35,17 @@ fn default_bind() -> Vec { ] } +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Crates { + #[serde(default = "default_max_publish_size")] + pub max_publish_size: ByteSize, +} + +fn default_max_publish_size() -> ByteSize { + ByteSize::mib(100) +} + #[derive(Clone, Debug, Deserialize)] #[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)] pub enum Auth { diff --git a/src/main.rs b/src/main.rs index b7d19d3..431f206 100644 --- a/src/main.rs +++ b/src/main.rs @@ -179,9 +179,6 @@ async fn get_download_crate( Ok(body) } -// TODO: Make configurable? -const MAX_BODY_SIZE: usize = 100 * 1024 * 1024; - #[derive(Deserialize)] #[allow(dead_code)] struct PublishRequest { @@ -302,21 +299,24 @@ async fn put_publish_crate( return Err(ErrorResponse::from_status(StatusCode::LENGTH_REQUIRED)); }; - if body_size > u64::try_from(MAX_BODY_SIZE).unwrap() { + if body_size > state.config.crates.max_publish_size.as_u64() { return Err(ErrorResponse::from_status(StatusCode::PAYLOAD_TOO_LARGE)); } - let body_data = Limited::new(body, MAX_BODY_SIZE) - .collect() - .await - .map_err(|e| { - if e.is::() { - ErrorResponse::from_status(StatusCode::PAYLOAD_TOO_LARGE) - } else { - ErrorResponse::from_status(StatusCode::INTERNAL_SERVER_ERROR) - } - })? - .to_bytes(); + let body_data = Limited::new( + body, + usize::try_from(state.config.crates.max_publish_size.as_u64()).unwrap(), + ) + .collect() + .await + .map_err(|e| { + if e.is::() { + ErrorResponse::from_status(StatusCode::PAYLOAD_TOO_LARGE) + } else { + ErrorResponse::from_status(StatusCode::INTERNAL_SERVER_ERROR) + } + })? + .to_bytes(); let json_length_bytes = body_data .get(0..4)