diff --git a/bffhd/actors/mod.rs b/bffhd/actors/mod.rs index bd8facf..2c28a9d 100644 --- a/bffhd/actors/mod.rs +++ b/bffhd/actors/mod.rs @@ -243,12 +243,16 @@ pub fn load( .compat(), ); - let mut actor_map: HashMap = config - .actor_connections + let mut actor_connections_data_vec: Vec<(String, String)> = vec![]; + for actor_connection in config.actor_connections.clone().into_iter() { + actor_connections_data_vec.push((actor_connection.machine, actor_connection.actor)); + } + + let mut actor_map: HashMap = actor_connections_data_vec .iter() .filter_map(|(k, v)| { - if let Some(resource) = resources.get_by_id(v) { - Some((k.clone(), resource.get_signal())) + if let Some(resource) = resources.get_by_id(k) { + Some((v.clone(), resource.get_signal())) } else { tracing::error!(actor=%k, machine=%v, "Machine configured for actor not found!"); None diff --git a/bffhd/authorization/permissions.rs b/bffhd/authorization/permissions.rs index 8360365..bfa0f23 100644 --- a/bffhd/authorization/permissions.rs +++ b/bffhd/authorization/permissions.rs @@ -32,7 +32,7 @@ pub struct PrivilegesBuf { // i.e. "bffh.perm" is not the same as "bffհ.реrm" (Armenian 'հ':Հ and Cyrillic 'е':Е) // See also https://util.unicode.org/UnicodeJsps/confusables.jsp pub struct PermissionBuf { - inner: String, + pub inner: String, } impl PermissionBuf { #[inline(always)] diff --git a/bffhd/authorization/roles.rs b/bffhd/authorization/roles.rs index ef9cb38..781813c 100644 --- a/bffhd/authorization/roles.rs +++ b/bffhd/authorization/roles.rs @@ -131,11 +131,11 @@ pub struct Role { /// This makes situations where different levels of access are required easier: Each higher /// level of access sets the lower levels of access as parent, inheriting their permission; if /// you are allowed to manage a machine you are then also allowed to use it and so on - parents: Vec, + pub parents: Vec, // If a role doesn't define permissions, default to an empty Vec. #[serde(default, skip_serializing_if = "Vec::is_empty")] - permissions: Vec, + pub permissions: Vec, } impl Role { diff --git a/bffhd/capnp/user.rs b/bffhd/capnp/user.rs index 3ef26d9..b463b38 100644 --- a/bffhd/capnp/user.rs +++ b/bffhd/capnp/user.rs @@ -143,7 +143,8 @@ impl admin::Server for User { // Only update if needed if !target.userdata.roles.iter().any(|r| r.as_str() == rolename) { target.userdata.roles.push(rolename.to_string()); - pry!(self.session + pry!(self + .session .users .put_user(self.user.get_username(), &target)); } @@ -168,7 +169,8 @@ impl admin::Server for User { // Only update if needed if target.userdata.roles.iter().any(|r| r.as_str() == rolename) { target.userdata.roles.retain(|r| r.as_str() != rolename); - pry!(self.session + pry!(self + .session .users .put_user(self.user.get_username(), &target)); } diff --git a/bffhd/config/dhall.rs b/bffhd/config/dhall.rs index 557cd49..19de5cf 100644 --- a/bffhd/config/dhall.rs +++ b/bffhd/config/dhall.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; -use crate::authorization::permissions::PrivilegesBuf; +use crate::authorization::permissions::{PermRule, PermissionBuf, PrivilegesBuf}; use crate::authorization::roles::Role; use crate::capnp::{Listen, TlsListen}; use crate::logging::LogConfig; @@ -60,28 +60,13 @@ pub struct MachineDescription { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { + pub spacename: String, + + pub instanceurl: String, + /// A list of address/port pairs to listen on. pub listens: Vec, - /// Machine descriptions to load - pub machines: HashMap, - - /// Actors to load and their configuration options - pub actors: HashMap, - - /// Initiators to load and their configuration options - pub initiators: HashMap, - - pub mqtt_url: String, - - pub actor_connections: Vec<(String, String)>, - pub init_connections: Vec<(String, String)>, - - pub db_path: PathBuf, - pub auditlog_path: PathBuf, - - pub roles: HashMap, - #[serde(flatten)] pub tlsconfig: TlsListen, @@ -94,9 +79,22 @@ pub struct Config { #[serde(default, skip)] pub logging: LogConfig, - pub spacename: String, + pub mqtt_url: String, + pub db_path: PathBuf, + pub auditlog_path: PathBuf, - pub instanceurl: String, + pub roles: HashMap, + + /// Machine descriptions to load + pub machines: HashMap, + + /// Actors to load and their configuration options + pub actors: HashMap, + pub actor_connections: Vec, + + /// Initiators to load and their configuration options + pub initiators: HashMap, + pub init_connections: Vec, } impl Config { @@ -111,6 +109,24 @@ pub struct ModuleConfig { pub params: HashMap, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ParamsConfig { + pub module: String, + pub params: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActorConnectionConfig { + pub machine: String, + pub actor: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InitiatorConnectionConfig { + pub machine: String, + pub initiator: String, +} + pub(crate) fn deser_option<'de, D, T>(d: D) -> std::result::Result, D::Error> where D: serde::Deserializer<'de>, @@ -123,50 +139,151 @@ impl Default for Config { fn default() -> Self { let mut actors: HashMap = HashMap::new(); let mut initiators: HashMap = HashMap::new(); - let machines = HashMap::new(); + let mut roles: HashMap = HashMap::new(); + let mut machines: HashMap = HashMap::new(); + + let mut initiator_123_params: HashMap = HashMap::new(); + initiator_123_params.insert("args".to_string(), "".to_string()); + initiator_123_params.insert("cmd".to_string(), "echo".to_string()); + + let mut actor_connections_vec: Vec = vec![ActorConnectionConfig { + machine: "resource_a".to_string(), + actor: "actor_123".to_string(), + }]; + + let mut initiator_connections_vec: Vec = + vec![InitiatorConnectionConfig { + machine: "resource_a".to_string(), + initiator: "initiator_123".to_string(), + }]; + + roles.insert( + "admin".to_string(), + Role { + parents: Vec::new(), + permissions: vec![ + PermRule::Base(PermissionBuf { + inner: "bffh.users.info".to_string(), + }), + PermRule::Base(PermissionBuf { + inner: "bffh.users.manage".to_string(), + }), + PermRule::Base(PermissionBuf { + inner: "bffh.users.admin".to_string(), + }), + ], + }, + ); + + roles.insert( + "member".to_string(), + Role { + parents: Vec::new(), + permissions: vec![ + PermRule::Base(PermissionBuf { + inner: "lab.some.disclose".to_string(), + }), + PermRule::Base(PermissionBuf { + inner: "lab.some.read".to_string(), + }), + PermRule::Base(PermissionBuf { + inner: "lab.some.write".to_string(), + }), + PermRule::Base(PermissionBuf { + inner: "lab.some.manage".to_string(), + }), + ], + }, + ); + + machines.insert( + "resource_a".to_string(), + MachineDescription { + name: "Resource A".to_string(), + description: Option::from("A description".to_string()), + wiki: Option::from("https://some.wiki.url".to_string()), + category: Option::from("A category".to_string()), + privs: PrivilegesBuf { + disclose: PermissionBuf { + inner: "lab.some.disclose".to_string(), + }, + read: PermissionBuf { + inner: "lab.some.read".to_string(), + }, + write: PermissionBuf { + inner: "lab.some.write".to_string(), + }, + manage: PermissionBuf { + inner: "lab.some.manage".to_string(), + }, + }, + }, + ); + + machines.insert( + "resource_b".to_string(), + MachineDescription { + name: "Resource B".to_string(), + description: Option::from("A description".to_string()), + wiki: Option::from("https://some.wiki.url".to_string()), + category: Option::from("A category".to_string()), + privs: PrivilegesBuf { + disclose: PermissionBuf { + inner: "lab.some.disclose".to_string(), + }, + read: PermissionBuf { + inner: "lab.some.read".to_string(), + }, + write: PermissionBuf { + inner: "lab.some.write".to_string(), + }, + manage: PermissionBuf { + inner: "lab.some.manage".to_string(), + }, + }, + }, + ); actors.insert( - "Actor".to_string(), + "actor_123".to_string(), ModuleConfig { module: "Shelly".to_string(), params: HashMap::new(), }, ); + initiators.insert( - "Initiator".to_string(), + "initiator_123".to_string(), ModuleConfig { - module: "TCP-Listen".to_string(), - params: HashMap::new(), + module: "Process".to_string(), + params: initiator_123_params, }, ); Config { + spacename: "fabaccess.sample.space".into(), + instanceurl: "https://fabaccess.sample.space".into(), listens: vec![Listen { address: "127.0.0.1".to_string(), port: None, }], - actors, - initiators, - machines, - mqtt_url: "tcp://localhost:1883".to_string(), - actor_connections: vec![("Testmachine".to_string(), "Actor".to_string())], - init_connections: vec![("Initiator".to_string(), "Testmachine".to_string())], - - db_path: PathBuf::from("/run/bffh/database"), - auditlog_path: PathBuf::from("/var/log/bffh/audit.log"), - roles: HashMap::new(), - tlsconfig: TlsListen { - certfile: PathBuf::from("./bffh.crt"), - keyfile: PathBuf::from("./bffh.key"), + certfile: PathBuf::from("/etc/bffh/certs/bffh.crt"), + keyfile: PathBuf::from("/etc/bffh/certs/bffh.key"), ..Default::default() }, - tlskeylog: None, verbosity: 0, logging: LogConfig::default(), - instanceurl: "".into(), - spacename: "".into(), + mqtt_url: "mqtt://127.0.0.1:1883".to_string(), + db_path: PathBuf::from("/var/lib/bffh/bffh.db"), + auditlog_path: PathBuf::from("/var/log/bffh/audit.json"), + roles, + machines, + actors, + actor_connections: actor_connections_vec, + initiators, + init_connections: initiator_connections_vec, } } } diff --git a/bffhd/initiators/mod.rs b/bffhd/initiators/mod.rs index 0b84195..89787a4 100644 --- a/bffhd/initiators/mod.rs +++ b/bffhd/initiators/mod.rs @@ -2,9 +2,7 @@ use crate::initiators::dummy::Dummy; use crate::initiators::process::Process; use crate::resources::modules::fabaccess::Status; use crate::session::SessionHandle; -use crate::{ - AuthenticationHandle, Config, Resource, ResourcesHandle, SessionManager, -}; +use crate::{AuthenticationHandle, Config, Resource, ResourcesHandle, SessionManager}; use executor::prelude::Executor; use futures_util::ready; use std::collections::HashMap; @@ -105,12 +103,16 @@ pub fn load( let span = tracing::info_span!("loading initiators"); let _guard = span.enter(); - let mut initiator_map: HashMap = config - .init_connections + let mut init_connections_data_vec: Vec<(String, String)> = vec![]; + for init_connection in config.init_connections.clone().into_iter() { + init_connections_data_vec.push((init_connection.machine, init_connection.initiator)); + } + + let mut initiator_map: HashMap = init_connections_data_vec .iter() .filter_map(|(k, v)| { - if let Some(resource) = resources.get_by_id(v) { - Some((k.clone(), resource.clone())) + if let Some(resource) = resources.get_by_id(k) { + Some((v.clone(), resource.clone())) } else { tracing::error!(initiator=%k, machine=%v, "Machine configured for initiator not found!"); diff --git a/bffhd/lib.rs b/bffhd/lib.rs index 6461f26..4c0bde6 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -208,9 +208,11 @@ impl Difluoroborane { pub fn dump_db(&mut self, file: &str) -> Result<(), miette::Error> { let users = self.users.dump_map()?; let state = self.statedb.dump_map()?; - let dump = DatabaseDump{users, state}; - let data = toml::ser::to_vec(&dump).map_err(|e| miette::Error::msg(format!("Serializing database dump failed: {}", e)))?; - std::fs::write(file, &data).map_err(|e| miette::Error::msg(format!("writing database dump failed: {}", e)))?; + let dump = DatabaseDump { users, state }; + let data = toml::ser::to_vec(&dump) + .map_err(|e| miette::Error::msg(format!("Serializing database dump failed: {}", e)))?; + std::fs::write(file, &data) + .map_err(|e| miette::Error::msg(format!("writing database dump failed: {}", e)))?; Ok(()) } @@ -236,7 +238,8 @@ impl Difluoroborane { self.resources.clone(), sessionmanager.clone(), authentication.clone(), - ).expect("initializing initiators failed"); + ) + .expect("initializing initiators failed"); // TODO 0.5: error handling. Add variant to BFFHError actors::load(self.executor.clone(), &self.config, self.resources.clone())?; diff --git a/bffhd/resources/mod.rs b/bffhd/resources/mod.rs index 56dccf2..94c6d5d 100644 --- a/bffhd/resources/mod.rs +++ b/bffhd/resources/mod.rs @@ -90,7 +90,11 @@ impl Inner { .unwrap() .log(self.id.as_str(), &format!("{}", state)); if let Err(e) = res { - tracing::error!("Writing to the audit log failed for {} {}: {e}", self.id.as_str(), state); + tracing::error!( + "Writing to the audit log failed for {} {}: {e}", + self.id.as_str(), + state + ); } self.signal.set(state); @@ -164,7 +168,9 @@ impl Resource { fn set_state(&self, state: MachineState) { let mut serializer = AllocSerializer::<1024>::default(); - serializer.serialize_value(&state).expect("serializing a MachineState shoud be infallible"); + serializer + .serialize_value(&state) + .expect("serializing a MachineState shoud be infallible"); let archived = ArchivedValue::new(serializer.into_serializer().into_inner()); self.inner.set_state(archived) } diff --git a/bffhd/resources/state/db.rs b/bffhd/resources/state/db.rs index d2237aa..9929ded 100644 --- a/bffhd/resources/state/db.rs +++ b/bffhd/resources/state/db.rs @@ -1,5 +1,5 @@ -use rkyv::ser::Serializer; use rkyv::ser::serializers::AllocSerializer; +use rkyv::ser::Serializer; use thiserror::Error; use crate::db; @@ -54,8 +54,7 @@ impl StateDB { } pub fn open_with_env(env: Arc) -> Result { - let db = RawDB::open(&env, Some("state")) - .map_err(|e| StateDBError::Open(e.into()))?; + let db = RawDB::open(&env, Some("state")).map_err(|e| StateDBError::Open(e.into()))?; Ok(Self::new(env, db)) } @@ -117,11 +116,14 @@ impl StateDB { pub fn dump_map(&self) -> miette::Result> { let mut map = std::collections::HashMap::new(); for (key, val) in self.get_all(&self.begin_ro_txn()?)? { - let key_str = core::str::from_utf8(&key).map_err(|_e| miette::Error::msg("state key not UTF8"))?.to_string(); - let val_state: State = rkyv::Deserialize::deserialize(val.as_ref(), &mut rkyv::Infallible).unwrap(); + let key_str = core::str::from_utf8(&key) + .map_err(|_e| miette::Error::msg("state key not UTF8"))? + .to_string(); + let val_state: State = + rkyv::Deserialize::deserialize(val.as_ref(), &mut rkyv::Infallible).unwrap(); map.insert(key_str, val_state); } - Ok(map) + Ok(map) } } diff --git a/bffhd/resources/state/mod.rs b/bffhd/resources/state/mod.rs index efc38c3..05ba0be 100644 --- a/bffhd/resources/state/mod.rs +++ b/bffhd/resources/state/mod.rs @@ -1,5 +1,5 @@ -use std::fmt::{Debug, Display, Formatter}; use std::fmt; +use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index ee4a6c5..ea316a7 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -173,7 +173,7 @@ impl Users { Ok(()) } - pub fn load_map(&mut self, dump: &HashMap) -> miette::Result<()> { + pub fn load_map(&mut self, dump: &HashMap) -> miette::Result<()> { let mut txn = unsafe { self.userdb.get_rw_txn() }?; self.userdb.clear_txn(&mut txn)?; @@ -194,7 +194,7 @@ impl Users { } pub fn dump_map(&self) -> miette::Result> { - return Ok(self.userdb.get_all()?) + return Ok(self.userdb.get_all()?); } pub fn dump_file(&self, path_str: &str, force: bool) -> miette::Result { let path = Path::new(path_str); diff --git a/bin/bffhd/main.rs b/bin/bffhd/main.rs index a3e64b3..6c89442 100644 --- a/bin/bffhd/main.rs +++ b/bin/bffhd/main.rs @@ -23,12 +23,12 @@ fn main() -> miette::Result<()> { build_kind=difluoroborane::env::BUILD_RUST_CHANNEL)) .about(clap::crate_description!()) .arg(Arg::new("config") - .help("Path to the config file to use") + .help("Path to the DHALL config file to use") .long("config") .short('c') .takes_value(true)) .arg(Arg::new("verbosity") - .help("Increase logging verbosity") + .help("Increase logging verbosity. Stackable from -v up to -vvv") .long("verbose") .short('v') .multiple_occurrences(true) @@ -47,18 +47,19 @@ fn main() -> miette::Result<()> { .arg(Arg::new("log level") .help("Set the desired log levels.") .long("log-level") - .takes_value(true)) + .takes_value(true) + .possible_values(["info", "warn", "error", "debug", "trace"])) .arg( Arg::new("print default") - .help("Print a default config to stdout instead of running") + .help("Print a default DHALL config to stdout instead of running") .long("print-default")) .arg( Arg::new("check config") - .help("Check config for validity") + .help("Check DHALL config for validity") .long("check")) .arg( Arg::new("dump-db") - .help("Dump all internal databases") + .help("Dump all internal databases (states and users) to the given file as TOML") .long("dump-db") .alias("dump") .conflicts_with("dump-users") @@ -83,29 +84,31 @@ fn main() -> miette::Result<()> { ) .arg( Arg::new("force") - .help("force ops that may clobber") + .help("Force owerwriting existing files") .long("force") ) .arg( Arg::new("load-users") - .help("Load users into the internal databases") + .help("Load users from TOML into the internal databases") .long("load-users") .alias("load") .takes_value(true) + .value_name("FILE") .conflicts_with("dump-db") .conflicts_with("load-db") .conflicts_with("dump-users") ) .arg( Arg::new("load-db") - .help("Load values into the internal databases") + .help("Load values from TOML into the internal databases") .long("load-db") .takes_value(true) + .value_name("FILE") .conflicts_with("dump-db") .conflicts_with("load-users") .conflicts_with("dump-users")) .arg(Arg::new("keylog") - .help("log TLS keys into PATH. If no path is specified the value of the envvar SSLKEYLOGFILE is used.") + .help("Log TLS keys into PATH. If no path is specified the value of the envvar SSLKEYLOGFILE is used.") .long("tls-key-log") .value_name("PATH") .takes_value(true) @@ -119,9 +122,7 @@ fn main() -> miette::Result<()> { Err(error) => error.exit(), }; - let configpath = matches - .value_of("config") - .unwrap_or("/etc/difluoroborane.dhall"); + let configpath = matches.value_of("config").unwrap_or("/etc/bffh/bffh.dhall"); // Check for the --print-default option first because we don't need to do anything else in that // case. @@ -130,7 +131,7 @@ fn main() -> miette::Result<()> { let encoded = serde_dhall::serialize(&config).to_string().unwrap(); // Direct writing to fd 1 is faster but also prevents any print-formatting that could - // invalidate the generated TOML + // invalidate the generated DHALL let stdout = io::stdout(); let mut handle = stdout.lock(); handle.write_all(encoded.as_bytes()).unwrap(); @@ -184,9 +185,13 @@ fn main() -> miette::Result<()> { } else if matches.is_present("load-users") { let bffh = Difluoroborane::new(config)?; - bffh.users.load_file(matches.value_of("load-users").unwrap())?; + bffh.users + .load_file(matches.value_of("load-users").unwrap())?; - tracing::info!("loaded users from {}", matches.value_of("load-users").unwrap()); + tracing::info!( + "loaded users from {}", + matches.value_of("load-users").unwrap() + ); return Ok(()); } else { diff --git a/tools/git-pre-commit-hook b/hooks/pre-commit similarity index 100% rename from tools/git-pre-commit-hook rename to hooks/pre-commit diff --git a/tools/git-pre-push-hook b/hooks/pre-push similarity index 100% rename from tools/git-pre-push-hook rename to hooks/pre-push