diff --git a/Cargo.lock b/Cargo.lock index 0b658e5..2f1611f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,9 +1473,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -2057,9 +2057,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2978,9 +2978,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "phf" @@ -3625,9 +3625,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0efcc0f4cd6c062c07e572ce4b806e3967fa029fcbfcc0aa98fb5910a37925" +checksum = "7878311587d0353a854d5be954fbe68bdf6e77873933b484d1e45db12bb2f8cf" dependencies = [ "aes", "aes-gcm", @@ -5162,9 +5162,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -5256,7 +5256,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.7.4" +version = "0.8.0" dependencies = [ "ansi_term", "anyhow", @@ -5292,7 +5292,7 @@ dependencies = [ [[package]] name = "warpgate-admin" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", @@ -5321,7 +5321,7 @@ dependencies = [ [[package]] name = "warpgate-common" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "argon2", @@ -5357,7 +5357,7 @@ dependencies = [ [[package]] name = "warpgate-core" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "argon2", @@ -5397,7 +5397,7 @@ dependencies = [ [[package]] name = "warpgate-database-protocols" -version = "0.7.4" +version = "0.8.0" dependencies = [ "bitflags 1.3.2", "bytes", @@ -5410,7 +5410,7 @@ dependencies = [ [[package]] name = "warpgate-db-entities" -version = "0.7.4" +version = "0.8.0" dependencies = [ "chrono", "poem-openapi", @@ -5423,7 +5423,7 @@ dependencies = [ [[package]] name = "warpgate-db-migrations" -version = "0.7.4" +version = "0.8.0" dependencies = [ "async-std", "chrono", @@ -5435,7 +5435,7 @@ dependencies = [ [[package]] name = "warpgate-protocol-http" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", @@ -5456,6 +5456,7 @@ dependencies = [ "tokio", "tokio-tungstenite 0.17.2", "tracing", + "url", "uuid", "warpgate-admin", "warpgate-common", @@ -5467,7 +5468,7 @@ dependencies = [ [[package]] name = "warpgate-protocol-mysql" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", @@ -5494,7 +5495,7 @@ dependencies = [ [[package]] name = "warpgate-protocol-ssh" -version = "0.7.4" +version = "0.8.0" dependencies = [ "ansi_term", "anyhow", @@ -5519,7 +5520,7 @@ dependencies = [ [[package]] name = "warpgate-sso" -version = "0.7.4" +version = "0.8.0" dependencies = [ "bytes", "data-encoding", @@ -5535,7 +5536,7 @@ dependencies = [ [[package]] name = "warpgate-web" -version = "0.7.4" +version = "0.8.0" dependencies = [ "rust-embed", "serde", diff --git a/warpgate-protocol-ssh/Cargo.toml b/warpgate-protocol-ssh/Cargo.toml index cb2bc7a..c3c9d3e 100644 --- a/warpgate-protocol-ssh/Cargo.toml +++ b/warpgate-protocol-ssh/Cargo.toml @@ -12,7 +12,7 @@ bimap = "0.6" bytes = "1.3" dialoguer = "0.10" futures = "0.3" -russh = { version = "0.38.0", features = ["vendored-openssl"] } +russh = { version = "0.39.0", features = ["vendored-openssl"] } # russh = { version = "0.35.0-beta.6", features = ["vendored-openssl"], path = "../../russh/russh"} russh-keys = { version = "0.38.0", features = ["vendored-openssl"] } # russh-keys = { version = "0.23.0-beta.1", features = ["vendored-openssl"], path = "../../russh/russh-keys" } diff --git a/warpgate-protocol-ssh/src/server/russh_handler.rs b/warpgate-protocol-ssh/src/server/russh_handler.rs index a3f3ccb..7dc0eb7 100644 --- a/warpgate-protocol-ssh/src/server/russh_handler.rs +++ b/warpgate-protocol-ssh/src/server/russh_handler.rs @@ -29,6 +29,7 @@ pub enum ServerHandlerEvent { PtyRequest(ServerChannelId, PtyRequest, oneshot::Sender<()>), ShellRequest(ServerChannelId, oneshot::Sender), AuthPublicKey(Secret, PublicKey, oneshot::Sender), + AuthPublicKeyOffer(Secret, PublicKey, oneshot::Sender), AuthPassword(Secret, Secret, oneshot::Sender), AuthKeyboardInteractive( Secret, @@ -178,6 +179,33 @@ impl russh::server::Handler for ServerHandler { Ok((self, session)) } + async fn auth_publickey_offered( + self, + user: &str, + key: &russh_keys::key::PublicKey, + ) -> Result<(Self, Auth), Self::Error> { + let user = Secret::new(user.to_string()); + let (tx, rx) = oneshot::channel(); + + self.send_event(ServerHandlerEvent::AuthPublicKeyOffer( + user, + key.clone(), + tx, + ))?; + + let result = rx.await.unwrap_or(false); + Ok(( + self, + if result { + Auth::Accept + } else { + Auth::Reject { + proceed_with_methods: None, + } + }, + )) + } + async fn auth_publickey( self, user: &str, diff --git a/warpgate-protocol-ssh/src/server/session.rs b/warpgate-protocol-ssh/src/server/session.rs index cff6b9a..5158537 100644 --- a/warpgate-protocol-ssh/src/server/session.rs +++ b/warpgate-protocol-ssh/src/server/session.rs @@ -466,6 +466,10 @@ impl ServerSession { let _ = reply.send(self._auth_publickey(username, key).await); } + ServerHandlerEvent::AuthPublicKeyOffer(username, key, reply) => { + let _ = reply.send(self._auth_publickey_offer(username, key).await); + } + ServerHandlerEvent::AuthPassword(username, password, reply) => { let _ = reply.send(self._auth_password(username, password).await); } @@ -1149,19 +1153,7 @@ impl ServerSession { .map_err(anyhow::Error::from) } - async fn _auth_publickey( - &mut self, - ssh_username: Secret, - key: PublicKey, - ) -> russh::server::Auth { - let selector: AuthSelector = ssh_username.expose_secret().into(); - - info!( - "Public key auth as {:?} with key {}", - selector, - key.public_key_base64() - ); - + fn _get_public_keys_from_of(&self, key: PublicKey) -> Vec { let mut keys = vec![key.clone()]; // Try all supported hash algorithms if let PublicKey::RSA { key, hash } = &key { @@ -1178,6 +1170,48 @@ impl ServerSession { } } } + keys + } + + async fn _auth_publickey_offer( + &mut self, + ssh_username: Secret, + key: PublicKey, + ) -> bool { + let keys = self._get_public_keys_from_of(key); + let selector: AuthSelector = ssh_username.expose_secret().into(); + + for key in keys { + if let Ok(true) = self + .try_validate_public_key_offer( + &selector, + Some(AuthCredential::PublicKey { + kind: key.name().to_string(), + public_key_bytes: Bytes::from(key.public_key_bytes()), + }), + ) + .await + { + return true; + } + } + false + } + + async fn _auth_publickey( + &mut self, + ssh_username: Secret, + key: PublicKey, + ) -> russh::server::Auth { + let selector: AuthSelector = ssh_username.expose_secret().into(); + + info!( + "Public key auth as {:?} with key {}", + selector, + key.public_key_base64() + ); + + let keys = self._get_public_keys_from_of(key); let mut result = Ok(AuthResult::Rejected); for key in keys { @@ -1361,6 +1395,29 @@ impl ServerSession { m } + async fn try_validate_public_key_offer( + &mut self, + selector: &AuthSelector, + credential: Option, + ) -> Result { + match selector { + AuthSelector::User { username, .. } => { + let cp = self.services.config_provider.clone(); + + if let Some(credential) = credential { + return Ok(cp + .lock() + .await + .validate_credential(username, &credential) + .await?); + } + + Ok(false) + } + _ => Ok(false), + } + } + async fn try_auth( &mut self, selector: &AuthSelector,