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
JavaScript
;
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;