mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-12-22 11:43:49 +01:00
User db & loading
This commit is contained in:
parent
c4dac55b23
commit
ddd8add270
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -773,6 +773,7 @@ dependencies = [
|
||||
"signal-hook",
|
||||
"signal-hook-async-std",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.2.25",
|
||||
|
@ -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" }
|
||||
|
@ -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()));
|
||||
}
|
||||
|
@ -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)) }
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,9 @@ impl AuthorizationHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup_user(&self, uid: impl AsRef<str>) -> Option<User> {
|
||||
unimplemented!()
|
||||
pub fn get_user_roles(&self, uid: impl AsRef<str>) -> Option<impl IntoIterator<Item=Role>> {
|
||||
unimplemented!();
|
||||
Some([])
|
||||
}
|
||||
|
||||
pub fn is_permitted<'a>(&self, roles: impl IntoIterator<Item=&'a Role>, perm: impl AsRef<Permission>) -> bool {
|
||||
|
@ -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,
|
||||
|
18
bffhd/lib.rs
18
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))?;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ pub struct 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()
|
||||
.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<StateAdapter>, output: DB<StateAdapter>) -> Self {
|
||||
Self { env: Arc::new(env), input, output }
|
||||
fn new(env: Arc<Environment>, input: DB<StateAdapter>, output: DB<StateAdapter>) -> Self {
|
||||
Self { env: env, input, output }
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
|
||||
@ -63,15 +64,19 @@ impl StateDB {
|
||||
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 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<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)
|
||||
-> Result<(), DBError>
|
||||
{
|
||||
|
@ -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<String>,
|
||||
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<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>;
|
||||
@ -79,4 +117,4 @@ impl UserDB {
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,17 +14,22 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<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(())
|
||||
}
|
||||
}
|
@ -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") {
|
||||
|
Loading…
Reference in New Issue
Block a user