From c6e22fc91bc6b0088b25b8f841c4de487c944607 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Mon, 25 Nov 2024 11:22:27 -0500 Subject: [PATCH] show client username, check whether credential exists before requesting confirmation from frontend --- src-tauri/src/clientinfo.rs | 15 ++++++++++--- src-tauri/src/credentials/mod.rs | 7 ++++++ src-tauri/src/srv/agent.rs | 3 +-- src-tauri/src/srv/creddy_server.rs | 28 ++++++++++++++++++------ src-tauri/src/srv/mod.rs | 1 - src-tauri/src/state.rs | 7 ++++++ src/views/Approve.svelte | 1 + src/views/approve/CollectResponse.svelte | 4 +++- 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src-tauri/src/clientinfo.rs b/src-tauri/src/clientinfo.rs index fcb44e6..122f6f0 100644 --- a/src-tauri/src/clientinfo.rs +++ b/src-tauri/src/clientinfo.rs @@ -5,7 +5,8 @@ use sysinfo::{ SystemExt, Pid, PidExt, - ProcessExt + ProcessExt, + UserExt, }; use serde::{Serialize, Deserialize}; @@ -16,13 +17,16 @@ use crate::errors::*; pub struct Client { pub pid: u32, pub exe: Option, + pub username: Option, } pub fn get_client(pid: u32, parent: bool) -> Result { let sys_pid = Pid::from_u32(pid); - let mut sys = System::new(); + let mut sys = System::new(); sys.refresh_process(sys_pid); + sys.refresh_users_list(); + let mut proc = sys.process(sys_pid) .ok_or(ClientInfoError::ProcessNotFound)?; @@ -34,10 +38,15 @@ pub fn get_client(pid: u32, parent: bool) -> Result { .ok_or(ClientInfoError::ParentProcessNotFound)?; } + let username = proc.user_id() + .map(|uid| sys.get_user_by_id(uid)) + .flatten() + .map(|u| u.name().to_owned()); + let exe = match proc.exe() { p if p == Path::new("") => None, p => Some(PathBuf::from(p)), }; - Ok(Client { pid: proc.pid().as_u32(), exe }) + Ok(Client { pid: proc.pid().as_u32(), exe, username }) } diff --git a/src-tauri/src/credentials/mod.rs b/src-tauri/src/credentials/mod.rs index 68d932d..29c4ca5 100644 --- a/src-tauri/src/credentials/mod.rs +++ b/src-tauri/src/credentials/mod.rs @@ -139,3 +139,10 @@ pub trait PersistentCredential: for<'a> Deserialize<'a> + Sized { Ok(creds) } } + + +pub fn random_uuid() -> Uuid { + // a bit weird to use salt() for this, but it's convenient + let random_bytes = Crypto::salt(); + Uuid::from_slice(&random_bytes[..16]).unwrap() +} diff --git a/src-tauri/src/srv/agent.rs b/src-tauri/src/srv/agent.rs index c76baae..72b38ce 100644 --- a/src-tauri/src/srv/agent.rs +++ b/src-tauri/src/srv/agent.rs @@ -6,12 +6,11 @@ use ssh_agent_lib::proto::message::{ }; use tauri::{AppHandle, Manager}; use tokio_stream::StreamExt; -use tokio::sync::oneshot; use tokio_util::codec::Framed; use crate::clientinfo; use crate::errors::*; -use crate::ipc::{Approval, RequestNotification, RequestNotificationDetail}; +use crate::ipc::{Approval, RequestNotificationDetail}; use crate::state::AppState; use super::{CloseWaiter, Stream}; diff --git a/src-tauri/src/srv/creddy_server.rs b/src-tauri/src/srv/creddy_server.rs index 5b7a9f0..8f71f53 100644 --- a/src-tauri/src/srv/creddy_server.rs +++ b/src-tauri/src/srv/creddy_server.rs @@ -1,17 +1,17 @@ use sqlx::types::uuid::Uuid; use tauri::{AppHandle, Manager}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::sync::oneshot; use crate::clientinfo::{self, Client}; use crate::credentials::{ + self, Credential, CredentialRecord, Crypto, - DockerCredential + DockerCredential, }; use crate::errors::*; -use crate::ipc::{Approval, AwsRequestNotification, RequestNotificationDetail, RequestResponse}; +use crate::ipc::{Approval, RequestNotificationDetail}; use crate::shortcuts::{self, ShortcutAction}; use crate::state::AppState; use super::{ @@ -116,11 +116,22 @@ async fn get_docker_credential( app_handle: AppHandle, waiter: CloseWaiter<'_>, ) -> Result { + let state = app_handle.state::(); + let credential_id = state.credential_id(&server_url).await.unwrap_or(None); + if credential_id.is_none() { + return Err( + HandlerError::NoCredentials( + GetCredentialsError::Load( + LoadCredentialsError::NoCredentials + ) + ) + ); + } + let detail = RequestNotificationDetail::new_docker(client, server_url.clone()); let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?; match response.approval { Approval::Approved => { - let state = app_handle.state::(); let creds = state.get_docker_credential(&server_url).await?; Ok(CliResponse::Credential(CliCredential::Docker(creds))) }, @@ -139,9 +150,12 @@ async fn store_docker_credential( // eventually ask the frontend to confirm here - // a bit weird but convenient - let random_bytes = Crypto::salt(); - let id = Uuid::from_slice(&random_bytes[..16]).unwrap(); + // for some reason Docker likes to call `store` immediately with whatever it gets + // back from every `get` operation, so we have to check for an existing credential + let id = state.credential_id(&docker_credential.server_url) + .await + .map_err(|e| GetCredentialsError::Load(e))? + .unwrap_or_else(|| credentials::random_uuid()); let record = CredentialRecord { id, diff --git a/src-tauri/src/srv/mod.rs b/src-tauri/src/srv/mod.rs index a541e79..096d2c2 100644 --- a/src-tauri/src/srv/mod.rs +++ b/src-tauri/src/srv/mod.rs @@ -9,7 +9,6 @@ use tokio::io::AsyncReadExt; use tokio::sync::oneshot; use serde::{Serialize, Deserialize}; -use crate::clientinfo::Client; use crate::credentials::{ AwsBaseCredential, AwsSessionCredential, diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index b3577f4..9249f03 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -148,6 +148,13 @@ impl AppState { } } + pub async fn credential_id(&self, name: &str) -> Result, LoadCredentialsError> { + let res = sqlx::query_scalar!(r#"SELECT id as "id: Uuid" FROM credentials WHERE name = ?"#, name) + .fetch_optional(&self.pool) + .await; + Ok(res?) + } + pub async fn save_credential(&self, record: CredentialRecord) -> Result<(), SaveCredentialsError> { let session = self.app_session.read().await; let crypto = session.try_get_crypto()?; diff --git a/src/views/Approve.svelte b/src/views/Approve.svelte index 65c4f5a..bb58a15 100644 --- a/src/views/Approve.svelte +++ b/src/views/Approve.svelte @@ -7,6 +7,7 @@ import ShowResponse from './approve/ShowResponse.svelte'; import Unlock from './Unlock.svelte'; + console.log($appState.currentRequest); // Extra 50ms so the window can finish disappearing before the redraw const rehideDelay = Math.min(5000, $appState.config.rehide_ms + 100); diff --git a/src/views/approve/CollectResponse.svelte b/src/views/approve/CollectResponse.svelte index 729243a..d1d2166 100644 --- a/src/views/approve/CollectResponse.svelte +++ b/src/views/approve/CollectResponse.svelte @@ -14,7 +14,7 @@ // Extract executable name from full path const client = $appState.currentRequest.client; const m = client.exe?.match(/\/([^/]+?$)|\\([^\\]+?$)/); - const appName = m[1] || m[2]; + const appName = m ? m[1] || m[2] : ''; const dispatch = createEventDispatcher(); @@ -61,6 +61,8 @@ {@html client.exe ? breakPath(client.exe) : 'Unknown'}
PID:
{client.pid} +
User:
+ {client.username ?? 'Unknown'}