From a43c38c118c6b705c757b47a88c0a573ebd80682 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Tue, 31 May 2022 13:11:48 +0200 Subject: [PATCH 1/3] Better errors when the db directory is missing --- bffhd/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bffhd/lib.rs b/bffhd/lib.rs index 8aeecc8..33e4549 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -84,7 +84,8 @@ impl Diflouroborane { let executor = Executor::new(); - let env = StateDB::open_env(&config.db_path)?; + let env = StateDB::open_env(&config.db_path) + .context("Failed to create state DB env. Does the parent directory for `db_path` exist?")?; let statedb = StateDB::create_with_env(env.clone()).context("Failed to open state DB file")?; From 17fd08b7e57a9267472046286dc60f45e444fb4b Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Tue, 31 May 2022 13:45:51 +0200 Subject: [PATCH 2/3] More trace output of role checking --- bffhd/authorization/roles.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bffhd/authorization/roles.rs b/bffhd/authorization/roles.rs index 25f924b..9dc76bf 100644 --- a/bffhd/authorization/roles.rs +++ b/bffhd/authorization/roles.rs @@ -67,17 +67,20 @@ impl Roles { role_id: &String, perm: &Permission, ) -> bool { + let _guard = tracing::debug_span!("tally", %role_id, perm=perm.as_str()); if let Some(role) = self.get(role_id) { // Only check and tally parents of a role at the role itself if it's the first time we // see it if !roles.contains(role_id) { for perm_rule in role.permissions.iter() { if perm_rule.match_perm(perm) { + tracing::debug!("Permission granted by direct role"); return true; } } for parent in role.parents.iter() { if self.permitted_tally(roles, parent, perm) { + tracing::debug!(%parent, "Permission granted by parent role"); return true; } } @@ -86,10 +89,13 @@ impl Roles { } } + tracing::trace!(%role_id, "Permission not granted by role"); false } pub fn is_permitted(&self, user: &UserData, perm: impl AsRef) -> bool { + let perm = perm.as_ref(); + tracing::debug!(perm=perm.as_str(), "Checking permission"); let mut seen = HashSet::new(); for role_id in user.roles.iter() { if self.permitted_tally(&mut seen, role_id, perm.as_ref()) { From 5f2214abe9aa9d4d3a7c7974b933b1f8c4a5c061 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Thu, 2 Jun 2022 17:46:26 +0200 Subject: [PATCH 3/3] Switch out anyhow for miette --- Cargo.lock | 190 +++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +- bffhd/actors/mod.rs | 11 +- bffhd/authentication/mod.rs | 10 +- bffhd/authorization/roles.rs | 2 +- bffhd/capnp/mod.rs | 2 +- bffhd/db/mod.rs | 76 +++++++++++++- bffhd/db/raw.rs | 1 + bffhd/db/typed.rs | 4 +- bffhd/error.rs | 160 ++++++++++++++++++----------- bffhd/lib.rs | 26 ++--- bffhd/resources/state/db.rs | 43 ++++++-- bffhd/tls.rs | 22 ++-- bffhd/users/db.rs | 10 +- bffhd/users/mod.rs | 18 ++-- bin/bffhd/main.rs | 2 +- 16 files changed, 454 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42b1dfa..e5717db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,21 @@ dependencies = [ "pretty", ] +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aes" version = "0.7.5" @@ -46,6 +61,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "annotate-snippets" version = "0.9.1" @@ -64,12 +88,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "anyhow" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" - [[package]] name = "api" version = "0.3.2" @@ -275,6 +293,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.0" @@ -800,13 +833,13 @@ dependencies = [ name = "diflouroborane" version = "0.4.2" dependencies = [ - "anyhow", "api", "async-channel", "async-compat", "async-net", "async-oneshot", "async-trait", + "backtrace", "capnp", "capnp-rpc", "chrono", @@ -826,6 +859,7 @@ dependencies = [ "libc", "linkme", "lmdb-rkv", + "miette", "nix", "once_cell", "pin-utils", @@ -847,6 +881,7 @@ dependencies = [ "signal-hook", "signal-hook-async-std", "tempfile", + "thiserror", "toml", "tracing", "tracing-futures", @@ -1195,6 +1230,12 @@ dependencies = [ "syn", ] +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + [[package]] name = "git2" version = "0.14.4" @@ -1328,6 +1369,12 @@ dependencies = [ "syn", ] +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + [[package]] name = "is_debug" version = "1.0.1" @@ -1543,6 +1590,46 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miette" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c90329e44f9208b55f45711f9558cec15d7ef8295cc65ecd6d4188ae8edc58c" +dependencies = [ + "atty", + "backtrace", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap 0.15.0", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.1" @@ -1639,6 +1726,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.10.0" @@ -1678,6 +1774,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "owo-colors" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" + [[package]] name = "parking" version = "2.0.0" @@ -1973,6 +2075,8 @@ version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -2134,6 +2238,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2429,6 +2539,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "socket2" version = "0.4.4" @@ -2473,6 +2589,34 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "supports-color" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" +dependencies = [ + "atty", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" +dependencies = [ + "atty", +] + +[[package]] +name = "supports-unicode" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" +dependencies = [ + "atty", +] + [[package]] name = "syn" version = "1.0.88" @@ -2507,6 +2651,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -2521,21 +2675,26 @@ name = "textwrap" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2773,6 +2932,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + [[package]] name = "unicode-normalization" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index d50d460..86e0029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,9 @@ pin-utils = "0.1.0" futures-util = "0.3" futures-lite = "1.12.0" async-net = "1.6.1" -anyhow = "1.0.56" +backtrace = "0.3.65" +miette = { version = "4.7.1", features = ["fancy"] } +thiserror = "1.0.31" toml = "0.5.8" # Well-known paths/dirs for e.g. cache diff --git a/bffhd/actors/mod.rs b/bffhd/actors/mod.rs index 701cd70..d91da4a 100644 --- a/bffhd/actors/mod.rs +++ b/bffhd/actors/mod.rs @@ -12,6 +12,7 @@ use std::future::Future; use std::pin::Pin; +use miette::IntoDiagnostic; use std::task::{Context, Poll}; use std::time::Duration; @@ -110,11 +111,11 @@ static ROOT_CERTS: Lazy = Lazy::new(|| { store }); -pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> anyhow::Result<()> { +pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> miette::Result<()> { let span = tracing::info_span!("loading actors"); let _guard = span; - let mqtt_url = Url::parse(config.mqtt_url.as_str())?; + let mqtt_url = Url::parse(config.mqtt_url.as_str()).into_diagnostic()?; let (transport, default_port) = match mqtt_url.scheme() { "mqtts" | "ssl" => ( rumqttc::Transport::tls_with_config( @@ -131,12 +132,12 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> scheme => { tracing::error!(%scheme, "MQTT url uses invalid scheme"); - anyhow::bail!("invalid config"); + miette::bail!("invalid config"); } }; let host = mqtt_url.host_str().ok_or_else(|| { tracing::error!("MQTT url must contain a hostname"); - anyhow::anyhow!("invalid config") + miette::miette!("invalid config") })?; let port = mqtt_url.port().unwrap_or(default_port); @@ -167,7 +168,7 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> } Err(error) => { tracing::error!(?error, "MQTT connection failed"); - anyhow::bail!("mqtt connection failed") + miette::bail!("mqtt connection failed") } } diff --git a/bffhd/authentication/mod.rs b/bffhd/authentication/mod.rs index da32036..f486cf1 100644 --- a/bffhd/authentication/mod.rs +++ b/bffhd/authentication/mod.rs @@ -1,4 +1,5 @@ use crate::users::Users; +use miette::{Context, IntoDiagnostic}; use rsasl::error::SessionError; use rsasl::mechname::Mechname; use rsasl::property::{AuthId, Password}; @@ -127,8 +128,13 @@ impl AuthenticationHandle { } } - pub fn start(&self, mechanism: &Mechname) -> anyhow::Result { - Ok(self.inner.rsasl.server_start(mechanism)?) + pub fn start(&self, mechanism: &Mechname) -> miette::Result { + Ok(self + .inner + .rsasl + .server_start(mechanism) + .into_diagnostic() + .wrap_err("Failed to start a SASL authentication with the given mechanism")?) } pub fn list_available_mechs(&self) -> impl IntoIterator { diff --git a/bffhd/authorization/roles.rs b/bffhd/authorization/roles.rs index 9dc76bf..0885eca 100644 --- a/bffhd/authorization/roles.rs +++ b/bffhd/authorization/roles.rs @@ -95,7 +95,7 @@ impl Roles { pub fn is_permitted(&self, user: &UserData, perm: impl AsRef) -> bool { let perm = perm.as_ref(); - tracing::debug!(perm=perm.as_str(), "Checking permission"); + tracing::debug!(perm = perm.as_str(), "Checking permission"); let mut seen = HashSet::new(); for role_id in user.roles.iter() { if self.permitted_tally(&mut seen, role_id, perm.as_ref()) { diff --git a/bffhd/capnp/mod.rs b/bffhd/capnp/mod.rs index 465a10f..d19ac28 100644 --- a/bffhd/capnp/mod.rs +++ b/bffhd/capnp/mod.rs @@ -60,7 +60,7 @@ impl APIServer { acceptor: TlsAcceptor, sessionmanager: SessionManager, authentication: AuthenticationHandle, - ) -> anyhow::Result { + ) -> miette::Result { let span = tracing::info_span!("binding API listen sockets"); let _guard = span.enter(); diff --git a/bffhd/db/mod.rs b/bffhd/db/mod.rs index bc43071..36a5690 100644 --- a/bffhd/db/mod.rs +++ b/bffhd/db/mod.rs @@ -1,7 +1,81 @@ +use thiserror::Error; + mod raw; + +use miette::{Diagnostic, LabeledSpan, Severity, SourceCode}; pub use raw::RawDB; +use std::fmt::{Debug, Display, Formatter}; mod typed; pub use typed::{Adapter, AlignedAdapter, ArchivedValue, DB}; -pub type Error = lmdb::Error; +pub type ErrorO = lmdb::Error; + +pub type Result = std::result::Result; + +#[repr(transparent)] +#[derive(Debug, Error)] +#[error(transparent)] +pub struct Error(#[from] lmdb::Error); + +impl Diagnostic for Error { + fn code<'a>(&'a self) -> Option> { + Some(Box::new(match self.0 { + lmdb::Error::KeyExist => "bffh::db::raw::key_exists".to_string(), + lmdb::Error::NotFound => "bffh::db::raw::not_found".to_string(), + lmdb::Error::PageNotFound => "bffh::db::raw::page_not_found".to_string(), + lmdb::Error::Corrupted => "bffh::db::raw::corrupted".to_string(), + lmdb::Error::Panic => "bffh::db::raw::panic".to_string(), + lmdb::Error::VersionMismatch => "bffh::db::raw::version_mismatch".to_string(), + lmdb::Error::Invalid => "bffh::db::raw::invalid".to_string(), + lmdb::Error::MapFull => "bffh::db::raw::map_full".to_string(), + lmdb::Error::DbsFull => "bffh::db::raw::dbs_full".to_string(), + lmdb::Error::ReadersFull => "bffh::db::raw::readers_full".to_string(), + lmdb::Error::TlsFull => "bffh::db::raw::tls_full".to_string(), + lmdb::Error::TxnFull => "bffh::db::raw::txn_full".to_string(), + lmdb::Error::CursorFull => "bffh::db::raw::cursor_full".to_string(), + lmdb::Error::PageFull => "bffh::db::raw::page_full".to_string(), + lmdb::Error::MapResized => "bffh::db::raw::map_resized".to_string(), + lmdb::Error::Incompatible => "bffh::db::raw::incompatible".to_string(), + lmdb::Error::BadRslot => "bffh::db::raw::bad_rslot".to_string(), + lmdb::Error::BadTxn => "bffh::db::raw::bad_txn".to_string(), + lmdb::Error::BadValSize => "bffh::db::raw::bad_val_size".to_string(), + lmdb::Error::BadDbi => "bffh::db::raw::bad_dbi".to_string(), + lmdb::Error::Other(n) => format!("bffh::db::raw::e{}", n), + })) + } + + fn severity(&self) -> Option { + Some(Severity::Error) + } + + fn help<'a>(&'a self) -> Option> { + match self.0 { + lmdb::Error::KeyExist => Some(Box::new("The provided key already exists in the database")), + lmdb::Error::NotFound => Some(Box::new("The requested key was not found in the database")), + lmdb::Error::PageNotFound => Some(Box::new("The requested page was not found. This usually indicates corruption.")), + lmdb::Error::Corrupted => None, + lmdb::Error::Panic => None, + lmdb::Error::VersionMismatch => None, + lmdb::Error::Invalid => None, + lmdb::Error::MapFull => None, + lmdb::Error::DbsFull => None, + lmdb::Error::ReadersFull => None, + lmdb::Error::TlsFull => None, + lmdb::Error::TxnFull => None, + lmdb::Error::CursorFull => None, + lmdb::Error::PageFull => None, + lmdb::Error::MapResized => None, + lmdb::Error::Incompatible => None, + lmdb::Error::BadRslot => Some(Box::new("This usually indicates that the operation can't complete because an incompatible transaction is still open.")), + lmdb::Error::BadTxn => None, + lmdb::Error::BadValSize => None, + lmdb::Error::BadDbi => None, + lmdb::Error::Other(_) => None, + } + } + + fn url<'a>(&'a self) -> Option> { + None + } +} diff --git a/bffhd/db/raw.rs b/bffhd/db/raw.rs index fc24f40..9ec84dd 100644 --- a/bffhd/db/raw.rs +++ b/bffhd/db/raw.rs @@ -1,3 +1,4 @@ +use super::Result; use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags}; #[derive(Debug, Clone)] diff --git a/bffhd/db/typed.rs b/bffhd/db/typed.rs index 96c8ed0..6de8a7c 100644 --- a/bffhd/db/typed.rs +++ b/bffhd/db/typed.rs @@ -142,11 +142,11 @@ impl DB { } pub fn del(&self, txn: &mut RwTransaction, key: &impl AsRef<[u8]>) -> Result<(), db::Error> { - self.db.del::<_, &[u8]>(txn, key, None) + Ok(self.db.del::<_, &[u8]>(txn, key, None)?) } pub fn clear(&self, txn: &mut RwTransaction) -> Result<(), db::Error> { - self.db.clear(txn) + Ok(self.db.clear(txn)?) } pub fn get_all<'txn, T: Transaction>( diff --git a/bffhd/error.rs b/bffhd/error.rs index 747ebcc..61db8a2 100644 --- a/bffhd/error.rs +++ b/bffhd/error.rs @@ -1,74 +1,116 @@ +use thiserror::Error; + use crate::db; use rsasl::error::SessionError; +use std::any::TypeId; +use std::error::Error as StdError; use std::fmt; +use std::fmt::Display; use std::io; -type DBError = db::Error; +use crate::resources::state::db::StateDBError; +use backtrace::{Backtrace, BacktraceFmt, PrintFmt}; +use miette::{Diagnostic, LabeledSpan, Severity, SourceCode}; #[derive(Debug)] +pub struct TracedError { + pub inner: E, + pub backtrace: Backtrace, +} + +impl fmt::Display for TracedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Error: {}", self.inner)?; + + let cwd = std::env::current_dir(); + let mut print_path = + move |fmt: &mut fmt::Formatter<'_>, path: backtrace::BytesOrWideString<'_>| { + let path = path.into_path_buf(); + if let Ok(cwd) = &cwd { + if let Ok(suffix) = path.strip_prefix(cwd) { + return fmt::Display::fmt(&suffix.display(), fmt); + } + } + fmt::Display::fmt(&path.display(), fmt) + }; + let mut bf = BacktraceFmt::new(f, PrintFmt::Short, &mut print_path); + bf.add_context()?; + + Ok(()) + } +} + +impl StdError for TracedError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.inner.source() + } +} + +impl Diagnostic for TracedError { + #[inline(always)] + fn code<'a>(&'a self) -> Option> { + self.inner.code() + } + + #[inline(always)] + fn severity(&self) -> Option { + self.inner.severity() + } + + #[inline(always)] + fn help<'a>(&'a self) -> Option> { + self.inner.help() + } + + #[inline(always)] + fn url<'a>(&'a self) -> Option> { + self.inner.url() + } + + #[inline(always)] + fn source_code(&self) -> Option<&dyn SourceCode> { + self.inner.source_code() + } + + #[inline(always)] + fn labels(&self) -> Option + '_>> { + self.inner.labels() + } + + #[inline(always)] + fn related<'a>(&'a self) -> Option + 'a>> { + self.inner.related() + } + + #[inline(always)] + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.inner.diagnostic_source() + } +} + +#[derive(Debug, Error, Diagnostic)] /// Shared error type -pub enum Error { +pub enum BffhError { + #[error("SASL error: {0:?}")] SASL(SessionError), - IO(io::Error), - Boxed(Box), - Capnp(capnp::Error), - DB(DBError), + #[error("IO error: {0}")] + IO(#[from] io::Error), + #[error("IO error: {0}")] + Boxed(#[from] Box), + #[error("IO error: {0}")] + Capnp(#[from] capnp::Error), + #[error("IO error: {0}")] + DB(#[from] db::Error), + #[error("You do not have the permission required to do that.")] Denied, + #[error("State DB operation failed")] + StateDB(#[from] StateDBError), } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::SASL(e) => { - write!(f, "SASL Error: {}", e) - } - Error::IO(e) => { - write!(f, "IO Error: {}", e) - } - Error::Boxed(e) => { - write!(f, "{}", e) - } - Error::Capnp(e) => { - write!(f, "Cap'n Proto Error: {}", e) - } - Error::DB(e) => { - write!(f, "DB Error: {:?}", e) - } - Error::Denied => { - write!(f, "You do not have the permission required to do that.") - } - } +impl From for BffhError { + fn from(e: SessionError) -> Self { + Self::SASL(e) } } -impl From for Error { - fn from(e: SessionError) -> Error { - Error::SASL(e) - } -} - -impl From for Error { - fn from(e: io::Error) -> Error { - Error::IO(e) - } -} - -impl From> for Error { - fn from(e: Box) -> Error { - Error::Boxed(e) - } -} - -impl From for Error { - fn from(e: capnp::Error) -> Error { - Error::Capnp(e) - } -} - -impl From for Error { - fn from(e: DBError) -> Error { - Error::DB(e) - } -} - -pub type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/bffhd/lib.rs b/bffhd/lib.rs index 33e4549..3c34192 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -42,9 +42,8 @@ mod tls; use std::sync::Arc; -use anyhow::Context; - use futures_util::StreamExt; +use miette::{Context, IntoDiagnostic, Report}; use once_cell::sync::OnceCell; use crate::audit::AuditLog; @@ -75,7 +74,7 @@ pub struct Diflouroborane { pub static RESOURCES: OnceCell = OnceCell::new(); impl Diflouroborane { - pub fn new(config: Config) -> anyhow::Result { + pub fn new(config: Config) -> miette::Result { logging::init(&config.logging); tracing::info!(version = env::VERSION, "Starting BFFH"); @@ -84,15 +83,16 @@ impl Diflouroborane { let executor = Executor::new(); - let env = StateDB::open_env(&config.db_path) - .context("Failed to create state DB env. Does the parent directory for `db_path` exist?")?; - let statedb = - StateDB::create_with_env(env.clone()).context("Failed to open state DB file")?; + let env = StateDB::open_env(&config.db_path)?; - let users = Users::new(env.clone()).context("Failed to open users DB file")?; + let statedb = StateDB::create_with_env(env.clone())?; + + let users = Users::new(env.clone())?; let roles = Roles::new(config.roles.clone()); - let _audit_log = AuditLog::new(&config).context("Failed to initialize audit log")?; + let _audit_log = AuditLog::new(&config) + .into_diagnostic() + .wrap_err("Failed to initialize audit log")?; let resources = ResourcesHandle::new(config.machines.iter().map(|(id, desc)| { Resource::new(Arc::new(resources::Inner::new( @@ -113,13 +113,15 @@ impl Diflouroborane { }) } - pub fn run(&mut self) -> anyhow::Result<()> { + pub fn run(&mut self) -> miette::Result<()> { let mut signals = signal_hook_async_std::Signals::new(&[SIGINT, SIGQUIT, SIGTERM]) - .context("Failed to construct signal handler")?; + .into_diagnostic() + .wrap_err("Failed to construct signal handler")?; actors::load(self.executor.clone(), &self.config, self.resources.clone())?; - let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet())?; + let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet()) + .into_diagnostic()?; let acceptor = tlsconfig.make_tls_acceptor(&self.config.tlsconfig)?; let sessionmanager = SessionManager::new(self.users.clone(), self.roles.clone()); diff --git a/bffhd/resources/state/db.rs b/bffhd/resources/state/db.rs index d3674c3..3abc0df 100644 --- a/bffhd/resources/state/db.rs +++ b/bffhd/resources/state/db.rs @@ -1,6 +1,12 @@ +use thiserror::Error; + use crate::db; use crate::db::{AlignedAdapter, ArchivedValue, RawDB, DB}; use lmdb::{DatabaseFlags, Environment, EnvironmentFlags, Transaction, WriteFlags}; +use miette::{Diagnostic, LabeledSpan, Severity, SourceCode}; +use std::any::TypeId; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; use std::{path::Path, sync::Arc}; use crate::resources::state::State; @@ -11,8 +17,24 @@ pub struct StateDB { db: DB>, } +#[derive(Debug, Error, Diagnostic)] +pub enum StateDBError { + #[error("opening the state db environment failed")] + #[diagnostic( + code(bffh::db::state::open_env), + help("does the parent directory for state_db exist?") + )] + OpenEnv(#[source] db::Error), + #[error("opening the state db failed")] + #[diagnostic(code(bffh::db::state::open))] + Open(#[source] db::Error), + #[error("creating the state db failed")] + #[diagnostic(code(bffh::db::state::create))] + Create(#[source] db::Error), +} + impl StateDB { - pub fn open_env>(path: P) -> lmdb::Result> { + pub fn open_env>(path: P) -> Result, StateDBError> { Environment::new() .set_flags( EnvironmentFlags::WRITE_MAP @@ -23,6 +45,7 @@ impl StateDB { .set_max_dbs(8) .open(path.as_ref()) .map(Arc::new) + .map_err(|e| StateDBError::OpenEnv(e.into())) } fn new(env: Arc, db: RawDB) -> Self { @@ -30,30 +53,32 @@ impl StateDB { Self { env, db } } - pub fn open_with_env(env: Arc) -> lmdb::Result { - let db = unsafe { RawDB::open(&env, Some("state"))? }; + pub fn open_with_env(env: Arc) -> Result { + let db = unsafe { RawDB::open(&env, Some("state")) }; + let db = db.map_err(|e| StateDBError::Open(e.into()))?; Ok(Self::new(env, db)) } - pub fn open>(path: P) -> lmdb::Result { + pub fn open>(path: P) -> Result { let env = Self::open_env(path)?; Self::open_with_env(env) } - pub fn create_with_env(env: Arc) -> lmdb::Result { + pub fn create_with_env(env: Arc) -> Result { let flags = DatabaseFlags::empty(); - let db = unsafe { RawDB::create(&env, Some("state"), flags)? }; + let db = unsafe { RawDB::create(&env, Some("state"), flags) }; + let db = db.map_err(|e| StateDBError::Create(e.into()))?; Ok(Self::new(env, db)) } - pub fn create>(path: P) -> lmdb::Result { + pub fn create>(path: P) -> Result { let env = Self::open_env(path)?; Self::create_with_env(env) } pub fn begin_ro_txn(&self) -> Result { - self.env.begin_ro_txn() + self.env.begin_ro_txn().map_err(db::Error::from) } pub fn get(&self, key: impl AsRef<[u8]>) -> Result>, db::Error> { @@ -72,7 +97,7 @@ impl StateDB { let mut txn = self.env.begin_rw_txn()?; let flags = WriteFlags::empty(); self.db.put(&mut txn, key, val, flags)?; - txn.commit() + Ok(txn.commit()?) } } diff --git a/bffhd/tls.rs b/bffhd/tls.rs index b0d7fc7..bc327cf 100644 --- a/bffhd/tls.rs +++ b/bffhd/tls.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use crate::capnp::TlsListen; use futures_rustls::TlsAcceptor; +use miette::IntoDiagnostic; use rustls::version::{TLS12, TLS13}; use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite}; use tracing::Level; @@ -74,26 +75,27 @@ impl TlsConfig { } } - pub fn make_tls_acceptor(&self, config: &TlsListen) -> anyhow::Result { + pub fn make_tls_acceptor(&self, config: &TlsListen) -> miette::Result { let span = tracing::debug_span!("tls"); let _guard = span.enter(); tracing::debug!(path = %config.certfile.as_path().display(), "reading certificates"); - let mut certfp = BufReader::new(File::open(config.certfile.as_path())?); - let certs = rustls_pemfile::certs(&mut certfp)? + let mut certfp = BufReader::new(File::open(config.certfile.as_path()).into_diagnostic()?); + let certs = rustls_pemfile::certs(&mut certfp) + .into_diagnostic()? .into_iter() .map(Certificate) .collect(); tracing::debug!(path = %config.keyfile.as_path().display(), "reading private key"); - let mut keyfp = BufReader::new(File::open(config.keyfile.as_path())?); - let key = match rustls_pemfile::read_one(&mut keyfp)? { + let mut keyfp = BufReader::new(File::open(config.keyfile.as_path()).into_diagnostic()?); + let key = match rustls_pemfile::read_one(&mut keyfp).into_diagnostic()? { Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => { PrivateKey(key) } _ => { tracing::error!("private key file invalid"); - anyhow::bail!("private key file must contain a PEM-encoded private key") + miette::bail!("private key file must contain a PEM-encoded private key") } }; @@ -105,15 +107,17 @@ impl TlsConfig { match min.as_str() { "tls12" => tls_builder.with_protocol_versions(&[&TLS12]), "tls13" => tls_builder.with_protocol_versions(&[&TLS13]), - x => anyhow::bail!("TLS version {} is invalid", x), + x => miette::bail!("TLS version {} is invalid", x), } } else { tls_builder.with_safe_default_protocol_versions() - }?; + } + .into_diagnostic()?; let mut tls_config = tls_builder .with_no_client_auth() - .with_single_cert(certs, key)?; + .with_single_cert(certs, key) + .into_diagnostic()?; if let Some(keylog) = &self.keylog { tls_config.key_log = keylog.clone(); diff --git a/bffhd/users/db.rs b/bffhd/users/db.rs index c1da213..ffd9816 100644 --- a/bffhd/users/db.rs +++ b/bffhd/users/db.rs @@ -2,7 +2,7 @@ use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags}; use rkyv::Infallible; use std::collections::HashMap; -use anyhow::Context; +use miette::{Context, IntoDiagnostic}; use std::sync::Arc; use crate::db; @@ -28,9 +28,11 @@ pub struct User { } impl User { - pub fn check_password(&self, pwd: &[u8]) -> anyhow::Result { + pub fn check_password(&self, pwd: &[u8]) -> miette::Result { if let Some(ref encoded) = self.userdata.passwd { - argon2::verify_encoded(encoded, pwd).context("Stored password is an invalid string") + argon2::verify_encoded(encoded, pwd) + .into_diagnostic() + .wrap_err("Stored password is an invalid string") } else { Ok(false) } @@ -109,7 +111,7 @@ impl UserDB { // TODO: Make an userdb-specific Transaction newtype to make this safe pub unsafe fn get_rw_txn(&self) -> Result { // The returned transaction is only valid for *this* environment. - self.env.begin_rw_txn() + Ok(self.env.begin_rw_txn()?) } pub unsafe fn new(env: Arc, db: RawDB) -> Self { diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index d938426..98d4b54 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -1,10 +1,10 @@ -use anyhow::Context; use lmdb::{Environment, Transaction}; use once_cell::sync::OnceCell; use rkyv::{Archive, Deserialize, Infallible, Serialize}; use std::collections::HashMap; use std::fmt::{Display, Formatter, Write}; +use miette::{Context, IntoDiagnostic}; use std::path::Path; use std::sync::Arc; @@ -65,7 +65,7 @@ pub struct Users { } impl Users { - pub fn new(env: Arc) -> anyhow::Result { + pub fn new(env: Arc) -> miette::Result { let span = tracing::debug_span!("users", ?env, "Creating Users handle"); let _guard = span.enter(); @@ -74,7 +74,7 @@ impl Users { tracing::debug!("Global resource not yet initialized, initializing…"); unsafe { UserDB::create(env) } }) - .context("Failed to open userdb")?; + .wrap_err("Failed to open userdb")?; Ok(Self { userdb }) } @@ -90,19 +90,19 @@ impl Users { }) } - pub fn put_user(&self, uid: &str, user: &db::User) -> Result<(), lmdb::Error> { + pub fn put_user(&self, uid: &str, user: &db::User) -> Result<(), crate::db::Error> { tracing::trace!(uid, ?user, "Updating user"); self.userdb.put(uid, user) } - pub fn del_user(&self, uid: &str) -> Result<(), lmdb::Error> { + pub fn del_user(&self, uid: &str) -> Result<(), crate::db::Error> { tracing::trace!(uid, "Deleting user"); self.userdb.delete(uid) } - pub fn load_file>(&self, path: P) -> anyhow::Result<()> { - let f = std::fs::read(path)?; - let map: HashMap = toml::from_slice(&f)?; + pub fn load_file>(&self, path: P) -> miette::Result<()> { + let f = std::fs::read(path).into_diagnostic()?; + let map: HashMap = toml::from_slice(&f).into_diagnostic()?; let mut txn = unsafe { self.userdb.get_rw_txn()? }; @@ -132,7 +132,7 @@ impl Users { } } - txn.commit()?; + txn.commit().map_err(crate::db::Error::from)?; Ok(()) } } diff --git a/bin/bffhd/main.rs b/bin/bffhd/main.rs index 6c4dd02..27b9146 100644 --- a/bin/bffhd/main.rs +++ b/bin/bffhd/main.rs @@ -6,7 +6,7 @@ use std::{env, io, io::Write, path::PathBuf}; use nix::NixPath; -fn main() -> anyhow::Result<()> { +fn main() -> miette::Result<()> { // Argument parsing // values for the name, description and version are pulled from `Cargo.toml`. let matches = Command::new(clap::crate_name!())