Перейти к основному содержимому

SSH Target Configuration

Configure and establish SSH connections for remote command execution with advanced features like connection pooling, tunneling, and secure authentication.

Installation

The SSH adapter requires the ssh2 module:

npm install @xec-sh/core ssh2
# or
yarn add @xec-sh/core ssh2

Basic Configuration

Simple SSH Connection

Connect to a remote server with basic authentication:

import { $ } from '@xec-sh/core';

// Password authentication
const result = await $({
ssh: {
host: 'server.example.com',
username: 'user',
password: 'password'
}
})`uname -a`;

// Key-based authentication
const output = await $({
ssh: {
host: 'server.example.com',
username: 'user',
privateKey: '/home/user/.ssh/id_rsa'
}
})`ls -la`;

// With passphrase-protected key
const data = await $({
ssh: {
host: 'server.example.com',
username: 'user',
privateKey: '/home/user/.ssh/id_ed25519',
passphrase: 'key-passphrase'
}
})`cat /etc/hostname`;

Advanced SSH Options

Configure detailed SSH connection parameters:

import { createExecutionEngine } from '@xec-sh/core';

const engine = createExecutionEngine({
adapters: {
ssh: {
// Connection pool settings
connectionPool: {
enabled: true,
maxConnections: 10,
idleTimeout: 300000, // 5 minutes
keepAlive: true,
keepAliveInterval: 30000, // 30 seconds
autoReconnect: true,
maxReconnectAttempts: 3,
reconnectDelay: 1000
},

// Default SSH connection options
defaultConnectOptions: {
port: 22,
readyTimeout: 20000,
strictHostKeyChecking: true,
algorithms: {
kex: ['ecdh-sha2-nistp256', 'ecdh-sha2-nistp384'],
cipher: ['aes128-gcm', 'aes256-gcm'],
serverHostKey: ['ssh-rsa', 'ssh-ed25519']
}
},

// Sudo configuration
sudo: {
enabled: false,
password: 'sudo-password',
prompt: '[sudo] password',
method: 'stdin'
},

// SFTP options
sftp: {
enabled: true,
concurrency: 5
}
}
}
});

Connection Methods

Direct Connection

Connect directly to SSH servers:

// Standard SSH port
await $({
ssh: {
host: '192.168.1.100',
username: 'admin'
}
})`systemctl status nginx`;

// Custom port
await $({
ssh: {
host: 'server.com',
port: 2222,
username: 'deploy'
}
})`docker ps`;

// IPv6 address
await $({
ssh: {
host: '2001:db8::1',
username: 'user'
}
})`ip addr show`;

Jump Host (Bastion)

Connect through intermediate servers:

// Through jump host
const result = await $({
ssh: {
host: 'internal.server',
username: 'user',
privateKey: '/path/to/key',
jumpHost: {
host: 'bastion.example.com',
username: 'jump-user',
privateKey: '/path/to/jump-key'
}
}
})`hostname`;

// Multiple jump hosts
const output = await $({
ssh: {
host: 'target.internal',
username: 'user',
jumpHosts: [
{
host: 'bastion1.example.com',
username: 'user1'
},
{
host: 'bastion2.internal',
username: 'user2'
}
]
}
})`pwd`;

SSH Agent

Use SSH agent for authentication:

// Use SSH agent
await $({
ssh: {
host: 'server.com',
username: 'user',
agent: process.env.SSH_AUTH_SOCK || '/tmp/ssh-agent.sock'
}
})`ls -la`;

// With agent forwarding
await $({
ssh: {
host: 'server.com',
username: 'user',
agent: process.env.SSH_AUTH_SOCK,
agentForward: true
}
})`ssh nested-server 'hostname'`;

Host Configuration

SSH Config File Integration

Use SSH config file settings:

import { readFileSync } from 'fs';
import { SSHConfig } from '@xec-sh/core';

// Parse SSH config
const config = SSHConfig.parse(
readFileSync(`${process.env.HOME}/.ssh/config`, 'utf8')
);

// Use config for host
const hostConfig = config.compute('myserver');
await $({
ssh: {
host: hostConfig.HostName,
port: hostConfig.Port,
username: hostConfig.User,
privateKey: hostConfig.IdentityFile[0]
}
})`uptime`;

Dynamic Host Configuration

Configure hosts dynamically:

// Host configuration factory
function createSSHConfig(environment) {
const configs = {
development: {
host: 'dev.example.com',
username: 'developer',
privateKey: '~/.ssh/dev_key'
},
staging: {
host: 'staging.example.com',
username: 'deploy',
privateKey: '~/.ssh/staging_key'
},
production: {
host: 'prod.example.com',
username: 'admin',
privateKey: '~/.ssh/prod_key',
strictHostKeyChecking: true
}
};

return configs[environment];
}

// Use dynamic config
const env = process.env.NODE_ENV || 'development';
await $({
ssh: createSSHConfig(env)
})`node --version`;

Host Key Verification

Manage SSH host key verification:

// Strict host key checking (default)
await $({
ssh: {
host: 'server.com',
username: 'user',
strictHostKeyChecking: true,
hostVerifier: (hostkey) => {
// Custom verification logic
const knownHosts = loadKnownHosts();
return knownHosts.includes(hostkey);
}
}
})`ls`;

// Skip host key checking (NOT recommended for production)
await $({
ssh: {
host: 'test.local',
username: 'test',
strictHostKeyChecking: false
}
})`echo "Test environment"`;

// Add host key hash
await $({
ssh: {
host: 'server.com',
username: 'user',
hostHash: 'sha256',
hostFingerprint: 'SHA256:1234567890abcdef...'
}
})`pwd`;

Connection Options

Timeout Configuration

Set various timeout values:

// Connection timeouts
await $({
ssh: {
host: 'slow.server.com',
username: 'user',
readyTimeout: 30000, // Connection ready timeout (30s)
keepaliveInterval: 10000, // Keepalive interval (10s)
keepaliveCountMax: 3 // Max keepalive attempts
},
timeout: 60000 // Command execution timeout (60s)
})`long-running-command`;

Compression

Enable compression for slow connections:

// Enable compression
await $({
ssh: {
host: 'remote.server.com',
username: 'user',
compress: true,
compression: {
level: 6 // Compression level (1-9)
}
}
})`cat large-file.txt`;

Cipher and Algorithm Selection

Configure encryption algorithms:

// Specify preferred algorithms
await $({
ssh: {
host: 'secure.server.com',
username: 'user',
algorithms: {
kex: [
'ecdh-sha2-nistp521',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp256'
],
cipher: [
'aes256-gcm@openssh.com',
'aes128-gcm@openssh.com',
'aes256-ctr'
],
serverHostKey: [
'ssh-ed25519',
'ecdsa-sha2-nistp256',
'ssh-rsa'
],
hmac: [
'hmac-sha2-512',
'hmac-sha2-256'
]
}
}
})`echo "Secure connection"`;

Environment Variables

Remote Environment Setup

Configure environment variables on remote hosts:

// Set remote environment variables
await $({
ssh: {
host: 'server.com',
username: 'user'
},
env: {
NODE_ENV: 'production',
APP_PORT: '3000',
DEBUG: 'app:*'
}
})`node app.js`;

// Export variables in command
await $({
ssh: sshConfig
})`
export DB_HOST=localhost
export DB_PORT=5432
psql -h $DB_HOST -p $DB_PORT
`;

Path Configuration

Manage remote PATH:

// Extend remote PATH
await $({
ssh: sshConfig,
env: {
PATH: '/usr/local/bin:/usr/bin:/bin:/custom/bin'
}
})`which custom-command`;

// Use specific shell with PATH
await $({
ssh: sshConfig
})`
source ~/.bashrc
export PATH=$PATH:/opt/tools/bin
tool --version
`;

Working Directory

Set Remote Working Directory

Execute commands in specific directories:

// Change working directory
await $({
ssh: sshConfig,
cwd: '/var/www/app'
})`npm install`;

// Create directory if needed
await $({
ssh: sshConfig
})`
mkdir -p /tmp/workspace
cd /tmp/workspace
pwd
`;

Error Handling

Connection Errors

Handle various connection failures:

async function connectWithRetry(config, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await $.with({ ssh: config })`hostname`;
} catch (error) {
console.log(`Attempt ${attempt} failed: ${error.message}`);

if (error.code === 'ECONNREFUSED') {
console.log('Connection refused - is SSH service running?');
} else if (error.code === 'ETIMEDOUT') {
console.log('Connection timeout - check network/firewall');
} else if (error.code === 'ENOTFOUND') {
console.log('Host not found - check hostname');
} else if (error.level === 'client-authentication') {
console.log('Authentication failed - check credentials');
break; // Don't retry auth failures
}

if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 2000 * attempt));
}
}
}
throw new Error('Failed to establish SSH connection');
}

Command Execution Errors

Handle remote command failures:

try {
await $.with({ ssh: sshConfig })`false`;
} catch (error) {
if (error.exitCode === 1) {
console.log('Command returned false');
} else if (error.exitCode === 127) {
console.log('Command not found on remote host');
} else if (error.signal) {
console.log(`Command terminated by signal: ${error.signal}`);
}

// Access error details
console.log('Remote host:', error.details?.host);
console.log('Remote stderr:', error.stderr);
}

Security Best Practices

Key Management

Secure SSH key handling:

import { SSHKeyValidator } from '@xec-sh/core';

// Validate SSH key before use
const keyPath = '/home/user/.ssh/id_rsa';
const validator = new SSHKeyValidator();

if (await validator.validate(keyPath)) {
await $({
ssh: {
host: 'server.com',
username: 'user',
privateKey: keyPath
}
})`ls`;
} else {
console.error('Invalid or insecure SSH key');
}

// Use encrypted key with passphrase
const passphrase = await getPassphraseSecurely();
await $({
ssh: {
host: 'server.com',
username: 'user',
privateKey: readFileSync('/path/to/encrypted/key'),
passphrase
}
})`secure-command`;

Secure Password Handling

Handle passwords securely:

import { SecurePasswordHandler } from '@xec-sh/core';

// Create secure password handler
const passwordHandler = new SecurePasswordHandler();

// Get password securely (from keychain, env, or prompt)
const password = await passwordHandler.getPassword('ssh-server');

await $({
ssh: {
host: 'server.com',
username: 'user',
password
}
})`ls`;

// Clean up
passwordHandler.clearPassword();

Audit Logging

Log SSH connections for audit:

class SSHAuditLogger {
async execute(sshConfig, command) {
const audit = {
timestamp: new Date().toISOString(),
host: sshConfig.host,
user: sshConfig.username,
command,
sourceIP: await this.getSourceIP()
};

console.log('SSH Audit:', audit);

try {
const result = await $.with({ ssh: sshConfig })`${command}`;
audit.success = true;
audit.exitCode = result.exitCode;
await this.logAudit(audit);
return result;
} catch (error) {
audit.success = false;
audit.error = error.message;
await this.logAudit(audit);
throw error;
}
}

async logAudit(entry) {
// Log to secure audit trail
await fs.appendFile('/var/log/ssh-audit.log',
JSON.stringify(entry) + '\n'
);
}
}

Performance Optimization

Connection Pooling

Reuse SSH connections efficiently:

// Enable connection pooling
const pooledSSH = {
host: 'server.com',
username: 'user',
connectionPool: {
enabled: true,
maxConnections: 5,
idleTimeout: 600000, // 10 minutes
keepAlive: true
}
};

// Multiple commands reuse the same connection
for (let i = 0; i < 100; i++) {
await $.with({ ssh: pooledSSH })`echo "Command ${i}"`;
}

Multiplexing

Use SSH multiplexing for performance:

// Configure multiplexing
await $({
ssh: {
host: 'server.com',
username: 'user',
multiplexing: {
enabled: true,
controlPath: '/tmp/ssh-mux-%h-%p-%r',
controlPersist: 600 // Keep master for 10 minutes
}
}
})`ls`;

Troubleshooting

Debug SSH Connections

Enable debugging for troubleshooting:

// Enable SSH debug output
await $({
ssh: {
host: 'server.com',
username: 'user',
debug: true,
debugLevel: 3 // 1-3, higher = more verbose
}
})`ls`;

// Custom debug handler
await $({
ssh: {
host: 'server.com',
username: 'user',
debug: (message) => {
console.log('[SSH Debug]', message);
}
}
})`pwd`;

Common Issues

Connection Timeout

// Increase timeout for slow networks
await $({
ssh: {
host: 'distant.server.com',
username: 'user',
readyTimeout: 60000, // 60 seconds
timeout: 120000 // 2 minutes for command
}
})`slow-command`;

Host Key Changes

// Handle changed host keys
try {
await $.with({ ssh: config })`ls`;
} catch (error) {
if (error.message.includes('Host key verification failed')) {
console.log('Host key has changed!');
// Update known_hosts or investigate security breach
}
}

Authentication Failures

// Try multiple authentication methods
const authMethods = [
{ privateKey: '~/.ssh/id_ed25519' },
{ privateKey: '~/.ssh/id_rsa' },
{ password: process.env.SSH_PASSWORD },
{ agent: process.env.SSH_AUTH_SOCK }
];

for (const auth of authMethods) {
try {
await $({
ssh: {
host: 'server.com',
username: 'user',
...auth
}
})`hostname`;
break;
} catch (error) {
console.log(`Auth method failed: ${JSON.stringify(auth)}`);
}
}

Next Steps