1
0
Fork 0
mirror of https://codeberg.org/tyy/aspm synced 2024-12-22 15:59:29 -07:00

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

This commit is contained in:
Tyler Beckman 2024-07-12 11:41:20 -06:00
parent 0c5452a1b8
commit a6b7790107
Signed by: Ty
GPG key ID: 2813440C772555A4
11 changed files with 117 additions and 16 deletions

5
.dockerignore Normal file
View file

@ -0,0 +1,5 @@
.vscode/*
./keys
./target
./*.jwk
./README.md

3
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target /target
*.jw[tk] *.jw[tk]
/server-data

30
Cargo.lock generated
View file

@ -1222,6 +1222,29 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -1703,6 +1726,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.4.0" version = "1.4.0"
@ -2339,6 +2368,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"clap", "clap",
"env_logger",
"naja-lib", "naja-lib",
"sea-orm", "sea-orm",
"serde", "serde",

26
Dockerfile Normal file
View file

@ -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"]

View file

@ -29,12 +29,16 @@ pub struct AspeDeleteCommand {
/// The ASPE server to upload this profile to /// The ASPE server to upload this profile to
#[clap(value_parser = Host::parse)] #[clap(value_parser = Host::parse)]
server: Host, server: Host,
/// (dev only) If the ASPE server is insecure (not HTTPS)
#[cfg(debug_assertions)]
#[arg(long)]
insecure: bool,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl NajaSubcommand for AspeDeleteCommand { impl NajaSubcommand for AspeDeleteCommand {
async fn execute(self, state: crate::CliState) -> Result<(), anyhow::Error> { 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 .await
.context("Unable to parse provided server into ASPE server, please verify that it is a valid ASPE server")?; .context("Unable to parse provided server into ASPE server, please verify that it is a valid ASPE server")?;

View file

@ -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 /// If passed, this command will only create new keys on the server, rather than updating if the key already exists
#[clap(long)] #[clap(long)]
no_update: bool, no_update: bool,
/// (dev only) If the ASPE server is insecure (not HTTPS)
#[cfg(debug_assertions)]
#[arg(long)]
insecure: bool,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl NajaSubcommand for AspeUploadCommand { impl NajaSubcommand for AspeUploadCommand {
async fn execute(self, state: crate::CliState) -> Result<(), anyhow::Error> { 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 .await
.context("Unable to parse provided server into ASPE server, please verify that it is a valid ASPE server")?; .context("Unable to parse provided server into ASPE server, please verify that it is a valid ASPE server")?;

View file

@ -21,6 +21,10 @@ pub struct ProfilesImportCommand {
/// The alias to give to the imported profile /// The alias to give to the imported profile
#[clap(trailing_var_arg = true)] #[clap(trailing_var_arg = true)]
alias: Vec<String>, alias: Vec<String>,
/// (dev only) If the ASPE server is insecure (not HTTPS)
#[cfg(debug_assertions)]
#[arg(long)]
insecure: bool,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -32,7 +36,7 @@ impl NajaSubcommand for ProfilesImportCommand {
(host.to_string(), fingerprint.to_string()) (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? .await?
.fetch_profile(&fingerprint) .fetch_profile(&fingerprint)
.await .await

View file

@ -20,6 +20,7 @@ pub struct AspeVersion {
#[derive(Clone)] #[derive(Clone)]
pub struct AspeServer { pub struct AspeServer {
pub host: Host, 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, client: reqwest::Client,
} }
@ -78,16 +79,18 @@ impl From<reqwest::Error> for AspeFetchFailure {
impl AspeServer { 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). /// 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<Self> { pub async fn new(host: Host, #[cfg(debug_assertions)] insecure: bool) -> anyhow::Result<Self> {
#[cfg(not(debug_assertions))] let insecure = false;
let server = Self { let server = Self {
host, host,
insecure,
client: reqwest::Client::builder() client: reqwest::Client::builder()
.user_agent(format!( .user_agent(format!(
"naja-lib/{version} ({repo})", "naja-lib/{version} ({repo})",
version = env!("CARGO_PKG_VERSION"), version = env!("CARGO_PKG_VERSION"),
repo = env!("CARGO_PKG_REPOSITORY") repo = env!("CARGO_PKG_REPOSITORY")
)) ))
.https_only(true) .https_only(!insecure)
.build()?, .build()?,
}; };
@ -101,7 +104,8 @@ impl AspeServer {
pub async fn version(&self) -> Result<(), reqwest::Error> { pub async fn version(&self) -> Result<(), reqwest::Error> {
self.client self.client
.get(format!( .get(format!(
"https://{host}/.well-known/aspe/version", "{scheme}://{host}/.well-known/aspe/version",
scheme = if self.insecure { "http" } else { "https" },
host = self.host host = self.host
)) ))
.header( .header(
@ -119,7 +123,8 @@ impl AspeServer {
pub async fn post_request(&self, jws: impl Into<String>) -> Result<(), AspeRequestFailure> { pub async fn post_request(&self, jws: impl Into<String>) -> Result<(), AspeRequestFailure> {
self.client self.client
.post(format!( .post(format!(
"https://{host}/.well-known/aspe/post", "{scheme}://{host}/.well-known/aspe/post",
scheme = if self.insecure { "http" } else { "https" },
host = self.host host = self.host
)) ))
.header( .header(
@ -141,8 +146,9 @@ impl AspeServer {
let res = self let res = self
.client .client
.get(format!( .get(format!(
"https://{host}/.well-known/aspe/id/{fingerprint}", "{scheme}://{host}/.well-known/aspe/id/{fingerprint}",
host = self.host, host = self.host,
scheme = if self.insecure { "http" } else { "https" },
fingerprint = fingerprint.as_ref() fingerprint = fingerprint.as_ref()
)) ))
.header( .header(
@ -167,12 +173,12 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn building_aspe_server_succeeds() { 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] #[tokio::test]
async fn building_invalid_aspe_server_fails() { 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") assert!(result.is_err(), "Constructing an AspeServer with an invalid domain should fail")
} }
} }

View file

@ -12,3 +12,4 @@ sea-orm = { version = "0.12", features = ["sqlx-sqlite", "runtime-tokio-rustls",
serde = { version = "1.0.204", features = ["derive"] } serde = { version = "1.0.204", features = ["derive"] }
naja-lib = { path = "../naja-lib" } naja-lib = { path = "../naja-lib" }
migrations = { path = "../server-migrations", package = "server-migrations" } migrations = { path = "../server-migrations", package = "server-migrations" }
env_logger = "0.11.3"

View file

@ -3,9 +3,10 @@ mod entities;
use std::{fs, io, path::PathBuf}; 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 clap::Parser;
use entities::{prelude::*, profiles}; use entities::{prelude::*, profiles};
use env_logger::Env;
use migrations::{Migrator, MigratorTrait as _}; use migrations::{Migrator, MigratorTrait as _};
use naja_lib::{ use naja_lib::{
aspe::requests::{AspeRequest, AspeRequestVariant}, aspe::requests::{AspeRequest, AspeRequestVariant},
@ -13,7 +14,7 @@ use naja_lib::{
utils::jwt::{JwtDeserializationError, JwtSerialize}, utils::jwt::{JwtDeserializationError, JwtSerialize},
}; };
use sea_orm::{ use sea_orm::{
ActiveValue, ColumnTrait as _, Database, DatabaseConnection, DbErr, EntityTrait, QueryFilter, ActiveValue, Database, DatabaseConnection, DbErr, EntityTrait,
}; };
use serde::Serialize; use serde::Serialize;
@ -69,14 +70,16 @@ async fn main() -> io::Result<()> {
domain: args.domain, domain: args.domain,
}; };
env_logger::init_from_env(Env::default().default_filter_or("info"));
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(Logger::default())
.app_data(web::Data::new(state.clone())) .app_data(web::Data::new(state.clone()))
.service(version) .service(version)
.service(get_by_fingerprint) .service(get_by_fingerprint)
.service(post_request) .service(post_request)
}) })
.bind((args.bind_address, args.port))? .bind_auto_h2c((args.bind_address, args.port))?
.run() .run()
.await .await
} }
@ -164,10 +167,9 @@ async fn post_request(
Ok(_) => { Ok(_) => {
// Must be AspeRequestvariant::Update { .. } // Must be AspeRequestvariant::Update { .. }
match Profiles::update(profiles::ActiveModel { match Profiles::update(profiles::ActiveModel {
fingerprint: ActiveValue::Unchanged(key.fingerprint.to_owned()),
jwt: ActiveValue::Set(profile_jws.to_owned()), jwt: ActiveValue::Set(profile_jws.to_owned()),
..Default::default()
}) })
.filter(profiles::Column::Fingerprint.eq(&key.fingerprint))
.exec(&state.db) .exec(&state.db)
.await .await
{ {

18
docker-compose.dev.yml Normal file
View file

@ -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/