chiark / gitweb /
sshkeys: Add AccountScope and AuthState variants
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 30 May 2021 13:13:01 +0000 (14:13 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 30 May 2021 13:14:05 +0000 (14:14 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
daemon/cmdlistener.rs
src/accounts.rs
src/commands.rs
src/config.rs

index e7a712ef65eed8c2845df8424d7bc92dbf976dc4..04d41de31ba9cc690b0aa68842548d798c5da66e 100644 (file)
@@ -55,6 +55,8 @@ type Euid = Result<Uid, ConnectionEuidDiscoverError>;
 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<W>(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<W>(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<AccountScope> {
   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;
 
index 4a40a06ac265629441bd26211ca382153ff38dc2..25708d78825dc8b20205def28dd5e62ebf320f10 100644 (file)
@@ -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(),
     }
   }
 }
index a19bf5fc1c5bae0e26457b77f4c6eafb7b9b4a77..18f69ec50d2985a6c39182b555d5ce8717009701 100644 (file)
@@ -8,6 +8,7 @@ use crate::prelude::*;
 pub enum MgmtCommand {
   Noop,
   SetSuperuser(bool),
+  SetRestrictedSshScope { id: sshkeys::Id, nonce: sshkeys::Nonce },
 
   CreateAccount(AccountDetails),
   UpdateAccount(AccountDetails),
index 29784ca22a432b1dc012ed819fae19ecc0480eff..b544407b348d7144f593d4015bc3d2fb2fe1692e 100644 (file)
@@ -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<String>,
   pub sendmail: Option<String>,
   pub ssh_proxy_bin: Option<String>,
+  pub ssh_proxy_user: Option<String>,
   pub authorized_keys: Option<String>,
   pub authorized_keys_include: Option<String>,
   pub debug_js_inject_file: Option<String>,
@@ -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<String>,
@@ -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 {