From a6b7790107bc58817afa199d249eb019946c3a04 Mon Sep 17 00:00:00 2001 From: Ty Date: Fri, 12 Jul 2024 11:41:20 -0600 Subject: [PATCH] naja-server: fix update bug, enable h2c, add logging, add docker configs; naja-lib: add insecure option when debug_assertions are enabled; nana-cli: add insecure option when debug_assertions are enabled --- .dockerignore | 5 ++++ .gitignore | 3 +- Cargo.lock | 30 +++++++++++++++++++ Dockerfile | 26 ++++++++++++++++ crates/naja-cli/src/commands/aspe/delete.rs | 6 +++- crates/naja-cli/src/commands/aspe/upload.rs | 6 +++- .../naja-cli/src/commands/profiles/import.rs | 6 +++- crates/naja-lib/src/aspe/mod.rs | 20 ++++++++----- crates/naja-server/Cargo.toml | 1 + crates/naja-server/src/main.rs | 12 ++++---- docker-compose.dev.yml | 18 +++++++++++ 11 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.dev.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b7f43a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.vscode/* +./keys +./target +./*.jwk +./README.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index 46f02c1..572b5b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -*.jw[tk] \ No newline at end of file +*.jw[tk] +/server-data \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ba28905..f2b2d0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1222,6 +1222,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1703,6 +1726,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "1.4.0" @@ -2339,6 +2368,7 @@ version = "0.1.0" dependencies = [ "actix-web", "clap", + "env_logger", "naja-lib", "sea-orm", "serde", diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..263aa95 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Using the `rust-musl-builder` as base image, instead of +# the official Rust toolchain +FROM clux/muslrust:1.79.0-stable AS chef +USER root +# Install cargo-chef +RUN cargo install cargo-chef --locked +WORKDIR /app + +FROM chef AS planner +COPY . . +# Cache all dependencies for the server +RUN cargo chef prepare --recipe-path recipe.json --bin naja-server + +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +# Build dependencies +RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json --package naja-server +COPY . . +# Build main binary +RUN cargo build --release --target x86_64-unknown-linux-musl --package naja-server + +FROM alpine AS runtime +COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/naja-server /usr/local/bin/ +HEALTHCHECK --interval=10s --timeout=3s \ + CMD wget --no-verbose --tries=1 --spider http://localhost:$NAJA_BIND_PORT/ || exit 1 +CMD ["/usr/local/bin/naja-server"] \ No newline at end of file diff --git a/crates/naja-cli/src/commands/aspe/delete.rs b/crates/naja-cli/src/commands/aspe/delete.rs index 7e0f020..883efe9 100644 --- a/crates/naja-cli/src/commands/aspe/delete.rs +++ b/crates/naja-cli/src/commands/aspe/delete.rs @@ -29,12 +29,16 @@ pub struct AspeDeleteCommand { /// The ASPE server to upload this profile to #[clap(value_parser = Host::parse)] server: Host, + /// (dev only) If the ASPE server is insecure (not HTTPS) + #[cfg(debug_assertions)] + #[arg(long)] + insecure: bool, } #[async_trait::async_trait] impl NajaSubcommand for AspeDeleteCommand { async fn execute(self, state: crate::CliState) -> Result<(), anyhow::Error> { - let server = AspeServer::new(self.server) + let server = AspeServer::new(self.server, #[cfg(debug_assertions)] self.insecure) .await .context("Unable to parse provided server into ASPE server, please verify that it is a valid ASPE server")?; diff --git a/crates/naja-cli/src/commands/aspe/upload.rs b/crates/naja-cli/src/commands/aspe/upload.rs index 8df80c5..5ce6acf 100644 --- a/crates/naja-cli/src/commands/aspe/upload.rs +++ b/crates/naja-cli/src/commands/aspe/upload.rs @@ -38,12 +38,16 @@ pub struct AspeUploadCommand { /// If passed, this command will only create new keys on the server, rather than updating if the key already exists #[clap(long)] no_update: bool, + /// (dev only) If the ASPE server is insecure (not HTTPS) + #[cfg(debug_assertions)] + #[arg(long)] + insecure: bool, } #[async_trait::async_trait] impl NajaSubcommand for AspeUploadCommand { async fn execute(self, state: crate::CliState) -> Result<(), anyhow::Error> { - let server = AspeServer::new(self.server) + let server = AspeServer::new(self.server, #[cfg(debug_assertions)] self.insecure) .await .context("Unable to parse provided server into ASPE server, please verify that it is a valid ASPE server")?; diff --git a/crates/naja-cli/src/commands/profiles/import.rs b/crates/naja-cli/src/commands/profiles/import.rs index f9fbe24..87ebe15 100644 --- a/crates/naja-cli/src/commands/profiles/import.rs +++ b/crates/naja-cli/src/commands/profiles/import.rs @@ -21,6 +21,10 @@ pub struct ProfilesImportCommand { /// The alias to give to the imported profile #[clap(trailing_var_arg = true)] alias: Vec, + /// (dev only) If the ASPE server is insecure (not HTTPS) + #[cfg(debug_assertions)] + #[arg(long)] + insecure: bool, } #[async_trait::async_trait] @@ -32,7 +36,7 @@ impl NajaSubcommand for ProfilesImportCommand { (host.to_string(), fingerprint.to_string()) }) }) { - Some((host, fingerprint)) => match aspe::AspeServer::new(Host::parse(&host).unwrap()) + Some((host, fingerprint)) => match aspe::AspeServer::new(Host::parse(&host).unwrap(), #[cfg(debug_assertions)] self.insecure) .await? .fetch_profile(&fingerprint) .await diff --git a/crates/naja-lib/src/aspe/mod.rs b/crates/naja-lib/src/aspe/mod.rs index 38893b3..ec8e5ee 100644 --- a/crates/naja-lib/src/aspe/mod.rs +++ b/crates/naja-lib/src/aspe/mod.rs @@ -20,6 +20,7 @@ pub struct AspeVersion { #[derive(Clone)] pub struct AspeServer { pub host: Host, + insecure: bool, // This is here for developing with local ASPE servers, but cannot actually be set to true in release binaries client: reqwest::Client, } @@ -78,16 +79,18 @@ impl From for AspeFetchFailure { impl AspeServer { /// Creates a new [AspeServer] instance given the specified domain. A domain and ONLY a domain should be provided (no scheme, no path, etc). - pub async fn new(host: Host) -> anyhow::Result { + pub async fn new(host: Host, #[cfg(debug_assertions)] insecure: bool) -> anyhow::Result { + #[cfg(not(debug_assertions))] let insecure = false; let server = Self { host, + insecure, client: reqwest::Client::builder() .user_agent(format!( "naja-lib/{version} ({repo})", version = env!("CARGO_PKG_VERSION"), repo = env!("CARGO_PKG_REPOSITORY") )) - .https_only(true) + .https_only(!insecure) .build()?, }; @@ -101,7 +104,8 @@ impl AspeServer { pub async fn version(&self) -> Result<(), reqwest::Error> { self.client .get(format!( - "https://{host}/.well-known/aspe/version", + "{scheme}://{host}/.well-known/aspe/version", + scheme = if self.insecure { "http" } else { "https" }, host = self.host )) .header( @@ -119,7 +123,8 @@ impl AspeServer { pub async fn post_request(&self, jws: impl Into) -> Result<(), AspeRequestFailure> { self.client .post(format!( - "https://{host}/.well-known/aspe/post", + "{scheme}://{host}/.well-known/aspe/post", + scheme = if self.insecure { "http" } else { "https" }, host = self.host )) .header( @@ -141,8 +146,9 @@ impl AspeServer { let res = self .client .get(format!( - "https://{host}/.well-known/aspe/id/{fingerprint}", + "{scheme}://{host}/.well-known/aspe/id/{fingerprint}", host = self.host, + scheme = if self.insecure { "http" } else { "https" }, fingerprint = fingerprint.as_ref() )) .header( @@ -167,12 +173,12 @@ mod tests { #[tokio::test] async fn building_aspe_server_succeeds() { - AspeServer::new(Host::Domain(String::from("keyoxide.org"))).await.expect("Contructing an AspeServer should succeed"); + AspeServer::new(Host::Domain(String::from("keyoxide.org")), false).await.expect("Contructing an AspeServer should succeed"); } #[tokio::test] async fn building_invalid_aspe_server_fails() { - let result = AspeServer::new(Host::Domain(String::from("example.com"))).await; + let result = AspeServer::new(Host::Domain(String::from("example.com")), false).await; assert!(result.is_err(), "Constructing an AspeServer with an invalid domain should fail") } } diff --git a/crates/naja-server/Cargo.toml b/crates/naja-server/Cargo.toml index cec4ed4..cf317ed 100644 --- a/crates/naja-server/Cargo.toml +++ b/crates/naja-server/Cargo.toml @@ -12,3 +12,4 @@ sea-orm = { version = "0.12", features = ["sqlx-sqlite", "runtime-tokio-rustls", serde = { version = "1.0.204", features = ["derive"] } naja-lib = { path = "../naja-lib" } migrations = { path = "../server-migrations", package = "server-migrations" } +env_logger = "0.11.3" diff --git a/crates/naja-server/src/main.rs b/crates/naja-server/src/main.rs index 4e24ada..115c30d 100644 --- a/crates/naja-server/src/main.rs +++ b/crates/naja-server/src/main.rs @@ -3,9 +3,10 @@ mod entities; use std::{fs, io, path::PathBuf}; -use actix_web::{get, http::header, post, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{get, http::header, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder}; use clap::Parser; use entities::{prelude::*, profiles}; +use env_logger::Env; use migrations::{Migrator, MigratorTrait as _}; use naja_lib::{ aspe::requests::{AspeRequest, AspeRequestVariant}, @@ -13,7 +14,7 @@ use naja_lib::{ utils::jwt::{JwtDeserializationError, JwtSerialize}, }; use sea_orm::{ - ActiveValue, ColumnTrait as _, Database, DatabaseConnection, DbErr, EntityTrait, QueryFilter, + ActiveValue, Database, DatabaseConnection, DbErr, EntityTrait, }; use serde::Serialize; @@ -69,14 +70,16 @@ async fn main() -> io::Result<()> { domain: args.domain, }; + env_logger::init_from_env(Env::default().default_filter_or("info")); HttpServer::new(move || { App::new() + .wrap(Logger::default()) .app_data(web::Data::new(state.clone())) .service(version) .service(get_by_fingerprint) .service(post_request) }) - .bind((args.bind_address, args.port))? + .bind_auto_h2c((args.bind_address, args.port))? .run() .await } @@ -164,10 +167,9 @@ async fn post_request( Ok(_) => { // Must be AspeRequestvariant::Update { .. } match Profiles::update(profiles::ActiveModel { + fingerprint: ActiveValue::Unchanged(key.fingerprint.to_owned()), jwt: ActiveValue::Set(profile_jws.to_owned()), - ..Default::default() }) - .filter(profiles::Column::Fingerprint.eq(&key.fingerprint)) .exec(&state.db) .await { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..a84de83 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,18 @@ +services: + server: + image: naja-server + build: + dockerfile: Dockerfile + network: host + environment: + NAJA_DATA_PATH: /home/naja/.local/share/naja-server/ + NAJA_BIND_ADDRESS: 0.0.0.0 + NAJA_BIND_PORT: 80 + NAJA_DOMAIN: localhost + RUST_LOG: actix_web=debug,actix_server=info + ports: + - 80:80 + restart: always + container_name: naja-server + volumes: + - ./server-data:/home/naja/.local/share/naja-server/