User db & loading

This commit is contained in:
Nadja Reitzenstein 2022-03-13 22:50:37 +01:00
parent c4dac55b23
commit ddd8add270
13 changed files with 189 additions and 48 deletions

1
Cargo.lock generated
View File

@ -773,6 +773,7 @@ dependencies = [
"signal-hook", "signal-hook",
"signal-hook-async-std", "signal-hook-async-std",
"tempfile", "tempfile",
"toml",
"tracing", "tracing",
"tracing-futures", "tracing-futures",
"tracing-subscriber 0.2.25", "tracing-subscriber 0.2.25",

View File

@ -34,6 +34,7 @@ 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" anyhow = "1.0.56"
toml = "0.5.8"
# Runtime # Runtime
executor = { path = "runtime/executor" } executor = { path = "runtime/executor" }

View File

@ -42,19 +42,19 @@ impl Actor for Process {
Status::Free => { Status::Free => {
command.arg("free"); command.arg("free");
} }
Status::InUse(by) => { Status::InUse(ref by) => {
command.arg("inuse").arg(format!("{}", by.get_username())); command.arg("inuse").arg(format!("{}", by.get_username()));
} }
Status::ToCheck(by) => { Status::ToCheck(ref by) => {
command.arg("tocheck") command.arg("tocheck")
.arg(format!("{}", by.get_username())); .arg(format!("{}", by.get_username()));
} }
Status::Blocked(by) => { Status::Blocked(ref by) => {
command.arg("blocked") command.arg("blocked")
.arg(format!("{}", by.get_username())); .arg(format!("{}", by.get_username()));
} }
Status::Disabled => { command.arg("disabled"); }, Status::Disabled => { command.arg("disabled"); },
Status::Reserved(by) => { Status::Reserved(ref by) => {
command.arg("reserved") command.arg("reserved")
.arg(format!("{}", by.get_username())); .arg(format!("{}", by.get_username()));
} }

View File

@ -1,11 +1,28 @@
use std::sync::Arc; use std::sync::Arc;
use rsasl::error::SASLError; use rsasl::error::{SASLError, SessionError};
use rsasl::mechname::Mechname; use rsasl::mechname::Mechname;
use rsasl::SASL; use rsasl::{Property, SASL};
use rsasl::session::Session; use rsasl::session::{Session, SessionData};
use rsasl::validate::Validation;
use crate::users::db::UserDB;
use crate::users::Users;
pub mod db; 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 { struct Inner {
rsasl: SASL, rsasl: SASL,
} }
@ -21,8 +38,9 @@ pub struct AuthenticationHandle {
} }
impl AuthenticationHandle { impl AuthenticationHandle {
pub fn new() -> Self { pub fn new(userdb: Users) -> Self {
let rsasl = SASL::new(); let mut rsasl = SASL::new();
rsasl.install_callback(Arc::new(Callback::new(userdb)));
Self { inner: Arc::new(Inner::new(rsasl)) } Self { inner: Arc::new(Inner::new(rsasl)) }
} }

View File

@ -27,8 +27,9 @@ impl AuthorizationHandle {
} }
} }
pub fn lookup_user(&self, uid: impl AsRef<str>) -> Option<User> { pub fn get_user_roles(&self, uid: impl AsRef<str>) -> Option<impl IntoIterator<Item=Role>> {
unimplemented!() unimplemented!();
Some([])
} }
pub fn is_permitted<'a>(&self, roles: impl IntoIterator<Item=&'a Role>, perm: impl AsRef<Permission>) -> bool { pub fn is_permitted<'a>(&self, roles: impl IntoIterator<Item=&'a Role>, perm: impl AsRef<Permission>) -> bool {

View File

@ -240,9 +240,13 @@ impl AdminServer for Machine {
"totakeover not implemented".to_string(), "totakeover not implemented".to_string(),
)), )),
}; };
self.resource.force_set(state); let resource = self.resource.clone();
Promise::ok(()) Promise::from_future(async move {
resource.force_set(state).await;
Ok(())
})
} }
fn force_set_user( fn force_set_user(
&mut self, &mut self,
_: admin::ForceSetUserParams, _: admin::ForceSetUserParams,
@ -252,6 +256,7 @@ impl AdminServer for Machine {
"method not implemented".to_string(), "method not implemented".to_string(),
)) ))
} }
fn get_admin_property_list( fn get_admin_property_list(
&mut self, &mut self,
_: admin::GetAdminPropertyListParams, _: admin::GetAdminPropertyListParams,

View File

@ -62,6 +62,8 @@ use crate::resources::search::ResourcesHandle;
use crate::resources::state::db::StateDB; use crate::resources::state::db::StateDB;
use crate::session::SessionManager; use crate::session::SessionManager;
use crate::tls::TlsConfig; use crate::tls::TlsConfig;
use crate::users::db::UserDB;
use crate::users::Users;
pub const RELEASE_STRING: &'static str = env!("BFFHD_RELEASE_STRING"); pub const RELEASE_STRING: &'static str = env!("BFFHD_RELEASE_STRING");
@ -82,8 +84,12 @@ impl Diflouroborane {
tracing::info!(version=RELEASE_STRING, "Starting"); tracing::info!(version=RELEASE_STRING, "Starting");
} }
pub fn setup(&mut self, config: &Config) -> anyhow::Result<()> { pub fn init_logging(config: &Config) {
logging::init(&config); logging::init(&config);
}
pub fn setup(&mut self, config: &Config) -> anyhow::Result<()> {
Self::init_logging(config);
let span = tracing::info_span!("setup"); let span = tracing::info_span!("setup");
let _guard = span.enter(); let _guard = span.enter();
@ -96,8 +102,12 @@ impl Diflouroborane {
SIGTERM, SIGTERM,
]).context("Failed to construct signal handler")?; ]).context("Failed to construct signal handler")?;
let statedb = StateDB::create(&config.db_path).context("Failed to open state DB")?; let env = StateDB::open_env(&config.db_path)?;
let statedb = Arc::new(statedb); 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)| { let resources = ResourcesHandle::new(config.machines.iter().map(|(id, desc)| {
Resource::new(Arc::new(resources::Inner::new(id.to_string(), statedb.clone(), desc.clone()))) 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 acceptor = tlsconfig.make_tls_acceptor(&config.tlsconfig)?;
let sessionmanager = SessionManager::new(); 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))?; let mut apiserver = self.executor.run(APIServer::bind(self.executor.clone(), &config.listens, acceptor, sessionmanager, authentication))?;

View File

@ -112,7 +112,7 @@ impl Resource {
if session.has_manage(self) // Default allow for managers if session.has_manage(self) // Default allow for managers
|| (session.has_write(self) // Decision tree for writers || (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. // Going from available to used by the person requesting is okay.
(Status::Free, Status::InUse(who)) (Status::Free, Status::InUse(who))
// Check that the person requesting does not request for somebody else. // 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 // Returning things we've been using is okay. This includes both if
// they're being freed or marked as to be checked. // they're being freed or marked as to be checked.
(Status::InUse(who), Status::Free | Status::ToCheck(_)) (Status::InUse(who), Status::Free | Status::ToCheck(_))
if who == user => true, if who == &user => true,
// Un-reserving things we reserved is okay // Un-reserving things we reserved is okay
(Status::Reserved(whom), Status::Free) (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 // Using things that we've reserved is okay. But the person requesting
// that has to be the person that reserved the machine. Otherwise // that has to be the person that reserved the machine. Otherwise
// somebody could make a machine reserved by a different user as used by // somebody could make a machine reserved by a different user as used by
// that different user but use it themself. // that different user but use it themself.
(Status::Reserved(whom), Status::InUse(who)) (Status::Reserved(whom), Status::InUse(who))
if user == whom && who == &whom => true, if whom == &user && who == whom => true,
// Default is deny. // Default is deny.
_ => false _ => false
}) })
// Default permissions everybody has // Default permissions everybody has
|| match (old.state, &new) { || match (&old.state, &new) {
// Returning things we've been using is okay. This includes both if // Returning things we've been using is okay. This includes both if
// they're being freed or marked as to be checked. // 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 // 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. // Default is deny.
_ => false, _ => false,

View File

@ -13,7 +13,6 @@ use crate::users::User;
/// Status of a Machine /// Status of a Machine
#[derive( #[derive(
Copy,
Clone, Clone,
PartialEq, PartialEq,
Eq, Eq,
@ -24,7 +23,7 @@ use crate::users::User;
serde::Serialize, serde::Serialize,
serde::Deserialize, serde::Deserialize,
)] )]
#[archive_attr(derive(Debug, PartialEq, serde::Serialize, serde::Deserialize))] #[archive_attr(derive(Debug, PartialEq))]
pub enum Status { pub enum Status {
/// Not currently used by anybody /// Not currently used by anybody
Free, Free,
@ -41,7 +40,6 @@ pub enum Status {
} }
#[derive( #[derive(
Copy,
Clone, Clone,
PartialEq, PartialEq,
Eq, Eq,
@ -115,7 +113,7 @@ impl MachineState {
pub fn check(user: User) -> Self { pub fn check(user: User) -> Self {
Self { Self {
state: Status::ToCheck(user), state: Status::ToCheck(user.clone()),
previous: Some(user), previous: Some(user),
} }
} }

View File

@ -41,7 +41,7 @@ pub struct StateDB {
} }
impl StateDB { impl StateDB {
fn open_env<P: AsRef<Path>>(path: P) -> lmdb::Result<Environment> { pub fn open_env<P: AsRef<Path>>(path: P) -> lmdb::Result<Arc<Environment>> {
Environment::new() Environment::new()
.set_flags( EnvironmentFlags::WRITE_MAP .set_flags( EnvironmentFlags::WRITE_MAP
| EnvironmentFlags::NO_SUB_DIR | EnvironmentFlags::NO_SUB_DIR
@ -49,10 +49,11 @@ impl StateDB {
| EnvironmentFlags::NO_READAHEAD) | EnvironmentFlags::NO_READAHEAD)
.set_max_dbs(2) .set_max_dbs(2)
.open(path.as_ref()) .open(path.as_ref())
.map(Arc::new)
} }
fn new(env: Environment, input: DB<StateAdapter>, output: DB<StateAdapter>) -> Self { fn new(env: Arc<Environment>, input: DB<StateAdapter>, output: DB<StateAdapter>) -> Self {
Self { env: Arc::new(env), input, output } Self { env: env, input, output }
} }
pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> { pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
@ -63,15 +64,19 @@ impl StateDB {
Ok(Self::new(env, input, output)) Ok(Self::new(env, input, output))
} }
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> { pub fn create_with_env(env: Arc<Environment>) -> lmdb::Result<Self> {
let flags = DatabaseFlags::empty(); let flags = DatabaseFlags::empty();
let env = Self::open_env(path)?;
let input = unsafe { DB::create(&env, Some("input"), flags)? }; let input = unsafe { DB::create(&env, Some("input"), flags)? };
let output = unsafe { DB::create(&env, Some("output"), flags)? }; let output = unsafe { DB::create(&env, Some("output"), flags)? };
Ok(Self::new(env, input, output)) Ok(Self::new(env, input, output))
} }
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
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) fn update_txn(&self, txn: &mut RwTransaction, key: impl AsRef<[u8]>, input: &State, output: &State)
-> Result<(), DBError> -> Result<(), DBError>
{ {

View File

@ -1,10 +1,12 @@
use crate::db::{AllocAdapter, Environment, RawDB, Result, DB}; use crate::db::{AllocAdapter, Environment, RawDB, Result, DB};
use crate::db::{DatabaseFlags, LMDBorrow, RoTransaction, WriteFlags}; use crate::db::{DatabaseFlags, LMDBorrow, RoTransaction, WriteFlags};
use lmdb::{RwTransaction, Transaction}; use lmdb::{RwTransaction, Transaction};
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use rkyv::{Archived, Deserialize}; use rkyv::{Archived, Deserialize};
use crate::authorization::roles::RoleIdentifier;
#[derive( #[derive(
Clone, Clone,
@ -18,9 +20,45 @@ use rkyv::{Archived, Deserialize};
serde::Deserialize, serde::Deserialize,
)] )]
pub struct User { pub struct User {
id: u128, pub id: String,
username: String, pub userdata: UserData,
roles: Vec<String>, }
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub passwd: Option<String>,
/// Additional data storage
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
kv: HashMap<String, String>,
}
impl UserData {
pub fn new(roles: Vec<String>) -> Self {
Self { roles, kv: HashMap::new(), passwd: None }
}
pub fn new_with_kv(roles: Vec<String>, kv: HashMap<String, String>) -> Self {
Self { roles, kv, passwd: None }
}
} }
type Adapter = AllocAdapter<User>; type Adapter = AllocAdapter<User>;
@ -79,4 +117,4 @@ impl UserDB {
Ok(out) Ok(out)
} }
} }

View File

@ -14,17 +14,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::collections::HashMap;
use rkyv::{Archive, Deserialize, Infallible, Serialize}; use rkyv::{Archive, Deserialize, Infallible, Serialize};
use std::ops::Deref; use std::ops::Deref;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context;
use lmdb::Environment;
pub mod db; pub mod db;
pub use crate::authentication::db::PassDB; 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( #[derive(
Copy,
Clone, Clone,
PartialEq, PartialEq,
Eq, Eq,
@ -35,18 +40,18 @@ use crate::authorization::roles::Role;
serde::Serialize, serde::Serialize,
serde::Deserialize, serde::Deserialize,
)] )]
#[archive_attr(derive(Debug, PartialEq, serde::Serialize, serde::Deserialize))] #[archive_attr(derive(Debug, PartialEq))]
pub struct User { pub struct User {
id: u64 id: String,
} }
impl User { impl User {
pub fn new(id: u64) -> Self { pub fn new(id: String) -> Self {
User { id } User { id }
} }
pub fn get_username(&self) -> &str { pub fn get_username(&self) -> &str {
unimplemented!() self.id.as_str()
} }
pub fn get_roles(&self) -> impl IntoIterator<Item=Role> { pub fn get_roles(&self) -> impl IntoIterator<Item=Role> {
@ -54,3 +59,45 @@ impl User {
[] []
} }
} }
pub struct Inner {
userdb: UserDB,
//passdb: PassDB,
}
#[derive(Clone)]
pub struct Users {
inner: Arc<Inner>
}
impl Users {
pub fn new(env: Arc<Environment>) -> anyhow::Result<Self> {
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<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let f = std::fs::read(path)?;
let mut map: HashMap<String, UserData> = 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(())
}
}

View File

@ -5,8 +5,11 @@ use std::net::ToSocketAddrs;
use std::os::unix::prelude::AsRawFd; use std::os::unix::prelude::AsRawFd;
use std::str::FromStr; use std::str::FromStr;
use std::{env, io, io::Write, path::PathBuf}; use std::{env, io, io::Write, path::PathBuf};
use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use lmdb::{Environment, EnvironmentFlags};
use nix::NixPath; use nix::NixPath;
use diflouroborane::users::Users;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
// Argument parsing // Argument parsing
@ -60,6 +63,7 @@ fn main() -> anyhow::Result<()> {
Arg::new("load") Arg::new("load")
.help("Load values into the internal databases") .help("Load values into the internal databases")
.long("load") .long("load")
.takes_value(true)
.conflicts_with("dump"), .conflicts_with("dump"),
) )
.arg(Arg::new("keylog") .arg(Arg::new("keylog")
@ -103,10 +107,25 @@ fn main() -> anyhow::Result<()> {
std::process::exit(-1); 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!() unimplemented!()
} else if matches.is_present("load") { } 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 { } else {
let keylog = matches.value_of("keylog"); let keylog = matches.value_of("keylog");
// When passed an empty string (i.e no value) take the value from the env // 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) keylog.map(PathBuf::from)
}; };
let mut config = config::read(&PathBuf::from_str(configpath).unwrap()).unwrap();
config.tlskeylog = keylog; config.tlskeylog = keylog;
config.verbosity = matches.occurrences_of("verbosity") as isize; config.verbosity = matches.occurrences_of("verbosity") as isize;
if config.verbosity == 0 && matches.is_present("quiet") { if config.verbosity == 0 && matches.is_present("quiet") {