Pushes code around until example loading compiles

This commit is contained in:
Gregor Reitzenstein 2020-11-19 14:53:14 +01:00
parent 5d9c1d5a64
commit 3b63e654e5
9 changed files with 272 additions and 29 deletions

14
examples/machines.toml Normal file
View File

@ -0,0 +1,14 @@
[e5408099-d3e5-440b-a92b-3aabf7683d6b]
name = "Somemachine"
disclose = "lab.some.disclose"
read = "lab.some.read"
write = "lab.some.write"
manage = "lab.some.admin"
[eaabebae-34d1-4a3a-912a-967b495d3d6e]
name = "Testmachine"
description = "An optional description"
disclose = "lab.test.read"
read = "lab.test.read"
write = "lab.test.write"
manage = "lab.test.admin"

20
examples/roles.toml Normal file
View File

@ -0,0 +1,20 @@
[testrole]
name = "Testrole"
permissions = [
"lab.test.*"
]
[somerole]
name = "Somerole"
parents = ["testparent%lmdb"]
permissions = [
"lab.some.admin"
]
[testparent]
name = "Testparent"
permissions = [
"lab.some.write",
"lab.some.read",
"lab.some.disclose",
]

View File

@ -1,5 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use slog::Logger;
use capnp::capability::{Params, Results, Promise}; use capnp::capability::{Params, Results, Promise};
use crate::schema::connection_capnp; use crate::schema::connection_capnp;
@ -17,6 +19,7 @@ pub struct Bootstrap {
impl Bootstrap { impl Bootstrap {
pub fn new(session: Arc<Session>) -> Self { pub fn new(session: Arc<Session>) -> Self {
info!(session.log, "Created Bootstrap");
Self { session } Self { session }
} }
} }
@ -31,7 +34,7 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
// TODO: When should we allow multiple auth and how do me make sure that does not leak // TODO: When should we allow multiple auth and how do me make sure that does not leak
// priviledges (e.g. due to previously issues caps)? // priviledges (e.g. due to previously issues caps)?
if self.session.user.is_none() { if self.session.user.is_none() {
res.get().set_auth(capnp_rpc::new_client(auth::Auth::new())) res.get().set_auth(capnp_rpc::new_client(auth::Auth::new(self.session.clone())))
} }
Promise::ok(()) Promise::ok(())

View File

@ -3,23 +3,29 @@
//! Authorization is over in `access.rs` //! Authorization is over in `access.rs`
//! Authentication using SASL //! Authentication using SASL
use std::sync::Arc;
use slog::Logger; use slog::Logger;
use rsasl::{ use rsasl::{
SASL, SASL,
Property, Property,
Session, Session as SaslSession,
ReturnCode, ReturnCode,
Callback, Callback,
SaslCtx, SaslCtx,
Step, Step,
}; };
use serde::{Serialize, Deserialize};
use capnp::capability::{Params, Results, Promise}; use capnp::capability::{Params, Results, Promise};
use crate::error::Result; use crate::error::Result;
use crate::config::Settings; use crate::config::Settings;
use crate::api::Session;
pub use crate::schema::auth_capnp; pub use crate::schema::auth_capnp;
pub struct AppData; pub struct AppData;
@ -27,7 +33,7 @@ pub struct SessionData;
struct CB; struct CB;
impl Callback<AppData, SessionData> for CB { impl Callback<AppData, SessionData> for CB {
fn callback(sasl: SaslCtx<AppData, SessionData>, session: Session<SessionData>, prop: Property) -> libc::c_int { fn callback(sasl: SaslCtx<AppData, SessionData>, session: SaslSession<SessionData>, prop: Property) -> libc::c_int {
let ret = match prop { let ret = match prop {
Property::GSASL_VALIDATE_SIMPLE => { Property::GSASL_VALIDATE_SIMPLE => {
let authid = session.get_property(Property::GSASL_AUTHID).unwrap().to_string_lossy(); let authid = session.get_property(Property::GSASL_AUTHID).unwrap().to_string_lossy();
@ -50,10 +56,11 @@ impl Callback<AppData, SessionData> for CB {
pub struct Auth { pub struct Auth {
pub ctx: SASL<AppData, SessionData>, pub ctx: SASL<AppData, SessionData>,
session: Arc<Session>,
} }
impl Auth { impl Auth {
pub fn new() -> Self { pub fn new(session: Arc<Session>) -> Self {
let mut ctx = SASL::new().unwrap(); let mut ctx = SASL::new().unwrap();
let mut appdata = Box::new(AppData); let mut appdata = Box::new(AppData);
@ -62,7 +69,9 @@ impl Auth {
ctx.install_callback::<CB>(); ctx.install_callback::<CB>();
Self { ctx } info!(session.log, "Auth created");
Self { ctx, session }
} }
} }
@ -156,20 +165,19 @@ impl auth_capnp::authentication::Server for Auth {
} }
} }
pub async fn init(log: Logger, config: Settings) -> Result<Auth> {
Ok(Auth::new())
}
// Use the newtype pattern here to make the type system work for us; even though AuthCId is for all // Use the newtype pattern here to make the type system work for us; even though AuthCId is for all
// intents and purposes just a String the compiler will still complain if you return or more // intents and purposes just a String the compiler will still complain if you return or more
// importantly pass a String intead of a AuthCId. This prevents bugs where you get an object from // importantly pass a String intead of a AuthCId. This prevents bugs where you get an object from
// 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 /// Authentication Identity
/// ///
/// Under the hood a string because the form depends heavily on the method /// Under the hood a string because the form depends heavily on the method
struct AuthCId(String); struct AuthCId(String);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
/// Authorization Identity /// Authorization Identity
/// ///
/// This identity is internal to FabAccess and completely independent from the authentication /// This identity is internal to FabAccess and completely independent from the authentication
@ -191,6 +199,7 @@ struct AuthZId {
} }
// 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)]
/// Authentication/Authorization user object. /// Authentication/Authorization user object.
/// ///
/// This struct contains the user as is passed to the actual authentication/authorization /// This struct contains the user as is passed to the actual authentication/authorization
@ -218,7 +227,7 @@ pub struct User {
/// Contains the authentication method used /// Contains the authentication method used
/// ///
/// For the most part this is the SASL method /// For the most part this is the SASL method
authMethod: String, auth_method: String,
/// Method-specific key-value pairs /// Method-specific key-value pairs
/// ///
@ -236,6 +245,7 @@ pub struct User {
// b) the given authcid may authenticate as the given authzid. E.g. if a given client certificate // b) the given authcid may authenticate as the given authzid. E.g. if a given client certificate
// has been configured for that user, if a GSSAPI user maps to a given user, // has been configured for that user, if a GSSAPI user maps to a given user,
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AuthError { pub enum AuthError {
/// Authentication ID is bad/unknown/.. /// Authentication ID is bad/unknown/..
BadAuthcid, BadAuthcid,
@ -247,7 +257,3 @@ pub enum AuthError {
NotAllowedAuthzid, NotAllowedAuthzid,
} }
fn grant_auth(user: User) -> std::result::Result<(), AuthError> {
unimplemented!()
}

View File

@ -1,5 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use slog::Logger;
use capnp::capability::Promise; use capnp::capability::Promise;
use capnp::Error; use capnp::Error;
@ -15,6 +17,7 @@ pub struct Machines {
impl Machines { impl Machines {
pub fn new(session: Arc<Session>) -> Self { pub fn new(session: Arc<Session>) -> Self {
info!(session.log, "Machines created");
Self { session } Self { session }
} }
} }

View File

@ -15,7 +15,7 @@ use crate::schema::connection_capnp;
/// Connection context /// Connection context
// TODO this should track over several connections // TODO this should track over several connections
pub struct Session { pub struct Session {
log: Logger, pub log: Logger,
pub user: Option<auth::User>, pub user: Option<auth::User>,
} }
@ -57,6 +57,7 @@ async fn handshake(log: &Logger, stream: &mut TcpStream) -> Result<()> {
pub async fn handle_connection(log: Logger, stream: TcpStream) -> Result<()> { pub async fn handle_connection(log: Logger, stream: TcpStream) -> Result<()> {
//handshake(&log, &mut stream).await?; //handshake(&log, &mut stream).await?;
info!(log, "New connection from on {:?}", stream);
let session = Arc::new(Session::new(log)); let session = Arc::new(Session::new(log));
let boots = Bootstrap::new(session); let boots = Bootstrap::new(session);
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots); let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);

View File

@ -4,16 +4,22 @@
use std::fmt; use std::fmt;
use std::collections::HashSet; use std::collections::HashSet;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::convert::TryInto;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::sync::Arc; use std::sync::Arc;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::convert::{TryFrom, Into};
use flexbuffers; use flexbuffers;
use serde::{Serialize, Deserialize}; use serde::{
Serialize,
Serializer,
Deserialize,
Deserializer,
};
use slog::Logger; use slog::Logger;
use lmdb::{Environment, Transaction, RwTransaction, Cursor}; use lmdb::{Environment, Transaction, RwTransaction, Cursor};
@ -98,15 +104,31 @@ pub trait RoleDB {
pub struct Role { pub struct Role {
name: String, name: String,
// If a role doesn't define parents, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
/// A Role can have parents, inheriting all permissions /// A Role can have parents, inheriting all permissions
/// ///
/// This makes situations where different levels of access are required easier: Each higher /// This makes situations where different levels of access are required easier: Each higher
/// level of access sets the lower levels of access as parent, inheriting their permission; if /// level of access sets the lower levels of access as parent, inheriting their permission; if
/// you are allowed to manage a machine you are then also allowed to use it and so on /// you are allowed to manage a machine you are then also allowed to use it and so on
parents: Vec<RoleIdentifier>, parents: Vec<RoleIdentifier>,
// If a role doesn't define permissions, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
permissions: Vec<PermRule>, permissions: Vec<PermRule>,
} }
impl Role {
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<RoleIdentifier, Role>> {
let content = fs::read(path)?;
let file_roles: HashMap<String, Role> = toml::from_slice(&content[..])?;
Ok(HashMap::from_iter(file_roles.into_iter().map(|(key, value)| {
(RoleIdentifier::local_from_str("lmdb".to_string(), key), value)
})))
}
}
type SourceID = String; type SourceID = String;
fn split_once(s: &str, split: char) -> Option<(&str, &str)> { fn split_once(s: &str, split: char) -> Option<(&str, &str)> {
@ -116,6 +138,7 @@ fn split_once(s: &str, split: char) -> Option<(&str, &str)> {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String")]
/// Universal (relative) id of a role /// Universal (relative) id of a role
pub enum RoleIdentifier { pub enum RoleIdentifier {
/// The role comes from this instance /// The role comes from this instance
@ -135,6 +158,16 @@ pub enum RoleIdentifier {
location: String, location: String,
} }
} }
impl fmt::Display for RoleIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source),
RoleIdentifier::Remote {name, location} => write!(f, "{}@{}", name, location),
}
}
}
impl std::str::FromStr for RoleIdentifier { impl std::str::FromStr for RoleIdentifier {
type Err = RoleFromStrError; type Err = RoleFromStrError;
@ -148,21 +181,42 @@ impl std::str::FromStr for RoleIdentifier {
} }
} }
} }
impl fmt::Display for RoleIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { impl TryFrom<String> for RoleIdentifier {
match self { type Error = RoleFromStrError;
RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source),
RoleIdentifier::Remote {name, location} => write!(f, "{}@{}", name, location), fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
if let Some((name, location)) = split_once(&s, '@') {
Ok(RoleIdentifier::Remote { name: name.to_string(), location: location.to_string() })
} else if let Some((name, source)) = split_once(&s, '%') {
Ok(RoleIdentifier::Local { name: name.to_string(), source: source.to_string() })
} else {
Err(RoleFromStrError::Invalid)
} }
} }
} }
impl RoleIdentifier {
pub fn local_from_str(source: String, name: String) -> Self {
RoleIdentifier::Local { name, source }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum RoleFromStrError { pub enum RoleFromStrError {
/// No '@' or '%' found. That's strange, huh? /// No '@' or '%' found. That's strange, huh?
Invalid Invalid
} }
impl fmt::Display for RoleFromStrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RoleFromStrError::Invalid
=> write!(f, "Rolename are of form 'name%source' or 'name@realm'."),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
/// An identifier for a permission /// An identifier for a permission
// XXX: Does remote permissions ever make sense? // XXX: Does remote permissions ever make sense?
@ -201,6 +255,7 @@ pub struct PrivilegesBuf {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(transparent)] #[repr(transparent)]
#[serde(transparent)]
/// An owned permission string /// An owned permission string
/// ///
/// This is under the hood just a fancy std::String. /// This is under the hood just a fancy std::String.
@ -242,6 +297,10 @@ impl PermissionBuf {
pub fn from_string(inner: String) -> Self { pub fn from_string(inner: String) -> Self {
Self { inner } Self { inner }
} }
pub fn into_string(self) -> String {
self.inner
}
} }
impl AsRef<str> for PermissionBuf { impl AsRef<str> for PermissionBuf {
#[inline(always)] #[inline(always)]
@ -317,8 +376,16 @@ impl PartialOrd for Permission {
} }
} }
impl AsRef<Permission> for Permission {
#[inline]
fn as_ref(&self) -> &Permission {
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String")]
#[serde(into = "String")]
pub enum PermRule { pub enum PermRule {
/// The permission is precise, /// The permission is precise,
/// ///
@ -364,6 +431,46 @@ impl fmt::Display for PermRule {
} }
} }
impl Into<String> for PermRule {
fn into(self) -> String {
match self {
PermRule::Base(perm) => perm.into_string(),
PermRule::Children(mut perm) => {
perm.push(Permission::new("+"));
perm.into_string()
},
PermRule::Subtree(mut perm) => {
perm.push(Permission::new("+"));
perm.into_string()
}
}
}
}
impl TryFrom<String> for PermRule {
type Error = &'static str;
fn try_from(mut input: String) -> std::result::Result<Self, Self::Error> {
// Check out specifically the last two chars
let len = input.len();
if len <= 2 {
Err("Input string for PermRule is too short")
} else {
match &input[len-2..len] {
".+" => {
input.truncate(len-2);
Ok(PermRule::Children(PermissionBuf::from_string(input)))
},
".*" => {
input.truncate(len-2);
Ok(PermRule::Subtree(PermissionBuf::from_string(input)))
},
_ => Ok(PermRule::Base(PermissionBuf::from_string(input))),
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -390,4 +497,35 @@ mod tests {
assert!(rule.match_perm(&perm)); assert!(rule.match_perm(&perm));
} }
#[test]
fn load_examples_roles_test() {
let roles = Role::load_file("examples/roles.toml")
.expect("Couldn't load the example role defs. Does `examples/roles.toml` exist?");
assert!(true)
}
#[test]
fn rules_from_string_test() {
assert_eq!(
PermRule::Base(PermissionBuf::from_string("bffh.perm".to_string())),
PermRule::try_from("bffh.perm".to_string()).unwrap()
);
assert_eq!(
PermRule::Children(PermissionBuf::from_string("bffh.perm".to_string())),
PermRule::try_from("bffh.perm.+".to_string()).unwrap()
);
assert_eq!(
PermRule::Subtree(PermissionBuf::from_string("bffh.perm".to_string())),
PermRule::try_from("bffh.perm.*".to_string()).unwrap()
);
}
#[test]
fn rules_from_string_edgecases_test() {
assert!(PermRule::try_from("*".to_string()).is_err());
assert!(PermRule::try_from("+".to_string()).is_err());
}
} }

View File

@ -1,3 +1,7 @@
use std::path::Path;
use std::collections::HashMap;
use std::fs;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use futures_signals::signal::Signal; use futures_signals::signal::Signal;
@ -19,6 +23,9 @@ use crate::db::machine::{MachineIdentifier, Status, MachineState};
/// machine, checking that the user who wants the machine (de)activated has the required /// machine, checking that the user who wants the machine (de)activated has the required
/// permissions. /// permissions.
pub struct Machine { pub struct Machine {
/// Globally unique machine readable identifier
id: MachineIdentifier,
/// Descriptor of the machine /// Descriptor of the machine
desc: MachineDescription, desc: MachineDescription,
@ -30,8 +37,9 @@ pub struct Machine {
} }
impl Machine { impl Machine {
pub fn new(desc: MachineDescription, perm: access::PermIdentifier) -> Machine { pub fn new(id: MachineIdentifier, desc: MachineDescription, perm: access::PermIdentifier) -> Machine {
Machine { Machine {
id: id,
desc: desc, desc: desc,
state: Mutable::new(MachineState { state: Status::Free}), state: Mutable::new(MachineState { state: Status::Free}),
} }
@ -72,19 +80,70 @@ impl Machine {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
/// A description of a machine /// A description of a machine
/// ///
/// This is the struct that a machine is serialized to/from. /// This is the struct that a machine is serialized to/from.
/// Combining this with the actual state of the system will return a machine /// Combining this with the actual state of the system will return a machine
pub struct MachineDescription { pub struct MachineDescription {
/// The main machine identifier. This must be unique.
id: MachineIdentifier,
/// The name of the machine. Doesn't need to be unique but is what humans will be presented. /// The name of the machine. Doesn't need to be unique but is what humans will be presented.
name: String, name: String,
/// An optional description of the Machine. /// An optional description of the Machine.
description: Option<String>, description: Option<String>,
/// The permission required /// The permission required
#[serde(flatten)]
privs: access::PrivilegesBuf, privs: access::PrivilegesBuf,
} }
impl MachineDescription {
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<MachineIdentifier, MachineDescription>> {
let content = fs::read(path)?;
Ok(toml::from_slice(&content[..])?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::iter::FromIterator;
use crate::db::access::{PermissionBuf, PrivilegesBuf};
#[test]
fn load_examples_descriptions_test() {
let machines = MachineDescription::load_file("examples/machines.toml")
.expect("Couldn't load the example machine defs. Does `examples/machines.toml` exist?");
let expected: HashMap<MachineIdentifier, MachineDescription>
= HashMap::from_iter(vec![
(Uuid::parse_str("e5408099-d3e5-440b-a92b-3aabf7683d6b").unwrap(),
MachineDescription {
name: "Somemachine".to_string(),
description: None,
privs: PrivilegesBuf {
disclose: PermissionBuf::from_string("lab.some.disclose".to_string()),
read: PermissionBuf::from_string("lab.some.read".to_string()),
write: PermissionBuf::from_string("lab.some.write".to_string()),
manage: PermissionBuf::from_string("lab.some.admin".to_string()),
},
}),
(Uuid::parse_str("eaabebae-34d1-4a3a-912a-967b495d3d6e").unwrap(),
MachineDescription {
name: "Testmachine".to_string(),
description: Some("An optional description".to_string()),
privs: PrivilegesBuf {
disclose: PermissionBuf::from_string("lab.test.read".to_string()),
read: PermissionBuf::from_string("lab.test.read".to_string()),
write: PermissionBuf::from_string("lab.test.write".to_string()),
manage: PermissionBuf::from_string("lab.test.admin".to_string()),
},
}),
].into_iter());
for u in ["e5408099-d3e5-440b-a92b-3aabf7683d6b", "eaabebae-34d1-4a3a-912a-967b495d3d6e"].iter() {
let uuid = Uuid::parse_str(u).unwrap();
assert_eq!(machines[&uuid], expected[&uuid]);
}
}
}

View File

@ -143,7 +143,6 @@ fn main() -> Result<(), Error> {
let env = Arc::new(env); let env = Arc::new(env);
let mdb = db::machine::init(log.new(o!("system" => "machines")), &config, env.clone()); let mdb = db::machine::init(log.new(o!("system" => "machines")), &config, env.clone());
let pdb = db::access::init(log.new(o!("system" => "permissions")), &config, env.clone()); let pdb = db::access::init(log.new(o!("system" => "permissions")), &config, env.clone());
let authentication_f = api::auth::init(log.new(o!("system" => "authentication")), config.clone());
// If --load or --dump is given we can stop at this point and load/dump the database and then // If --load or --dump is given we can stop at this point and load/dump the database and then
// exit. // exit.