mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-22 14:57:56 +01:00
Status commit
This commit is contained in:
parent
9227b632e4
commit
b203edf206
995
Cargo.lock
generated
995
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
|
@ -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
|
||||||
///
|
///
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>,
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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>;
|
||||||
|
@ -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};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user