diff --git a/schema b/schema index 18ed9c2..c9283eb 160000 --- a/schema +++ b/schema @@ -1 +1 @@ -Subproject commit 18ed9c2ae6a221f57d19e255165c7ebc4508e9af +Subproject commit c9283ebd696ed6dd428a7c3d24820889f7ab4bf3 diff --git a/src/api.rs b/src/api.rs index 0918dde..02bacea 100644 --- a/src/api.rs +++ b/src/api.rs @@ -7,6 +7,9 @@ use slog::Logger; use std::sync::Arc; use capnp::capability::{Promise}; +use rsasl::mechname::Mechname; +use rsasl::SASL; +use auth::State; use crate::schema::connection_capnp; use crate::connection::Session; @@ -31,99 +34,85 @@ pub struct Bootstrap { db: Databases, nw: Arc, - - session: Rc>>, + ctx: SASL, } impl Bootstrap { pub fn new(log: Logger, db: Databases, nw: Arc) -> Self { info!(log, "Created Bootstrap"); - let session = Rc::new(RefCell::new(None)); - Self { session, db, nw, log } + let mut ctx = SASL::new(); + 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::bootstrap::*; +use crate::api::auth::Auth; use crate::RELEASE; 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( &mut self, _: GetAPIVersionParams, - mut results: GetAPIVersionResults - ) -> 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); + _: GetAPIVersionResults, + ) -> Promise<(), ::capnp::Error> { Promise::ok(()) } fn get_server_release( &mut self, _: GetServerReleaseParams, - mut results: GetServerReleaseResults - ) -> Promise<(), capnp::Error> { - let mut builder = results.get(); - builder.set_name("bffh"); - builder.set_release(RELEASE); + mut result: GetServerReleaseResults, + ) -> Promise<(), ::capnp::Error> { + let mut builder = result.get(); + builder.set_name("bffhd"); + 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(()) } } diff --git a/src/api/auth.rs b/src/api/auth.rs index 662d3d5..7d518b0 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -3,32 +3,35 @@ //! Authorization is over in `access.rs` //! Authentication using SASL -use std::sync::Arc; -use std::rc::Rc; use std::cell::RefCell; use std::io::Cursor; - +use std::rc::Rc; +use std::sync::Arc; 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::error::SessionError; use rsasl::mechname::Mechname; use rsasl::property::{AuthId, Password}; -use rsasl::SASL; +use rsasl::session::Session as RsaslSession; 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; -pub use crate::schema::authenticationsystem_capnp as auth_system; 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::user::{Internal as UserDB, User, UserId}; +use crate::network::Network; pub struct AppData { userdb: Arc, @@ -37,7 +40,7 @@ pub struct SessionData { authz: Option, } -struct CB { +pub struct CB { userdb: Arc, } impl CB { @@ -47,168 +50,151 @@ impl 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 { validations::SIMPLE => { - let authid = session .get_property::() .ok_or(SessionError::no_property::())?; - let pass = session.get_property::() - .ok_or(SessionError::no_property::())?; + let pass = session + .get_property::() + .ok_or(SessionError::no_property::())?; - if self.userdb.login(authid.as_ref(), pass.as_bytes()).unwrap().is_some() { - return Ok(()) + if self + .userdb + .login(authid.as_ref(), pass.as_bytes()) + .unwrap() + .is_some() + { + return Ok(()); } SessionError::AuthenticationFailure } - _ => { - SessionError::no_validate(validation) - } + _ => SessionError::no_validate(validation), }; Err(ret) } } +pub enum State { + InvalidMechanism, + Finished, + Aborted, + Running(RsaslSession), +} + pub struct Auth { - pub ctx: SASL, - session: Rc>>, userdb: Arc, access: Arc, + state: State, log: Logger, + network: Arc, } impl Auth { - pub fn new(log: Logger, dbs: Databases, session: Rc>>) -> Self { - let mut ctx = SASL::new(); - ctx.install_callback(Arc::new(CB::new(dbs.userdb.clone()))); + pub fn new(log: Logger, dbs: Databases, state: State, network: Arc) -> Self { + Self { + 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::*; -impl authentication_system::Server for Auth { - fn mechanisms(&mut self, - _: authentication_system::MechanismsParams, - mut res: authentication_system::MechanismsResults +impl authentication::Server for Auth { + fn step( + &mut self, + params: authentication::StepParams, + mut results: authentication::StepResults, ) -> Promise<(), capnp::Error> { - /*let mechs = match self.ctx.server_mech_list() { - Ok(m) => m, - Err(e) => { - return Promise::err(capnp::Error { - kind: capnp::ErrorKind::Failed, - description: format!("SASL Failure: {}", e), - }) - }, - }; + let mut builder = results.get(); + if let State::Running(mut session) = std::mem::replace(&mut self.state, State::Aborted) { + let data: &[u8] = pry!(pry!(params.get()).get_data()); + let mut out = Cursor::new(Vec::new()); + match session.step(Some(data), &mut out) { + Ok(Step::Done(data)) => { + self.state = State::Finished; - let mechvec: Vec<&str> = mechs.iter().collect(); + let uid = pry!(session.get_property::().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); - 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"); + let mut builder = builder.init_successful(); + if data.is_some() { + builder.set_additional_data(out.into_inner().as_slice()); + } + + let mut builder = builder.init_session(); + 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(()) } - // TODO: return Outcome instead of exceptions - fn start(&mut self, - params: authentication_system::StartParams, - mut res: authentication_system::StartResults + fn abort( + &mut self, + _: authentication::AbortParams, + _: authentication::AbortResults, ) -> 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()); - - // 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::() - .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(()) - } - } + self.state = State::Aborted; + Promise::ok(()) } } @@ -262,11 +248,11 @@ pub struct AuthenticationData { // Authentication has two parts: Granting the authentication itself and then performing the // 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 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 -// 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 { @@ -278,5 +264,4 @@ pub enum AuthError { MalformedAuthzid, /// User may not use that authorization id NotAllowedAuthzid, - } diff --git a/src/api/machine.rs b/src/api/machine.rs index c2774b3..d2e1027 100644 --- a/src/api/machine.rs +++ b/src/api/machine.rs @@ -27,59 +27,6 @@ impl 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( &mut self, _: 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 { fn force_free(&mut self, _: manage::ForceFreeParams, @@ -229,6 +170,60 @@ impl manage::Server for Machine { }; 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 { @@ -244,6 +239,9 @@ impl admin::Server for Machine { APIMState::InUse => MachineState::used(Some(uid)), APIMState::Reserved => MachineState::reserved(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 f = async move { diff --git a/src/api/machines.rs b/src/api/machines.rs index 03c0be1..6635902 100644 --- a/src/api/machines.rs +++ b/src/api/machines.rs @@ -125,7 +125,7 @@ impl machines::Server for Machines { let permissions = &session.as_ref().unwrap().perms; if let Some(machine) = network.machines.get(&id) { - let mut builder = results.get().init_machine(); + let mut builder = results.get(); fill_machine_builder( &mut builder, &machine, @@ -171,7 +171,7 @@ impl machines::Server for Machines { let permissions = &session.as_ref().unwrap().perms; if let Some(machine) = network.machines.get(&id) { - let mut builder = results.get().init_machine(); + let mut builder = results.get(); fill_machine_builder( &mut builder, &machine,