From ddd8add270acec7133dd9fdda9527a56b86978b0 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Sun, 13 Mar 2022 22:50:37 +0100 Subject: [PATCH] User db & loading --- Cargo.lock | 1 + Cargo.toml | 1 + bffhd/actors/process.rs | 8 ++-- bffhd/authentication/mod.rs | 28 ++++++++++--- bffhd/authorization/mod.rs | 5 ++- bffhd/capnp/machine.rs | 9 ++++- bffhd/lib.rs | 18 +++++++-- bffhd/resources/mod.rs | 14 +++---- bffhd/resources/modules/fabaccess.rs | 6 +-- bffhd/resources/state/db.rs | 15 ++++--- bffhd/users/db.rs | 48 +++++++++++++++++++--- bffhd/users/mod.rs | 59 +++++++++++++++++++++++++--- bin/bffhd/main.rs | 25 ++++++++++-- 13 files changed, 189 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd65160..ecfbe58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -773,6 +773,7 @@ dependencies = [ "signal-hook", "signal-hook-async-std", "tempfile", + "toml", "tracing", "tracing-futures", "tracing-subscriber 0.2.25", diff --git a/Cargo.toml b/Cargo.toml index c865912..3ae0838 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ futures-util = "0.3" futures-lite = "1.12.0" async-net = "1.6.1" anyhow = "1.0.56" +toml = "0.5.8" # Runtime executor = { path = "runtime/executor" } diff --git a/bffhd/actors/process.rs b/bffhd/actors/process.rs index 08da96a..066d3d4 100644 --- a/bffhd/actors/process.rs +++ b/bffhd/actors/process.rs @@ -42,19 +42,19 @@ impl Actor for Process { Status::Free => { command.arg("free"); } - Status::InUse(by) => { + Status::InUse(ref by) => { command.arg("inuse").arg(format!("{}", by.get_username())); } - Status::ToCheck(by) => { + Status::ToCheck(ref by) => { command.arg("tocheck") .arg(format!("{}", by.get_username())); } - Status::Blocked(by) => { + Status::Blocked(ref by) => { command.arg("blocked") .arg(format!("{}", by.get_username())); } Status::Disabled => { command.arg("disabled"); }, - Status::Reserved(by) => { + Status::Reserved(ref by) => { command.arg("reserved") .arg(format!("{}", by.get_username())); } diff --git a/bffhd/authentication/mod.rs b/bffhd/authentication/mod.rs index d3bff8d..6921a39 100644 --- a/bffhd/authentication/mod.rs +++ b/bffhd/authentication/mod.rs @@ -1,11 +1,28 @@ use std::sync::Arc; -use rsasl::error::SASLError; +use rsasl::error::{SASLError, SessionError}; use rsasl::mechname::Mechname; -use rsasl::SASL; -use rsasl::session::Session; +use rsasl::{Property, SASL}; +use rsasl::session::{Session, SessionData}; +use rsasl::validate::Validation; +use crate::users::db::UserDB; +use crate::users::Users; pub mod db; +struct Callback { + users: Users, +} +impl Callback { + pub fn new(users: Users) -> Self { + Self { users, } + } +} +impl rsasl::callback::Callback for Callback { + fn validate(&self, session: &mut SessionData, validation: Validation, mechanism: &Mechname) -> Result<(), SessionError> { + todo!() + } +} + struct Inner { rsasl: SASL, } @@ -21,8 +38,9 @@ pub struct AuthenticationHandle { } impl AuthenticationHandle { - pub fn new() -> Self { - let rsasl = SASL::new(); + pub fn new(userdb: Users) -> Self { + let mut rsasl = SASL::new(); + rsasl.install_callback(Arc::new(Callback::new(userdb))); Self { inner: Arc::new(Inner::new(rsasl)) } } diff --git a/bffhd/authorization/mod.rs b/bffhd/authorization/mod.rs index 7ed788d..0055cbb 100644 --- a/bffhd/authorization/mod.rs +++ b/bffhd/authorization/mod.rs @@ -27,8 +27,9 @@ impl AuthorizationHandle { } } - pub fn lookup_user(&self, uid: impl AsRef) -> Option { - unimplemented!() + pub fn get_user_roles(&self, uid: impl AsRef) -> Option> { + unimplemented!(); + Some([]) } pub fn is_permitted<'a>(&self, roles: impl IntoIterator, perm: impl AsRef) -> bool { diff --git a/bffhd/capnp/machine.rs b/bffhd/capnp/machine.rs index 590891b..3aed94a 100644 --- a/bffhd/capnp/machine.rs +++ b/bffhd/capnp/machine.rs @@ -240,9 +240,13 @@ impl AdminServer for Machine { "totakeover not implemented".to_string(), )), }; - self.resource.force_set(state); - Promise::ok(()) + let resource = self.resource.clone(); + Promise::from_future(async move { + resource.force_set(state).await; + Ok(()) + }) } + fn force_set_user( &mut self, _: admin::ForceSetUserParams, @@ -252,6 +256,7 @@ impl AdminServer for Machine { "method not implemented".to_string(), )) } + fn get_admin_property_list( &mut self, _: admin::GetAdminPropertyListParams, diff --git a/bffhd/lib.rs b/bffhd/lib.rs index 832fd88..92df9af 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -62,6 +62,8 @@ use crate::resources::search::ResourcesHandle; use crate::resources::state::db::StateDB; use crate::session::SessionManager; use crate::tls::TlsConfig; +use crate::users::db::UserDB; +use crate::users::Users; pub const RELEASE_STRING: &'static str = env!("BFFHD_RELEASE_STRING"); @@ -82,8 +84,12 @@ impl Diflouroborane { tracing::info!(version=RELEASE_STRING, "Starting"); } - pub fn setup(&mut self, config: &Config) -> anyhow::Result<()> { + pub fn init_logging(config: &Config) { logging::init(&config); + } + + pub fn setup(&mut self, config: &Config) -> anyhow::Result<()> { + Self::init_logging(config); let span = tracing::info_span!("setup"); let _guard = span.enter(); @@ -96,8 +102,12 @@ impl Diflouroborane { SIGTERM, ]).context("Failed to construct signal handler")?; - let statedb = StateDB::create(&config.db_path).context("Failed to open state DB")?; - let statedb = Arc::new(statedb); + let env = StateDB::open_env(&config.db_path)?; + let statedb = Arc::new(StateDB::create_with_env(env.clone()) + .context("Failed to open state DB file")?); + + let userdb = Users::new(env.clone()).context("Failed to open users DB file")?; + let resources = ResourcesHandle::new(config.machines.iter().map(|(id, desc)| { Resource::new(Arc::new(resources::Inner::new(id.to_string(), statedb.clone(), desc.clone()))) })); @@ -110,7 +120,7 @@ impl Diflouroborane { let acceptor = tlsconfig.make_tls_acceptor(&config.tlsconfig)?; let sessionmanager = SessionManager::new(); - let authentication = AuthenticationHandle::new(); + let authentication = AuthenticationHandle::new(userdb.clone()); let mut apiserver = self.executor.run(APIServer::bind(self.executor.clone(), &config.listens, acceptor, sessionmanager, authentication))?; diff --git a/bffhd/resources/mod.rs b/bffhd/resources/mod.rs index a37f60d..ea648d0 100644 --- a/bffhd/resources/mod.rs +++ b/bffhd/resources/mod.rs @@ -112,7 +112,7 @@ impl Resource { if session.has_manage(self) // Default allow for managers || (session.has_write(self) // Decision tree for writers - && match (old.state, &new) { + && match (&old.state, &new) { // Going from available to used by the person requesting is okay. (Status::Free, Status::InUse(who)) // Check that the person requesting does not request for somebody else. @@ -126,30 +126,30 @@ impl Resource { // Returning things we've been using is okay. This includes both if // they're being freed or marked as to be checked. (Status::InUse(who), Status::Free | Status::ToCheck(_)) - if who == user => true, + if who == &user => true, // Un-reserving things we reserved is okay (Status::Reserved(whom), Status::Free) - if user == whom => true, + if whom == &user => true, // Using things that we've reserved is okay. But the person requesting // that has to be the person that reserved the machine. Otherwise // somebody could make a machine reserved by a different user as used by // that different user but use it themself. (Status::Reserved(whom), Status::InUse(who)) - if user == whom && who == &whom => true, + if whom == &user && who == whom => true, // Default is deny. _ => false }) // Default permissions everybody has - || match (old.state, &new) { + || match (&old.state, &new) { // Returning things we've been using is okay. This includes both if // they're being freed or marked as to be checked. - (Status::InUse(who), Status::Free | Status::ToCheck(_)) if who == user => true, + (Status::InUse(who), Status::Free | Status::ToCheck(_)) if who == &user => true, // Un-reserving things we reserved is okay - (Status::Reserved(whom), Status::Free) if user == whom => true, + (Status::Reserved(whom), Status::Free) if whom == &user => true, // Default is deny. _ => false, diff --git a/bffhd/resources/modules/fabaccess.rs b/bffhd/resources/modules/fabaccess.rs index 42b0b11..aff7275 100644 --- a/bffhd/resources/modules/fabaccess.rs +++ b/bffhd/resources/modules/fabaccess.rs @@ -13,7 +13,6 @@ use crate::users::User; /// Status of a Machine #[derive( - Copy, Clone, PartialEq, Eq, @@ -24,7 +23,7 @@ use crate::users::User; serde::Serialize, serde::Deserialize, )] -#[archive_attr(derive(Debug, PartialEq, serde::Serialize, serde::Deserialize))] +#[archive_attr(derive(Debug, PartialEq))] pub enum Status { /// Not currently used by anybody Free, @@ -41,7 +40,6 @@ pub enum Status { } #[derive( - Copy, Clone, PartialEq, Eq, @@ -115,7 +113,7 @@ impl MachineState { pub fn check(user: User) -> Self { Self { - state: Status::ToCheck(user), + state: Status::ToCheck(user.clone()), previous: Some(user), } } diff --git a/bffhd/resources/state/db.rs b/bffhd/resources/state/db.rs index 91116b5..6634b16 100644 --- a/bffhd/resources/state/db.rs +++ b/bffhd/resources/state/db.rs @@ -41,7 +41,7 @@ pub struct StateDB { } impl StateDB { - fn open_env>(path: P) -> lmdb::Result { + pub fn open_env>(path: P) -> lmdb::Result> { Environment::new() .set_flags( EnvironmentFlags::WRITE_MAP | EnvironmentFlags::NO_SUB_DIR @@ -49,10 +49,11 @@ impl StateDB { | EnvironmentFlags::NO_READAHEAD) .set_max_dbs(2) .open(path.as_ref()) + .map(Arc::new) } - fn new(env: Environment, input: DB, output: DB) -> Self { - Self { env: Arc::new(env), input, output } + fn new(env: Arc, input: DB, output: DB) -> Self { + Self { env: env, input, output } } pub fn open>(path: P) -> lmdb::Result { @@ -63,15 +64,19 @@ impl StateDB { Ok(Self::new(env, input, output)) } - pub fn create>(path: P) -> lmdb::Result { + pub fn create_with_env(env: Arc) -> lmdb::Result { let flags = DatabaseFlags::empty(); - let env = Self::open_env(path)?; let input = unsafe { DB::create(&env, Some("input"), flags)? }; let output = unsafe { DB::create(&env, Some("output"), flags)? }; Ok(Self::new(env, input, output)) } + pub fn create>(path: P) -> lmdb::Result { + let env = Self::open_env(path)?; + Self::create_with_env(env) + } + fn update_txn(&self, txn: &mut RwTransaction, key: impl AsRef<[u8]>, input: &State, output: &State) -> Result<(), DBError> { diff --git a/bffhd/users/db.rs b/bffhd/users/db.rs index a2e51be..62a4370 100644 --- a/bffhd/users/db.rs +++ b/bffhd/users/db.rs @@ -1,10 +1,12 @@ use crate::db::{AllocAdapter, Environment, RawDB, Result, DB}; use crate::db::{DatabaseFlags, LMDBorrow, RoTransaction, WriteFlags}; use lmdb::{RwTransaction, Transaction}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; +use std::path::Path; use std::sync::Arc; use rkyv::{Archived, Deserialize}; +use crate::authorization::roles::RoleIdentifier; #[derive( Clone, @@ -18,9 +20,45 @@ use rkyv::{Archived, Deserialize}; serde::Deserialize, )] pub struct User { - id: u128, - username: String, - roles: Vec, + pub id: String, + pub userdata: UserData, +} + +#[derive( +Clone, +PartialEq, +Eq, +Debug, +rkyv::Archive, +rkyv::Serialize, +rkyv::Deserialize, +serde::Serialize, +serde::Deserialize, +)] +/// Data on an user to base decisions on +/// +/// This of course includes authorization data, i.e. that users set roles +pub struct UserData { + /// A Person has N ≥ 0 roles. + /// Persons are only ever given roles, not permissions directly + pub roles: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub passwd: Option, + + /// Additional data storage + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + kv: HashMap, +} + +impl UserData { + pub fn new(roles: Vec) -> Self { + Self { roles, kv: HashMap::new(), passwd: None } + } + pub fn new_with_kv(roles: Vec, kv: HashMap) -> Self { + Self { roles, kv, passwd: None } + } } type Adapter = AllocAdapter; @@ -79,4 +117,4 @@ impl UserDB { Ok(out) } -} +} \ No newline at end of file diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index 5616ddc..daa4930 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -14,17 +14,22 @@ * along with this program. If not, see . */ +use std::collections::HashMap; use rkyv::{Archive, Deserialize, Infallible, Serialize}; use std::ops::Deref; +use std::path::Path; use std::sync::Arc; +use anyhow::Context; +use lmdb::Environment; pub mod db; pub use crate::authentication::db::PassDB; -use crate::authorization::roles::Role; +use crate::authorization::roles::{Role, RoleIdentifier}; +use crate::UserDB; +use crate::users::db::UserData; #[derive( - Copy, Clone, PartialEq, Eq, @@ -35,18 +40,18 @@ use crate::authorization::roles::Role; serde::Serialize, serde::Deserialize, )] -#[archive_attr(derive(Debug, PartialEq, serde::Serialize, serde::Deserialize))] +#[archive_attr(derive(Debug, PartialEq))] pub struct User { - id: u64 + id: String, } impl User { - pub fn new(id: u64) -> Self { + pub fn new(id: String) -> Self { User { id } } pub fn get_username(&self) -> &str { - unimplemented!() + self.id.as_str() } pub fn get_roles(&self) -> impl IntoIterator { @@ -54,3 +59,45 @@ impl User { [] } } + +pub struct Inner { + userdb: UserDB, + //passdb: PassDB, +} + +#[derive(Clone)] +pub struct Users { + inner: Arc +} + +impl Users { + pub fn new(env: Arc) -> anyhow::Result { + let userdb = unsafe { UserDB::create(env.clone()).unwrap() }; + //let passdb = unsafe { PassDB::create(env).unwrap() }; + Ok(Self { inner: Arc::new(Inner { userdb }) }) + } + + pub fn load_file>(&self, path: P) -> anyhow::Result<()> { + let f = std::fs::read(path)?; + let mut map: HashMap = toml::from_slice(&f)?; + + for (uid, mut userdata) in map { + userdata.passwd = userdata.passwd.map(|pw| if !pw.starts_with("$argon2") { + let config = argon2::Config::default(); + let salt: [u8; 16] = rand::random(); + let hash = argon2::hash_encoded(pw.as_bytes(), &salt, &config) + .expect(&format!("Failed to hash password for {}: ", uid)); + tracing::debug!("Hashed pw for {} to {}", uid, hash); + + hash + } else { + pw + }); + let user = db::User { id: uid.clone(), userdata }; + tracing::trace!(%uid, ?user, "Storing user object"); + self.inner.userdb.put(uid.as_str(), &user); + } + + Ok(()) + } +} \ No newline at end of file diff --git a/bin/bffhd/main.rs b/bin/bffhd/main.rs index 758f6ae..ce25638 100644 --- a/bin/bffhd/main.rs +++ b/bin/bffhd/main.rs @@ -5,8 +5,11 @@ use std::net::ToSocketAddrs; use std::os::unix::prelude::AsRawFd; use std::str::FromStr; use std::{env, io, io::Write, path::PathBuf}; +use std::sync::Arc; use anyhow::Context; +use lmdb::{Environment, EnvironmentFlags}; use nix::NixPath; +use diflouroborane::users::Users; fn main() -> anyhow::Result<()> { // Argument parsing @@ -60,6 +63,7 @@ fn main() -> anyhow::Result<()> { Arg::new("load") .help("Load values into the internal databases") .long("load") + .takes_value(true) .conflicts_with("dump"), ) .arg(Arg::new("keylog") @@ -103,10 +107,25 @@ fn main() -> anyhow::Result<()> { std::process::exit(-1); } } - } else if matches.is_present("dump") { + } + + let mut config = config::read(&PathBuf::from_str(configpath).unwrap()).unwrap(); + + if matches.is_present("dump") { unimplemented!() } else if matches.is_present("load") { - unimplemented!() + Diflouroborane::init_logging(&config); + let env = Environment::new() + .set_flags( EnvironmentFlags::WRITE_MAP + | EnvironmentFlags::NO_SUB_DIR + | EnvironmentFlags::NO_TLS + | EnvironmentFlags::NO_READAHEAD) + .set_max_dbs(2) + .open(config.db_path.as_ref()) + .map(Arc::new)?; + let userdb = Users::new(env).context("Failed to open users DB file")?; + userdb.load_file(matches.value_of("load").unwrap()); + return Ok(()) } else { let keylog = matches.value_of("keylog"); // When passed an empty string (i.e no value) take the value from the env @@ -121,8 +140,6 @@ fn main() -> anyhow::Result<()> { keylog.map(PathBuf::from) }; - let mut config = config::read(&PathBuf::from_str(configpath).unwrap()).unwrap(); - config.tlskeylog = keylog; config.verbosity = matches.occurrences_of("verbosity") as isize; if config.verbosity == 0 && matches.is_present("quiet") {