From cfe610c75f11ac26f286745b3c66b87fcabeb93b Mon Sep 17 00:00:00 2001 From: Serge NOEL Date: Thu, 4 Dec 2025 09:32:45 +0100 Subject: [PATCH] Simplification Web-Gateway --- .gitignore | 40 ++++ web-gateway/.env.example | 11 +- web-gateway/Dockerfile | 7 +- web-gateway/README.md | 103 +++++++---- .../chart/rdp-web-gateway/templates/NOTES.txt | 3 +- .../rdp-web-gateway/templates/configmap.yaml | 6 +- .../rdp-web-gateway/templates/deployment.yaml | 26 +-- .../chart/rdp-web-gateway/templates/pvc.yaml | 17 -- .../rdp-web-gateway/templates/secret.yaml | 13 -- web-gateway/chart/rdp-web-gateway/values.yaml | 29 ++- web-gateway/package.json | 5 +- web-gateway/public/js/app.js | 89 +++++---- web-gateway/src/logger.js | 41 ----- web-gateway/src/rdpProxyHandler.js | 61 ++---- web-gateway/src/server.js | 174 +++++++++--------- web-gateway/src/sessionManager.js | 94 ---------- 16 files changed, 292 insertions(+), 427 deletions(-) create mode 100644 .gitignore delete mode 100644 web-gateway/chart/rdp-web-gateway/templates/pvc.yaml delete mode 100644 web-gateway/chart/rdp-web-gateway/templates/secret.yaml delete mode 100644 web-gateway/src/logger.js delete mode 100644 web-gateway/src/sessionManager.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef8bc48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# .gitignore + +# Build artifacts +src/build/ +src/bin/ +*.o +*.so +*.a + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Environment files +.env +.env.local + +# Custom values (may contain sensitive info) +my-values.yaml +*-values.yaml +!values.yaml + +# Test files +test/ +*.test + +# Temporary files +tmp/ +temp/ diff --git a/web-gateway/.env.example b/web-gateway/.env.example index 2390c67..a39b170 100644 --- a/web-gateway/.env.example +++ b/web-gateway/.env.example @@ -3,14 +3,13 @@ # Server PORT=8080 NODE_ENV=production -LOG_LEVEL=info # RDP Broker Connection RDP_BROKER_HOST=rdpbroker RDP_BROKER_PORT=3389 -# Session Configuration -SESSION_TIMEOUT=3600000 - -# Security (set these in production) -# SESSION_SECRET=your-secret-key-here +# Optional: Pre-configure RDP Targets +# Format: JSON array of target objects +# If not set, RdpBroker will provide targets dynamically +# Example: +# RDP_TARGETS=[{"name":"Server1","host":"srv1.example.com","port":3389,"description":"Production Server"},{"name":"Server2","host":"srv2.example.com","port":3389,"description":"Development Server"}] diff --git a/web-gateway/Dockerfile b/web-gateway/Dockerfile index cf44eb8..1638d3a 100644 --- a/web-gateway/Dockerfile +++ b/web-gateway/Dockerfile @@ -7,7 +7,8 @@ WORKDIR /app COPY package*.json ./ # Install dependencies -RUN npm ci --only=production +RUN npm install \ + && npm ci --only=production # Production stage FROM node:18-alpine @@ -19,10 +20,6 @@ RUN apk add --no-cache dumb-init RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 -# Create necessary directories -RUN mkdir -p /var/log/rdp-web-gateway && \ - chown -R nodejs:nodejs /var/log/rdp-web-gateway - WORKDIR /app # Copy dependencies from builder diff --git a/web-gateway/README.md b/web-gateway/README.md index 39164e9..77df151 100644 --- a/web-gateway/README.md +++ b/web-gateway/README.md @@ -7,9 +7,11 @@ HTML5 WebSocket-based gateway for accessing RDP connections through a web browse - 🌐 **Browser-Based Access** - Connect to RDP sessions from any modern web browser - 🔒 **Secure WebSocket** - Real-time bidirectional communication - 🎨 **Modern UI** - Clean, responsive interface -- 🔑 **Session Management** - Automatic session cleanup and timeout -- 📊 **Activity Monitoring** - Track active connections +- 🔑 **Simplified Authentication** - Credentials passed directly to RdpBroker +- 📊 **Service Health Monitoring** - Automatic RdpBroker availability checks +- 🎯 **Dynamic Target Loading** - Targets fetched from configuration or RdpBroker - ⚡ **Low Latency** - Optimized for performance +- ☁️ **Kubernetes Native** - Console-only logging for cloud environments ## Architecture @@ -63,10 +65,23 @@ Edit `.env` file: PORT=8080 RDP_BROKER_HOST=rdpbroker RDP_BROKER_PORT=3389 -LOG_LEVEL=info -SESSION_TIMEOUT=3600000 +NODE_ENV=production + +# Optional: Pre-configure RDP targets (JSON array) +# If not set, RdpBroker will provide targets dynamically +RDP_TARGETS=[{"name":"Server1","host":"srv1.example.com","port":3389,"description":"Production Server"}] ``` +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `PORT` | Web server listening port | `8080` | +| `RDP_BROKER_HOST` | RdpBroker hostname | `rdpbroker` | +| `RDP_BROKER_PORT` | RdpBroker port | `3389` | +| `RDP_TARGETS` | JSON array of pre-configured targets | `null` | +| `NODE_ENV` | Environment mode | `development` | + ## Usage ### Access the Web Interface @@ -78,43 +93,55 @@ SESSION_TIMEOUT=3600000 ### API Endpoints -#### POST /api/auth/login -Authenticate user and create session. - -```json -{ - "username": "user@domain.com", - "password": "password" -} -``` +#### GET /health +Health check endpoint for monitoring the web gateway. Response: ```json { - "success": true, - "sessionId": "uuid" + "status": "healthy", + "version": "1.0.0", + "uptime": 12345 +} +``` + +#### GET /api/broker-status +Check if RdpBroker service is available. + +Response: +```json +{ + "available": true, + "broker": "rdpbroker:3389", + "timestamp": "2025-12-04T10:30:00.000Z" } ``` #### GET /api/targets -Get available RDP targets (requires X-Session-ID header). +Fetch available RDP targets. -Response: +**Success Response (200):** ```json { "targets": [ { - "name": "Server 01", - "host": "192.168.1.10", + "name": "Windows Server 2022", + "host": "ws2022.example.com", "port": 3389, - "description": "Production Server" + "description": "Production Windows Server" } - ] + ], + "timestamp": "2025-12-04T10:30:00.000Z" } ``` -#### POST /api/auth/logout -Logout and destroy session (requires X-Session-ID header). +**Service Unavailable (503):** +```json +{ + "error": "RdpBroker service is unavailable. Please contact your administrator.", + "timestamp": "2025-12-04T10:30:00.000Z" +} +``` ### WebSocket Protocol @@ -126,7 +153,8 @@ Connect to `ws://localhost:8080/ws/rdp` ```json { "type": "connect", - "sessionId": "uuid", + "username": "user@domain.com", + "password": "password123", "target": { "name": "Server 01", "host": "192.168.1.10", @@ -209,20 +237,21 @@ helm install rdp-web-gateway ./chart/rdp-web-gateway -n rdpbroker - Firefox 88+ - Safari 14+ - Opera 76+ - ## Security Considerations - Use HTTPS/WSS in production -- Implement rate limiting -- Set strong session secrets +- Credentials are passed directly to RdpBroker (no storage in web-gateway) +- Implement rate limiting at ingress level - Enable CORS restrictions - Regular security audits - +- All authentication handled by RdpBroker → Samba ADs +- Regular security audits ## Performance Tuning -- Adjust session timeout based on usage - Configure WebSocket buffer sizes -- Enable compression for HTTP responses +- Use CDN for static assets in production +- Enable HTTP compression (already included) +- Adjust resource limits in Kubernetes - Use CDN for static assets in production ## Troubleshooting @@ -253,9 +282,21 @@ location /ws/ { proxy_set_header Connection "upgrade"; } ``` - ### High memory usage +Adjust resource limits in Kubernetes values.yaml + +## Logging + +All logs go to stdout/stderr for Kubernetes: + +```bash +# View logs +kubectl logs -f deployment/rdp-web-gateway -n rdpbroker + +# Follow logs for all pods +kubectl logs -f -l app=rdp-web-gateway -n rdpbroker +``` Reduce session timeout or implement session limits per user. ## Development diff --git a/web-gateway/chart/rdp-web-gateway/templates/NOTES.txt b/web-gateway/chart/rdp-web-gateway/templates/NOTES.txt index 5420906..c5dc58c 100644 --- a/web-gateway/chart/rdp-web-gateway/templates/NOTES.txt +++ b/web-gateway/chart/rdp-web-gateway/templates/NOTES.txt @@ -33,5 +33,6 @@ Configuration: - RDP Broker: {{ .Values.config.rdpBroker.host }}:{{ .Values.config.rdpBroker.port }} - Server Port: {{ .Values.config.server.port }} -- Log Level: {{ .Values.config.server.logLevel }} - Replicas: {{ if .Values.autoscaling.enabled }}{{ .Values.autoscaling.minReplicas }}-{{ .Values.autoscaling.maxReplicas }} (autoscaling){{ else }}{{ .Values.replicaCount }}{{ end }} + +Note: Authentication is handled by RdpBroker. Logs are sent to stdout for Kubernetes. diff --git a/web-gateway/chart/rdp-web-gateway/templates/configmap.yaml b/web-gateway/chart/rdp-web-gateway/templates/configmap.yaml index 5470418..7220ee4 100644 --- a/web-gateway/chart/rdp-web-gateway/templates/configmap.yaml +++ b/web-gateway/chart/rdp-web-gateway/templates/configmap.yaml @@ -12,10 +12,6 @@ data: "port": {{ .Values.config.rdpBroker.port }} }, "server": { - "port": {{ .Values.config.server.port }}, - "logLevel": "{{ .Values.config.server.logLevel }}" - }, - "session": { - "timeout": {{ .Values.config.session.timeout }} + "port": {{ .Values.config.server.port }} } } diff --git a/web-gateway/chart/rdp-web-gateway/templates/deployment.yaml b/web-gateway/chart/rdp-web-gateway/templates/deployment.yaml index 7255b2f..3982a2a 100644 --- a/web-gateway/chart/rdp-web-gateway/templates/deployment.yaml +++ b/web-gateway/chart/rdp-web-gateway/templates/deployment.yaml @@ -41,23 +41,16 @@ spec: value: {{ .Values.config.rdpBroker.host | quote }} - name: RDP_BROKER_PORT value: {{ .Values.config.rdpBroker.port | quote }} - - name: LOG_LEVEL - value: {{ .Values.config.server.logLevel | quote }} - - name: SESSION_TIMEOUT - value: {{ .Values.config.session.timeout | quote }} + {{- if .Values.config.rdpTargets }} + - name: RDP_TARGETS + value: {{ .Values.config.rdpTargets | toJson | quote }} + {{- end }} - name: NODE_ENV value: "production" {{- range .Values.env }} - name: {{ .name }} value: {{ .value | quote }} {{- end }} - {{- if .Values.secrets.sessionSecret }} - - name: SESSION_SECRET - valueFrom: - secretKeyRef: - name: {{ include "rdp-web-gateway.fullname" . }}-secrets - key: sessionSecret - {{- end }} ports: - name: http containerPort: {{ .Values.config.server.port }} @@ -68,17 +61,6 @@ spec: {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} - {{- if .Values.persistence.enabled }} - volumeMounts: - - name: logs - mountPath: {{ .Values.persistence.mountPath }} - {{- end }} - {{- if .Values.persistence.enabled }} - volumes: - - name: logs - persistentVolumeClaim: - claimName: {{ include "rdp-web-gateway.fullname" . }}-logs - {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/web-gateway/chart/rdp-web-gateway/templates/pvc.yaml b/web-gateway/chart/rdp-web-gateway/templates/pvc.yaml deleted file mode 100644 index 0f3f1f8..0000000 --- a/web-gateway/chart/rdp-web-gateway/templates/pvc.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.persistence.enabled }} -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ include "rdp-web-gateway.fullname" . }}-logs - labels: - {{- include "rdp-web-gateway.labels" . | nindent 4 }} -spec: - accessModes: - - {{ .Values.persistence.accessMode }} - resources: - requests: - storage: {{ .Values.persistence.size }} - {{- if .Values.persistence.storageClass }} - storageClassName: {{ .Values.persistence.storageClass }} - {{- end }} -{{- end }} diff --git a/web-gateway/chart/rdp-web-gateway/templates/secret.yaml b/web-gateway/chart/rdp-web-gateway/templates/secret.yaml deleted file mode 100644 index 67b813e..0000000 --- a/web-gateway/chart/rdp-web-gateway/templates/secret.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if .Values.secrets }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "rdp-web-gateway.fullname" . }}-secrets - labels: - {{- include "rdp-web-gateway.labels" . | nindent 4 }} -type: Opaque -data: - {{- if .Values.secrets.sessionSecret }} - sessionSecret: {{ .Values.secrets.sessionSecret | b64enc | quote }} - {{- end }} -{{- end }} diff --git a/web-gateway/chart/rdp-web-gateway/values.yaml b/web-gateway/chart/rdp-web-gateway/values.yaml index 6b8fd47..c4317a6 100644 --- a/web-gateway/chart/rdp-web-gateway/values.yaml +++ b/web-gateway/chart/rdp-web-gateway/values.yaml @@ -82,29 +82,26 @@ config: # Server configuration server: port: 8080 - logLevel: "info" - # Session configuration - session: - timeout: 3600000 # 1 hour in milliseconds + # Optional: Pre-configure RDP targets + # If not set, targets will be managed by RdpBroker + # Format: JSON array of target objects + rdpTargets: null + # Example: + # - name: "Windows Server 2022" + # host: "ws2022.example.com" + # port: 3389 + # description: "Production Windows Server" + # - name: "Development Server" + # host: "dev.example.com" + # port: 3389 + # description: "Development environment" # Environment variables env: [] # - name: CUSTOM_VAR # value: "value" -# Secrets (for sensitive configuration) -secrets: {} - # sessionSecret: "your-secret-key" - -# Persistence for logs -persistence: - enabled: false - storageClass: "" - accessMode: ReadWriteOnce - size: 5Gi - mountPath: /var/log/rdp-web-gateway - # Liveness and readiness probes livenessProbe: httpGet: diff --git a/web-gateway/package.json b/web-gateway/package.json index 70f65e5..5bff03d 100644 --- a/web-gateway/package.json +++ b/web-gateway/package.json @@ -20,13 +20,10 @@ "dependencies": { "express": "^4.18.2", "ws": "^8.14.2", - "node-rdpjs": "^0.3.2", "dotenv": "^16.3.1", "compression": "^1.7.4", "helmet": "^7.1.0", - "cors": "^2.8.5", - "uuid": "^9.0.1", - "winston": "^3.11.0" + "cors": "^2.8.5" }, "devDependencies": { "nodemon": "^3.0.2" diff --git a/web-gateway/public/js/app.js b/web-gateway/public/js/app.js index 019079a..8944ef7 100644 --- a/web-gateway/public/js/app.js +++ b/web-gateway/public/js/app.js @@ -5,7 +5,7 @@ class RDPWebGateway { this.ctx = null; this.currentUser = null; this.currentTarget = null; - this.sessionId = null; + this.credentials = null; this.init(); } @@ -63,27 +63,24 @@ class RDPWebGateway { errorMessage.style.display = 'none'; try { - const response = await fetch('/api/auth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ username, password }), - }); - - const data = await response.json(); - - if (response.ok) { - this.currentUser = username; - this.sessionId = data.sessionId; - await this.loadTargets(); - this.showTargetsView(); - } else { - this.showError(errorMessage, data.error || 'Authentication failed'); + // Check if RdpBroker service is available + const statusResponse = await fetch('/api/broker-status'); + const statusData = await statusResponse.json(); + + if (!statusData.available) { + this.showError(errorMessage, 'RDP service is currently unavailable. Please contact your administrator.'); + return; } + + // Store credentials temporarily - will be sent to RdpBroker + this.currentUser = username; + this.credentials = { username, password }; + + // Load targets and show targets view + await this.loadTargets(); } catch (error) { console.error('Login error:', error); - this.showError(errorMessage, 'Connection error. Please try again.'); + this.showError(errorMessage, 'Connection error. Please check your network and try again.'); } finally { loginBtn.disabled = false; btnText.style.display = 'block'; @@ -93,25 +90,45 @@ class RDPWebGateway { async loadTargets() { try { - const response = await fetch('/api/targets', { - headers: { - 'X-Session-ID': this.sessionId, - }, - }); - + const response = await fetch('/api/targets'); + if (!response.ok) { + if (response.status === 503) { + const error = await response.json(); + throw new Error(error.error || 'Service unavailable'); + } throw new Error('Failed to load targets'); } const data = await response.json(); - this.displayTargets(data.targets); + this.showTargetsView(data.targets); } catch (error) { - console.error('Load targets error:', error); - const targetsError = document.getElementById('targetsError'); - this.showError(targetsError, 'Failed to load available desktops'); + console.error('Error loading targets:', error); + // Show error in targets view + this.showTargetsView(null, error.message); } } + showTargetsView(targets = null, errorMsg = null) { + document.getElementById('loginCard').style.display = 'none'; + document.getElementById('targetsCard').style.display = 'block'; + document.getElementById('rdpViewer').style.display = 'none'; + document.getElementById('currentUser').textContent = this.currentUser; + + if (errorMsg) { + const targetsList = document.getElementById('targetsList'); + targetsList.innerHTML = ` +
+

⚠️ ${this.escapeHtml(errorMsg)}

+ +
+ `; + return; + } + + this.displayTargets(targets); + } + displayTargets(targets) { const targetsList = document.getElementById('targetsList'); targetsList.innerHTML = ''; @@ -153,10 +170,11 @@ class RDPWebGateway { this.ws.onopen = () => { console.log('WebSocket connected'); - // Send connection request + // Send credentials and connection request to RdpBroker this.ws.send(JSON.stringify({ type: 'connect', - sessionId: this.sessionId, + username: this.credentials.username, + password: this.credentials.password, target: target, })); }; @@ -324,7 +342,7 @@ class RDPWebGateway { } this.currentUser = null; this.currentTarget = null; - this.sessionId = null; + this.credentials = null; this.showLoginView(); } @@ -336,13 +354,6 @@ class RDPWebGateway { document.getElementById('password').value = ''; } - showTargetsView() { - document.getElementById('loginCard').style.display = 'none'; - document.getElementById('targetsCard').style.display = 'block'; - document.getElementById('rdpViewer').style.display = 'none'; - document.getElementById('currentUser').textContent = this.currentUser; - } - showRDPViewer() { document.getElementById('loginCard').style.display = 'none'; document.getElementById('targetsCard').style.display = 'none'; diff --git a/web-gateway/src/logger.js b/web-gateway/src/logger.js deleted file mode 100644 index e332f2f..0000000 --- a/web-gateway/src/logger.js +++ /dev/null @@ -1,41 +0,0 @@ -const winston = require('winston'); - -const logger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: winston.format.combine( - winston.format.timestamp({ - format: 'YYYY-MM-DD HH:mm:ss' - }), - winston.format.errors({ stack: true }), - winston.format.splat(), - winston.format.json() - ), - defaultMeta: { service: 'rdp-web-gateway' }, - transports: [ - // Write to console - new winston.transports.Console({ - format: winston.format.combine( - winston.format.colorize(), - winston.format.printf(({ timestamp, level, message, ...metadata }) => { - let msg = `${timestamp} [${level}]: ${message}`; - if (Object.keys(metadata).length > 0) { - msg += ` ${JSON.stringify(metadata)}`; - } - return msg; - }) - ) - }), - // Write to file - new winston.transports.File({ - filename: '/var/log/rdp-web-gateway/error.log', - level: 'error', - handleExceptions: true - }), - new winston.transports.File({ - filename: '/var/log/rdp-web-gateway/combined.log', - handleExceptions: true - }) - ] -}); - -module.exports = logger; diff --git a/web-gateway/src/rdpProxyHandler.js b/web-gateway/src/rdpProxyHandler.js index 9fcb52f..7c71986 100644 --- a/web-gateway/src/rdpProxyHandler.js +++ b/web-gateway/src/rdpProxyHandler.js @@ -1,13 +1,10 @@ const net = require('net'); -const logger = require('./logger'); class RDPProxyHandler { - constructor(websocket, sessionManager, rdpBrokerHost, rdpBrokerPort) { + constructor(websocket, rdpBrokerHost, rdpBrokerPort) { this.ws = websocket; - this.sessionManager = sessionManager; this.rdpBrokerHost = rdpBrokerHost; this.rdpBrokerPort = rdpBrokerPort; - this.session = null; this.rdpSocket = null; this.isConnected = false; } @@ -28,40 +25,33 @@ class RDPProxyHandler { this.handleSpecialCommand(message); break; default: - logger.warn(`Unknown message type: ${message.type}`); + console.warn(`${new Date().toISOString()} [WARN] Unknown message type: ${message.type}`); } } catch (error) { - logger.error('Error handling message:', error); + console.error(`${new Date().toISOString()} [ERROR] Error handling message:`, error); this.sendError('Failed to process message'); } } async handleConnect(message) { - const { sessionId, target } = message; + const { username, password, target } = message; - // Validate session - this.session = this.sessionManager.getSession(sessionId); - if (!this.session) { - return this.sendError('Invalid session'); - } - - logger.info(`Connecting to RDP Broker for session ${sessionId}, target: ${target.name}`); + console.log(`${new Date().toISOString()} [INFO] Connecting to RDP Broker, target: ${target?.name || 'unknown'}`); try { // Connect to RDP Broker this.rdpSocket = new net.Socket(); this.rdpSocket.connect(this.rdpBrokerPort, this.rdpBrokerHost, () => { - logger.info(`Connected to RDP Broker at ${this.rdpBrokerHost}:${this.rdpBrokerPort}`); + console.log(`${new Date().toISOString()} [INFO] Connected to RDP Broker at ${this.rdpBrokerHost}:${this.rdpBrokerPort}`); this.isConnected = true; - // Send authentication to RdpBroker - // In real implementation, this would follow the RDP protocol - this.sendAuthToBroker(); + // Send credentials to RdpBroker (it will handle authentication) + this.sendAuthToBroker(username, password); this.ws.send(JSON.stringify({ type: 'connected', - target: target.name + target: target?.name || 'RDP Server' })); // Set canvas size @@ -75,20 +65,19 @@ class RDPProxyHandler { // Handle data from RDP Broker this.rdpSocket.on('data', (data) => { // Forward RDP data to WebSocket client - // In production, this would be properly decoded RDP frames if (this.ws.readyState === 1) { // WebSocket.OPEN this.ws.send(data); } }); this.rdpSocket.on('error', (error) => { - logger.error('RDP socket error:', error); + console.error(`${new Date().toISOString()} [ERROR] RDP socket error:`, error); this.sendError('Connection to RDP broker failed'); this.cleanup(); }); this.rdpSocket.on('close', () => { - logger.info('RDP connection closed'); + console.log(`${new Date().toISOString()} [INFO] RDP connection closed`); this.isConnected = false; if (this.ws.readyState === 1) { this.ws.close(); @@ -96,25 +85,19 @@ class RDPProxyHandler { }); } catch (error) { - logger.error('Connection error:', error); + console.error(`${new Date().toISOString()} [ERROR] Connection error:`, error); this.sendError('Failed to connect to RDP broker'); } } - sendAuthToBroker() { - // This is a simplified version - // In production, implement proper RDP protocol handshake + sendAuthToBroker(username, password) { + // Send credentials directly to RdpBroker + // RdpBroker will handle Samba AD authentication - if (!this.rdpSocket || !this.session) return; + if (!this.rdpSocket) return; - const authData = { - username: this.session.username, - password: this.session.data.password - }; - - // Send authentication data // Format: "Username: \nPassword: \n" - const authMessage = `${authData.username}\n${authData.password}\n`; + const authMessage = `${username}\n${password}\n`; this.rdpSocket.write(authMessage); } @@ -164,10 +147,10 @@ class RDPProxyHandler { command: 'ctrl-alt-del' }); this.rdpSocket.write(cadData + '\n'); - logger.info('Sent Ctrl+Alt+Del'); + console.log(`${new Date().toISOString()} [INFO] Sent Ctrl+Alt+Del`); break; default: - logger.warn(`Unknown special command: ${message.action}`); + console.warn(`${new Date().toISOString()} [WARN] Unknown special command: ${message.action}`); } } @@ -186,12 +169,6 @@ class RDPProxyHandler { this.rdpSocket = null; } this.isConnected = false; - - if (this.session) { - this.sessionManager.updateSession(this.session.id, { - rdpConnection: null - }); - } } } diff --git a/web-gateway/src/server.js b/web-gateway/src/server.js index cafff07..4919b59 100644 --- a/web-gateway/src/server.js +++ b/web-gateway/src/server.js @@ -2,11 +2,10 @@ const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const path = require('path'); +const net = require('net'); const compression = require('compression'); const helmet = require('helmet'); const cors = require('cors'); -const logger = require('./logger'); -const SessionManager = require('./sessionManager'); const RDPProxyHandler = require('./rdpProxyHandler'); class RDPWebGatewayServer { @@ -18,7 +17,6 @@ class RDPWebGatewayServer { path: '/ws/rdp' }); - this.sessionManager = new SessionManager(); this.port = process.env.PORT || 8080; this.rdpBrokerHost = process.env.RDP_BROKER_HOST || 'rdpbroker'; this.rdpBrokerPort = process.env.RDP_BROKER_PORT || 3389; @@ -45,7 +43,7 @@ class RDPWebGatewayServer { // Logging this.app.use((req, res, next) => { - logger.info(`${req.method} ${req.url}`); + console.log(`${new Date().toISOString()} [INFO] ${req.method} ${req.url}`); next(); }); } @@ -56,70 +54,44 @@ class RDPWebGatewayServer { res.json({ status: 'healthy', version: '1.0.0', - sessions: this.sessionManager.getActiveSessionCount() + uptime: process.uptime() }); }); - // Authentication endpoint - this.app.post('/api/auth/login', async (req, res) => { - try { - const { username, password } = req.body; - - if (!username || !password) { - return res.status(400).json({ error: 'Username and password required' }); - } - - // Create session - const session = this.sessionManager.createSession(username, { - password, - ipAddress: req.ip - }); - - logger.info(`User ${username} authenticated, session: ${session.id}`); - - res.json({ - success: true, - sessionId: session.id - }); - } catch (error) { - logger.error('Login error:', error); - res.status(500).json({ error: 'Authentication failed' }); - } + // RdpBroker health check endpoint + this.app.get('/api/broker-status', async (req, res) => { + const isAvailable = await this.checkRdpBrokerHealth(); + res.json({ + available: isAvailable, + broker: `${this.rdpBrokerHost}:${this.rdpBrokerPort}`, + timestamp: new Date().toISOString() + }); }); - // Get available targets + // Get targets list this.app.get('/api/targets', async (req, res) => { try { - const sessionId = req.headers['x-session-id']; - - if (!sessionId) { - return res.status(401).json({ error: 'Session ID required' }); + const isAvailable = await this.checkRdpBrokerHealth(); + if (!isAvailable) { + return res.status(503).json({ + error: 'RdpBroker service is unavailable. Please contact your administrator.', + timestamp: new Date().toISOString() + }); } - const session = this.sessionManager.getSession(sessionId); - if (!session) { - return res.status(401).json({ error: 'Invalid session' }); - } - - // In a real implementation, this would fetch from RdpBroker - // For now, return static list - const targets = await this.fetchTargetsFromBroker(session); - - res.json({ targets }); + // Parse targets from environment variable + const targets = this.parseTargetsFromEnv(); + res.json({ targets, timestamp: new Date().toISOString() }); } catch (error) { - logger.error('Targets fetch error:', error); - res.status(500).json({ error: 'Failed to fetch targets' }); + console.error(`${new Date().toISOString()} [ERROR] Error fetching targets:`, error); + res.status(500).json({ + error: 'Failed to fetch targets', + timestamp: new Date().toISOString() + }); } }); - // Logout - this.app.post('/api/auth/logout', (req, res) => { - const sessionId = req.headers['x-session-id']; - if (sessionId) { - this.sessionManager.destroySession(sessionId); - } - res.json({ success: true }); - }); + // API endpoints are removed - authentication handled by RdpBroker // Catch all - serve index.html this.app.get('*', (req, res) => { @@ -129,11 +101,10 @@ class RDPWebGatewayServer { setupWebSocket() { this.wss.on('connection', (ws, req) => { - logger.info('New WebSocket connection'); + console.log(`${new Date().toISOString()} [INFO] New WebSocket connection`); const proxyHandler = new RDPProxyHandler( - ws, - this.sessionManager, + ws, this.rdpBrokerHost, this.rdpBrokerPort ); @@ -143,7 +114,7 @@ class RDPWebGatewayServer { const message = JSON.parse(data.toString()); proxyHandler.handleMessage(message); } catch (error) { - logger.error('WebSocket message error:', error); + console.error(`${new Date().toISOString()} [ERROR] WebSocket message error:`, error); ws.send(JSON.stringify({ type: 'error', error: 'Invalid message format' @@ -152,50 +123,71 @@ class RDPWebGatewayServer { }); ws.on('close', () => { - logger.info('WebSocket connection closed'); + console.log(`${new Date().toISOString()} [INFO] WebSocket connection closed`); proxyHandler.cleanup(); }); ws.on('error', (error) => { - logger.error('WebSocket error:', error); + console.error(`${new Date().toISOString()} [ERROR] WebSocket error:`, error); proxyHandler.cleanup(); }); }); } - async fetchTargetsFromBroker(session) { - // This is a simplified version - // In production, this would communicate with RdpBroker - // to get the actual list of targets - - // For now, return example targets - return [ - { - name: "Windows Server 01", - host: "192.168.1.10", + + + // Check if RdpBroker is available + checkRdpBrokerHealth() { + return new Promise((resolve) => { + const socket = new net.Socket(); + const timeout = 3000; // 3 second timeout + + socket.setTimeout(timeout); + + socket.on('connect', () => { + socket.destroy(); + resolve(true); + }); + + socket.on('timeout', () => { + socket.destroy(); + resolve(false); + }); + + socket.on('error', () => { + resolve(false); + }); + + socket.connect(this.rdpBrokerPort, this.rdpBrokerHost); + }); + } + + // Parse targets from environment variable + parseTargetsFromEnv() { + const targetsEnv = process.env.RDP_TARGETS; + if (!targetsEnv) { + // Return default message if no targets configured + return [{ + name: 'Default', + host: 'via-rdpbroker', port: 3389, - description: "Production Web Server" - }, - { - name: "Windows Server 02", - host: "192.168.1.11", - port: 3389, - description: "Database Server" - }, - { - name: "Development Desktop", - host: "dev-machine.local", - port: 3389, - description: "Developer Workstation" - } - ]; + description: 'Targets will be provided by RdpBroker' + }]; + } + + try { + return JSON.parse(targetsEnv); + } catch (error) { + console.error(`${new Date().toISOString()} [ERROR] Failed to parse RDP_TARGETS:`, error); + return []; + } } start() { this.server.listen(this.port, () => { - logger.info(`RDP Web Gateway server running on port ${this.port}`); - logger.info(`RDP Broker: ${this.rdpBrokerHost}:${this.rdpBrokerPort}`); - logger.info(`WebSocket endpoint: ws://localhost:${this.port}/ws/rdp`); + console.log(`${new Date().toISOString()} [INFO] RDP Web Gateway server running on port ${this.port}`); + console.log(`${new Date().toISOString()} [INFO] RDP Broker: ${this.rdpBrokerHost}:${this.rdpBrokerPort}`); + console.log(`${new Date().toISOString()} [INFO] WebSocket endpoint: ws://localhost:${this.port}/ws/rdp`); }); } } @@ -206,9 +198,9 @@ server.start(); // Graceful shutdown process.on('SIGTERM', () => { - logger.info('SIGTERM received, shutting down gracefully...'); + console.log(`${new Date().toISOString()} [INFO] SIGTERM received, shutting down gracefully...`); server.server.close(() => { - logger.info('Server closed'); + console.log(`${new Date().toISOString()} [INFO] Server closed`); process.exit(0); }); }); diff --git a/web-gateway/src/sessionManager.js b/web-gateway/src/sessionManager.js deleted file mode 100644 index 43994ea..0000000 --- a/web-gateway/src/sessionManager.js +++ /dev/null @@ -1,94 +0,0 @@ -const { v4: uuidv4 } = require('uuid'); -const logger = require('./logger'); - -class SessionManager { - constructor() { - this.sessions = new Map(); - this.sessionTimeout = 3600000; // 1 hour - - // Cleanup inactive sessions every 5 minutes - setInterval(() => this.cleanupSessions(), 300000); - } - - createSession(username, userData = {}) { - const sessionId = uuidv4(); - const session = { - id: sessionId, - username, - createdAt: Date.now(), - lastActivity: Date.now(), - data: userData, - rdpConnection: null - }; - - this.sessions.set(sessionId, session); - logger.info(`Session created: ${sessionId} for user ${username}`); - - return session; - } - - getSession(sessionId) { - const session = this.sessions.get(sessionId); - if (session) { - session.lastActivity = Date.now(); - } - return session; - } - - updateSession(sessionId, data) { - const session = this.sessions.get(sessionId); - if (session) { - session.data = { ...session.data, ...data }; - session.lastActivity = Date.now(); - } - return session; - } - - destroySession(sessionId) { - const session = this.sessions.get(sessionId); - if (session) { - logger.info(`Session destroyed: ${sessionId}`); - - // Cleanup any active RDP connection - if (session.rdpConnection) { - session.rdpConnection.close(); - } - - this.sessions.delete(sessionId); - return true; - } - return false; - } - - cleanupSessions() { - const now = Date.now(); - let cleaned = 0; - - for (const [sessionId, session] of this.sessions.entries()) { - if (now - session.lastActivity > this.sessionTimeout) { - logger.info(`Cleaning up inactive session: ${sessionId}`); - this.destroySession(sessionId); - cleaned++; - } - } - - if (cleaned > 0) { - logger.info(`Cleaned up ${cleaned} inactive session(s)`); - } - } - - getActiveSessionCount() { - return this.sessions.size; - } - - getAllSessions() { - return Array.from(this.sessions.values()).map(session => ({ - id: session.id, - username: session.username, - createdAt: session.createdAt, - lastActivity: session.lastActivity - })); - } -} - -module.exports = SessionManager;