diff --git a/docker-compose.yml b/docker-compose.yml index cb6b0a9..a4b1fa7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -241,7 +241,7 @@ services: - 9.9.9.9 # Quad9 (безопасность + блокировка вредоносных доменов) - 8.8.8.8 # Google (надежность, fallback) volumes: - - ~/.ssh:/root/.ssh:rw + - ~/.ssh:/home/webssh/.ssh:rw - /var/run/docker.sock:/var/run/docker.sock:ro # Только чтение для безопасности - /tmp:/tmp # для временных файлов - ./ssl:/app/ssl # для доступа к ключу шифрования diff --git a/webssh-agent/agent.js b/webssh-agent/agent.js index 1b78974..457ddaf 100644 --- a/webssh-agent/agent.js +++ b/webssh-agent/agent.js @@ -3,6 +3,7 @@ const cors = require('cors'); const { exec } = require('child_process'); const fs = require('fs-extra'); const path = require('path'); +const os = require('os'); const http = require('http'); const WebSocket = require('ws'); @@ -15,6 +16,8 @@ const { createAllUsers } = require('./utils/userUtils'); const { cleanupVdsServer, setupRootSshKeys, disablePasswordAuth, setupFirewall } = require('./utils/cleanupUtils'); const { createSshKeys } = require('./utils/localUtils'); +const PUBLIC_KEY_PATH = path.join(os.homedir(), '.ssh', 'id_rsa.pub'); + const app = express(); const server = http.createServer(app); const PORT = process.env.PORT || 3000; @@ -321,7 +324,7 @@ app.post('/vds/setup', logRequest, async (req, res) => { sendWebSocketLog('success', '✅ SSH ключи созданы', 'ssh_keys', 20); // Читаем созданный публичный ключ с хоста - const publicKeyContent = await fs.readFile('/root/.ssh/id_rsa.pub', 'utf8'); + const publicKeyContent = await fs.readFile(PUBLIC_KEY_PATH, 'utf8'); const publicKeyLine = publicKeyContent.trim(); // 2. Настройка SSH ключей для root diff --git a/webssh-agent/utils/localUtils.js b/webssh-agent/utils/localUtils.js index ee426fb..c96693f 100644 --- a/webssh-agent/utils/localUtils.js +++ b/webssh-agent/utils/localUtils.js @@ -1,6 +1,13 @@ const { exec } = require('child_process'); +const os = require('os'); +const path = require('path'); const log = require('./logger'); +const sshDir = path.join(os.homedir(), '.ssh'); +const privateKeyPath = path.join(sshDir, 'id_rsa'); +const publicKeyPath = `${privateKeyPath}.pub`; +const sshConfigPath = path.join(sshDir, 'config'); + /** * Выполнение команд локально (на хосте) */ @@ -24,20 +31,20 @@ const createSshKeys = async (email) => { return new Promise((resolve) => { // Сначала исправляем права доступа к SSH конфигу - exec('chmod 600 /root/.ssh/config 2>/dev/null || true', (configError) => { + exec(`mkdir -p "${sshDir}" && chmod 700 "${sshDir}" && chmod 600 "${sshConfigPath}" 2>/dev/null || true`, (configError) => { if (configError) { log.warn('Не удалось исправить права доступа к SSH конфигу: ' + configError.message); } // Создаем SSH ключи - exec(`ssh-keygen -t rsa -b 4096 -C "${email}" -f ~/.ssh/id_rsa -N ""`, (error, stdout, stderr) => { + exec(`ssh-keygen -t rsa -b 4096 -C "${email}" -f "${privateKeyPath}" -N ""`, (error, stdout, stderr) => { if (error) { log.error('Ошибка создания SSH ключей: ' + error.message); } else { log.success('SSH ключи успешно созданы на хосте'); // Устанавливаем правильные права доступа к созданным ключам - exec('chmod 600 /root/.ssh/id_rsa && chmod 644 /root/.ssh/id_rsa.pub', (permError) => { + exec(`chmod 600 "${privateKeyPath}" && chmod 644 "${publicKeyPath}"`, (permError) => { if (permError) { log.warn('Не удалось установить права доступа к SSH ключам: ' + permError.message); } else { diff --git a/webssh-agent/utils/sshUtils.js b/webssh-agent/utils/sshUtils.js index 9210039..d53568e 100644 --- a/webssh-agent/utils/sshUtils.js +++ b/webssh-agent/utils/sshUtils.js @@ -1,26 +1,26 @@ const { exec } = require('child_process'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); const log = require('./logger'); -/** - * Исправление прав доступа к SSH конфигурации - */ -const fixSshPermissions = async () => { - return new Promise((resolve) => { - // Исправляем владельца и права доступа к SSH конфигу - exec('chown root:root /root/.ssh/config 2>/dev/null || true && chmod 600 /root/.ssh/config 2>/dev/null || true', (error) => { - if (error) { - log.warn('Не удалось исправить права доступа к SSH конфигу: ' + error.message); - } else { - log.info('Права доступа к SSH конфигу исправлены'); - } - resolve(); - }); - }); +const sshDir = path.join(os.homedir(), '.ssh'); +const privateKeyPath = path.join(sshDir, 'id_rsa'); +const publicKeyPath = `${privateKeyPath}.pub`; +const sshConfigPath = path.join(sshDir, 'config'); + +const ensureSshPermissions = async () => { + try { + await fs.ensureDir(sshDir); + await fs.chmod(sshDir, 0o700).catch(() => {}); + await fs.chmod(privateKeyPath, 0o600).catch(() => {}); + await fs.chmod(publicKeyPath, 0o644).catch(() => {}); + await fs.chmod(sshConfigPath, 0o600).catch(() => {}); + } catch (error) { + log.warn('Не удалось скорректировать права доступа к SSH директории: ' + error.message); + } }; -/** - * Выполнение SSH команд с поддержкой ключей и пароля - */ const execSshCommand = async (command, options = {}) => { const { sshHost, @@ -29,26 +29,28 @@ const execSshCommand = async (command, options = {}) => { sshConnectPassword, vdsIp } = options; - - // Исправляем права доступа к SSH конфигу перед выполнением команды - await fixSshPermissions(); - - // Сначала пробуем подключиться с SSH ключами (без BatchMode для возможности fallback на пароль) - let sshCommand = `ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${command.replace(/"/g, '\\"')}"`; - + + await ensureSshPermissions(); + + const privateKeyExists = await fs.pathExists(privateKeyPath); + const escapedCommand = command.replace(/"/g, '\\"'); + + let sshCommand = `ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${escapedCommand}"`; + + if (privateKeyExists) { + sshCommand = `ssh -i "${privateKeyPath}" -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${escapedCommand}"`; + } + log.info(`🔍 Выполняем SSH команду: ${sshCommand}`); - + return new Promise((resolve) => { exec(sshCommand, (error, stdout, stderr) => { log.info(`📤 SSH результат - код: ${error ? error.code : 0}, stdout: "${stdout}", stderr: "${stderr}"`); - + if (error && error.code === 255 && sshConnectPassword) { - // Если подключение с ключами не удалось, пробуем с паролем log.info('SSH ключи не сработали, пробуем с паролем...'); - const passwordCommand = `sshpass -p "${sshConnectPassword}" ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${command.replace(/"/g, '\\"')}"`; - - log.info(`🔍 Выполняем SSH команду с паролем: ${passwordCommand}`); - + const passwordCommand = `sshpass -p "${sshConnectPassword}" ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${escapedCommand}"`; + exec(passwordCommand, (passwordError, passwordStdout, passwordStderr) => { log.info(`📤 SSH с паролем результат - код: ${passwordError ? passwordError.code : 0}, stdout: "${passwordStdout}", stderr: "${passwordStderr}"`); resolve({ @@ -68,9 +70,6 @@ const execSshCommand = async (command, options = {}) => { }); }; -/** - * Выполнение SCP команд с поддержкой ключей и пароля - */ const execScpCommand = async (sourcePath, targetPath, options = {}) => { const { sshHost, @@ -79,19 +78,23 @@ const execScpCommand = async (sourcePath, targetPath, options = {}) => { sshConnectPassword, vdsIp } = options; - - // Исправляем права доступа к SSH конфигу перед выполнением команды - await fixSshPermissions(); - - const scpCommand = `scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`; - + + await ensureSshPermissions(); + + const privateKeyExists = await fs.pathExists(privateKeyPath); + + let scpCommand = `scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`; + + if (privateKeyExists) { + scpCommand = `scp -i "${privateKeyPath}" -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`; + } + return new Promise((resolve) => { exec(scpCommand, (error, stdout, stderr) => { if (error && error.code === 255 && sshConnectPassword) { - // Если SCP с ключами не удался, пробуем с паролем log.info('SCP с ключами не сработал, пробуем с паролем...'); const passwordScpCommand = `sshpass -p "${sshConnectPassword}" scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`; - + exec(passwordScpCommand, (passwordError, passwordStdout, passwordStderr) => { if (passwordError) { log.error('❌ Ошибка SCP: ' + passwordError.message); @@ -123,5 +126,5 @@ const execScpCommand = async (sourcePath, targetPath, options = {}) => { module.exports = { execSshCommand, execScpCommand, - fixSshPermissions + fixSshPermissions: ensureSshPermissions };