diff --git a/src/backend/hosts/file-manager/index.ts b/src/backend/hosts/file-manager/index.ts index dcce4418..3e1584f5 100644 --- a/src/backend/hosts/file-manager/index.ts +++ b/src/backend/hosts/file-manager/index.ts @@ -10,7 +10,7 @@ import { createCorsMiddleware } from "../../utils/cors-config.js"; import { createCompressionMiddleware } from "../../utils/compression-config.js"; import cookieParser from "cookie-parser"; import axios from "axios"; -import ssh2Pkg, { Client as SSHClient } from "ssh2"; +import ssh2Pkg, { Client as SSHClient, type ConnectConfig } from "ssh2"; import { SSH_ALGORITHMS } from "../../utils/ssh-algorithms.js"; import { createCurrentHostResolutionRepository } from "../../database/repositories/factory.js"; import { fileLogger } from "../../utils/logger.js"; @@ -829,6 +829,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { let resolvedSocks5Username = socks5Username; let resolvedSocks5Password = socks5Password; let resolvedSocks5ProxyChain = socks5ProxyChain; + let resolvedVaultProfileId: number | null = null; if (hostId && userId && !password && !sshKey) { try { const { resolveHostById, resolveHostBySyncId } = @@ -855,6 +856,9 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { hostKeepaliveInterval = resolvedHost.terminalConfig?.keepaliveInterval; hostKeepaliveCountMax = resolvedHost.terminalConfig?.keepaliveCountMax; resolvedScpLegacy = resolvedHost.scpLegacy ?? false; + resolvedVaultProfileId = + (resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? + null; if (resolvedHost.useSocks5) { resolvedUseSocks5 = resolvedHost.useSocks5; resolvedSocks5Host = resolvedHost.socks5Host; @@ -924,6 +928,9 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { hostKeepaliveInterval = resolvedHost.terminalConfig?.keepaliveInterval; hostKeepaliveCountMax = resolvedHost.terminalConfig?.keepaliveCountMax; resolvedScpLegacy = resolvedHost.scpLegacy ?? false; + resolvedVaultProfileId = + (resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? + null; if (resolvedHost.useSocks5) { resolvedUseSocks5 = resolvedHost.useSocks5; resolvedSocks5Host = resolvedHost.socks5Host; @@ -1175,6 +1182,38 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { connectionLogs, }); } + } else if (resolvedCredentials.authType === "vault") { + try { + const { setupVaultSshSignerAuth } = + await import("../vault-ssh-connect.js"); + await setupVaultSshSignerAuth(config as ConnectConfig, client, { + id: hostId, + username: resolvedUsername, + userId, + vaultProfile: { id: resolvedVaultProfileId }, + }); + connectionLogs.push( + createConnectionLog( + "info", + "sftp_auth", + "Using cached Vault-signed certificate", + ), + ); + } catch (vaultError) { + const message = getErrorMessage(vaultError); + fileLogger.warn("Vault authentication unavailable for file manager", { + operation: "file_connect_vault_auth", + sessionId, + hostId, + error: message, + }); + connectionLogs.push(createConnectionLog("error", "sftp_auth", message)); + return res.status(401).json({ + error: message, + requiresVaultAuth: true, + connectionLogs, + }); + } } else if (resolvedCredentials.authType === "agent") { const result = await applyAgentAuth(config, resolvedTerminalConfig); if ("error" in result) { diff --git a/src/backend/tests/hosts/file-manager/vault-auth.test.ts b/src/backend/tests/hosts/file-manager/vault-auth.test.ts new file mode 100644 index 00000000..b4f1fb6e --- /dev/null +++ b/src/backend/tests/hosts/file-manager/vault-auth.test.ts @@ -0,0 +1,25 @@ +import fs from "fs"; +import path from "path"; +import { describe, expect, it } from "vitest"; + +describe("file manager Vault authentication", () => { + it("configures the cached Vault certificate before connecting", () => { + const source = fs.readFileSync( + path.resolve(__dirname, "../../../hosts/file-manager/index.ts"), + "utf8", + ); + const vaultBranch = source.indexOf( + 'resolvedCredentials.authType === "vault"', + ); + const agentBranch = source.indexOf( + 'resolvedCredentials.authType === "agent"', + vaultBranch, + ); + const body = source.slice(vaultBranch, agentBranch); + + expect(vaultBranch).toBeGreaterThan(-1); + expect(agentBranch).toBeGreaterThan(vaultBranch); + expect(body).toContain("setupVaultSshSignerAuth"); + expect(body).toContain("requiresVaultAuth: true"); + }); +});