fixed #854 - show session details during OOB auth

This commit is contained in:
Eugene Pankov 2023-08-07 21:54:24 +02:00 committed by Eugene
parent fc1a93b9e3
commit 0bc9ae1b1a
10 changed files with 147 additions and 30 deletions

24
Cargo.lock generated
View file

@ -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",

View file

@ -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<Uuid>,
username: String,
protocol: String,
force_rejected: bool,
policy: Box<dyn CredentialPolicy + Sync + Send>,
valid_credentials: Vec<AuthCredential>,
started: DateTime<Utc>,
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<SessionId>,
username: String,
protocol: String,
policy: Box<dyn CredentialPolicy + Sync + Send>,
) -> 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<SessionId> {
&self.session_id
}
pub fn username(&self) -> &str {
&self.username
}
@ -49,6 +72,14 @@ impl AuthState {
&self.protocol
}
pub fn started(&self) -> &DateTime<Utc> {
&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);
}

View file

@ -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<Mutex<AuthState>>), 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()));

View file

@ -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<String>,
pub started: DateTime<Utc>,
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<Mutex<AuthState>>,
services: &Services,
) -> poem::Result<AuthStateResponse> {
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(),
})))
}

View file

@ -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)
}

View file

@ -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;

View file

@ -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::<Vec<_>>()
.join(" ")
)),
instructions: Cow::Borrowed(""),
prompts: Cow::Owned(vec![(Cow::Borrowed("Press Enter when done: "), true)]),

View file

@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"title": "Warpgate Web Admin",
"version": "0.7.1"
"version": "0.7.4"
},
"servers": [
{

View file

@ -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 () {
}
</script>
<style lang="scss">
.identification-string {
display: flex;
font-size: 3rem;
.card {
padding: 0rem 0.5rem;
border-radius: .5rem;
margin-right: .5rem;
}
}
</style>
{#await init()}
<DelayedSpinner />
{:then}
@ -36,7 +50,25 @@ async function reject () {
<h1>Authorization request</h1>
</div>
<p>Authorize this {authState.protocol} session?</p>
<div class="mb-5">
<div class="mb-2">Ensure this security key matches your authentication prompt:</div>
<div class="identification-string">
{#each authState.identificationString as char}
<div class="card bg-secondary text-light">
<div class="card-body">{char}</div>
</div>
{/each}
</div> </div>
<div class="mb-3">
<div>
Authorize this {authState.protocol} session?
</div>
<small>
Requested <RelativeDate date={authState.started} />
{#if authState.address}from {authState.address}{/if}
</small>
</div>
{#if authState.state === ApiAuthState.Success}
<Alert color="success">

View file

@ -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"
}
}
},