Update API

This commit is contained in:
Nadja Reitzenstein 2022-03-13 23:31:00 +01:00
parent 0531156b9e
commit 15f31ffd7c
5 changed files with 244 additions and 272 deletions

2
schema

@ -1 +1 @@
Subproject commit 18ed9c2ae6a221f57d19e255165c7ebc4508e9af Subproject commit c9283ebd696ed6dd428a7c3d24820889f7ab4bf3

View File

@ -7,6 +7,9 @@ use slog::Logger;
use std::sync::Arc; use std::sync::Arc;
use capnp::capability::{Promise}; use capnp::capability::{Promise};
use rsasl::mechname::Mechname;
use rsasl::SASL;
use auth::State;
use crate::schema::connection_capnp; use crate::schema::connection_capnp;
use crate::connection::Session; use crate::connection::Session;
@ -31,99 +34,85 @@ pub struct Bootstrap {
db: Databases, db: Databases,
nw: Arc<Network>, nw: Arc<Network>,
ctx: SASL,
session: Rc<RefCell<Option<Session>>>,
} }
impl Bootstrap { impl Bootstrap {
pub fn new(log: Logger, db: Databases, nw: Arc<Network>) -> Self { pub fn new(log: Logger, db: Databases, nw: Arc<Network>) -> Self {
info!(log, "Created Bootstrap"); info!(log, "Created Bootstrap");
let session = Rc::new(RefCell::new(None)); let mut ctx = SASL::new();
Self { session, db, nw, log } ctx.install_callback(Arc::new(auth::CB::new(db.userdb.clone())));
Self { db, nw, log, ctx }
} }
} }
use connection_capnp::{API_VERSION_MAJOR, API_VERSION_MINOR, API_VERSION_PATCH}; use connection_capnp::{API_VERSION_MAJOR, API_VERSION_MINOR, API_VERSION_PATCH};
use connection_capnp::bootstrap::*; use connection_capnp::bootstrap::*;
use crate::api::auth::Auth;
use crate::RELEASE; use crate::RELEASE;
impl connection_capnp::bootstrap::Server for Bootstrap { impl connection_capnp::bootstrap::Server for Bootstrap {
fn authentication_system(&mut self,
_: AuthenticationSystemParams,
mut res: AuthenticationSystemResults
) -> Promise<(), capnp::Error> {
// TODO: Forbid mutltiple authentication for now
// 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)?
// If this Rc has a strong count of 1 then there's no other cap issued yet meaning we can
// safely transform the inner session with an auth.
if Rc::strong_count(&self.session) == 1 {
let session = Rc::clone(&self.session);
let db = self.db.clone();
res.get().set_authentication_system(capnp_rpc::new_client(
auth::Auth::new(self.log.new(o!()), db, session))
);
}
Promise::ok(())
}
fn machine_system(&mut self,
_: MachineSystemParams,
mut res: MachineSystemResults
) -> Promise<(), capnp::Error> {
if let Some(session) = self.session.borrow().deref() {
debug!(self.log, "Giving MachineSystem cap to user {} with perms:", session.authzid);
for r in session.perms.iter() {
debug!(session.log, " {}", r);
}
// TODO actual permission check and stuff
// Right now we only check that the user has authenticated at all.
let c = capnp_rpc::new_client(Machines::new(Rc::clone(&self.session), self.nw.clone()));
res.get().set_machine_system(c);
}
Promise::ok(())
}
fn user_system(
&mut self,
_: UserSystemParams,
mut results: UserSystemResults
) -> Promise<(), capnp::Error> {
if self.session.borrow().is_some() {
// TODO actual permission check and stuff
// Right now we only check that the user has authenticated at all.
let c = capnp_rpc::new_client(Users::new(Rc::clone(&self.session), self.db.userdb.clone()));
results.get().set_user_system(c);
}
Promise::ok(())
}
fn get_a_p_i_version( fn get_a_p_i_version(
&mut self, &mut self,
_: GetAPIVersionParams, _: GetAPIVersionParams,
mut results: GetAPIVersionResults _: GetAPIVersionResults,
) -> Promise<(), capnp::Error> { ) -> Promise<(), ::capnp::Error> {
let builder = results.get();
let mut builder = builder.init_version();
builder.set_major(API_VERSION_MAJOR);
builder.set_minor(API_VERSION_MINOR);
builder.set_patch(API_VERSION_PATCH);
Promise::ok(()) Promise::ok(())
} }
fn get_server_release( fn get_server_release(
&mut self, &mut self,
_: GetServerReleaseParams, _: GetServerReleaseParams,
mut results: GetServerReleaseResults mut result: GetServerReleaseResults,
) -> Promise<(), capnp::Error> { ) -> Promise<(), ::capnp::Error> {
let mut builder = results.get(); let mut builder = result.get();
builder.set_name("bffh"); builder.set_name("bffhd");
builder.set_release(RELEASE); builder.set_release(crate::RELEASE);
Promise::ok(())
}
fn mechanisms(
&mut self,
_: MechanismsParams,
mut result: MechanismsResults,
) -> Promise<(), ::capnp::Error> {
let mut builder = result.get();
let mechs: Vec<_> = self.ctx.server_mech_list()
.into_iter()
.map(|m| m.mechanism.as_str())
.collect();
let mut mechbuilder = builder.init_mechs(mechs.len() as u32);
for (i,m) in mechs.iter().enumerate() {
mechbuilder.set(i as u32, m);
}
Promise::ok(())
}
fn create_session(
&mut self,
params: CreateSessionParams,
mut result: CreateSessionResults,
) -> Promise<(), ::capnp::Error> {
let params = pry!(params.get());
let mechanism: &str = pry!(params.get_mechanism());
let mechname = mechanism.as_bytes();
let state = if let Ok(mechname) = Mechname::new(mechname) {
if let Ok(session) = self.ctx.server_start(mechname) {
State::Running(session)
} else {
State::Aborted
}
} else {
State::InvalidMechanism
};
let auth = Auth::new(self.log.clone(), self.db.clone(), state, self.nw.clone());
let mut builder = result.get();
builder.set_authentication(capnp_rpc::new_client(auth));
Promise::ok(()) Promise::ok(())
} }
} }

View File

@ -3,32 +3,35 @@
//! Authorization is over in `access.rs` //! Authorization is over in `access.rs`
//! Authentication using SASL //! Authentication using SASL
use std::sync::Arc;
use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use std::io::Cursor; use std::io::Cursor;
use std::rc::Rc;
use std::sync::Arc;
use slog::Logger; use slog::Logger;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use capnp::capability::{Promise}; use crate::api::machines::Machines;
use capnp::capability::Promise;
use rsasl::callback::Callback; use rsasl::callback::Callback;
use rsasl::error::SessionError; use rsasl::error::SessionError;
use rsasl::mechname::Mechname; use rsasl::mechname::Mechname;
use rsasl::property::{AuthId, Password}; use rsasl::property::{AuthId, Password};
use rsasl::SASL; use rsasl::session::Session as RsaslSession;
use rsasl::session::Step; use rsasl::session::Step;
use rsasl::validate::{Validation, validations}; use rsasl::validate::{validations, Validation};
use rsasl::SASL;
use crate::api::users::Users;
use crate::api::Session; use crate::api::Session;
pub use crate::schema::authenticationsystem_capnp as auth_system;
use crate::db::Databases; use crate::db::Databases;
pub use crate::schema::authenticationsystem_capnp as auth_system;
use crate::db::user::{Internal as UserDB, User};
use crate::db::access::AccessControl as AccessDB; use crate::db::access::AccessControl as AccessDB;
use crate::db::user::{Internal as UserDB, User, UserId};
use crate::network::Network;
pub struct AppData { pub struct AppData {
userdb: Arc<UserDB>, userdb: Arc<UserDB>,
@ -37,7 +40,7 @@ pub struct SessionData {
authz: Option<User>, authz: Option<User>,
} }
struct CB { pub struct CB {
userdb: Arc<UserDB>, userdb: Arc<UserDB>,
} }
impl CB { impl CB {
@ -47,170 +50,153 @@ impl CB {
} }
impl Callback for CB { impl Callback for CB {
fn validate(&self, session: &mut rsasl::session::SessionData, validation: Validation, _mechanism: &Mechname) -> Result<(), SessionError> { fn validate(
&self,
session: &mut rsasl::session::SessionData,
validation: Validation,
_mechanism: &Mechname,
) -> Result<(), SessionError> {
let ret = match validation { let ret = match validation {
validations::SIMPLE => { validations::SIMPLE => {
let authid = session let authid = session
.get_property::<AuthId>() .get_property::<AuthId>()
.ok_or(SessionError::no_property::<AuthId>())?; .ok_or(SessionError::no_property::<AuthId>())?;
let pass = session.get_property::<Password>() let pass = session
.get_property::<Password>()
.ok_or(SessionError::no_property::<Password>())?; .ok_or(SessionError::no_property::<Password>())?;
if self.userdb.login(authid.as_ref(), pass.as_bytes()).unwrap().is_some() { if self
return Ok(()) .userdb
.login(authid.as_ref(), pass.as_bytes())
.unwrap()
.is_some()
{
return Ok(());
} }
SessionError::AuthenticationFailure SessionError::AuthenticationFailure
} }
_ => { _ => SessionError::no_validate(validation),
SessionError::no_validate(validation)
}
}; };
Err(ret) Err(ret)
} }
} }
pub enum State {
InvalidMechanism,
Finished,
Aborted,
Running(RsaslSession),
}
pub struct Auth { pub struct Auth {
pub ctx: SASL,
session: Rc<RefCell<Option<Session>>>,
userdb: Arc<UserDB>, userdb: Arc<UserDB>,
access: Arc<AccessDB>, access: Arc<AccessDB>,
state: State,
log: Logger, log: Logger,
network: Arc<Network>,
} }
impl Auth { impl Auth {
pub fn new(log: Logger, dbs: Databases, session: Rc<RefCell<Option<Session>>>) -> Self { pub fn new(log: Logger, dbs: Databases, state: State, network: Arc<Network>) -> Self {
let mut ctx = SASL::new(); Self {
ctx.install_callback(Arc::new(CB::new(dbs.userdb.clone()))); log,
userdb: dbs.userdb.clone(),
access: dbs.access.clone(),
state,
network,
}
}
Self { log, ctx, session, userdb: dbs.userdb.clone(), access: dbs.access.clone() } fn build_error(&self, response: response::Builder) {
use crate::schema::authenticationsystem_capnp::response::Error as ErrorCode;
if let State::Running(_) = self.state {
return;
}
let mut builder = response.init_failed();
match self.state {
State::InvalidMechanism => builder.set_code(ErrorCode::BadMechanism),
State::Finished => builder.set_code(ErrorCode::Aborted),
State::Aborted => builder.set_code(ErrorCode::Aborted),
_ => unreachable!(),
}
} }
} }
use crate::schema::authenticationsystem_capnp::*; use crate::schema::authenticationsystem_capnp::*;
impl authentication_system::Server for Auth { impl authentication::Server for Auth {
fn mechanisms(&mut self, fn step(
_: authentication_system::MechanismsParams, &mut self,
mut res: authentication_system::MechanismsResults params: authentication::StepParams,
mut results: authentication::StepResults,
) -> Promise<(), capnp::Error> { ) -> Promise<(), capnp::Error> {
/*let mechs = match self.ctx.server_mech_list() { let mut builder = results.get();
Ok(m) => m, if let State::Running(mut session) = std::mem::replace(&mut self.state, State::Aborted) {
Err(e) => { let data: &[u8] = pry!(pry!(params.get()).get_data());
return Promise::err(capnp::Error {
kind: capnp::ErrorKind::Failed,
description: format!("SASL Failure: {}", e),
})
},
};
let mechvec: Vec<&str> = mechs.iter().collect();
let mut res_mechs = res.get().init_mechs(mechvec.len() as u32);
for (i, m) in mechvec.into_iter().enumerate() {
res_mechs.set(i as u32, m);
}*/
// For now, only PLAIN
let mut res_mechs = res.get().init_mechs(1);
res_mechs.set(0, "PLAIN");
Promise::ok(())
}
// TODO: return Outcome instead of exceptions
fn start(&mut self,
params: authentication_system::StartParams,
mut res: authentication_system::StartResults
) -> Promise<(), capnp::Error> {
let req = pry!(pry!(params.get()).get_request());
// Extract the MECHANISM the client wants to use and start a session.
// Or fail at that and thrown an exception TODO: return Outcome
let mech = pry!(req.get_mechanism());
if pry!(req.get_mechanism()) != "PLAIN" {
return Promise::err(capnp::Error {
kind: capnp::ErrorKind::Failed,
description: format!("Invalid SASL mech"),
})
}
let mech = Mechname::new(mech.as_bytes()).unwrap();
let mut session = match self.ctx.server_start(mech) {
Ok(s) => s,
Err(e) =>
return Promise::err(capnp::Error {
kind: capnp::ErrorKind::Failed,
description: format!("SASL error: {}", e),
}),
};
let mut out = Cursor::new(Vec::new()); let mut out = Cursor::new(Vec::new());
match session.step(Some(data), &mut out) {
Ok(Step::Done(data)) => {
self.state = State::Finished;
// If the client has provided initial data go use that let uid = pry!(session.get_property::<AuthId>().ok_or(capnp::Error::failed(
use request::initial_response::Which; "Authentication didn't provide an authid as required".to_string()
let step_res = match req.get_initial_response().which() { )));
Err(capnp::NotInSchema(_)) => let user = self.userdb.get_user(uid.as_str()).unwrap()
return Promise::err(capnp::Error { .expect("Just auth'ed user was not found?!");
kind: capnp::ErrorKind::Failed,
description: "Initial data is badly formatted".to_string(),
}),
Ok(Which::None(_)) => { let mut builder = builder.init_successful();
// FIXME: Actually this needs to indicate NO data instead of SOME data of 0 length if data.is_some() {
session.step(Option::<&[u8]>::None, &mut out) builder.set_additional_data(out.into_inner().as_slice());
} }
Ok(Which::Initial(data)) => {
session.step(Some(pry!(data)), &mut out)
}
};
// The step may either return an error, a success or the need for more data
// TODO: Set the session user. Needs a lookup though <.>
match step_res {
Ok(Step::Done(b)) => {
let user = session
.get_property::<AuthId>()
.and_then(|data| {
self.userdb.get_user(data.as_str()).unwrap()
})
.expect("Authentication returned OK but the given AuthId is invalid");
let mut builder = builder.init_session();
let perms = pry!(self.access.collect_permrules(&user.data) let perms = pry!(self.access.collect_permrules(&user.data)
.map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))); .map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e))));
self.session.replace(Some(Session::new(
self.log.new(o!()),
user.id,
"".to_string(),
user.data.roles.into_boxed_slice(),
perms.into_boxed_slice()
)));
let mut outcome = pry!(res.get().get_response()).init_outcome(); let session = Rc::new(RefCell::new(Some(Session::new(
outcome.reborrow().set_result(response::Result::Successful); self.log.clone(),
if b.is_some() { user.id,
outcome.init_additional_data().set_additional(&out.get_ref()); uid.to_string(),
user.data.roles.into_boxed_slice(),
perms.into_boxed_slice(),
))));
builder.set_machine_system(capnp_rpc::new_client(Machines::new(
session.clone(),
self.network.clone(),
)));
builder.set_user_system(capnp_rpc::new_client(Users::new(
session.clone(),
self.userdb.clone(),
)));
} }
Promise::ok(()) Ok(Step::NeedsMore(_)) => {
}, self.state = State::Aborted;
Ok(Step::NeedsMore(b)) => { self.build_error(builder);
if b.is_some() {
pry!(res.get().get_response()).set_challence(&out.get_ref());
} }
Err(_) => {
self.state = State::Aborted;
self.build_error(builder);
}
}
} else {
self.build_error(builder);
}
Promise::ok(()) Promise::ok(())
} }
Err(e) => {
let mut outcome = pry!(res.get().get_response()).init_outcome(); fn abort(
outcome.reborrow().set_result(response::Result::InvalidCredentials); &mut self,
let text = format!("{}", e); _: authentication::AbortParams,
outcome.set_help_text(&text); _: authentication::AbortResults,
) -> Promise<(), capnp::Error> {
self.state = State::Aborted;
Promise::ok(()) Promise::ok(())
} }
} }
}
}
// 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
@ -278,5 +264,4 @@ pub enum AuthError {
MalformedAuthzid, MalformedAuthzid,
/// User may not use that authorization id /// User may not use that authorization id
NotAllowedAuthzid, NotAllowedAuthzid,
} }

View File

@ -27,59 +27,6 @@ impl Machine {
} }
impl info::Server for Machine { impl info::Server for Machine {
fn get_machine_info_extended(
&mut self,
_: info::GetMachineInfoExtendedParams,
mut results: info::GetMachineInfoExtendedResults,
) -> Promise<(), capnp::Error> {
let machine = self.machine.get_inner();
let perms = self.perms.clone();
let f = async move {
if perms.manage {
let builder = results.get();
let mut extinfo = builder.init_machine_info_extended();
let guard = machine.lock().await;
// "previous" user
if let Some(user) = guard.get_previous() {
let mut previous = extinfo.reborrow().init_transfer_user();
previous.set_username(&user.uid);
}
let state = guard.read_state();
let state_lock = state.lock_ref();
match state_lock.state {
Status::Free => {}
Status::InUse(ref user) => if user.is_some() {
let user = user.as_ref().unwrap();
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
Status::ToCheck(ref user) => {
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
Status::Blocked(ref user) => {
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
Status::Disabled => {}
Status::Reserved(ref user) => {
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
}
}
Ok(())
};
let g = smol::future::race(f, smol::Timer::after(Duration::from_secs(4))
.map(|_| Err(capnp::Error::failed("Waiting for machine lock timed out!".to_string()))));
Promise::from_future(g)
}
fn get_reservation_list( fn get_reservation_list(
&mut self, &mut self,
_: info::GetReservationListParams, _: info::GetReservationListParams,
@ -170,12 +117,6 @@ impl in_use::Server for Machine {
} }
} }
impl transfer::Server for Machine {
}
impl check::Server for Machine {
}
impl manage::Server for Machine { impl manage::Server for Machine {
fn force_free(&mut self, fn force_free(&mut self,
_: manage::ForceFreeParams, _: manage::ForceFreeParams,
@ -229,6 +170,60 @@ impl manage::Server for Machine {
}; };
Promise::from_future(f) Promise::from_future(f)
} }
fn get_machine_info_extended(
&mut self,
_: manage::GetMachineInfoExtendedParams,
mut results: manage::GetMachineInfoExtendedResults,
) -> Promise<(), capnp::Error> {
let machine = self.machine.get_inner();
let perms = self.perms.clone();
let f = async move {
if perms.manage {
let builder = results.get();
let mut extinfo = builder;
let guard = machine.lock().await;
// "previous" user
if let Some(user) = guard.get_previous() {
let mut previous = extinfo.reborrow().init_last_user();
previous.set_username(&user.uid);
}
let state = guard.read_state();
let state_lock = state.lock_ref();
match state_lock.state {
Status::Free => {}
Status::InUse(ref user) => if user.is_some() {
let user = user.as_ref().unwrap();
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
Status::ToCheck(ref user) => {
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
Status::Blocked(ref user) => {
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
Status::Disabled => {}
Status::Reserved(ref user) => {
let mut current = extinfo.init_current_user();
current.set_username(&user.uid);
}
}
}
Ok(())
};
let g = smol::future::race(f, smol::Timer::after(Duration::from_secs(4))
.map(|_| Err(capnp::Error::failed("Waiting for machine lock timed out!".to_string()))));
Promise::from_future(g)
}
} }
impl admin::Server for Machine { impl admin::Server for Machine {
@ -244,6 +239,9 @@ impl admin::Server for Machine {
APIMState::InUse => MachineState::used(Some(uid)), APIMState::InUse => MachineState::used(Some(uid)),
APIMState::Reserved => MachineState::reserved(uid), APIMState::Reserved => MachineState::reserved(uid),
APIMState::ToCheck => MachineState::check(uid), APIMState::ToCheck => MachineState::check(uid),
APIMState::Totakeover => return Promise::err(::capnp::Error::unimplemented(
"totakeover not implemented".to_string(),
)),
}; };
let machine = self.machine.get_inner(); let machine = self.machine.get_inner();
let f = async move { let f = async move {

View File

@ -125,7 +125,7 @@ impl machines::Server for Machines {
let permissions = &session.as_ref().unwrap().perms; let permissions = &session.as_ref().unwrap().perms;
if let Some(machine) = network.machines.get(&id) { if let Some(machine) = network.machines.get(&id) {
let mut builder = results.get().init_machine(); let mut builder = results.get();
fill_machine_builder( fill_machine_builder(
&mut builder, &mut builder,
&machine, &machine,
@ -171,7 +171,7 @@ impl machines::Server for Machines {
let permissions = &session.as_ref().unwrap().perms; let permissions = &session.as_ref().unwrap().perms;
if let Some(machine) = network.machines.get(&id) { if let Some(machine) = network.machines.get(&id) {
let mut builder = results.get().init_machine(); let mut builder = results.get();
fill_machine_builder( fill_machine_builder(
&mut builder, &mut builder,
&machine, &machine,