finish extremely basic implementation of docker credentials

This commit is contained in:
Joseph Montanaro 2024-11-25 07:57:59 -05:00
parent 8bcdc5420a
commit 9bc9cb56c1
6 changed files with 64 additions and 26 deletions

View File

@ -13,11 +13,7 @@ use super::{
pub fn docker_store(global_args: GlobalArgs) -> anyhow::Result<()> { pub fn docker_store(global_args: GlobalArgs) -> anyhow::Result<()> {
let input: DockerCredential = serde_json::from_reader(io::stdin())?; let input: DockerCredential = serde_json::from_reader(io::stdin())?;
let req = CliRequest::SaveCredential { let req = CliRequest::StoreDockerCredential(input);
name: input.username.clone(),
is_default: false, // is_default doesn't really mean anything for Docker credentials
credential: CliCredential::Docker(input),
};
match super::make_request(global_args.server_addr, &req)?? { match super::make_request(global_args.server_addr, &req)?? {
CliResponse::Empty => Ok(()), CliResponse::Empty => Ok(()),
@ -41,3 +37,17 @@ pub fn docker_get(global_args: GlobalArgs) -> anyhow::Result<()> {
} }
Ok(()) Ok(())
} }
pub fn docker_erase(global_args: GlobalArgs) -> anyhow::Result<()> {
let mut server_url = String::new();
io::stdin().read_to_string(&mut server_url)?;
let req = CliRequest::EraseDockerCredential {
server_url: server_url.trim().to_owned()
};
match super::make_request(global_args.server_addr, &req)?? {
CliResponse::Empty => Ok(()),
r => bail!("Unexpected response from server: {r}"),
}
}

View File

@ -193,7 +193,7 @@ pub fn exec(args: ExecArgs, global: GlobalArgs) -> anyhow::Result<()> {
pub fn invoke_shortcut(args: InvokeArgs, global: GlobalArgs) -> anyhow::Result<()> { pub fn invoke_shortcut(args: InvokeArgs, global: GlobalArgs) -> anyhow::Result<()> {
let req = CliRequest::InvokeShortcut(args.shortcut_action); let req = CliRequest::InvokeShortcut{action: args.shortcut_action};
match make_request(global.server_addr, &req)?? { match make_request(global.server_addr, &req)?? {
CliResponse::Empty => Ok(()), CliResponse::Empty => Ok(()),
r => bail!("Unexpected response from server: {r}"), r => bail!("Unexpected response from server: {r}"),
@ -205,7 +205,7 @@ pub fn docker_credential_helper(cmd: DockerCmd, global_args: GlobalArgs) -> anyh
match cmd { match cmd {
DockerCmd::Get => docker::docker_get(global_args), DockerCmd::Get => docker::docker_get(global_args),
DockerCmd::Store => docker::docker_store(global_args), DockerCmd::Store => docker::docker_store(global_args),
DockerCmd::Erase => todo!(), DockerCmd::Erase => docker::docker_erase(global_args),
} }
} }

View File

@ -7,5 +7,6 @@ CREATE TABLE docker_credentials (
server_url TEXT UNIQUE NOT NULL, server_url TEXT UNIQUE NOT NULL,
username TEXT NOT NULL, username TEXT NOT NULL,
secret_enc BLOB NOT NULL, secret_enc BLOB NOT NULL,
nonce BLOB NOT NULL nonce BLOB NOT NULL,
FOREIGN KEY(id) REFERENCES credentials(id) ON DELETE CASCADE
); );

View File

@ -7,7 +7,8 @@ use crate::clientinfo::{self, Client};
use crate::credentials::{ use crate::credentials::{
Credential, Credential,
CredentialRecord, CredentialRecord,
Crypto Crypto,
DockerCredential
}; };
use crate::errors::*; use crate::errors::*;
use crate::ipc::{Approval, AwsRequestNotification, RequestNotificationDetail, RequestResponse}; use crate::ipc::{Approval, AwsRequestNotification, RequestNotificationDetail, RequestResponse};
@ -55,13 +56,16 @@ async fn handle(
CliRequest::GetAwsCredential{ name, base } => get_aws_credentials( CliRequest::GetAwsCredential{ name, base } => get_aws_credentials(
name, base, client, app_handle, waiter name, base, client, app_handle, waiter
).await, ).await,
CliRequest::GetDockerCredential{ server_url } => get_docker_credentials ( CliRequest::GetDockerCredential{ server_url } => get_docker_credential (
server_url, client, app_handle, waiter server_url, client, app_handle, waiter
).await, ).await,
CliRequest::SaveCredential{ name, is_default, credential } => save_credential( CliRequest::StoreDockerCredential(docker_credential) => store_docker_credential(
name, is_default, credential, app_handle docker_credential, app_handle, waiter
).await, ).await,
CliRequest::InvokeShortcut(action) => invoke_shortcut(action).await, CliRequest::EraseDockerCredential { server_url } => erase_docker_credential(
server_url, app_handle, waiter
).await,
CliRequest::InvokeShortcut{ action } => invoke_shortcut(action).await,
}; };
// doesn't make sense to send the error to the client if the client has already left // doesn't make sense to send the error to the client if the client has already left
@ -106,7 +110,7 @@ async fn get_aws_credentials(
} }
} }
async fn get_docker_credentials( async fn get_docker_credential(
server_url: String, server_url: String,
client: Client, client: Client,
app_handle: AppHandle, app_handle: AppHandle,
@ -126,24 +130,39 @@ async fn get_docker_credentials(
} }
} }
pub async fn save_credential( async fn store_docker_credential(
name: String, docker_credential: DockerCredential,
is_default: bool,
credential: Credential,
app_handle: AppHandle, app_handle: AppHandle,
_waiter: CloseWaiter<'_>,
) -> Result<CliResponse, HandlerError> { ) -> Result<CliResponse, HandlerError> {
let state = app_handle.state::<AppState>(); let state = app_handle.state::<AppState>();
// eventually ask the frontend to unlock here // eventually ask the frontend to confirm here
// a bit weird but convenient // a bit weird but convenient
let random_bytes = Crypto::salt(); let random_bytes = Crypto::salt();
let id = Uuid::from_slice(&random_bytes[..16]).unwrap(); let id = Uuid::from_slice(&random_bytes[..16]).unwrap();
let record = CredentialRecord { let record = CredentialRecord {
id, name, is_default, credential id,
name: docker_credential.server_url.clone(),
is_default: false,
credential: Credential::Docker(docker_credential)
}; };
state.save_credential(record).await?; state.save_credential(record).await?;
Ok(CliResponse::Empty) Ok(CliResponse::Empty)
} }
async fn erase_docker_credential(
server_url: String,
app_handle: AppHandle,
_waiter: CloseWaiter<'_>
) -> Result<CliResponse, HandlerError> {
let state = app_handle.state::<AppState>();
// eventually ask the frontend to confirm here
state.delete_credential_by_name(&server_url).await?;
Ok(CliResponse::Empty)
}

View File

@ -13,7 +13,6 @@ use crate::clientinfo::Client;
use crate::credentials::{ use crate::credentials::{
AwsBaseCredential, AwsBaseCredential,
AwsSessionCredential, AwsSessionCredential,
Credential,
DockerCredential, DockerCredential,
}; };
use crate::errors::*; use crate::errors::*;
@ -30,6 +29,7 @@ use platform::Stream;
// so that we avoid polluting the standalone CLI with a bunch of dependencies // so that we avoid polluting the standalone CLI with a bunch of dependencies
// that would make it impossible to build a completely static-linked version // that would make it impossible to build a completely static-linked version
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum CliRequest { pub enum CliRequest {
GetAwsCredential { GetAwsCredential {
name: Option<String>, name: Option<String>,
@ -38,12 +38,13 @@ pub enum CliRequest {
GetDockerCredential { GetDockerCredential {
server_url: String, server_url: String,
}, },
SaveCredential { StoreDockerCredential(DockerCredential),
name: String, EraseDockerCredential {
is_default: bool, server_url: String,
credential: Credential, },
InvokeShortcut{
action: ShortcutAction,
}, },
InvokeShortcut(ShortcutAction),
} }

View File

@ -161,6 +161,13 @@ impl AppState {
Ok(()) Ok(())
} }
pub async fn delete_credential_by_name(&self, name: &str) -> Result<(), SaveCredentialsError> {
sqlx::query!("DELETE FROM credentials WHERE name = ?", name)
.execute(&self.pool)
.await?;
Ok(())
}
pub async fn list_credentials(&self) -> Result<Vec<CredentialRecord>, GetCredentialsError> { pub async fn list_credentials(&self) -> Result<Vec<CredentialRecord>, GetCredentialsError> {
let session = self.app_session.read().await; let session = self.app_session.read().await;
let crypto = session.try_get_crypto()?; let crypto = session.try_get_crypto()?;