From 965baae093c22d2a67a1e4d76c85da9910806994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 10 Apr 2025 14:35:10 +0200 Subject: [PATCH] feat(core): Add a new option to customize SSH tunnel idle timeout (#14522) --- .../__tests__/ssh-clients-manager.test.ts | 6 ++++-- .../core/src/execution-engine/ssh-clients-manager.ts | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/execution-engine/__tests__/ssh-clients-manager.test.ts b/packages/core/src/execution-engine/__tests__/ssh-clients-manager.test.ts index d58fe22802..a549cbbd25 100644 --- a/packages/core/src/execution-engine/__tests__/ssh-clients-manager.test.ts +++ b/packages/core/src/execution-engine/__tests__/ssh-clients-manager.test.ts @@ -1,9 +1,11 @@ +import { mock } from 'jest-mock-extended'; import type { SSHCredentials } from 'n8n-workflow'; import { Client } from 'ssh2'; import { SSHClientsManager } from '../ssh-clients-manager'; describe('SSHClientsManager', () => { + const idleTimeout = 5 * 60; const credentials: SSHCredentials = { sshAuthenticateWith: 'password', sshHost: 'example.com', @@ -20,7 +22,7 @@ describe('SSHClientsManager', () => { jest.clearAllMocks(); jest.useFakeTimers(); - sshClientsManager = new SSHClientsManager(); + sshClientsManager = new SSHClientsManager(mock({ idleTimeout })); connectSpy.mockImplementation(function (this: Client) { this.emit('ready'); return this; @@ -59,7 +61,7 @@ describe('SSHClientsManager', () => { await sshClientsManager.getClient({ ...credentials, sshHost: 'host2' }); await sshClientsManager.getClient({ ...credentials, sshHost: 'host3' }); - jest.advanceTimersByTime(6 * 60 * 1000); + jest.advanceTimersByTime((idleTimeout + 1) * 1000); sshClientsManager.cleanupStaleConnections(); expect(endSpy).toHaveBeenCalledTimes(3); diff --git a/packages/core/src/execution-engine/ssh-clients-manager.ts b/packages/core/src/execution-engine/ssh-clients-manager.ts index a29c5facc7..4156cb3ec3 100644 --- a/packages/core/src/execution-engine/ssh-clients-manager.ts +++ b/packages/core/src/execution-engine/ssh-clients-manager.ts @@ -1,13 +1,21 @@ +import { Config, Env } from '@n8n/config'; import { Service } from '@n8n/di'; import type { SSHCredentials } from 'n8n-workflow'; import { createHash } from 'node:crypto'; import { Client, type ConnectConfig } from 'ssh2'; +@Config +class SSHClientsConfig { + /** How many seconds before an idle SSH tunnel is closed */ + @Env('N8N_SSH_TUNNEL_IDLE_TIMEOUT') + idleTimeout: number = 5 * 60; +} + @Service() export class SSHClientsManager { readonly clients = new Map(); - constructor() { + constructor(private readonly config: SSHClientsConfig) { // Close all SSH connections when the process exits process.on('exit', () => this.onShutdown()); @@ -67,7 +75,7 @@ export class SSHClientsManager { const now = Date.now(); for (const [hash, { client, lastUsed }] of clients.entries()) { - if (now - lastUsed.getTime() > 5 * 60 * 1000) { + if (now - lastUsed.getTime() > this.config.idleTimeout * 1000) { client.end(); clients.delete(hash); }