UNPKG

n8n-nodes-customssh

Version:

n8n community node for advanced SSH connections with configurable ciphers and network device support

204 lines (203 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SshConnectionManager = void 0; const ssh2_1 = require("ssh2"); const LoggingUtils_1 = require("../utils/LoggingUtils"); const CipherUtils_1 = require("../utils/CipherUtils"); /** * Manager for SSH connections */ class SshConnectionManager { /** * Create a new SSH client connection */ static async createConnection(host, port, username, password, cipher, options) { var _a, _b, _c; const conn = new ssh2_1.Client(); // Get compatibility settings const compatibilityLevel = ((_a = options.advancedSecurity) === null || _a === void 0 ? void 0 : _a.compatibilityLevel) || 'medium'; const securityLevel = ((_b = options.advancedSecurity) === null || _b === void 0 ? void 0 : _b.securityLevel) || 'medium'; const allowLegacyAlgorithms = ((_c = options.advancedSecurity) === null || _c === void 0 ? void 0 : _c.allowLegacyAlgorithms) !== false; // For Aruba OS switches, we need higher compatibility if (options.deviceType === 'aruba-os') { LoggingUtils_1.LoggingUtils.log('Using high compatibility settings for Aruba OS switch', options.verboseLogging || false); // Override compatibility settings for Aruba OS options.advancedSecurity = { compatibilityLevel: 'high', securityLevel: 'low', allowLegacyAlgorithms: true, }; } await new Promise((resolve, reject) => { var _a, _b, _c, _d; const connectTimeout = setTimeout(() => { conn.end(); reject(new Error(`Connection timeout after ${options.connTimeout}ms`)); }, options.connTimeout); conn.on('ready', () => { clearTimeout(connectTimeout); LoggingUtils_1.LoggingUtils.log(`Successfully connected to ${host}:${port} with cipher ${cipher}`, options.verboseLogging || false); resolve(); }); conn.on('error', (err) => { clearTimeout(connectTimeout); reject(err); }); conn.on('keyboard-interactive', (name, instructions, lang, prompts, finish) => { LoggingUtils_1.LoggingUtils.log('Interactive auth detected', options.verboseLogging || false); // Handle keyboard-interactive authentication const responses = []; for (const prompt of prompts) { if (prompt.prompt.toLowerCase().includes('password')) { responses.push(password); } else { responses.push(''); // Empty response for non-password prompts } } finish(responses); }); try { // Configure algorithms based on compatibility settings const kexAlgorithms = CipherUtils_1.CipherUtils.configureKexAlgorithms(((_a = options.advancedSecurity) === null || _a === void 0 ? void 0 : _a.compatibilityLevel) || compatibilityLevel); const hmacAlgorithms = CipherUtils_1.CipherUtils.configureHmacAlgorithms(((_b = options.advancedSecurity) === null || _b === void 0 ? void 0 : _b.securityLevel) || securityLevel); // Determine if we're connecting with a single cipher or allowing multiple const cipherAlgorithms = options.fallbackCiphers ? ((_c = options.advancedSecurity) === null || _c === void 0 ? void 0 : _c.allowLegacyAlgorithms) || allowLegacyAlgorithms ? CipherUtils_1.CipherUtils.configureCiphers('all') : CipherUtils_1.CipherUtils.configureCiphers('secure-only') : [cipher]; // Server host key algorithms with proper typing let serverHostKeys = ((_d = options.advancedSecurity) === null || _d === void 0 ? void 0 : _d.allowLegacyAlgorithms) || allowLegacyAlgorithms ? [ 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'rsa-sha2-512', 'rsa-sha2-256', ] : [ 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'rsa-sha2-512', 'rsa-sha2-256', ]; // For Aruba OS, we want to use the most compatible algorithms if (options.deviceType === 'aruba-os') { // Aruba OS switches might need specific algorithms serverHostKeys = [ 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', ]; } // Connect with enhanced algorithm options using 'hmac' instead of 'mac' if (options.verboseLogging) { LoggingUtils_1.LoggingUtils.log(`Connecting with algorithms: ${JSON.stringify({ cipher: cipherAlgorithms, kex: kexAlgorithms, hmac: hmacAlgorithms, serverHostKey: serverHostKeys, })}`, true); } conn.connect({ host, port, username, password, algorithms: { cipher: cipherAlgorithms, kex: kexAlgorithms, hmac: hmacAlgorithms, serverHostKey: serverHostKeys, }, tryKeyboard: true, readyTimeout: options.connTimeout, }); } catch (error) { reject(new Error(`Failed to configure SSH connection: ${error.message}`)); } }); return conn; } /** * Create a shell stream from an SSH client */ static async createShell(client, options) { return new Promise((resolve, reject) => { // Define the shell options according to the correct type const shellOptions = { term: options.terminalType || 'vt100', rows: 24, cols: 80, // Fix the ECHO value to match expected type modes: { ECHO: 1, // Use type assertion to specify it's exactly 1, not just a number TTY_OP_ISPEED: 115200, TTY_OP_OSPEED: 115200, }, }; client.shell(shellOptions, (err, stream) => { if (err) { reject(new Error(`Failed to open shell: ${err.message}`)); return; } // Set up error handler with proper type stream.on('error', (streamErr) => { reject(new Error(`Shell stream error: ${streamErr.message}`)); }); // Initialize the shell with a few Enter key presses to trigger prompt if (options.sendInitialCR) { LoggingUtils_1.LoggingUtils.log('Sending initial CR to stimulate prompt', options.verboseLogging || false); stream.write('\r\n'); // Many devices need a small delay after connection before they're ready setTimeout(() => { // Send another CR and wait longer for Aruba switches stream.write('\r\n'); // Add special handling for different Aruba switch types if (options.deviceType === 'aruba') { setTimeout(() => { // Send a harmless command to stimulate output stream.write(' \r\n'); resolve(stream); }, 500); } else if (options.deviceType === 'aruba-os') { // Aruba OS might need a longer delay and multiple CR attempts setTimeout(() => { // Send a specific series of carriage returns for Aruba OS stream.write('\r\n'); setTimeout(() => { // Send another CR - this helps with terminal negotiation stream.write('\r\n'); setTimeout(() => { // Third CR often needed for Aruba OS stream.write('\r\n'); setTimeout(() => { // Finally send the 'no pag' command to help with pagination stream.write('no pag\r\n'); resolve(stream); }, 500); }, 500); }, 500); }, 1000); } else { resolve(stream); } }, 1000); // Increased from 500ms to 1000ms } else { resolve(stream); } }); }); } } exports.SshConnectionManager = SshConnectionManager;