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,168 +50,151 @@ 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
.ok_or(SessionError::no_property::<Password>())?; .get_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 { let mut out = Cursor::new(Vec::new());
kind: capnp::ErrorKind::Failed, match session.step(Some(data), &mut out) {
description: format!("SASL Failure: {}", e), Ok(Step::Done(data)) => {
}) self.state = State::Finished;
},
};
let mechvec: Vec<&str> = mechs.iter().collect(); let uid = pry!(session.get_property::<AuthId>().ok_or(capnp::Error::failed(
"Authentication didn't provide an authid as required".to_string()
)));
let user = self.userdb.get_user(uid.as_str()).unwrap()
.expect("Just auth'ed user was not found?!");
let mut res_mechs = res.get().init_mechs(mechvec.len() as u32); let mut builder = builder.init_successful();
for (i, m) in mechvec.into_iter().enumerate() { if data.is_some() {
res_mechs.set(i as u32, m); builder.set_additional_data(out.into_inner().as_slice());
}*/ }
// For now, only PLAIN
let mut res_mechs = res.get().init_mechs(1); let mut builder = builder.init_session();
res_mechs.set(0, "PLAIN"); let perms = pry!(self.access.collect_permrules(&user.data)
.map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e))));
let session = Rc::new(RefCell::new(Some(Session::new(
self.log.clone(),
user.id,
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(),
)));
}
Ok(Step::NeedsMore(_)) => {
self.state = State::Aborted;
self.build_error(builder);
}
Err(_) => {
self.state = State::Aborted;
self.build_error(builder);
}
}
} else {
self.build_error(builder);
}
Promise::ok(()) Promise::ok(())
} }
// TODO: return Outcome instead of exceptions fn abort(
fn start(&mut self, &mut self,
params: authentication_system::StartParams, _: authentication::AbortParams,
mut res: authentication_system::StartResults _: authentication::AbortResults,
) -> Promise<(), capnp::Error> { ) -> Promise<(), capnp::Error> {
let req = pry!(pry!(params.get()).get_request()); self.state = State::Aborted;
Promise::ok(())
// 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());
// If the client has provided initial data go use that
use request::initial_response::Which;
let step_res = match req.get_initial_response().which() {
Err(capnp::NotInSchema(_)) =>
return Promise::err(capnp::Error {
kind: capnp::ErrorKind::Failed,
description: "Initial data is badly formatted".to_string(),
}),
Ok(Which::None(_)) => {
// FIXME: Actually this needs to indicate NO data instead of SOME data of 0 length
session.step(Option::<&[u8]>::None, &mut out)
}
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 perms = pry!(self.access.collect_permrules(&user.data)
.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();
outcome.reborrow().set_result(response::Result::Successful);
if b.is_some() {
outcome.init_additional_data().set_additional(&out.get_ref());
}
Promise::ok(())
},
Ok(Step::NeedsMore(b)) => {
if b.is_some() {
pry!(res.get().get_response()).set_challence(&out.get_ref());
}
Promise::ok(())
}
Err(e) => {
let mut outcome = pry!(res.get().get_response()).init_outcome();
outcome.reborrow().set_result(response::Result::InvalidCredentials);
let text = format!("{}", e);
outcome.set_help_text(&text);
Promise::ok(())
}
}
} }
} }
@ -262,11 +248,11 @@ pub struct AuthenticationData {
// Authentication has two parts: Granting the authentication itself and then performing the // Authentication has two parts: Granting the authentication itself and then performing the
// authentication. // authentication.
// Granting the authentication checks if // Granting the authentication checks if
// a) the given authcid fits with the given (authMethod, kvs). In general a failure here indicates // a) the given authcid fits with the given (authMethod, kvs). In general a failure here indicates
// a programming failure — the authcid come from the same source as that tuple // a programming failure — the authcid come from the same source as that tuple
// 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)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AuthError { pub enum AuthError {
@ -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,