From e0173496c67df87b449f2ff99e6387a745f4d446 Mon Sep 17 00:00:00 2001 From: Aydin Mercan Date: Fri, 27 May 2022 10:48:32 +0300 Subject: [PATCH] main: draft layout for subcommands & identity I think that API key generation and revocation should be done by calling the binary. Also, nuking all keys should be accessible. Public identity regeneration can use `init` again for now. I am thinking about seperating between confidential and public clients in the structure that keeps track of them. The map keeping track of the confidential should use the client base url as well. Expecting to overhaul how its done after checking RFC 8725 and other JOSE damage reduction strategies more throughly. (ugh) --- README.md | 2 +- schema.sql | 16 +++++- src/controller/mod.rs | 4 +- src/database.rs | 9 ++++ src/engine/jwk.rs | 11 +++++ src/engine/mod.rs | 3 ++ src/main.rs | 112 ++++++++++++++++++++++++++++++++++-------- src/server.rs | 11 +++++ 8 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 src/engine/jwk.rs create mode 100644 src/engine/mod.rs create mode 100644 src/server.rs diff --git a/README.md b/README.md index e85f4f0..d040cd6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ For administration and usage, a reference handbook will be provided in the futur ## Usage ```sh -chibiauth --database=/path/to/database/of/chibiauth.db --port 9909 +chibiauth run --database=/path/to/database/of/chibiauth.db --port 9909 --base-url 'https://example.com' ``` * ChibiAuth will bind to localhost, bring your own TLS terminator. diff --git a/schema.sql b/schema.sql index c3d3246..834c637 100644 --- a/schema.sql +++ b/schema.sql @@ -28,6 +28,18 @@ CREATE TABLE IF NOT EXISTS oidc_claims ( updated_at INTEGER ) STRICT; +CREATE TABLE IF NOT EXISTS public_jwk ( + key_id TEXT PRIMARY KEY NOT NULL, + algorithm TEXT NOT NULL, + pem TEXT NOT NULL +) STRICT; + +CREATE TABLE IF NOT EXISTS secret_jwk ( + key_id TEXT PRIMARY KEY NOT NULL, + algorithm TEXT NOT NULL, + secret TEXT NOT NULL +) STRICT; + CREATE TABLE IF NOT EXISTS sessions ( cookie BLOB PRIMARY KEY NOT NULL, uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE @@ -36,6 +48,6 @@ CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS reset_token ( selector TEXT PRIMARY KEY NOT NULL, uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE, - verifier TEXT NOT NULL - expires_at TEXT NOT NULL, + verifier TEXT NOT NULL, + expires_at TEXT NOT NULL ) STRICT; diff --git a/src/controller/mod.rs b/src/controller/mod.rs index f90557b..85f2b55 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,9 +1,9 @@ use std::sync::Arc; -use axum::{AddExtensionLayer, Router}; +use axum::Router; use crate::CsrfMap; pub fn setup_router(csrf_map: Arc) -> Router { - Router::new().layer(AddExtensionLayer::new(csrf_map)) + Router::new() } diff --git a/src/database.rs b/src/database.rs index 6641fba..a0138f2 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,8 +1,11 @@ use anyhow::{bail, Result}; +use tracing::{debug, error, info, warn}; use rusqlite::Connection; pub fn setup_connection_and_for_replication(path: String) -> Result { + debug!(target = ?path, "opening database"); + let db = Connection::open(path)?; let mode: String = db.pragma_update_and_check(None, "journal_mode", "WAL", |row| row.get(0))?; @@ -11,6 +14,12 @@ pub fn setup_connection_and_for_replication(path: String) -> Result bail!("couldn't set journaling to WAL"); } + let secure_delete: u64 = db.pragma_query_value(None, "secure_delete", |row| row.get(0))?; + + if secure_delete == 0 { + warn!("secure delete is off, beware of lingering secrets in the database"); + } + db.pragma_update(None, "busy_timeout", "5000")?; db.pragma_update(None, "synchronous", "NORMAL")?; db.pragma_update(None, "foreign_keys", "ON")?; diff --git a/src/engine/jwk.rs b/src/engine/jwk.rs new file mode 100644 index 0000000..ca1a099 --- /dev/null +++ b/src/engine/jwk.rs @@ -0,0 +1,11 @@ +use std::collections::HashMap; + +pub struct JwkEngine { + /// For publicly listed keys. + public: HashMap, + + /// For confidential clients using HS256/384/512. + secret: HashMap<(String, String), String>, +} + +impl JwkEngine {} diff --git a/src/engine/mod.rs b/src/engine/mod.rs new file mode 100644 index 0000000..009db5a --- /dev/null +++ b/src/engine/mod.rs @@ -0,0 +1,3 @@ +mod jwk; + +pub use jwk::JwkEngine; diff --git a/src/main.rs b/src/main.rs index f102c64..85598c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,43 +1,113 @@ mod controller; mod database; +mod engine; +mod server; use std::collections::HashMap; -use std::sync::Arc; +use std::option::Option; +use std::process::{ExitCode, Termination}; use anyhow::Result; -use clap::Parser; +use clap::{Parser, Subcommand}; use parking_lot::RwLock; -use crate::controller::setup_router; - pub type CsrfMap = RwLock>; -#[derive(Parser)] -struct Args { - #[clap(short, long)] - pub database: String, +#[repr(u8)] +enum ServerResult { + Success = 0, + Failure = 1, +} - #[clap(short, long)] - pub port: u16, +impl Termination for ServerResult { + fn report(self) -> ExitCode { + ExitCode::from(self as u8) + } +} + +#[derive(Parser)] +#[clap(version)] +struct Args { + #[clap(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Start the server + Run { + #[clap(short, long)] + database: String, + + #[clap(short, long)] + url: String, + + #[clap(short, long)] + port: u16, + }, + + /// Initialize the database and/or regenerate public identities + Init { + #[clap(short, long)] + database: String, + }, + + /// Control API keys + Api { + #[clap(short, long)] + database: String, + + #[clap(short, long)] + generate: bool, + + #[clap(short, long)] + revoke: Option, + + #[clap(short = 'R', long)] + revoke_all: bool, + }, } fn setup_default_subscriber() -> Result<()> { - tracing_subscriber::fmt::init(); + tracing_subscriber::fmt().pretty().init(); + + std::panic::set_hook(Box::new(|panic| { + if let Some(location) = panic.location() { + tracing::error!( + message = %panic, + panic.file = location.file(), + panic.line = location.line(), + panic.column = location.column(), + ); + } else { + tracing::error!(message = %panic); + } + })); Ok(()) } -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> ServerResult { + if setup_default_subscriber().is_err() { + return ServerResult::Failure; + } + let args = Args::parse(); - setup_default_subscriber()?; + match args.command { + Command::Init { database } => {} + Command::Run { + database, + port, + url, + } => {} + Command::Api { + database, + generate, + revoke, + revoke_all, + } => {} + } - let csrf_map = Arc::new(RwLock::new(HashMap::::new())); - - let db = crate::database::setup_connection_and_for_replication(args.database)?; - - let router = setup_router(csrf_map); - - Ok(()) + ServerResult::Success } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..a7b6a93 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,11 @@ +use anyhow::Result; + +pub fn run() { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build(); + + if let Err(err) = runtime { + return; + } +}