From 017be3397469a1dd71ec9e59688705f15688d04c Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 10 Jul 2026 08:01:57 +0800 Subject: [PATCH] Support Vault auth for monitors (#1004) --- src/backend/ssh/host-metrics.ts | 11 ++++++++ src/backend/ssh/tmux-monitor.ts | 8 ++++++ src/backend/ssh/vault-ssh-connect.ts | 39 ++++++++++++++++++++++++++++ src/types/index.ts | 5 +++- 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/backend/ssh/vault-ssh-connect.ts diff --git a/src/backend/ssh/host-metrics.ts b/src/backend/ssh/host-metrics.ts index 4ff506b3..fcfe354f 100644 --- a/src/backend/ssh/host-metrics.ts +++ b/src/backend/ssh/host-metrics.ts @@ -104,6 +104,7 @@ interface SSHHostWithCredentials { rdpPort?: number; vncPort?: number; telnetPort?: number; + vaultProfile?: { id?: number | null } | null; } type StatusEntry = { @@ -1128,6 +1129,8 @@ async function buildSshConfig( // no credentials needed } else if (host.authType === "opkssh") { // cert auth setup happens in createSshFactory (needs client instance) + } else if (host.authType === "vault") { + // cert auth setup happens in createSshFactory (needs client instance) } else if (host.authType === "credential") { if (host.password) { base.password = host.password; @@ -1186,6 +1189,10 @@ function createSshFactory(host: SSHHostWithCredentials): () => Promise { } const { setupOPKSSHCertAuth } = await import("./opkssh-cert-auth.js"); await setupOPKSSHCertAuth(config, client, token, host.username); + } else if (host.authType === "vault") { + const { setupVaultSshSignerAuth } = + await import("./vault-ssh-connect.js"); + await setupVaultSshSignerAuth(config, client, host); } const proxyConfig: SOCKS5Config | null = @@ -2070,6 +2077,10 @@ app.post("/metrics/start/:id", validateHostId, async (req, res) => { } const { setupOPKSSHCertAuth } = await import("./opkssh-cert-auth.js"); await setupOPKSSHCertAuth(config, client, token, host.username); + } else if (host.authType === "vault") { + const { setupVaultSshSignerAuth } = + await import("./vault-ssh-connect.js"); + await setupVaultSshSignerAuth(config, client, host); } const connectionPromise = new Promise<{ diff --git a/src/backend/ssh/tmux-monitor.ts b/src/backend/ssh/tmux-monitor.ts index 701b1b94..d504efcf 100644 --- a/src/backend/ssh/tmux-monitor.ts +++ b/src/backend/ssh/tmux-monitor.ts @@ -95,6 +95,8 @@ async function buildSshConfig(host: SSHHost): Promise { } } else if (host.authType === "none") { // no credentials needed + } else if (host.authType === "vault") { + // cert auth setup happens in connectToHost (needs client instance) } else if (host.authType === "agent") { const result = await applyAgentAuth( base as Record, @@ -118,6 +120,12 @@ export function connectToHost(host: SSHHost): () => Promise { const config = await buildSshConfig(host); const client = new Client(); + if (host.authType === "vault") { + const { setupVaultSshSignerAuth } = + await import("./vault-ssh-connect.js"); + await setupVaultSshSignerAuth(config, client, host); + } + const proxyConfig: SOCKS5Config | null = host.useSocks5 && (host.socks5Host || diff --git a/src/backend/ssh/vault-ssh-connect.ts b/src/backend/ssh/vault-ssh-connect.ts new file mode 100644 index 00000000..61637e85 --- /dev/null +++ b/src/backend/ssh/vault-ssh-connect.ts @@ -0,0 +1,39 @@ +import type { Client, ConnectConfig } from "ssh2"; + +type VaultAuthHost = { + id: number; + username: string; + userId?: string | null; + vaultProfile?: { id?: number | null } | null; +}; + +export async function setupVaultSshSignerAuth( + config: ConnectConfig, + client: Client, + host: VaultAuthHost, +): Promise { + if (!host.userId) { + throw new Error("Vault SSH signer authentication requires a user session"); + } + + const vaultProfileId = host.vaultProfile?.id; + if (!vaultProfileId) { + throw new Error("Host has no Vault signer profile configured"); + } + + const { getVaultCert } = await import("./vault-signer-auth.js"); + const cert = await getVaultCert(host.userId, vaultProfileId); + if (!cert) { + throw new Error( + "Vault SSH signer authentication required. Please open a Terminal connection first.", + ); + } + + const { setupOPKSSHCertAuth } = await import("./opkssh-cert-auth.js"); + await setupOPKSSHCertAuth( + config, + client, + { privateKey: cert.privateKey, sshCert: cert.sshCert }, + host.username, + ); +} diff --git a/src/types/index.ts b/src/types/index.ts index 67f6a2cf..fd7c2007 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -109,7 +109,8 @@ export interface Host { | "none" | "opkssh" | "tailscale" - | "agent"; + | "agent" + | "vault"; useWarpgate?: boolean; password?: string; key?: string; @@ -123,6 +124,8 @@ export interface Host { autostartKeyPassword?: string; credentialId?: number; + vaultProfileId?: number | null; + vaultProfile?: { id?: number | null }; overrideCredentialUsername?: boolean; userId?: string; enableTerminal: boolean;