diff --git a/CHANGELOG.md b/CHANGELOG.md index f715cfe..7cdad22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,3 +16,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 requests * Packages of architecture "any" are part of every architecture's database + * Bearer authentication for private routes diff --git a/Cargo.lock b/Cargo.lock index d9c6efe..804a3d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,6 +243,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64" version = "0.21.2" @@ -1762,7 +1768,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64", + "base64 0.21.2", ] [[package]] @@ -2255,7 +2261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" dependencies = [ "atoi", - "base64", + "base64 0.21.2", "bigdecimal", "bitflags 2.3.3", "byteorder", @@ -2302,7 +2308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" dependencies = [ "atoi", - "base64", + "base64 0.21.2", "bigdecimal", "bitflags 2.3.3", "byteorder", @@ -2616,6 +2622,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8bd22a874a2d0b70452d5597b12c537331d49060824a95f49f108994f94aa4c" dependencies = [ + "base64 0.20.0", "bitflags 2.3.3", "bytes", "futures-core", diff --git a/server/Cargo.toml b/server/Cargo.toml index c147eae..bf4fd7f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -18,7 +18,7 @@ sha256 = "1.1.4" tokio = { version = "1.29.1", features = ["full"] } tokio-util = { version = "0.7.8", features = ["io"] } tower = { version = "0.4.13", features = ["make"] } -tower-http = { version = "0.4.1", features = ["fs", "trace"] } +tower-http = { version = "0.4.1", features = ["fs", "trace", "auth"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } uuid = { version = "1.4.0", features = ["v4"] } diff --git a/server/src/cli.rs b/server/src/cli.rs index 0725e64..8b160d5 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -18,6 +18,8 @@ pub struct Cli { pub pkg_dir: PathBuf, /// Directory where repository metadata & SQLite database is stored pub data_dir: PathBuf, + /// API key to authenticate private routes with + pub api_key: String, /// Database connection URL; either sqlite:// or postgres://. Defaults to rieter.sqlite in the /// data directory @@ -72,6 +74,7 @@ impl Cli { data_dir: self.data_dir.clone(), repo_dir: self.data_dir.join("repos"), pkg_dir: self.pkg_dir.clone(), + api_key: self.api_key.clone(), }; let repo_manager = RepoGroupManager::new(&config.repo_dir, &self.pkg_dir); @@ -84,7 +87,7 @@ impl Cli { // build our application with a single route let app = Router::new() .nest("/api", crate::api::router()) - .merge(crate::repo::router()) + .merge(crate::repo::router(&self.api_key)) .with_state(global) .layer(TraceLayer::new_for_http()); diff --git a/server/src/main.rs b/server/src/main.rs index e3dbf36..c2b0eaf 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -16,6 +16,7 @@ pub struct Config { data_dir: PathBuf, repo_dir: PathBuf, pkg_dir: PathBuf, + api_key: String, } #[derive(Clone)] diff --git a/server/src/repo/mod.rs b/server/src/repo/mod.rs index 0a28969..2bad3d5 100644 --- a/server/src/repo/mod.rs +++ b/server/src/repo/mod.rs @@ -17,15 +17,28 @@ use std::sync::Arc; use tokio::{fs, io::AsyncWriteExt}; use tower::util::ServiceExt; use tower_http::services::{ServeDir, ServeFile}; +use tower_http::validate_request::ValidateRequestHeaderLayer; use uuid::Uuid; -pub fn router() -> Router { +pub fn router(api_key: &str) -> Router { Router::new() - .route("/:repo", post(post_package_archive).delete(delete_repo)) - .route("/:repo/:arch", delete(delete_arch_repo)) + .route( + "/:repo", + post(post_package_archive) + .delete(delete_repo) + .route_layer(ValidateRequestHeaderLayer::bearer(api_key)), + ) + .route( + "/:repo/:arch", + delete(delete_arch_repo).route_layer(ValidateRequestHeaderLayer::bearer(api_key)), + ) + // Routes added after the layer do not get that layer applied, so the GET requests will not + // be authorized .route( "/:repo/:arch/:filename", - delete(delete_package).get(get_file), + delete(delete_package) + .route_layer(ValidateRequestHeaderLayer::bearer(api_key)) + .get(get_file), ) } @@ -57,6 +70,8 @@ async fn post_package_archive( match res { // Insert the newly added package into the database Ok(pkg) => { + tracing::info!("Added '{}' to repository '{}'", pkg.file_name(), repo); + // Query the repo for its ID, or create it if it does not already exist let repo_entity = db_repo::Entity::find() .filter(db_repo::Column::Name.eq(&repo)) @@ -150,10 +165,14 @@ async fn delete_repo( ) -> crate::Result { let clone = Arc::clone(&global.repo_manager); + let repo_clone = repo.clone(); let repo_removed = - tokio::task::spawn_blocking(move || clone.write().unwrap().remove_repo(&repo)).await??; + tokio::task::spawn_blocking(move || clone.write().unwrap().remove_repo(&repo_clone)) + .await??; if repo_removed { + tracing::info!("Removed repository '{}'", repo); + Ok(StatusCode::OK) } else { Ok(StatusCode::NOT_FOUND) @@ -166,11 +185,14 @@ async fn delete_arch_repo( ) -> crate::Result { let clone = Arc::clone(&global.repo_manager); + let log = format!("Removed architecture '{}' from repository '{}'", arch, repo); let repo_removed = tokio::task::spawn_blocking(move || clone.write().unwrap().remove_repo_arch(&repo, &arch)) .await??; if repo_removed { + tracing::info!(log); + Ok(StatusCode::OK) } else { Ok(StatusCode::NOT_FOUND) @@ -190,6 +212,7 @@ async fn delete_package( } let name = name_parts[..name_parts.len() - 3].join("-"); + let log = format!("Removed '{}' from repository '{}'", file_name, repo); let clone = Arc::clone(&global.repo_manager); @@ -199,6 +222,8 @@ async fn delete_package( .await??; if pkg_removed { + tracing::info!(log); + Ok(StatusCode::OK) } else { Ok(StatusCode::NOT_FOUND)