Merge branch 'feature/bettererrors' into development

* feature/bettererrors:
  Switch out anyhow for miette
  More trace output of role checking
  Better errors when the db directory is missing
This commit is contained in:
Nadja Reitzenstein 2022-06-02 17:48:54 +02:00
commit 9100811c50
16 changed files with 458 additions and 124 deletions

190
Cargo.lock generated
View File

@ -23,6 +23,21 @@ dependencies = [
"pretty", "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]] [[package]]
name = "aes" name = "aes"
version = "0.7.5" version = "0.7.5"
@ -46,6 +61,15 @@ dependencies = [
"version_check", "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]] [[package]]
name = "annotate-snippets" name = "annotate-snippets"
version = "0.9.1" version = "0.9.1"
@ -64,12 +88,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "anyhow"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
[[package]] [[package]]
name = "api" name = "api"
version = "0.3.2" version = "0.3.2"
@ -275,6 +293,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 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]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -800,13 +833,13 @@ dependencies = [
name = "diflouroborane" name = "diflouroborane"
version = "0.4.2" version = "0.4.2"
dependencies = [ dependencies = [
"anyhow",
"api", "api",
"async-channel", "async-channel",
"async-compat", "async-compat",
"async-net", "async-net",
"async-oneshot", "async-oneshot",
"async-trait", "async-trait",
"backtrace",
"capnp", "capnp",
"capnp-rpc", "capnp-rpc",
"chrono", "chrono",
@ -826,6 +859,7 @@ dependencies = [
"libc", "libc",
"linkme", "linkme",
"lmdb-rkv", "lmdb-rkv",
"miette",
"nix", "nix",
"once_cell", "once_cell",
"pin-utils", "pin-utils",
@ -847,6 +881,7 @@ dependencies = [
"signal-hook", "signal-hook",
"signal-hook-async-std", "signal-hook-async-std",
"tempfile", "tempfile",
"thiserror",
"toml", "toml",
"tracing", "tracing",
"tracing-futures", "tracing-futures",
@ -1195,6 +1230,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]] [[package]]
name = "git2" name = "git2"
version = "0.14.4" version = "0.14.4"
@ -1328,6 +1369,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "is_ci"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]] [[package]]
name = "is_debug" name = "is_debug"
version = "1.0.1" version = "1.0.1"
@ -1543,6 +1590,46 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "mio" name = "mio"
version = "0.8.1" version = "0.8.1"
@ -1639,6 +1726,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "object"
version = "0.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.10.0" version = "1.10.0"
@ -1678,6 +1774,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "owo-colors"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.0.0" version = "2.0.0"
@ -1973,6 +2075,8 @@ version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [ dependencies = [
"aho-corasick",
"memchr",
"regex-syntax", "regex-syntax",
] ]
@ -2134,6 +2238,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -2429,6 +2539,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.4" version = "0.4.4"
@ -2473,6 +2589,34 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 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]] [[package]]
name = "syn" name = "syn"
version = "1.0.88" version = "1.0.88"
@ -2507,6 +2651,16 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"
@ -2521,21 +2675,26 @@ name = "textwrap"
version = "0.15.0" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.30" version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.30" version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2773,6 +2932,15 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 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]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
version = "0.1.19" version = "0.1.19"

View File

@ -37,7 +37,9 @@ pin-utils = "0.1.0"
futures-util = "0.3" futures-util = "0.3"
futures-lite = "1.12.0" futures-lite = "1.12.0"
async-net = "1.6.1" 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" toml = "0.5.8"
# Well-known paths/dirs for e.g. cache # Well-known paths/dirs for e.g. cache

View File

@ -12,6 +12,7 @@ use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use miette::IntoDiagnostic;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
@ -110,11 +111,11 @@ static ROOT_CERTS: Lazy<RootCertStore> = Lazy::new(|| {
store 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 span = tracing::info_span!("loading actors");
let _guard = span; 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() { let (transport, default_port) = match mqtt_url.scheme() {
"mqtts" | "ssl" => ( "mqtts" | "ssl" => (
rumqttc::Transport::tls_with_config( rumqttc::Transport::tls_with_config(
@ -131,12 +132,12 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) ->
scheme => { scheme => {
tracing::error!(%scheme, "MQTT url uses invalid 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(|| { let host = mqtt_url.host_str().ok_or_else(|| {
tracing::error!("MQTT url must contain a hostname"); 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); let port = mqtt_url.port().unwrap_or(default_port);
@ -167,7 +168,7 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) ->
} }
Err(error) => { Err(error) => {
tracing::error!(?error, "MQTT connection failed"); tracing::error!(?error, "MQTT connection failed");
anyhow::bail!("mqtt connection failed") miette::bail!("mqtt connection failed")
} }
} }

View File

@ -1,4 +1,5 @@
use crate::users::Users; use crate::users::Users;
use miette::{Context, IntoDiagnostic};
use rsasl::error::SessionError; use rsasl::error::SessionError;
use rsasl::mechname::Mechname; use rsasl::mechname::Mechname;
use rsasl::property::{AuthId, Password}; use rsasl::property::{AuthId, Password};
@ -127,8 +128,13 @@ impl AuthenticationHandle {
} }
} }
pub fn start(&self, mechanism: &Mechname) -> anyhow::Result<Session> { pub fn start(&self, mechanism: &Mechname) -> miette::Result<Session> {
Ok(self.inner.rsasl.server_start(mechanism)?) 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<Item = &Mechname> { pub fn list_available_mechs(&self) -> impl IntoIterator<Item = &Mechname> {

View File

@ -67,17 +67,20 @@ impl Roles {
role_id: &String, role_id: &String,
perm: &Permission, perm: &Permission,
) -> bool { ) -> bool {
let _guard = tracing::debug_span!("tally", %role_id, perm=perm.as_str());
if let Some(role) = self.get(role_id) { 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 // Only check and tally parents of a role at the role itself if it's the first time we
// see it // see it
if !roles.contains(role_id) { if !roles.contains(role_id) {
for perm_rule in role.permissions.iter() { for perm_rule in role.permissions.iter() {
if perm_rule.match_perm(perm) { if perm_rule.match_perm(perm) {
tracing::debug!("Permission granted by direct role");
return true; return true;
} }
} }
for parent in role.parents.iter() { for parent in role.parents.iter() {
if self.permitted_tally(roles, parent, perm) { if self.permitted_tally(roles, parent, perm) {
tracing::debug!(%parent, "Permission granted by parent role");
return true; return true;
} }
} }
@ -86,10 +89,13 @@ impl Roles {
} }
} }
tracing::trace!(%role_id, "Permission not granted by role");
false false
} }
pub fn is_permitted(&self, user: &UserData, perm: impl AsRef<Permission>) -> bool { pub fn is_permitted(&self, user: &UserData, perm: impl AsRef<Permission>) -> bool {
let perm = perm.as_ref();
tracing::debug!(perm = perm.as_str(), "Checking permission");
let mut seen = HashSet::new(); let mut seen = HashSet::new();
for role_id in user.roles.iter() { for role_id in user.roles.iter() {
if self.permitted_tally(&mut seen, role_id, perm.as_ref()) { if self.permitted_tally(&mut seen, role_id, perm.as_ref()) {

View File

@ -60,7 +60,7 @@ impl APIServer {
acceptor: TlsAcceptor, acceptor: TlsAcceptor,
sessionmanager: SessionManager, sessionmanager: SessionManager,
authentication: AuthenticationHandle, authentication: AuthenticationHandle,
) -> anyhow::Result<Self> { ) -> miette::Result<Self> {
let span = tracing::info_span!("binding API listen sockets"); let span = tracing::info_span!("binding API listen sockets");
let _guard = span.enter(); let _guard = span.enter();

View File

@ -1,7 +1,81 @@
use thiserror::Error;
mod raw; mod raw;
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
pub use raw::RawDB; pub use raw::RawDB;
use std::fmt::{Debug, Display, Formatter};
mod typed; mod typed;
pub use typed::{Adapter, AlignedAdapter, ArchivedValue, DB}; pub use typed::{Adapter, AlignedAdapter, ArchivedValue, DB};
pub type Error = lmdb::Error; pub type ErrorO = lmdb::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[repr(transparent)]
#[derive(Debug, Error)]
#[error(transparent)]
pub struct Error(#[from] lmdb::Error);
impl Diagnostic for Error {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
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<Severity> {
Some(Severity::Error)
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
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<Box<dyn Display + 'a>> {
None
}
}

View File

@ -1,3 +1,4 @@
use super::Result;
use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags}; use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -142,11 +142,11 @@ impl<A: Adapter> DB<A> {
} }
pub fn del(&self, txn: &mut RwTransaction, key: &impl AsRef<[u8]>) -> Result<(), db::Error> { 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> { 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>( pub fn get_all<'txn, T: Transaction>(

View File

@ -1,74 +1,116 @@
use thiserror::Error;
use crate::db; use crate::db;
use rsasl::error::SessionError; use rsasl::error::SessionError;
use std::any::TypeId;
use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::fmt::Display;
use std::io; 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)] #[derive(Debug)]
pub struct TracedError<E: Diagnostic> {
pub inner: E,
pub backtrace: Backtrace,
}
impl<E: Diagnostic> fmt::Display for TracedError<E> {
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<E: 'static + Diagnostic> StdError for TracedError<E> {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner.source()
}
}
impl<E: 'static + Diagnostic> Diagnostic for TracedError<E> {
#[inline(always)]
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.inner.code()
}
#[inline(always)]
fn severity(&self) -> Option<Severity> {
self.inner.severity()
}
#[inline(always)]
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.inner.help()
}
#[inline(always)]
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.inner.url()
}
#[inline(always)]
fn source_code(&self) -> Option<&dyn SourceCode> {
self.inner.source_code()
}
#[inline(always)]
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
self.inner.labels()
}
#[inline(always)]
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
self.inner.related()
}
#[inline(always)]
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.inner.diagnostic_source()
}
}
#[derive(Debug, Error, Diagnostic)]
/// Shared error type /// Shared error type
pub enum Error { pub enum BffhError {
#[error("SASL error: {0:?}")]
SASL(SessionError), SASL(SessionError),
IO(io::Error), #[error("IO error: {0}")]
Boxed(Box<dyn std::error::Error>), IO(#[from] io::Error),
Capnp(capnp::Error), #[error("IO error: {0}")]
DB(DBError), Boxed(#[from] Box<dyn std::error::Error>),
#[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, Denied,
#[error("State DB operation failed")]
StateDB(#[from] StateDBError),
} }
impl fmt::Display for Error { impl From<SessionError> for BffhError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn from(e: SessionError) -> Self {
match self { Self::SASL(e)
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<SessionError> for Error { pub type Result<T> = std::result::Result<T, BffhError>;
fn from(e: SessionError) -> Error {
Error::SASL(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IO(e)
}
}
impl From<Box<dyn std::error::Error>> for Error {
fn from(e: Box<dyn std::error::Error>) -> Error {
Error::Boxed(e)
}
}
impl From<capnp::Error> for Error {
fn from(e: capnp::Error) -> Error {
Error::Capnp(e)
}
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DB(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -42,9 +42,8 @@ mod tls;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context;
use futures_util::StreamExt; use futures_util::StreamExt;
use miette::{Context, IntoDiagnostic, Report};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use crate::audit::AuditLog; use crate::audit::AuditLog;
@ -75,7 +74,7 @@ pub struct Diflouroborane {
pub static RESOURCES: OnceCell<ResourcesHandle> = OnceCell::new(); pub static RESOURCES: OnceCell<ResourcesHandle> = OnceCell::new();
impl Diflouroborane { impl Diflouroborane {
pub fn new(config: Config) -> anyhow::Result<Self> { pub fn new(config: Config) -> miette::Result<Self> {
logging::init(&config.logging); logging::init(&config.logging);
tracing::info!(version = env::VERSION, "Starting BFFH"); tracing::info!(version = env::VERSION, "Starting BFFH");
@ -85,13 +84,15 @@ impl Diflouroborane {
let executor = Executor::new(); let executor = Executor::new();
let env = StateDB::open_env(&config.db_path)?; let env = StateDB::open_env(&config.db_path)?;
let statedb =
StateDB::create_with_env(env.clone()).context("Failed to open state DB file")?;
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 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)| { let resources = ResourcesHandle::new(config.machines.iter().map(|(id, desc)| {
Resource::new(Arc::new(resources::Inner::new( Resource::new(Arc::new(resources::Inner::new(
@ -112,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]) 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())?; 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 acceptor = tlsconfig.make_tls_acceptor(&self.config.tlsconfig)?;
let sessionmanager = SessionManager::new(self.users.clone(), self.roles.clone()); let sessionmanager = SessionManager::new(self.users.clone(), self.roles.clone());

View File

@ -1,6 +1,12 @@
use thiserror::Error;
use crate::db; use crate::db;
use crate::db::{AlignedAdapter, ArchivedValue, RawDB, DB}; use crate::db::{AlignedAdapter, ArchivedValue, RawDB, DB};
use lmdb::{DatabaseFlags, Environment, EnvironmentFlags, Transaction, WriteFlags}; 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 std::{path::Path, sync::Arc};
use crate::resources::state::State; use crate::resources::state::State;
@ -11,8 +17,24 @@ pub struct StateDB {
db: DB<AlignedAdapter<State>>, db: DB<AlignedAdapter<State>>,
} }
#[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 { impl StateDB {
pub fn open_env<P: AsRef<Path>>(path: P) -> lmdb::Result<Arc<Environment>> { pub fn open_env<P: AsRef<Path>>(path: P) -> Result<Arc<Environment>, StateDBError> {
Environment::new() Environment::new()
.set_flags( .set_flags(
EnvironmentFlags::WRITE_MAP EnvironmentFlags::WRITE_MAP
@ -23,6 +45,7 @@ impl StateDB {
.set_max_dbs(8) .set_max_dbs(8)
.open(path.as_ref()) .open(path.as_ref())
.map(Arc::new) .map(Arc::new)
.map_err(|e| StateDBError::OpenEnv(e.into()))
} }
fn new(env: Arc<Environment>, db: RawDB) -> Self { fn new(env: Arc<Environment>, db: RawDB) -> Self {
@ -30,30 +53,32 @@ impl StateDB {
Self { env, db } Self { env, db }
} }
pub fn open_with_env(env: Arc<Environment>) -> lmdb::Result<Self> { pub fn open_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
let db = unsafe { RawDB::open(&env, Some("state"))? }; let db = unsafe { RawDB::open(&env, Some("state")) };
let db = db.map_err(|e| StateDBError::Open(e.into()))?;
Ok(Self::new(env, db)) Ok(Self::new(env, db))
} }
pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> { pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, StateDBError> {
let env = Self::open_env(path)?; let env = Self::open_env(path)?;
Self::open_with_env(env) Self::open_with_env(env)
} }
pub fn create_with_env(env: Arc<Environment>) -> lmdb::Result<Self> { pub fn create_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
let flags = DatabaseFlags::empty(); 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)) Ok(Self::new(env, db))
} }
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> { pub fn create<P: AsRef<Path>>(path: P) -> Result<Self, StateDBError> {
let env = Self::open_env(path)?; let env = Self::open_env(path)?;
Self::create_with_env(env) Self::create_with_env(env)
} }
pub fn begin_ro_txn(&self) -> Result<impl Transaction + '_, db::Error> { pub fn begin_ro_txn(&self) -> Result<impl Transaction + '_, db::Error> {
self.env.begin_ro_txn() self.env.begin_ro_txn().map_err(db::Error::from)
} }
pub fn get(&self, key: impl AsRef<[u8]>) -> Result<Option<ArchivedValue<State>>, db::Error> { pub fn get(&self, key: impl AsRef<[u8]>) -> Result<Option<ArchivedValue<State>>, db::Error> {
@ -72,7 +97,7 @@ impl StateDB {
let mut txn = self.env.begin_rw_txn()?; let mut txn = self.env.begin_rw_txn()?;
let flags = WriteFlags::empty(); let flags = WriteFlags::empty();
self.db.put(&mut txn, key, val, flags)?; self.db.put(&mut txn, key, val, flags)?;
txn.commit() Ok(txn.commit()?)
} }
} }

View File

@ -6,6 +6,7 @@ use std::sync::Arc;
use crate::capnp::TlsListen; use crate::capnp::TlsListen;
use futures_rustls::TlsAcceptor; use futures_rustls::TlsAcceptor;
use miette::IntoDiagnostic;
use rustls::version::{TLS12, TLS13}; use rustls::version::{TLS12, TLS13};
use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite}; use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite};
use tracing::Level; use tracing::Level;
@ -74,26 +75,27 @@ impl TlsConfig {
} }
} }
pub fn make_tls_acceptor(&self, config: &TlsListen) -> anyhow::Result<TlsAcceptor> { pub fn make_tls_acceptor(&self, config: &TlsListen) -> miette::Result<TlsAcceptor> {
let span = tracing::debug_span!("tls"); let span = tracing::debug_span!("tls");
let _guard = span.enter(); let _guard = span.enter();
tracing::debug!(path = %config.certfile.as_path().display(), "reading certificates"); tracing::debug!(path = %config.certfile.as_path().display(), "reading certificates");
let mut certfp = BufReader::new(File::open(config.certfile.as_path())?); let mut certfp = BufReader::new(File::open(config.certfile.as_path()).into_diagnostic()?);
let certs = rustls_pemfile::certs(&mut certfp)? let certs = rustls_pemfile::certs(&mut certfp)
.into_diagnostic()?
.into_iter() .into_iter()
.map(Certificate) .map(Certificate)
.collect(); .collect();
tracing::debug!(path = %config.keyfile.as_path().display(), "reading private key"); tracing::debug!(path = %config.keyfile.as_path().display(), "reading private key");
let mut keyfp = BufReader::new(File::open(config.keyfile.as_path())?); let mut keyfp = BufReader::new(File::open(config.keyfile.as_path()).into_diagnostic()?);
let key = match rustls_pemfile::read_one(&mut keyfp)? { let key = match rustls_pemfile::read_one(&mut keyfp).into_diagnostic()? {
Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => { Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => {
PrivateKey(key) PrivateKey(key)
} }
_ => { _ => {
tracing::error!("private key file invalid"); 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() { match min.as_str() {
"tls12" => tls_builder.with_protocol_versions(&[&TLS12]), "tls12" => tls_builder.with_protocol_versions(&[&TLS12]),
"tls13" => tls_builder.with_protocol_versions(&[&TLS13]), "tls13" => tls_builder.with_protocol_versions(&[&TLS13]),
x => anyhow::bail!("TLS version {} is invalid", x), x => miette::bail!("TLS version {} is invalid", x),
} }
} else { } else {
tls_builder.with_safe_default_protocol_versions() tls_builder.with_safe_default_protocol_versions()
}?; }
.into_diagnostic()?;
let mut tls_config = tls_builder let mut tls_config = tls_builder
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(certs, key)?; .with_single_cert(certs, key)
.into_diagnostic()?;
if let Some(keylog) = &self.keylog { if let Some(keylog) = &self.keylog {
tls_config.key_log = keylog.clone(); tls_config.key_log = keylog.clone();

View File

@ -2,7 +2,7 @@ use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
use rkyv::Infallible; use rkyv::Infallible;
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::Context; use miette::{Context, IntoDiagnostic};
use std::sync::Arc; use std::sync::Arc;
use crate::db; use crate::db;
@ -28,9 +28,11 @@ pub struct User {
} }
impl User { impl User {
pub fn check_password(&self, pwd: &[u8]) -> anyhow::Result<bool> { pub fn check_password(&self, pwd: &[u8]) -> miette::Result<bool> {
if let Some(ref encoded) = self.userdata.passwd { 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 { } else {
Ok(false) Ok(false)
} }
@ -109,7 +111,7 @@ impl UserDB {
// TODO: Make an userdb-specific Transaction newtype to make this safe // TODO: Make an userdb-specific Transaction newtype to make this safe
pub unsafe fn get_rw_txn(&self) -> Result<RwTransaction, db::Error> { pub unsafe fn get_rw_txn(&self) -> Result<RwTransaction, db::Error> {
// The returned transaction is only valid for *this* environment. // 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<Environment>, db: RawDB) -> Self { pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {

View File

@ -1,10 +1,10 @@
use anyhow::Context;
use lmdb::{Environment, Transaction}; use lmdb::{Environment, Transaction};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use rkyv::{Archive, Deserialize, Infallible, Serialize}; use rkyv::{Archive, Deserialize, Infallible, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter, Write}; use std::fmt::{Display, Formatter, Write};
use miette::{Context, IntoDiagnostic};
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
@ -65,7 +65,7 @@ pub struct Users {
} }
impl Users { impl Users {
pub fn new(env: Arc<Environment>) -> anyhow::Result<Self> { pub fn new(env: Arc<Environment>) -> miette::Result<Self> {
let span = tracing::debug_span!("users", ?env, "Creating Users handle"); let span = tracing::debug_span!("users", ?env, "Creating Users handle");
let _guard = span.enter(); let _guard = span.enter();
@ -74,7 +74,7 @@ impl Users {
tracing::debug!("Global resource not yet initialized, initializing…"); tracing::debug!("Global resource not yet initialized, initializing…");
unsafe { UserDB::create(env) } unsafe { UserDB::create(env) }
}) })
.context("Failed to open userdb")?; .wrap_err("Failed to open userdb")?;
Ok(Self { 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"); tracing::trace!(uid, ?user, "Updating user");
self.userdb.put(uid, 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"); tracing::trace!(uid, "Deleting user");
self.userdb.delete(uid) self.userdb.delete(uid)
} }
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> { pub fn load_file<P: AsRef<Path>>(&self, path: P) -> miette::Result<()> {
let f = std::fs::read(path)?; let f = std::fs::read(path).into_diagnostic()?;
let map: HashMap<String, UserData> = toml::from_slice(&f)?; let map: HashMap<String, UserData> = toml::from_slice(&f).into_diagnostic()?;
let mut txn = unsafe { self.userdb.get_rw_txn()? }; 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(()) Ok(())
} }
} }

View File

@ -6,7 +6,7 @@ use std::{env, io, io::Write, path::PathBuf};
use nix::NixPath; use nix::NixPath;
fn main() -> anyhow::Result<()> { fn main() -> miette::Result<()> {
// Argument parsing // Argument parsing
// values for the name, description and version are pulled from `Cargo.toml`. // values for the name, description and version are pulled from `Cargo.toml`.
let matches = Command::new(clap::crate_name!()) let matches = Command::new(clap::crate_name!())