ssh-bridge-ai
Version:
One Command Magic SSH with Invisible Analytics - Connect to any server instantly with 'sshbridge user@server'. Zero setup, zero friction, pure magic. Industry-standard security with behind-the-scenes business intelligence.
521 lines (453 loc) • 17 kB
JavaScript
const logger = require('./utils/logger');
const { EnhancedSSHClient } = require('./ssh-enhanced');
/**
* SSH Connection Validation and Testing Framework
* Implements comprehensive testing strategies from the ssh_time_fix.md document
*/
class SSHConnectionTester {
constructor(serverConfig) {
this.serverConfig = serverConfig;
this.testResults = {};
this.connection = null;
}
/**
* Run comprehensive server capability tests
*/
async validateServerCapabilities() {
logger.info('Starting comprehensive server capability validation');
const tests = [
{ name: 'SSH Service', test: this.testSSHService.bind(this) },
{ name: 'Authentication', test: this.testAuthentication.bind(this) },
{ name: 'Shell Access', test: this.testShellAccess.bind(this) },
{ name: 'Home Directory', test: this.testHomeDirectory.bind(this) },
{ name: 'Permissions', test: this.testPermissions.bind(this) },
{ name: 'Environment', test: this.testEnvironment.bind(this) },
{ name: 'Network Stability', test: this.testNetworkStability.bind(this) },
{ name: 'Command Execution', test: this.testCommandExecution.bind(this) }
];
const results = {};
for (const test of tests) {
try {
logger.info(`Running test: ${test.name}`);
results[test.name] = await test.test();
logger.info(`Test ${test.name} completed: ${results[test.name].success ? 'PASS' : 'FAIL'}`);
} catch (error) {
logger.error(`Test ${test.name} failed with error`, { error: error.message });
results[test.name] = { success: false, error: error.message };
}
}
this.testResults = results;
return results;
}
/**
* Test SSH service availability
*/
async testSSHService() {
try {
// Basic connection test without authentication
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
readyTimeout: 10000, // 10 seconds for service test
keepalive: false
});
// Try to connect (this will fail at authentication, but proves service is running)
await ssh.connect();
return { success: true, message: 'SSH service is running and accessible' };
} catch (error) {
if (error.message.includes('Authentication failed') || error.message.includes('Permission denied')) {
return { success: true, message: 'SSH service is running (authentication failed as expected)' };
} else if (error.message.includes('Connection refused') || error.message.includes('ECONNREFUSED')) {
return { success: false, error: 'SSH service is not running or not accessible' };
} else if (error.message.includes('timeout') || error.message.includes('ETIMEDOUT')) {
return { success: false, error: 'SSH service connection timeout' };
} else {
return { success: false, error: `SSH service test failed: ${error.message}` };
}
}
}
/**
* Test authentication methods
*/
async testAuthentication() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey,
readyTimeout: 30000
});
await ssh.connect();
return {
success: true,
message: 'Authentication successful',
method: this.serverConfig.password ? 'password' : 'private_key'
};
} catch (error) {
return {
success: false,
error: `Authentication failed: ${error.message}`,
method: this.serverConfig.password ? 'password' : 'private_key'
};
}
}
/**
* Test shell access and functionality
*/
async testShellAccess() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey,
progressiveFallback: true
});
await ssh.connect();
// Test basic shell commands
const testCommands = [
'echo "shell_test"',
'whoami',
'pwd',
'uname -a'
];
const results = {};
for (const command of testCommands) {
try {
const result = await ssh.exec(command);
results[command] = { success: true, output: result.stdout.trim() };
} catch (error) {
results[command] = { success: false, error: error.message };
}
}
const successfulCommands = Object.values(results).filter(r => r.success).length;
const totalCommands = testCommands.length;
return {
success: successfulCommands > 0,
message: `Shell access test: ${successfulCommands}/${totalCommands} commands successful`,
details: results,
shellType: await this.detectShellType(ssh)
};
} catch (error) {
return { success: false, error: `Shell access test failed: ${error.message}` };
}
}
/**
* Test home directory accessibility
*/
async testHomeDirectory() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey
});
await ssh.connect();
// Test home directory access
const homeTest = await ssh.exec('echo $HOME && ls -la $HOME 2>/dev/null || echo "HOME_DIR_ACCESS_DENIED"');
if (homeTest.stdout.includes('HOME_DIR_ACCESS_DENIED')) {
return {
success: false,
error: 'Home directory exists but is not accessible',
homePath: homeTest.stdout.split('\n')[0],
accessible: false
};
}
const homePath = homeTest.stdout.split('\n')[0];
const homeContents = homeTest.stdout.split('\n').slice(1).join('\n');
return {
success: true,
message: 'Home directory is accessible',
homePath: homePath,
accessible: true,
contents: homeContents,
hasFiles: homeContents.length > 0
};
} catch (error) {
return { success: false, error: `Home directory test failed: ${error.message}` };
}
}
/**
* Test user permissions and access rights
*/
async testPermissions() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey
});
await ssh.connect();
// Test various permission levels
const permissionTests = [
{ command: 'id', description: 'User identity' },
{ command: 'groups', description: 'User groups' },
{ command: 'ls -la /tmp', description: 'Temp directory access' },
{ command: 'ls -la /var/tmp', description: 'Var temp access' },
{ command: 'ls -la /home', description: 'Home directory listing' },
{ command: 'ls -la /usr/local', description: 'Local bin access' }
];
const results = {};
for (const test of permissionTests) {
try {
const result = await ssh.exec(test.command);
results[test.description] = { success: true, output: result.stdout.trim() };
} catch (error) {
results[test.description] = { success: false, error: error.message };
}
}
const successfulTests = Object.values(results).filter(r => r.success).length;
const totalTests = permissionTests.length;
return {
success: successfulTests > 0,
message: `Permission test: ${successfulTests}/${totalTests} tests successful`,
details: results,
userInfo: results['User identity']?.output || 'Unknown'
};
} catch (error) {
return { success: false, error: `Permission test failed: ${error.message}` };
}
}
/**
* Test environment variables and shell environment
*/
async testEnvironment() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey
});
await ssh.connect();
// Test environment variables
const envTests = [
'echo $SHELL',
'echo $PATH',
'echo $HOME',
'echo $USER',
'echo $TERM',
'echo $LANG'
];
const results = {};
for (const test of envTests) {
try {
const result = await ssh.exec(test);
const varName = test.split('$')[1];
results[varName] = { success: true, value: result.stdout.trim() };
} catch (error) {
const varName = test.split('$')[1];
results[varName] = { success: false, error: error.message };
}
}
return {
success: true,
message: 'Environment test completed',
details: results,
shell: results.SHELL?.value || 'Unknown',
path: results.PATH?.value || 'Unknown',
home: results.HOME?.value || 'Unknown'
};
} catch (error) {
return { success: false, error: `Environment test failed: ${error.message}` };
}
}
/**
* Test network stability and connection quality
*/
async testNetworkStability() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey,
keepalive: true,
keepaliveInterval: 10
});
await ssh.connect();
// Test connection stability over time
const stabilityTest = await ssh.testConnectionStability(30000); // 30 seconds
return {
success: stabilityTest.success,
message: stabilityTest.success ? 'Network connection is stable' : 'Network connection is unstable',
duration: stabilityTest.duration,
tests: stabilityTest.tests,
averageResponseTime: stabilityTest.averageResponseTime,
failureReason: stabilityTest.failureReason
};
} catch (error) {
return { success: false, error: `Network stability test failed: ${error.message}` };
}
}
/**
* Test command execution capabilities
*/
async testCommandExecution() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey
});
await ssh.connect();
// Test various command types
const commandTests = [
{ command: 'echo "test"', type: 'Basic output' },
{ command: 'date', type: 'System command' },
{ command: 'ls -la', type: 'File listing' },
{ command: 'ps aux | head -5', type: 'Process listing' },
{ command: 'df -h', type: 'Disk usage' },
{ command: 'free -h', type: 'Memory usage' }
];
const results = {};
for (const test of commandTests) {
try {
const result = await ssh.exec(test.command);
results[test.type] = { success: true, output: result.stdout.trim() };
} catch (error) {
results[test.type] = { success: false, error: error.message };
}
}
const successfulCommands = Object.values(results).filter(r => r.success).length;
const totalCommands = commandTests.length;
return {
success: successfulCommands > 0,
message: `Command execution test: ${successfulCommands}/${totalCommands} commands successful`,
details: results,
executionRate: (successfulCommands / totalCommands) * 100
};
} catch (error) {
return { success: false, error: `Command execution test failed: ${error.message}` };
}
}
/**
* Detect the type of shell available
*/
async detectShellType(ssh) {
try {
const shellResult = await ssh.exec('echo $SHELL');
const shellPath = shellResult.stdout.trim();
if (shellPath.includes('bash')) return 'bash';
if (shellPath.includes('zsh')) return 'zsh';
if (shellPath.includes('ksh')) return 'ksh';
if (shellPath.includes('dash')) return 'dash';
if (shellPath.includes('sh')) return 'sh';
return 'unknown';
} catch (error) {
return 'unknown';
}
}
/**
* Generate comprehensive test report
*/
generateTestReport() {
const totalTests = Object.keys(this.testResults).length;
const passedTests = Object.values(this.testResults).filter(r => r.success).length;
const failedTests = totalTests - passedTests;
const successRate = (passedTests / totalTests) * 100;
const report = {
summary: {
totalTests,
passedTests,
failedTests,
successRate: `${successRate.toFixed(1)}%`
},
results: this.testResults,
recommendations: this.generateRecommendations(),
timestamp: new Date().toISOString()
};
return report;
}
/**
* Generate recommendations based on test results
*/
generateRecommendations() {
const recommendations = [];
if (!this.testResults['SSH Service']?.success) {
recommendations.push('SSH service is not accessible. Check if SSH daemon is running and firewall rules.');
}
if (!this.testResults['Authentication']?.success) {
recommendations.push('Authentication failed. Verify username, password, or SSH key.');
}
if (!this.testResults['Shell Access']?.success) {
recommendations.push('Shell access is limited. Try alternative shell strategies.');
}
if (!this.testResults['Home Directory']?.success) {
recommendations.push('Home directory issues detected. Use temporary directory workaround.');
}
if (!this.testResults['Permissions']?.success) {
recommendations.push('Limited permissions detected. Check user account restrictions.');
}
if (!this.testResults['Network Stability']?.success) {
recommendations.push('Network connection is unstable. Enable keepalive and increase timeouts.');
}
if (recommendations.length === 0) {
recommendations.push('All tests passed. Server appears to be fully compatible.');
}
return recommendations;
}
/**
* Run a quick connectivity test
*/
async quickConnectivityTest() {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey,
readyTimeout: 15000,
progressiveFallback: false
});
await ssh.connect();
const result = await ssh.exec('echo "connectivity_test"');
await ssh.dispose();
return {
success: true,
message: 'Quick connectivity test passed',
response: result.stdout.trim()
};
} catch (error) {
return {
success: false,
error: `Quick connectivity test failed: ${error.message}`
};
}
}
/**
* Test specific command execution
*/
async testSpecificCommand(command, options = {}) {
try {
const testConnection = `${this.serverConfig.username}@${this.serverConfig.hostname}`;
const ssh = new EnhancedSSHClient(testConnection, {
port: this.serverConfig.port || 22,
password: this.serverConfig.password,
key: this.serverConfig.privateKey,
...options
});
await ssh.connect();
const result = await ssh.exec(command);
await ssh.dispose();
return {
success: true,
command: command,
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode
};
} catch (error) {
return {
success: false,
command: command,
error: error.message
};
}
}
}
module.exports = { SSHConnectionTester };