From: Ian Jackson Date: Sun, 30 May 2021 13:13:01 +0000 (+0100) Subject: sshkeys: Add AccountScope and AuthState variants X-Git-Tag: otter-0.7.0~212 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=646ae0bf7d573fc9bb9573b49f60507b8eee7918;p=otter.git sshkeys: Add AccountScope and AuthState variants Signed-off-by: Ian Jackson --- diff --git a/daemon/cmdlistener.rs b/daemon/cmdlistener.rs index e7a712ef..04d41de3 100644 --- a/daemon/cmdlistener.rs +++ b/daemon/cmdlistener.rs @@ -55,6 +55,8 @@ type Euid = Result; enum AuthState { None { euid: Euid }, Superuser { euid: Euid, auth: AuthorisationSuperuser }, + Ssh { id: sshkeys::Id, nonce: sshkeys::Nonce, + auth: Authorisation<(sshkeys::Id, sshkeys::Nonce)>, }, } #[derive(Debug,Clone)] @@ -141,6 +143,7 @@ fn execute_and_respond(cs: &mut CommandStreamData, cmd: MgmtCommand, let preserve_euid = match &cs.authstate { AuthState::None { euid, .. } => euid, AuthState::Superuser { euid, .. } => euid, + AuthState::Ssh { .. } => throw!(ME::AuthorisationError), }.clone(); if !enable { @@ -153,6 +156,14 @@ fn execute_and_respond(cs: &mut CommandStreamData, cmd: MgmtCommand, } Fine }, + MC::SetRestrictedSshScope { id, nonce } => { + let good_uid = Some(config().ssh_proxy_uid); + let auth = cs.authorised_uid(good_uid, Some("SetRestrictedScope")) + .map_err(|_| ME::AuthorisationError)?; + let auth = auth.therefore_ok(); + cs.authstate = AuthState::Ssh { id, nonce, auth }; + Fine + }, MC::CreateAccount(AccountDetails { account, nick, timezone, access, layout @@ -1527,6 +1538,8 @@ impl CommandStreamData<'_> { let &client_euid = match &self.authstate { AuthState::Superuser { euid, .. } => euid, AuthState::None { euid, .. } => euid, + AuthState::Ssh { .. } => throw!(anyhow!( + "{}: cannot authorise by uid as ,now in AuthState::Ssh")), }.as_ref().map_err(|e| e.clone())?; let server_uid = Uid::current(); if client_euid.is_root() || @@ -1727,11 +1740,27 @@ fn authorise_scope_direct(cs: &CommandStreamData, ag: &AccountsGuard, } #[throws(AuthorisationError)] -fn do_authorise_scope(cs: &CommandStreamData, _ag: &AccountsGuard, +fn do_authorise_scope(cs: &CommandStreamData, ag: &AccountsGuard, wanted: &AccountScope) -> Authorisation { match &cs.authstate { &AuthState::Superuser { auth, .. } => return auth.into(), + + &AuthState::Ssh { id: sshkey_id, ref nonce, auth } => { + let wanted_base_account = AccountName { + scope: wanted.clone(), + subaccount: default(), + }; + if_chain!{ + if let Ok::<_,AccountNotFound> + ((record, _acctid)) = ag.lookup(&wanted_base_account); + if let + Some(auth) = record.ssh_keys.check(ag, sshkey_id, &nonce, auth); + then { return Ok(auth) } + else { throw!(AuthorisationError("ssh key not authorised".into())); } + } + }, + _ => {}, } @@ -1744,6 +1773,13 @@ fn do_authorise_scope(cs: &CommandStreamData, _ag: &AccountsGuard, y.therefore_ok() } + AccountScope::Ssh{..} => { + // Should have been dealt with earlier, when we checked authstate. + throw!(AuthorisationError( + "account must be accessed via ssh proxy" + .into())); + } + AccountScope::Unix { user: wanted } => { struct InUserList; diff --git a/src/accounts.rs b/src/accounts.rs index 4a40a06a..25708d78 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -22,6 +22,7 @@ slotmap::new_key_type!{ pub enum AccountScope { Server, Unix { user: String }, + Ssh { user: String }, } @@ -150,6 +151,11 @@ impl AccountScope { f(":")?; f(user)?; } + AS::Ssh { user } => { + f("ssh")?; + f(":")?; + pct(user, &mut f)?; + } }; for n in ns { f(":")?; @@ -204,6 +210,7 @@ impl AccountName { match &self.scope { AS::Server => "*SERVER*".into(), AS::Unix { user } => user.clone(), + AS::Ssh { user } => user.clone(), } } } diff --git a/src/commands.rs b/src/commands.rs index a19bf5fc..18f69ec5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -8,6 +8,7 @@ use crate::prelude::*; pub enum MgmtCommand { Noop, SetSuperuser(bool), + SetRestrictedSshScope { id: sshkeys::Id, nonce: sshkeys::Nonce }, CreateAccount(AccountDetails), UpdateAccount(AccountDetails), diff --git a/src/config.rs b/src/config.rs index 29784ca2..b544407b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,7 @@ // There is NO WARRANTY. use crate::prelude::*; +use pwd::Passwd; pub const EXIT_SPACE : i32 = 2; pub const EXIT_NOTFOUND : i32 = 4; @@ -39,6 +40,7 @@ pub struct ServerConfigSpec { pub specs_dir: Option, pub sendmail: Option, pub ssh_proxy_bin: Option, + pub ssh_proxy_user: Option, pub authorized_keys: Option, pub authorized_keys_include: Option, pub debug_js_inject_file: Option, @@ -72,6 +74,7 @@ pub struct ServerConfig { pub specs_dir: String, pub sendmail: String, pub ssh_proxy_bin: String, + pub ssh_proxy_uid: Uid, pub authorized_keys: String, pub authorized_keys_include: String, pub debug_js_inject: Arc, @@ -126,7 +129,7 @@ impl ServerConfigSpec { template_dir, specs_dir, nwtemplate_dir, wasm_dir, libexec_dir, usvg_bin, log, bundled_sources, shapelibs, sendmail, debug_js_inject_file, check_bundled_sources, fake_rng, - ssh_proxy_bin, authorized_keys, authorized_keys_include, + ssh_proxy_bin, ssh_proxy_user, authorized_keys, authorized_keys_include, } = self; let game_rng = fake_rng.make_game_rng(); @@ -172,6 +175,22 @@ impl ServerConfigSpec { || format!("{}.static", authorized_keys) ); + let ssh_proxy_uid = match ssh_proxy_user { + None => Uid::current(), + Some(spec) => Uid::from_raw(if let Ok(num) = spec.parse() { + num + } else { + let pwent = (|| Ok::<_,AE>({ + Passwd::from_name(&spec) + .map_err(|e| anyhow!("lookup failed: {}", e))? + .ok_or_else(|| anyhow!("does not exist"))? + }))() + .with_context(|| spec.clone()) + .context("ssh_proxy_uidr")?; + pwent.uid + }) + }; + let shapelibs = shapelibs.unwrap_or_else(||{ let glob = defpath(None, DEFAULT_LIBRARY_GLOB); vec![ shapelib::Config1::PathGlob(glob) ] @@ -253,7 +272,7 @@ impl ServerConfigSpec { template_dir, specs_dir, nwtemplate_dir, wasm_dir, libexec_dir, bundled_sources, shapelibs, sendmail, usvg_bin, debug_js_inject, check_bundled_sources, game_rng, prctx, - ssh_proxy_bin, authorized_keys, authorized_keys_include, + ssh_proxy_bin, ssh_proxy_uid, authorized_keys, authorized_keys_include, }; trace_dbg!("config resolved", &server); Ok(WholeServerConfig {