From 7e8cb70c41779d5a4aae218799632bd69ca6618d Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Mon, 7 Aug 2023 21:54:24 +0200 Subject: [PATCH] fixed #854 - show session details during OOB auth --- Cargo.lock | 24 ++++++------- warpgate-common/src/auth/state.rs | 31 +++++++++++++++++ warpgate-core/src/auth_state_store.rs | 11 ++++-- warpgate-protocol-http/src/api/auth.rs | 29 +++++++++++++--- warpgate-protocol-http/src/common.rs | 4 ++- warpgate-protocol-mysql/src/session.rs | 6 +++- warpgate-protocol-ssh/src/server/session.rs | 20 ++++++++--- .../src/admin/lib/openapi-schema.json | 2 +- warpgate-web/src/gateway/OutOfBandAuth.svelte | 34 ++++++++++++++++++- .../src/gateway/lib/openapi-schema.json | 16 +++++++-- 10 files changed, 147 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad29b773..20d9c0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4976,7 +4976,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.7.3" +version = "0.7.4" dependencies = [ "ansi_term", "anyhow", @@ -5012,7 +5012,7 @@ dependencies = [ [[package]] name = "warpgate-admin" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "async-trait", @@ -5041,7 +5041,7 @@ dependencies = [ [[package]] name = "warpgate-common" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "argon2", @@ -5077,7 +5077,7 @@ dependencies = [ [[package]] name = "warpgate-core" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "argon2", @@ -5117,7 +5117,7 @@ dependencies = [ [[package]] name = "warpgate-database-protocols" -version = "0.7.3" +version = "0.7.4" dependencies = [ "bitflags", "bytes", @@ -5130,7 +5130,7 @@ dependencies = [ [[package]] name = "warpgate-db-entities" -version = "0.7.3" +version = "0.7.4" dependencies = [ "chrono", "poem-openapi", @@ -5143,7 +5143,7 @@ dependencies = [ [[package]] name = "warpgate-db-migrations" -version = "0.7.3" +version = "0.7.4" dependencies = [ "async-std", "chrono", @@ -5155,7 +5155,7 @@ dependencies = [ [[package]] name = "warpgate-protocol-http" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "async-trait", @@ -5187,7 +5187,7 @@ dependencies = [ [[package]] name = "warpgate-protocol-mysql" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "async-trait", @@ -5214,7 +5214,7 @@ dependencies = [ [[package]] name = "warpgate-protocol-ssh" -version = "0.7.3" +version = "0.7.4" dependencies = [ "ansi_term", "anyhow", @@ -5239,7 +5239,7 @@ dependencies = [ [[package]] name = "warpgate-sso" -version = "0.7.3" +version = "0.7.4" dependencies = [ "bytes", "data-encoding", @@ -5255,7 +5255,7 @@ dependencies = [ [[package]] name = "warpgate-web" -version = "0.7.3" +version = "0.7.4" dependencies = [ "rust-embed", "serde", diff --git a/warpgate-common/src/auth/state.rs b/warpgate-common/src/auth/state.rs index 897afdf3..0fe7dc37 100644 --- a/warpgate-common/src/auth/state.rs +++ b/warpgate-common/src/auth/state.rs @@ -1,8 +1,11 @@ use std::collections::HashSet; +use chrono::{DateTime, Utc}; +use rand::Rng; use uuid::Uuid; use super::{AuthCredential, CredentialKind, CredentialPolicy, CredentialPolicyResponse}; +use crate::SessionId; #[derive(Debug, Clone)] pub enum AuthResult { @@ -13,27 +16,43 @@ pub enum AuthResult { pub struct AuthState { id: Uuid, + session_id: Option, username: String, protocol: String, force_rejected: bool, policy: Box, valid_credentials: Vec, + started: DateTime, + identification_string: String, +} + +fn generate_identification_string() -> String { + let mut s = String::new(); + let mut rng = rand::thread_rng(); + for _ in 0..4 { + s.push_str(&format!("{:X}", rng.gen_range(0..16))); + } + s } impl AuthState { pub fn new( id: Uuid, + session_id: Option, username: String, protocol: String, policy: Box, ) -> Self { Self { id, + session_id, username, protocol, force_rejected: false, policy, valid_credentials: vec![], + started: Utc::now(), + identification_string: generate_identification_string(), } } @@ -41,6 +60,10 @@ impl AuthState { &self.id } + pub fn session_id(&self) -> &Option { + &self.session_id + } + pub fn username(&self) -> &str { &self.username } @@ -49,6 +72,14 @@ impl AuthState { &self.protocol } + pub fn started(&self) -> &DateTime { + &self.started + } + + pub fn identification_string(&self) -> &str { + &self.identification_string + } + pub fn add_valid_credential(&mut self, credential: AuthCredential) { self.valid_credentials.push(credential); } diff --git a/warpgate-core/src/auth_state_store.rs b/warpgate-core/src/auth_state_store.rs index 2875944f..8d8225ec 100644 --- a/warpgate-core/src/auth_state_store.rs +++ b/warpgate-core/src/auth_state_store.rs @@ -6,7 +6,7 @@ use once_cell::sync::Lazy; use tokio::sync::{broadcast, Mutex}; use uuid::Uuid; use warpgate_common::auth::{AuthResult, AuthState}; -use warpgate_common::WarpgateError; +use warpgate_common::{WarpgateError, SessionId}; use crate::ConfigProvider; @@ -49,6 +49,7 @@ impl AuthStateStore { pub async fn create( &mut self, + session_id: Option<&SessionId>, username: &str, protocol: &str, ) -> Result<(Uuid, Arc>), WarpgateError> { @@ -63,7 +64,13 @@ impl AuthStateStore { return Err(WarpgateError::UserNotFound) }; - let state = AuthState::new(id, username.to_string(), protocol.to_string(), policy); + let state = AuthState::new( + id, + session_id.copied(), + username.to_string(), + protocol.to_string(), + policy, + ); self.store .insert(id, (Arc::new(Mutex::new(state)), Instant::now())); diff --git a/warpgate-protocol-http/src/api/auth.rs b/warpgate-protocol-http/src/api/auth.rs index d00cd903..0f7f5c69 100644 --- a/warpgate-protocol-http/src/api/auth.rs +++ b/warpgate-protocol-http/src/api/auth.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use chrono::{DateTime, Utc}; use poem::session::Session; use poem::web::Data; use poem::Request; @@ -66,7 +67,10 @@ enum LogoutResponse { #[derive(Object)] struct AuthStateResponseInternal { pub protocol: String, + pub address: Option, + pub started: DateTime, pub state: ApiAuthState, + pub identification_string: String, } #[derive(ApiResponse)] @@ -214,7 +218,7 @@ impl Api { let Some(state_arc) = store.get(&state_id.0) else { return Ok(AuthStateResponse::NotFound); }; - serialize_auth_state_inner(state_arc).await + serialize_auth_state_inner(state_arc, *services).await } #[oai( @@ -237,7 +241,7 @@ impl Api { state_arc.lock().await.reject(); store.complete(&state_id.0).await; session.clear_auth_state(); - serialize_auth_state_inner(state_arc).await + serialize_auth_state_inner(state_arc, *services).await } #[oai( @@ -256,7 +260,7 @@ impl Api { let Some(state_arc) = state_arc else { return Ok(AuthStateResponse::NotFound); }; - serialize_auth_state_inner(state_arc).await + serialize_auth_state_inner(state_arc, *services).await } #[oai( @@ -284,7 +288,7 @@ impl Api { if let AuthResult::Accepted { .. } = auth_result { services.auth_state_store.lock().await.complete(&id).await; } - serialize_auth_state_inner(state_arc).await + serialize_auth_state_inner(state_arc, *services).await } #[oai( @@ -304,7 +308,7 @@ impl Api { }; state_arc.lock().await.reject(); services.auth_state_store.lock().await.complete(&id).await; - serialize_auth_state_inner(state_arc).await + serialize_auth_state_inner(state_arc, *services).await } } @@ -339,10 +343,25 @@ async fn get_auth_state( async fn serialize_auth_state_inner( state_arc: Arc>, + services: &Services, ) -> poem::Result { let state = state_arc.lock().await; + + let session_state_store = services.state.lock().await; + let session_state = state + .session_id() + .and_then(|session_id| session_state_store.sessions.get(&session_id)); + + let peer_addr = match session_state { + Some(x) => x.lock().await.remote_address, + None => None, + }; + Ok(AuthStateResponse::Ok(Json(AuthStateResponseInternal { protocol: state.protocol().to_string(), + address: peer_addr.map(|x| x.ip().to_string()), + started: state.started().clone(), state: state.verify().into(), + identification_string: state.identification_string().to_owned(), }))) } diff --git a/warpgate-protocol-http/src/common.rs b/warpgate-protocol-http/src/common.rs index 1c0cafea..325a8425 100644 --- a/warpgate-protocol-http/src/common.rs +++ b/warpgate-protocol-http/src/common.rs @@ -198,7 +198,9 @@ pub async fn get_auth_state_for_request( match session.get_auth_state_id() { Some(id) => Ok(store.get(&id.0).ok_or(WarpgateError::InconsistentState)?), None => { - let (id, state) = store.create(username, crate::common::PROTOCOL_NAME).await?; + let (id, state) = store + .create(None, username, crate::common::PROTOCOL_NAME) + .await?; session.set(AUTH_STATE_ID_SESSION_KEY, AuthStateId(id)); Ok(state) } diff --git a/warpgate-protocol-mysql/src/session.rs b/warpgate-protocol-mysql/src/session.rs index fd8c12a1..49366dc1 100644 --- a/warpgate-protocol-mysql/src/session.rs +++ b/warpgate-protocol-mysql/src/session.rs @@ -183,7 +183,11 @@ impl MySqlSession { .auth_state_store .lock() .await - .create(&username, crate::common::PROTOCOL_NAME) + .create( + Some(&self.server_handle.lock().await.id()), + &username, + crate::common::PROTOCOL_NAME, + ) .await? .1; let mut state = state_arc.lock().await; diff --git a/warpgate-protocol-ssh/src/server/session.rs b/warpgate-protocol-ssh/src/server/session.rs index fec9c502..23a44da6 100644 --- a/warpgate-protocol-ssh/src/server/session.rs +++ b/warpgate-protocol-ssh/src/server/session.rs @@ -228,7 +228,7 @@ impl ServerSession { .auth_state_store .lock() .await - .create(username, crate::PROTOCOL_NAME) + .create(Some(&self.id), username, crate::PROTOCOL_NAME) .await? .1; self.auth_state = Some(state); @@ -1279,6 +1279,8 @@ impl ServerSession { let Some(auth_state) = self.auth_state.as_ref() else { return russh::server::Auth::Reject { proceed_with_methods: None}; }; + let identification_string = + auth_state.lock().await.identification_string().to_owned(); let auth_state_id = *auth_state.lock().await.id(); let event = self .services @@ -1311,11 +1313,19 @@ impl ServerSession { russh::server::Auth::Partial { name: Cow::Owned(format!( concat!( - "----------------------------------------------------------------\n", - "Warpgate authentication: please open {} in your browser\n", - "----------------------------------------------------------------\n" + "-----------------------------------------------------------------------\n", + "Warpgate authentication: please open the following URL in your browser:\n", + "{}\n\n", + "Make sure you're seeing this security key: {}\n", + "-----------------------------------------------------------------------\n" ), - login_url + login_url, + identification_string + .chars() + .into_iter() + .map(|x| x.to_string()) + .collect::>() + .join(" ") )), instructions: Cow::Borrowed(""), prompts: Cow::Owned(vec![(Cow::Borrowed("Press Enter when done: "), true)]), diff --git a/warpgate-web/src/admin/lib/openapi-schema.json b/warpgate-web/src/admin/lib/openapi-schema.json index b7288cc5..4577e407 100644 --- a/warpgate-web/src/admin/lib/openapi-schema.json +++ b/warpgate-web/src/admin/lib/openapi-schema.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "title": "Warpgate Web Admin", - "version": "0.7.1" + "version": "0.7.4" }, "servers": [ { diff --git a/warpgate-web/src/gateway/OutOfBandAuth.svelte b/warpgate-web/src/gateway/OutOfBandAuth.svelte index 7fd54076..23cb8374 100644 --- a/warpgate-web/src/gateway/OutOfBandAuth.svelte +++ b/warpgate-web/src/gateway/OutOfBandAuth.svelte @@ -4,6 +4,7 @@ import { Alert } from 'sveltestrap' import { api, ApiAuthState, AuthStateResponseInternal } from 'gateway/lib/api' import AsyncButton from 'common/AsyncButton.svelte' import DelayedSpinner from 'common/DelayedSpinner.svelte' +import RelativeDate from 'admin/RelativeDate.svelte' export let params: { stateId: string } let authState: AuthStateResponseInternal @@ -29,6 +30,19 @@ async function reject () { } + + {#await init()} {:then} @@ -36,7 +50,25 @@ async function reject () {

Authorization request

-

Authorize this {authState.protocol} session?

+
+
Ensure this security key matches your authentication prompt:
+
+ {#each authState.identificationString as char} +
+
{char}
+
+ {/each} +
+ +
+
+ Authorize this {authState.protocol} session? +
+ + Requested + {#if authState.address}from {authState.address}{/if} + +
{#if authState.state === ApiAuthState.Success} diff --git a/warpgate-web/src/gateway/lib/openapi-schema.json b/warpgate-web/src/gateway/lib/openapi-schema.json index eef609f8..4d95a128 100644 --- a/warpgate-web/src/gateway/lib/openapi-schema.json +++ b/warpgate-web/src/gateway/lib/openapi-schema.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "title": "Warpgate HTTP proxy", - "version": "0.7.1" + "version": "0.7.4" }, "servers": [ { @@ -386,14 +386,26 @@ "type": "object", "required": [ "protocol", - "state" + "started", + "state", + "identification_string" ], "properties": { "protocol": { "type": "string" }, + "address": { + "type": "string" + }, + "started": { + "type": "string", + "format": "date-time" + }, "state": { "$ref": "#/components/schemas/ApiAuthState" + }, + "identification_string": { + "type": "string" } } },