Status commit

This commit is contained in:
Gregor Reitzenstein 2020-11-24 14:16:22 +01:00
parent 9227b632e4
commit b203edf206
10 changed files with 580 additions and 588 deletions

995
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -55,5 +55,10 @@ lmdb-rkv = "0.14"
async-trait = "0.1" async-trait = "0.1"
lazy_static = "1.4.0"
rust-argon2 = "0.8"
rand = "0.7"
[build-dependencies] [build-dependencies]
capnpc = "0.13" capnpc = "0.13"

View File

@ -172,58 +172,34 @@ impl auth_capnp::authentication::Server for Auth {
// somewhere and pass it somewhere else and in between don't check if it's the right type and // somewhere and pass it somewhere else and in between don't check if it's the right type and
// accidentally pass the authzid where the authcid should have gone. // accidentally pass the authzid where the authcid should have gone.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
/// Authentication Identity
///
/// Under the hood a string because the form depends heavily on the method
struct AuthCId(String);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
/// Authorization Identity
///
/// This identity is internal to FabAccess and completely independent from the authentication
/// method or source
struct AuthZId {
/// Main User ID. Generally an user name or similar
uid: String,
/// Sub user ID.
///
/// Can change scopes for permissions, e.g. having a +admin account with more permissions than
/// the default account and +dashboard et.al. accounts that have restricted permissions for
/// their applications
subuid: String,
/// Realm this account originates.
///
/// The Realm is usually described by a domain name but local policy may dictate an unrelated
/// mapping
realm: String,
}
// What is a man?! A miserable little pile of secrets! // What is a man?! A miserable little pile of secrets!
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
/// Authentication/Authorization user object. /// Authentication/Authorization user object.
/// ///
/// This struct contains the user as is passed to the actual authentication/authorization /// This struct describes the user as can be gathered from API authentication exchanges.
/// subsystems /// Specifically this is the value bffh gets after a successful authentication.
/// ///
pub struct User { pub struct AuthenticationData {
/// Contains the Authentication ID used /// Contains the Authentication ID used
/// ///
/// The authentication ID is an identifier for the authentication exchange. This is different /// The authentication ID is an identifier for the authentication exchange. This is
/// than the ID of the user to be authenticated; for example when using x509 the authcid is /// conceptually different than the ID of the user to be authenticated; for example when using
/// the dn of the certificate, when using GSSAPI the authcid is of form `<userid>@<REALM>` /// x509 the authcid is the dn of the certificate, when using GSSAPI the authcid is of form
authcid: AuthCId, /// `<ID>@<REALM>`
authcid: String,
/// Contains the Authorization ID /// Authorization ID
/// ///
/// This is the identifier of the user to *authenticate as*. This in several cases is different /// The authzid represents the identity that a client wants to act as. In our case this is
/// to the `authcid`: /// always an user id. If unset no preference is indicated and the server will authenticate the
/// client as whatever user — if any — they associate with the authcid. Setting the authzid is
/// useful in a number if situations:
/// If somebody wants to authenticate as somebody else, su-style. /// If somebody wants to authenticate as somebody else, su-style.
/// If a person wants to authenticate as a higher-permissions account, e.g. foo may set authzid foo+admin /// If a person wants to authenticate as a higher-permissions account, e.g. foo may set authzid foo+admin
/// to split normal user and "admin" accounts. /// to split normal user and "admin" accounts.
/// If a method requires a specific authcid that is different from the identifier of the user /// If a method requires a specific authcid that is different from the identifier of the user
/// to authenticate as, e.g. GSSAPI, x509 client certificates, API TOKEN authentication. /// to authenticate as, e.g. GSSAPI, x509 client certificates, API TOKEN authentication.
authzid: AuthZId, authzid: String,
/// Contains the authentication method used /// Contains the authentication method used
/// ///

View File

@ -13,20 +13,33 @@ use capnp_rpc::{twoparty, rpc_twoparty_capnp};
use crate::schema::connection_capnp; use crate::schema::connection_capnp;
use crate::db::Databases; use crate::db::Databases;
use crate::db::access::{AccessControl, Permission};
use crate::builtin;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Connection context /// Connection context
// TODO this should track over several connections // TODO this should track over several connections
pub struct Session { pub struct Session {
// Session-spezific log
pub log: Logger, pub log: Logger,
pub user: Option<auth::User>, authz_data: Option<AuthorizationContext>,
accessdb: Arc<AccessControl>,
} }
impl Session { impl Session {
pub fn new(log: Logger) -> Self { pub fn new(log: Logger, accessdb: Arc<AccessControl>) -> Self {
let user = None; let user = None;
Session { log, user } Session { log, user, accessdb }
}
/// Check if the current session has a certain permission
pub async fn check_permission<P: AsRef<Permission>>(&self, perm: &P) -> Result<bool> {
if let Some(user) = self.user.as_ref() {
self.accessdb.check(user, perm).await
} else {
self.accessdb.check_roles(builtin::DEFAULT_ROLEIDS, perm).await
}
} }
} }
@ -61,7 +74,7 @@ pub async fn handle_connection(log: Logger, mut stream: TcpStream, db: Databases
handshake(&log, &mut stream).await?; handshake(&log, &mut stream).await?;
info!(log, "New connection from on {:?}", stream); info!(log, "New connection from on {:?}", stream);
let session = Arc::new(Session::new(log)); let session = Arc::new(Session::new(log, db.access.clone()));
let boots = Bootstrap::new(session, db); let boots = Bootstrap::new(session, db);
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots); let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);

View File

@ -15,6 +15,9 @@ pub mod user;
/// Stores&Retrieves Machines /// Stores&Retrieves Machines
pub mod machine; pub mod machine;
/// Authenticate users
pub mod pass;
#[derive(Clone)] #[derive(Clone)]
pub struct Databases { pub struct Databases {
pub access: Arc<access::AccessControl>, pub access: Arc<access::AccessControl>,

View File

@ -58,9 +58,33 @@ impl AccessControl {
return Ok(false); return Ok(false);
} }
pub async fn check_roles<P: AsRef<Permission>>(&self, roles: &[RoleIdentifier], perm: &P)
-> Result<bool>
{
for v in self.sources.values() {
if v.check_roles(roles, perm.as_ref())? {
return Ok(true);
}
}
return Ok(false);
}
}
impl fmt::Debug for AccessControl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let b = f.debug_struct("AccessControl");
for (name, roledb) in self.sources.iter() {
b.field(name, &roledb.get_type_name().to_string());
}
b.finish()
}
} }
pub trait RoleDB { pub trait RoleDB {
fn get_type_name(&self) -> &'static str;
fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>>; fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>>;
/// Check if a given user has the given permission /// Check if a given user has the given permission
@ -130,8 +154,6 @@ pub trait RoleDB {
/// assign to all users. /// assign to all users.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Role { pub struct Role {
name: String,
// If a role doesn't define parents, default to an empty Vec. // If a role doesn't define parents, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
/// A Role can have parents, inheriting all permissions /// A Role can have parents, inheriting all permissions
@ -328,6 +350,10 @@ impl PermissionBuf {
Self { inner } Self { inner }
} }
pub fn from_perm(perm: &Permission) -> Self {
Self { inner: perm.inner.to_string() }
}
pub fn into_string(self) -> String { pub fn into_string(self) -> String {
self.inner self.inner
} }
@ -368,7 +394,7 @@ pub struct Permission {
inner: str inner: str
} }
impl Permission { impl Permission {
pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Permission { pub const fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Permission {
unsafe { &*(s.as_ref() as *const str as *const Permission) } unsafe { &*(s.as_ref() as *const str as *const Permission) }
} }

View File

@ -150,6 +150,10 @@ impl Internal {
} }
impl RoleDB for Internal { impl RoleDB for Internal {
fn get_type_name(&self) -> &'static str {
"Internal"
}
fn check(&self, user: &User, perm: &Permission) -> Result<bool> { fn check(&self, user: &User, perm: &Permission) -> Result<bool> {
let txn = self.env.begin_ro_txn()?; let txn = self.env.begin_ro_txn()?;
self._check(&txn, user, &perm) self._check(&txn, user, &perm)

View File

@ -5,7 +5,7 @@ use std::collections::HashMap;
/// A Person, from the Authorization perspective /// A Person, from the Authorization perspective
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub struct User { pub struct AuthzContext {
/// The identification of this user. /// The identification of this user.
pub id: UserIdentifier, pub id: UserIdentifier,
@ -18,37 +18,16 @@ pub struct User {
kv: HashMap<Box<[u8]>, Box<[u8]>>, kv: HashMap<Box<[u8]>, Box<[u8]>>,
} }
/// Locally unique identifier for an user
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct UserIdentifier {
/// Main UID. Must be unique in this instance so that the tuple (uid, location) is globally
/// unique.
uid: String,
/// Subordinate ID. Must be unique for this user, i.e. the tuple (uid, subuid) must be unique
/// but two different uids can have the same subuid. `None` means no subuid is set and the ID
/// refers to the main users
subuid: Option<String>,
/// Location of the instance the user comes from. `None` means the local instance.
location: Option<String>,
}
impl UserIdentifier {
pub fn new(uid: String, subuid: Option<String>, location: Option<String>) -> Self {
Self { uid, subuid, location }
}
}
impl fmt::Display for UserIdentifier { impl fmt::Display for UserIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let r = write!(f, "{}", self.uid); let r = write!(f, "{}", self.uid)?;
if let Some(ref s) = self.subuid { if let Some(ref s) = self.subuid {
write!(f, "+{}", s)?; write!(f, "+{}", s)?;
} }
if let Some(ref l) = self.location { if let Some(ref l) = self.location {
write!(f, "@{}", l)?; write!(f, "@{}", l)?;
} }
r Ok(r)
} }
} }
@ -56,24 +35,3 @@ impl fmt::Display for UserIdentifier {
pub trait UserDB { pub trait UserDB {
fn get_user(&self, uid: UserIdentifier) -> Option<User>; fn get_user(&self, uid: UserIdentifier) -> Option<User>;
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_uid_test() {
let uid = "testuser".to_string();
let suid = "testsuid".to_string();
let location = "testloc".to_string();
assert_eq!("testuser",
format!("{}", UserIdentifier::new(uid.clone(), None, None)));
assert_eq!("testuser+testsuid",
format!("{}", UserIdentifier::new(uid.clone(), Some(suid.clone()), None)));
assert_eq!("testuser+testsuid",
format!("{}", UserIdentifier::new(uid.clone(), Some(suid.clone()), None)));
assert_eq!("testuser+testsuid@testloc",
format!("{}", UserIdentifier::new(uid, Some(suid), Some(location))));
}
}

View File

@ -24,6 +24,7 @@ pub enum Error {
MQTT(mqtt::Error), MQTT(mqtt::Error),
Config(config::ConfigError), Config(config::ConfigError),
BadVersion((u32,u32)), BadVersion((u32,u32)),
Argon2(argon2::Error)
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -144,4 +145,10 @@ impl From<config::ConfigError> for Error {
} }
} }
impl From<argon2::Error> for Error {
fn from(e: argon2::Error) -> Error {
Error::Argon2(e)
}
}
pub(crate) type Result<T> = std::result::Result<T, Error>; pub(crate) type Result<T> = std::result::Result<T, Error>;

View File

@ -17,6 +17,7 @@ mod registries;
mod schema; mod schema;
mod db; mod db;
mod machine; mod machine;
mod builtin;
use clap::{App, Arg}; use clap::{App, Arg};