/** * Copyright (c) 2024-2025 Тарабанов Александр Викторович * All rights reserved. * * This software is proprietary and confidential. * Unauthorized copying, modification, or distribution is prohibited. * * For licensing inquiries: info@hb3-accelerator.com * Website: https://hb3-accelerator.com * GitHub: https://github.com/VC-HB3-Accelerator */ /** * Сервис для управления WEB SSH туннелем * Взаимодействует с локальным агентом на порту 3000 */ const LOCAL_AGENT_URL = 'http://localhost:3000'; const API_BASE_PATH = '/api'; const normalizeDomainToAscii = (domain) => { if (!domain) return null; try { const normalized = domain.trim().toLowerCase(); const url = new URL(`http://${normalized}`); return url.hostname; } catch (error) { console.warn('[WebSshService] Некорректное доменное имя:', domain, error.message); return null; } }; // Функция для генерации nginx конфигурации function getNginxConfig(domain, serverPort) { return `# Rate limiting для защиты от DDoS limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=api_limit_per_ip:10m rate=50r/s; # Блокировка известных сканеров и вредоносных ботов map $http_user_agent $bad_bot { default 0; ~*bot 1; ~*crawler 1; ~*spider 1; ~*scanner 1; ~*sqlmap 1; ~*nikto 1; ~*dirb 1; ~*gobuster 1; ~*wfuzz 1; ~*burp 1; ~*zap 1; ~*nessus 1; ~*openvas 1; } server { listen 80; server_name ${domain}; # Блокировка подозрительных ботов if ($bad_bot = 1) { return 403; } # Защита от path traversal if ($request_uri ~* "(\\\\.\\\\.|\\\\.\\\\./|\\\\.\\\\.\\\\.\\\\|\\\\.\\\\.%2f|\\\\.\\\\.%5c)") { return 404; } # Защита от опасных расширений location ~* \\\\.(zip|rar|7z|tar|gz|bz2|xz|sql|sqlite|db|bak|backup|old|csv|php|asp|aspx|jsp|cgi|pl|py|sh|bash|exe|bat|cmd|com|pif|scr|vbs|vbe|jar|war|ear|dll|so|dylib|bin|sys|ini|log|tmp|temp|swp|swo|~)$ { return 404; } # Защита от доступа к чувствительным файлам location ~* /(\\\\.htaccess|\\\\.htpasswd|\\\\.env|\\\\.git|\\\\.svn|\\\\.DS_Store|Thumbs\\\\.db|web\\\\.config|robots\\\\.txt|sitemap\\\\.xml)$ { deny all; return 404; } # Основной location для фронтенда location / { # Rate limiting для основных страниц limit_req zone=req_limit_per_ip burst=20 nodelay; proxy_pass http://localhost:${serverPort}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Базовые заголовки безопасности add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:;" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; } # API проксирование к backend через туннель location /api/ { # Rate limiting для API (более строгое) limit_req zone=api_limit_per_ip burst=100 nodelay; proxy_pass http://localhost:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Заголовки безопасности для API add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; } # WebSocket поддержка location /ws { proxy_pass http://localhost:8000/ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Скрытие информации о сервере server_tokens off; }`; } class WebSshService { constructor() { this.isAgentRunning = false; this.connectionStatus = { connected: false, domain: null, vdsConfigured: false }; } /** * Проверка доступности локального агента */ async checkAgentStatus() { try { const response = await fetch(`${LOCAL_AGENT_URL}/health`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (response.ok) { this.isAgentRunning = true; return { running: true }; } this.isAgentRunning = false; return { running: false }; } catch (error) { // console.error('Агент не доступен:', error); this.isAgentRunning = false; return { running: false, error: error.message }; } } /** * Автоматическая установка и запуск агента * В новой архитектуре агент всегда запускается в Docker (dapp-webssh-agent), * поэтому здесь просто проверяем его доступность. */ async installAndStartAgent() { const status = await this.checkAgentStatus(); if (status.running) { return { success: true, message: 'Агент уже запущен' }; } return { success: false, message: 'WebSSH Agent не запущен. Убедитесь, что контейнер dapp-webssh-agent работает (docker compose up -d webssh-agent).' }; } /** * Получение IP адреса из DNS записей домена через backend API */ async getDomainIP(domain) { try { console.log(`Получение IP адреса для домена ${domain}...`); // Используем backend API для проверки DNS const asciiDomain = normalizeDomainToAscii(domain); if (!asciiDomain) { return { success: false, error: 'Некорректное доменное имя' }; } const response = await fetch(`${API_BASE_PATH}/dns-check/${encodeURIComponent(asciiDomain)}`); const data = await response.json(); if (data.success) { console.log(`DNS запись найдена: ${domain} → ${data.ip}`); return { success: true, ip: data.ip }; } else { console.log(`DNS запись для домена ${domain} не найдена: ${data.message}`); return { success: false, error: data.message }; } } catch (error) { console.warn(`Не удалось получить IP из DNS: ${error.message}`); return { success: false, error: error.message }; } } /** * Настройка существующей VDS для туннелей */ async setupVDS(config) { try { // Получаем IP адрес из DNS записей домена if (config.domain) { const dnsResult = await this.getDomainIP(config.domain); if (!dnsResult.success) { return { success: false, message: `Домен ${config.domain} не настроен или недоступен: ${dnsResult.error}` }; } // Добавляем полученный IP в конфигурацию config.vdsIp = dnsResult.ip; } // Проверяем, что агент запущен (в Docker) const agentStatus = await this.checkAgentStatus(); if (!agentStatus.running) { const installResult = await this.installAndStartAgent(); if (!installResult.success) { return installResult; } } // API ключ больше не нужен - агент защищен сетевым доступом // Отправляем конфигурацию VDS агенту const response = await fetch(`${LOCAL_AGENT_URL}/vds/setup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ vdsIp: config.vdsIp, domain: config.domain, email: config.email, ubuntuUser: config.ubuntuUser, dockerUser: config.dockerUser, sshUser: 'root', // SSH пользователь для настройки ключей (root) sshHost: config.sshHost, // SSH хост для подключения sshPort: config.sshPort, // SSH порт для подключения sshConnectUser: config.sshUser, // SSH пользователь для подключения sshConnectPassword: config.sshPassword // SSH пароль для подключения }) }); if (response.ok) { const result = await response.json(); if (result.success) { this.connectionStatus = { connected: true, domain: config.domain, vdsIp: config.vdsIp }; } return result; } else { const error = await response.json(); return { success: false, message: error.message || 'Ошибка при настройке VDS' }; } } catch (error) { // console.error('Ошибка при настройке VDS:', error); return { success: false, message: `Ошибка подключения к агенту: ${error.message}` }; } } /** * Настройка почтового сервера */ async setupMailServer(ssh, config, domain) { const mailDomain = config.mailDomain || `mail.${domain}`; const adminEmail = config.adminEmail || config.email; const adminPassword = config.adminPassword || 'Admin123!'; // Настройка Postfix const postfixConfig = ` # Основные настройки myhostname = ${mailDomain} mydomain = ${domain} myorigin = $mydomain inet_interfaces = all inet_protocols = ipv4 mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain relayhost = mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = all home_mailbox = Maildir/ # SSL настройки smtpd_use_tls = yes smtpd_tls_cert_file = /etc/letsencrypt/live/${domain}/fullchain.pem smtpd_tls_key_file = /etc/letsencrypt/live/${domain}/privkey.pem smtpd_tls_security_level = may smtpd_tls_auth_only = no smtp_tls_security_level = may smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt smtpd_tls_received_header = yes smtpd_tls_session_cache_timeout = 3600s tls_random_source = dev:/dev/urandom # Аутентификация smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes smtpd_sasl_security_options = noanonymous smtpd_sasl_local_domain = $myhostname smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination `; await ssh.execCommand(`echo '${postfixConfig}' > /etc/postfix/main.cf`); // Настройка Dovecot const dovecotConfig = ` # Основные настройки protocols = imap pop3 lmtp mail_location = maildir:~/Maildir namespace inbox { inbox = yes } passdb { driver = pam } userdb { driver = passwd } service auth { unix_listener /var/spool/postfix/private/auth { mode = 0666 user = postfix group = postfix } } ssl = required ssl_cert = /etc/dovecot/dovecot.conf`); // Создание пользователя для почты await ssh.execCommand(`useradd -m -s /bin/bash ${adminEmail.split('@')[0]}`); await ssh.execCommand(`echo '${adminEmail.split('@')[0]}:${adminPassword}' | chpasswd`); // Настройка Roundcube если включен if (config.enableWebmail) { const roundcubeConfig = ` server { listen 80; server_name ${mailDomain}; location / { proxy_pass http://localhost/roundcube/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } `; await ssh.execCommand(`echo '${roundcubeConfig}' > /etc/nginx/sites-available/${mailDomain}`); await ssh.execCommand(`ln -sf /etc/nginx/sites-available/${mailDomain} /etc/nginx/sites-enabled/`); } // Получение SSL для почтового домена await ssh.execCommand(`certbot --nginx -d ${mailDomain} --non-interactive --agree-tos --email ${adminEmail}`); // Запуск сервисов await ssh.execCommand('systemctl enable postfix dovecot'); await ssh.execCommand('systemctl restart postfix dovecot nginx'); // Создание DNS записей const dnsRecords = ` # Добавьте эти DNS записи в ваш домен: # MX запись: ${domain} -> ${mailDomain} (приоритет 10) # A запись: ${mailDomain} -> IP вашего сервера # SPF запись: v=spf1 mx a ip4:IP_СЕРВЕРА ~all # DKIM запись: (будет создан автоматически) `; console.log('DNS записи для настройки:', dnsRecords); } /** * Получение кода агента для установки */ getAgentCode() { return ` const express = require('express'); const cors = require('cors'); const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); const { NodeSSH } = require('node-ssh'); const app = express(); const PORT = 12345; // Middleware app.use(cors()); app.use(express.json()); // Состояние туннеля let tunnelState = { connected: false, domain: null, tunnelId: null, sshProcess: null }; // Здоровье агента app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Создание туннеля app.post('/tunnel/create', async (req, res) => { try { const { domain, email, sshHost, sshUser, sshKey, localPort, serverPort, sshPort } = req.body; // console.log('Создание туннеля для домена:', domain); // Сохраняем SSH ключ во временный файл const keyPath = path.join(__dirname, 'temp_ssh_key'); fs.writeFileSync(keyPath, sshKey, { mode: 0o600 }); // Подключаемся к серверу и настраиваем NGINX const ssh = new NodeSSH(); await ssh.connect({ host: sshHost, username: sshUser, privateKey: sshKey, port: sshPort }); // Установка NGINX, certbot и почтовых сервисов const installPackages = 'apt-get update && apt-get install -y nginx certbot python3-certbot-nginx'; const mailPackages = config.enableMail ? 'postfix dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-managesieved dovecot-sieve dovecot-mysql mysql-server roundcube roundcube-mysql' : ''; await ssh.execCommand(\`\${installPackages} \${mailPackages}\`); // Создание конфигурации NGINX с полной защитой const nginxConfig = \`# Rate limiting для защиты от DDoS limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=api_limit_per_ip:10m rate=50r/s; # Блокировка известных сканеров и вредоносных ботов map $http_user_agent $bad_bot { default 0; ~*bot 1; ~*crawler 1; ~*spider 1; ~*scanner 1; ~*sqlmap 1; ~*nikto 1; ~*dirb 1; ~*gobuster 1; ~*wfuzz 1; ~*burp 1; ~*zap 1; ~*nessus 1; ~*openvas 1; } server { listen 80; server_name \${domain}; # Блокировка подозрительных ботов if ($bad_bot = 1) { return 403; } # Защита от path traversal if ($request_uri ~* "(\\\\.\\\\.|\\\\.\\\\./|\\\\.\\\\.\\\\.\\\\|\\\\.\\\\.%2f|\\\\.\\\\.%5c)") { return 404; } # Защита от опасных расширений location ~* \\\\.(zip|rar|7z|tar|gz|bz2|xz|sql|sqlite|db|bak|backup|old|csv|php|asp|aspx|jsp|cgi|pl|py|sh|bash|exe|bat|cmd|com|pif|scr|vbs|vbe|jar|war|ear|dll|so|dylib|bin|sys|ini|log|tmp|temp|swp|swo|~)$ { return 404; } # Защита от доступа к чувствительным файлам location ~* /(\\\\.htaccess|\\\\.htpasswd|\\\\.env|\\\\.git|\\\\.svn|\\\\.DS_Store|Thumbs\\\\.db|web\\\\.config|robots\\\\.txt|sitemap\\\\.xml)$ { deny all; return 404; } # Основной location для фронтенда location / { # Rate limiting для основных страниц limit_req zone=req_limit_per_ip burst=20 nodelay; proxy_pass http://localhost:\${serverPort}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Базовые заголовки безопасности add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:;" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; } # API проксирование к backend через туннель location /api/ { # Rate limiting для API (более строгое) limit_req zone=api_limit_per_ip burst=100 nodelay; proxy_pass http://localhost:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Заголовки безопасности для API add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; } # WebSocket поддержка location /ws { proxy_pass http://localhost:8000/ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Скрытие информации о сервере server_tokens off; }\`; await ssh.execCommand(\`echo '\${nginxConfig}' > /etc/nginx/sites-available/\${domain}\`); await ssh.execCommand(\`ln -sf /etc/nginx/sites-available/\${domain} /etc/nginx/sites-enabled/\`); await ssh.execCommand('nginx -t && systemctl reload nginx'); // Получение SSL сертификата await ssh.execCommand(\`certbot --nginx -d \${domain} --non-interactive --agree-tos --email \${email}\`); // Настройка почты если включена if (config.enableMail) { await setupMailServer(ssh, config, domain); } ssh.dispose(); // Создание SSH туннелей для frontend и backend const tunnelId = Date.now().toString(); // SSH туннель для frontend (порт 9000) const frontendSshArgs = [ '-i', keyPath, '-p', sshPort.toString(), '-R', \`\${serverPort}:localhost:\${localPort}\`, '-N', '-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'ServerAliveInterval=60', '-o', 'ServerAliveCountMax=3', \`\${sshUser}@\${sshHost}\` ]; // SSH туннель для backend (порт 8000) const backendSshArgs = [ '-i', keyPath, '-p', sshPort.toString(), '-R', '8000:localhost:8000', '-N', '-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'ServerAliveInterval=60', '-o', 'ServerAliveCountMax=3', \`\${sshUser}@\${sshHost}\` ]; // Запускаем оба SSH туннеля const frontendSshProcess = spawn('ssh', frontendSshArgs); const backendSshProcess = spawn('ssh', backendSshArgs); frontendSshProcess.on('error', (error) => { // console.error('Frontend SSH процесс ошибка:', error); }); backendSshProcess.on('error', (error) => { // console.error('Backend SSH процесс ошибка:', error); }); frontendSshProcess.on('close', (code) => { // console.log('Frontend SSH процесс завершен с кодом:', code); tunnelState.connected = false; }); backendSshProcess.on('close', (code) => { // console.log('Backend SSH процесс завершен с кодом:', code); tunnelState.connected = false; }); // Обновляем состояние tunnelState = { connected: true, domain, tunnelId, frontendSshProcess, backendSshProcess }; res.json({ success: true, message: 'Туннель успешно создан', tunnelId, domain }); } catch (error) { // console.error('Ошибка создания туннеля:', error); res.status(500).json({ success: false, message: error.message }); } }); // Отключение туннелей app.post('/tunnel/disconnect', (req, res) => { try { if (tunnelState.frontendSshProcess) { tunnelState.frontendSshProcess.kill(); } if (tunnelState.backendSshProcess) { tunnelState.backendSshProcess.kill(); } tunnelState = { connected: false, domain: null, tunnelId: null, frontendSshProcess: null, backendSshProcess: null }; res.json({ success: true, message: 'Туннель отключен' }); } catch (error) { // console.error('Ошибка отключения туннеля:', error); res.status(500).json({ success: false, message: error.message }); } }); // Статус туннеля app.get('/tunnel/status', (req, res) => { res.json({ connected: tunnelState.connected, domain: tunnelState.domain, tunnelId: tunnelState.tunnelId }); }); // Запуск сервера app.listen(PORT, 'localhost', () => { // console.log(\`WebSSH Agent запущен на порту \${PORT}\`); }); `; } } // Создаем композабл для использования в компонентах export function useWebSshService() { const service = new WebSshService(); return { checkAgentStatus: () => service.checkAgentStatus(), installAndStartAgent: () => service.installAndStartAgent(), setupVDS: (config) => service.setupVDS(config) }; } export default WebSshService;