Skip to content

Commit b270f10

Browse files
ellieclaudegithub-actions[bot]
authored
feat: SSH certificate authentication support (#327)
* feat: add SSH certificate authentication support Adds support for SSH certificate-based authentication with automatic fallback to key-based auth when certificates are invalid or expired. - Auto-detect companion certificate files (e.g., id_ed25519-cert.pub) - Validate certificate timing (expiry, not-yet-valid) - Emit warning events for certificate issues while allowing connection - Show toast notifications for certificate problems - Document known limitation: agent-held certificates not supported 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add SSH certificate configuration UI and validation - Add certificate config UI (path/paste modes) to SSH settings modal - Improve certificate validation with expiry/not-yet-valid warnings - Add integration tests for certificate authentication - Update Docker test infrastructure with CA key generation - Fix toast overflow for long messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: use time crate for SystemTime formatting Replace 50-line manual calendar math with OffsetDateTime::from().to_string() from the time crate (already a dependency). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: extract try_publickey_auth helper for cert fallbacks De-duplicate the 3-line publickey auth pattern that was repeated in each certificate validation fallback path. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: flatten certificate config matching with early returns Replace nested match statements with if-let early returns for clearer control flow in key_auth_from_content_with_cert and key_auth_with_cert_config. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle WorkspaceError variants without message field Some WorkspaceError variants (WorkspaceNotWatched, RunbookNotFound, etc.) don't have a message field. Use type guard to safely access it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * format * clippy * fix: emit SSH certificate warnings for command execution Certificate authentication warnings were being emitted for PTY sessions but not for command execution (exec). This caused an inconsistency where users running commands wouldn't see certificate warnings, but interactive shell users would. Changes: - Added warnings_tx channel to Exec message in ssh_pool.rs - Modified Exec handler to capture and send auth_result.warnings - Updated script.rs to receive and emit warnings as GC events - Warnings now flow consistently for both PTY and exec operations Fixes issue identified in code review where auth_result was discarded on line 618 during exec connection. Co-authored-by: Ellie Huxtable <ellie@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Ellie Huxtable <ellie@users.noreply.github.com>
1 parent bdf7051 commit b270f10

38 files changed

+1459
-259
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3+
/**
4+
* Data for block error lifecycle event
5+
*/
36
export type BlockErrorData = { message: string, };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3+
/**
4+
* Data for block finished lifecycle event
5+
*/
36
export type BlockFinishedData = { exit_code: number | null, success: boolean, };

crates/atuin-desktop-runtime/bindings/BlockLifecycleEvent.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@
22
import type { BlockErrorData } from "./BlockErrorData";
33
import type { BlockFinishedData } from "./BlockFinishedData";
44

5-
export type BlockLifecycleEvent = { "type": "started" } | { "type": "finished", "data": BlockFinishedData } | { "type": "cancelled" } | { "type": "error", "data": BlockErrorData };
5+
/**
6+
* Block lifecycle events
7+
*
8+
* Indicates state transitions during block execution.
9+
*/
10+
export type BlockLifecycleEvent = { "type": "started", "data": string } | { "type": "finished", "data": BlockFinishedData } | { "type": "cancelled" } | { "type": "error", "data": BlockErrorData } | { "type": "paused" };

crates/atuin-desktop-runtime/bindings/ClientPrompt.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,9 @@ import type { PromptIcon } from "./PromptIcon";
33
import type { PromptInput } from "./PromptInput";
44
import type { PromptOption } from "./PromptOption";
55

6+
/**
7+
* A prompt displayed to the user in the client application
8+
*
9+
* Prompts can include text input fields, dropdowns, and action buttons.
10+
*/
611
export type ClientPrompt = { title: string, prompt: string, icon: PromptIcon | null, input: PromptInput | null, options: Array<PromptOption>, };
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3-
export type ClientPromptResult = { button: string, value: string | null, };
3+
/**
4+
* The result from a client prompt interaction
5+
*/
6+
export type ClientPromptResult = {
7+
/**
8+
* The value of the button that was clicked
9+
*/
10+
button: string,
11+
/**
12+
* The value entered in an input field, if any
13+
*/
14+
value: string | null, };
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2-
import type { BlockOutput } from "./BlockOutput";
32
import type { ClientPrompt } from "./ClientPrompt";
43
import type { ResolvedContext } from "./ResolvedContext";
4+
import type { StreamingBlockOutput } from "./StreamingBlockOutput";
5+
import type { JsonValue } from "./serde_json/JsonValue";
56

6-
export type DocumentBridgeMessage = { "type": "blockContextUpdate", "data": { blockId: string, context: ResolvedContext, } } | { "type": "blockOutput", "data": { blockId: string, output: BlockOutput, } } | { "type": "clientPrompt", "data": { executionId: string, promptId: string, prompt: ClientPrompt, } };
7+
/**
8+
* Messages sent from the runtime to the client application
9+
*
10+
* These messages communicate execution state, output, and context updates
11+
* to the desktop application frontend.
12+
*/
13+
export type DocumentBridgeMessage = { "type": "blockContextUpdate", "data": { blockId: string, context: ResolvedContext, } } | { "type": "blockStateChanged", "data": { blockId: string, state: JsonValue, } } | { "type": "blockExecutionOutputChanged", "data": { blockId: string, } } | { "type": "blockOutput", "data": { blockId: string, output: StreamingBlockOutput, } } | { "type": "clientPrompt", "data": { executionId: string, promptId: string, prompt: ClientPrompt, } };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3+
/**
4+
* Current status of block execution
5+
*/
36
export type ExecutionStatus = { "type": "Running" } | { "type": "Success" } | { "type": "Failed", "data": string } | { "type": "Cancelled" };

crates/atuin-desktop-runtime/bindings/GCEvent.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import type { PtyMetadata } from "./PtyMetadata";
33

44
/**
5-
* Grand Central Event - all events that can be emitted by the runtime
5+
* Events emitted by the runtime for monitoring and telemetry
6+
*
7+
* These events provide visibility into runtime operations including block execution,
8+
* SSH connections, PTY lifecycle, and runbook state changes.
69
*/
7-
export type GCEvent = { "type": "ptyOpened", "data": PtyMetadata } | { "type": "ptyClosed", "data": { pty_id: string, } } | { "type": "blockStarted", "data": { block_id: string, runbook_id: string, } } | { "type": "blockFinished", "data": { block_id: string, runbook_id: string, success: boolean, } } | { "type": "blockFailed", "data": { block_id: string, runbook_id: string, error: string, } } | { "type": "blockCancelled", "data": { block_id: string, runbook_id: string, } } | { "type": "sshConnected", "data": { host: string, username: string | null, } } | { "type": "sshConnectionFailed", "data": { host: string, error: string, } } | { "type": "sshDisconnected", "data": { host: string, } } | { "type": "runbookStarted", "data": { runbook_id: string, } } | { "type": "runbookCompleted", "data": { runbook_id: string, } } | { "type": "runbookFailed", "data": { runbook_id: string, error: string, } };
10+
export type GCEvent = { "type": "serialExecutionStarted", "data": { runbook_id: string, } } | { "type": "serialExecutionCompleted", "data": { runbook_id: string, } } | { "type": "serialExecutionCancelled", "data": { runbook_id: string, } } | { "type": "serialExecutionFailed", "data": { runbook_id: string, error: string, } } | { "type": "serialExecutionPaused", "data": { runbook_id: string, block_id: string, } } | { "type": "ptyOpened", "data": PtyMetadata } | { "type": "ptyClosed", "data": { pty_id: string, } } | { "type": "blockStarted", "data": { block_id: string, runbook_id: string, } } | { "type": "blockFinished", "data": { block_id: string, runbook_id: string, success: boolean, } } | { "type": "blockFailed", "data": { block_id: string, runbook_id: string, error: string, } } | { "type": "blockCancelled", "data": { block_id: string, runbook_id: string, } } | { "type": "sshConnected", "data": { host: string, username: string | null, } } | { "type": "sshConnectionFailed", "data": { host: string, error: string, } } | { "type": "sshDisconnected", "data": { host: string, } } | { "type": "sshCertificateLoadFailed", "data": { host: string, cert_path: string, error: string, } } | { "type": "sshCertificateExpired", "data": { host: string, cert_path: string, valid_until: string, } } | { "type": "sshCertificateNotYetValid", "data": { host: string, cert_path: string, valid_from: string, } } | { "type": "runbookStarted", "data": { runbook_id: string, } } | { "type": "runbookCompleted", "data": { runbook_id: string, } } | { "type": "runbookFailed", "data": { runbook_id: string, error: string, } };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3+
/**
4+
* Icon types for client prompts
5+
*/
36
export type PromptIcon = { "type": "info" } | { "type": "warning" } | { "type": "error" } | { "type": "success" } | { "type": "question" };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3+
/**
4+
* Input types for client prompts
5+
*/
36
export type PromptInput = { "type": "string" } | { "type": "text" } | { "type": "dropdown", "data": Array<[string, string]> };

0 commit comments

Comments
 (0)