Simplification Web-Gateway
This commit is contained in:
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@@ -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/
|
||||||
@@ -3,14 +3,13 @@
|
|||||||
# Server
|
# Server
|
||||||
PORT=8080
|
PORT=8080
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
LOG_LEVEL=info
|
|
||||||
|
|
||||||
# RDP Broker Connection
|
# RDP Broker Connection
|
||||||
RDP_BROKER_HOST=rdpbroker
|
RDP_BROKER_HOST=rdpbroker
|
||||||
RDP_BROKER_PORT=3389
|
RDP_BROKER_PORT=3389
|
||||||
|
|
||||||
# Session Configuration
|
# Optional: Pre-configure RDP Targets
|
||||||
SESSION_TIMEOUT=3600000
|
# Format: JSON array of target objects
|
||||||
|
# If not set, RdpBroker will provide targets dynamically
|
||||||
# Security (set these in production)
|
# Example:
|
||||||
# SESSION_SECRET=your-secret-key-here
|
# RDP_TARGETS=[{"name":"Server1","host":"srv1.example.com","port":3389,"description":"Production Server"},{"name":"Server2","host":"srv2.example.com","port":3389,"description":"Development Server"}]
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ WORKDIR /app
|
|||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci --only=production
|
RUN npm install \
|
||||||
|
&& npm ci --only=production
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
@@ -19,10 +20,6 @@ RUN apk add --no-cache dumb-init
|
|||||||
RUN addgroup -g 1001 -S nodejs && \
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
adduser -S nodejs -u 1001
|
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
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy dependencies from builder
|
# Copy dependencies from builder
|
||||||
|
|||||||
@@ -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
|
- 🌐 **Browser-Based Access** - Connect to RDP sessions from any modern web browser
|
||||||
- 🔒 **Secure WebSocket** - Real-time bidirectional communication
|
- 🔒 **Secure WebSocket** - Real-time bidirectional communication
|
||||||
- 🎨 **Modern UI** - Clean, responsive interface
|
- 🎨 **Modern UI** - Clean, responsive interface
|
||||||
- 🔑 **Session Management** - Automatic session cleanup and timeout
|
- 🔑 **Simplified Authentication** - Credentials passed directly to RdpBroker
|
||||||
- 📊 **Activity Monitoring** - Track active connections
|
- 📊 **Service Health Monitoring** - Automatic RdpBroker availability checks
|
||||||
|
- 🎯 **Dynamic Target Loading** - Targets fetched from configuration or RdpBroker
|
||||||
- ⚡ **Low Latency** - Optimized for performance
|
- ⚡ **Low Latency** - Optimized for performance
|
||||||
|
- ☁️ **Kubernetes Native** - Console-only logging for cloud environments
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -63,10 +65,23 @@ Edit `.env` file:
|
|||||||
PORT=8080
|
PORT=8080
|
||||||
RDP_BROKER_HOST=rdpbroker
|
RDP_BROKER_HOST=rdpbroker
|
||||||
RDP_BROKER_PORT=3389
|
RDP_BROKER_PORT=3389
|
||||||
LOG_LEVEL=info
|
NODE_ENV=production
|
||||||
SESSION_TIMEOUT=3600000
|
|
||||||
|
# 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
|
## Usage
|
||||||
|
|
||||||
### Access the Web Interface
|
### Access the Web Interface
|
||||||
@@ -78,43 +93,55 @@ SESSION_TIMEOUT=3600000
|
|||||||
|
|
||||||
### API Endpoints
|
### API Endpoints
|
||||||
|
|
||||||
#### POST /api/auth/login
|
#### GET /health
|
||||||
Authenticate user and create session.
|
Health check endpoint for monitoring the web gateway.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "user@domain.com",
|
|
||||||
"password": "password"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"status": "healthy",
|
||||||
"sessionId": "uuid"
|
"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 /api/targets
|
||||||
Get available RDP targets (requires X-Session-ID header).
|
Fetch available RDP targets.
|
||||||
|
|
||||||
Response:
|
**Success Response (200):**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"name": "Server 01",
|
"name": "Windows Server 2022",
|
||||||
"host": "192.168.1.10",
|
"host": "ws2022.example.com",
|
||||||
"port": 3389,
|
"port": 3389,
|
||||||
"description": "Production Server"
|
"description": "Production Windows Server"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"timestamp": "2025-12-04T10:30:00.000Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### POST /api/auth/logout
|
**Service Unavailable (503):**
|
||||||
Logout and destroy session (requires X-Session-ID header).
|
```json
|
||||||
|
{
|
||||||
|
"error": "RdpBroker service is unavailable. Please contact your administrator.",
|
||||||
|
"timestamp": "2025-12-04T10:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### WebSocket Protocol
|
### WebSocket Protocol
|
||||||
|
|
||||||
@@ -126,7 +153,8 @@ Connect to `ws://localhost:8080/ws/rdp`
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "connect",
|
"type": "connect",
|
||||||
"sessionId": "uuid",
|
"username": "user@domain.com",
|
||||||
|
"password": "password123",
|
||||||
"target": {
|
"target": {
|
||||||
"name": "Server 01",
|
"name": "Server 01",
|
||||||
"host": "192.168.1.10",
|
"host": "192.168.1.10",
|
||||||
@@ -209,20 +237,21 @@ helm install rdp-web-gateway ./chart/rdp-web-gateway -n rdpbroker
|
|||||||
- Firefox 88+
|
- Firefox 88+
|
||||||
- Safari 14+
|
- Safari 14+
|
||||||
- Opera 76+
|
- Opera 76+
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
- Use HTTPS/WSS in production
|
- Use HTTPS/WSS in production
|
||||||
- Implement rate limiting
|
- Credentials are passed directly to RdpBroker (no storage in web-gateway)
|
||||||
- Set strong session secrets
|
- Implement rate limiting at ingress level
|
||||||
- Enable CORS restrictions
|
- Enable CORS restrictions
|
||||||
- Regular security audits
|
- Regular security audits
|
||||||
|
- All authentication handled by RdpBroker → Samba ADs
|
||||||
|
- Regular security audits
|
||||||
## Performance Tuning
|
## Performance Tuning
|
||||||
|
|
||||||
- Adjust session timeout based on usage
|
|
||||||
- Configure WebSocket buffer sizes
|
- 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
|
- Use CDN for static assets in production
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
@@ -253,9 +282,21 @@ location /ws/ {
|
|||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### High memory usage
|
### 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.
|
Reduce session timeout or implement session limits per user.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
@@ -33,5 +33,6 @@
|
|||||||
Configuration:
|
Configuration:
|
||||||
- RDP Broker: {{ .Values.config.rdpBroker.host }}:{{ .Values.config.rdpBroker.port }}
|
- RDP Broker: {{ .Values.config.rdpBroker.host }}:{{ .Values.config.rdpBroker.port }}
|
||||||
- Server Port: {{ .Values.config.server.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 }}
|
- 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.
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ data:
|
|||||||
"port": {{ .Values.config.rdpBroker.port }}
|
"port": {{ .Values.config.rdpBroker.port }}
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"port": {{ .Values.config.server.port }},
|
"port": {{ .Values.config.server.port }}
|
||||||
"logLevel": "{{ .Values.config.server.logLevel }}"
|
|
||||||
},
|
|
||||||
"session": {
|
|
||||||
"timeout": {{ .Values.config.session.timeout }}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,23 +41,16 @@ spec:
|
|||||||
value: {{ .Values.config.rdpBroker.host | quote }}
|
value: {{ .Values.config.rdpBroker.host | quote }}
|
||||||
- name: RDP_BROKER_PORT
|
- name: RDP_BROKER_PORT
|
||||||
value: {{ .Values.config.rdpBroker.port | quote }}
|
value: {{ .Values.config.rdpBroker.port | quote }}
|
||||||
- name: LOG_LEVEL
|
{{- if .Values.config.rdpTargets }}
|
||||||
value: {{ .Values.config.server.logLevel | quote }}
|
- name: RDP_TARGETS
|
||||||
- name: SESSION_TIMEOUT
|
value: {{ .Values.config.rdpTargets | toJson | quote }}
|
||||||
value: {{ .Values.config.session.timeout | quote }}
|
{{- end }}
|
||||||
- name: NODE_ENV
|
- name: NODE_ENV
|
||||||
value: "production"
|
value: "production"
|
||||||
{{- range .Values.env }}
|
{{- range .Values.env }}
|
||||||
- name: {{ .name }}
|
- name: {{ .name }}
|
||||||
value: {{ .value | quote }}
|
value: {{ .value | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.secrets.sessionSecret }}
|
|
||||||
- name: SESSION_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: {{ include "rdp-web-gateway.fullname" . }}-secrets
|
|
||||||
key: sessionSecret
|
|
||||||
{{- end }}
|
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: {{ .Values.config.server.port }}
|
containerPort: {{ .Values.config.server.port }}
|
||||||
@@ -68,17 +61,6 @@ spec:
|
|||||||
{{- toYaml .Values.readinessProbe | nindent 12 }}
|
{{- toYaml .Values.readinessProbe | nindent 12 }}
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
{{- 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 }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|||||||
@@ -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 }}
|
|
||||||
@@ -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 }}
|
|
||||||
@@ -82,29 +82,26 @@ config:
|
|||||||
# Server configuration
|
# Server configuration
|
||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
logLevel: "info"
|
|
||||||
|
|
||||||
# Session configuration
|
# Optional: Pre-configure RDP targets
|
||||||
session:
|
# If not set, targets will be managed by RdpBroker
|
||||||
timeout: 3600000 # 1 hour in milliseconds
|
# 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
|
# Environment variables
|
||||||
env: []
|
env: []
|
||||||
# - name: CUSTOM_VAR
|
# - name: CUSTOM_VAR
|
||||||
# value: "value"
|
# 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
|
# Liveness and readiness probes
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
|
|||||||
@@ -20,13 +20,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"ws": "^8.14.2",
|
"ws": "^8.14.2",
|
||||||
"node-rdpjs": "^0.3.2",
|
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5"
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"winston": "^3.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.2"
|
"nodemon": "^3.0.2"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class RDPWebGateway {
|
|||||||
this.ctx = null;
|
this.ctx = null;
|
||||||
this.currentUser = null;
|
this.currentUser = null;
|
||||||
this.currentTarget = null;
|
this.currentTarget = null;
|
||||||
this.sessionId = null;
|
this.credentials = null;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@@ -63,27 +63,24 @@ class RDPWebGateway {
|
|||||||
errorMessage.style.display = 'none';
|
errorMessage.style.display = 'none';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/auth/login', {
|
// Check if RdpBroker service is available
|
||||||
method: 'POST',
|
const statusResponse = await fetch('/api/broker-status');
|
||||||
headers: {
|
const statusData = await statusResponse.json();
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
if (!statusData.available) {
|
||||||
|
this.showError(errorMessage, 'RDP service is currently unavailable. Please contact your administrator.');
|
||||||
if (response.ok) {
|
return;
|
||||||
this.currentUser = username;
|
|
||||||
this.sessionId = data.sessionId;
|
|
||||||
await this.loadTargets();
|
|
||||||
this.showTargetsView();
|
|
||||||
} else {
|
|
||||||
this.showError(errorMessage, data.error || 'Authentication failed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
} catch (error) {
|
||||||
console.error('Login error:', 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 {
|
} finally {
|
||||||
loginBtn.disabled = false;
|
loginBtn.disabled = false;
|
||||||
btnText.style.display = 'block';
|
btnText.style.display = 'block';
|
||||||
@@ -93,25 +90,45 @@ class RDPWebGateway {
|
|||||||
|
|
||||||
async loadTargets() {
|
async loadTargets() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/targets', {
|
const response = await fetch('/api/targets');
|
||||||
headers: {
|
|
||||||
'X-Session-ID': this.sessionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
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');
|
throw new Error('Failed to load targets');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.displayTargets(data.targets);
|
this.showTargetsView(data.targets);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Load targets error:', error);
|
console.error('Error loading targets:', error);
|
||||||
const targetsError = document.getElementById('targetsError');
|
// Show error in targets view
|
||||||
this.showError(targetsError, 'Failed to load available desktops');
|
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 = `
|
||||||
|
<div style="text-align: center; padding: 20px;">
|
||||||
|
<p style="color: var(--error-color); margin-bottom: 10px;">⚠️ ${this.escapeHtml(errorMsg)}</p>
|
||||||
|
<button onclick="location.reload()" class="btn btn-secondary">Retry</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.displayTargets(targets);
|
||||||
|
}
|
||||||
|
|
||||||
displayTargets(targets) {
|
displayTargets(targets) {
|
||||||
const targetsList = document.getElementById('targetsList');
|
const targetsList = document.getElementById('targetsList');
|
||||||
targetsList.innerHTML = '';
|
targetsList.innerHTML = '';
|
||||||
@@ -153,10 +170,11 @@ class RDPWebGateway {
|
|||||||
|
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
// Send connection request
|
// Send credentials and connection request to RdpBroker
|
||||||
this.ws.send(JSON.stringify({
|
this.ws.send(JSON.stringify({
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
sessionId: this.sessionId,
|
username: this.credentials.username,
|
||||||
|
password: this.credentials.password,
|
||||||
target: target,
|
target: target,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
@@ -324,7 +342,7 @@ class RDPWebGateway {
|
|||||||
}
|
}
|
||||||
this.currentUser = null;
|
this.currentUser = null;
|
||||||
this.currentTarget = null;
|
this.currentTarget = null;
|
||||||
this.sessionId = null;
|
this.credentials = null;
|
||||||
this.showLoginView();
|
this.showLoginView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,13 +354,6 @@ class RDPWebGateway {
|
|||||||
document.getElementById('password').value = '';
|
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() {
|
showRDPViewer() {
|
||||||
document.getElementById('loginCard').style.display = 'none';
|
document.getElementById('loginCard').style.display = 'none';
|
||||||
document.getElementById('targetsCard').style.display = 'none';
|
document.getElementById('targetsCard').style.display = 'none';
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
const net = require('net');
|
const net = require('net');
|
||||||
const logger = require('./logger');
|
|
||||||
|
|
||||||
class RDPProxyHandler {
|
class RDPProxyHandler {
|
||||||
constructor(websocket, sessionManager, rdpBrokerHost, rdpBrokerPort) {
|
constructor(websocket, rdpBrokerHost, rdpBrokerPort) {
|
||||||
this.ws = websocket;
|
this.ws = websocket;
|
||||||
this.sessionManager = sessionManager;
|
|
||||||
this.rdpBrokerHost = rdpBrokerHost;
|
this.rdpBrokerHost = rdpBrokerHost;
|
||||||
this.rdpBrokerPort = rdpBrokerPort;
|
this.rdpBrokerPort = rdpBrokerPort;
|
||||||
this.session = null;
|
|
||||||
this.rdpSocket = null;
|
this.rdpSocket = null;
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
}
|
}
|
||||||
@@ -28,40 +25,33 @@ class RDPProxyHandler {
|
|||||||
this.handleSpecialCommand(message);
|
this.handleSpecialCommand(message);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.warn(`Unknown message type: ${message.type}`);
|
console.warn(`${new Date().toISOString()} [WARN] Unknown message type: ${message.type}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error handling message:', error);
|
console.error(`${new Date().toISOString()} [ERROR] Error handling message:`, error);
|
||||||
this.sendError('Failed to process message');
|
this.sendError('Failed to process message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConnect(message) {
|
async handleConnect(message) {
|
||||||
const { sessionId, target } = message;
|
const { username, password, target } = message;
|
||||||
|
|
||||||
// Validate session
|
console.log(`${new Date().toISOString()} [INFO] Connecting to RDP Broker, target: ${target?.name || 'unknown'}`);
|
||||||
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}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Connect to RDP Broker
|
// Connect to RDP Broker
|
||||||
this.rdpSocket = new net.Socket();
|
this.rdpSocket = new net.Socket();
|
||||||
|
|
||||||
this.rdpSocket.connect(this.rdpBrokerPort, this.rdpBrokerHost, () => {
|
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;
|
this.isConnected = true;
|
||||||
|
|
||||||
// Send authentication to RdpBroker
|
// Send credentials to RdpBroker (it will handle authentication)
|
||||||
// In real implementation, this would follow the RDP protocol
|
this.sendAuthToBroker(username, password);
|
||||||
this.sendAuthToBroker();
|
|
||||||
|
|
||||||
this.ws.send(JSON.stringify({
|
this.ws.send(JSON.stringify({
|
||||||
type: 'connected',
|
type: 'connected',
|
||||||
target: target.name
|
target: target?.name || 'RDP Server'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Set canvas size
|
// Set canvas size
|
||||||
@@ -75,20 +65,19 @@ class RDPProxyHandler {
|
|||||||
// Handle data from RDP Broker
|
// Handle data from RDP Broker
|
||||||
this.rdpSocket.on('data', (data) => {
|
this.rdpSocket.on('data', (data) => {
|
||||||
// Forward RDP data to WebSocket client
|
// Forward RDP data to WebSocket client
|
||||||
// In production, this would be properly decoded RDP frames
|
|
||||||
if (this.ws.readyState === 1) { // WebSocket.OPEN
|
if (this.ws.readyState === 1) { // WebSocket.OPEN
|
||||||
this.ws.send(data);
|
this.ws.send(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rdpSocket.on('error', (error) => {
|
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.sendError('Connection to RDP broker failed');
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rdpSocket.on('close', () => {
|
this.rdpSocket.on('close', () => {
|
||||||
logger.info('RDP connection closed');
|
console.log(`${new Date().toISOString()} [INFO] RDP connection closed`);
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
if (this.ws.readyState === 1) {
|
if (this.ws.readyState === 1) {
|
||||||
this.ws.close();
|
this.ws.close();
|
||||||
@@ -96,25 +85,19 @@ class RDPProxyHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Connection error:', error);
|
console.error(`${new Date().toISOString()} [ERROR] Connection error:`, error);
|
||||||
this.sendError('Failed to connect to RDP broker');
|
this.sendError('Failed to connect to RDP broker');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAuthToBroker() {
|
sendAuthToBroker(username, password) {
|
||||||
// This is a simplified version
|
// Send credentials directly to RdpBroker
|
||||||
// In production, implement proper RDP protocol handshake
|
// 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: <username>\nPassword: <password>\n"
|
// Format: "Username: <username>\nPassword: <password>\n"
|
||||||
const authMessage = `${authData.username}\n${authData.password}\n`;
|
const authMessage = `${username}\n${password}\n`;
|
||||||
this.rdpSocket.write(authMessage);
|
this.rdpSocket.write(authMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +147,10 @@ class RDPProxyHandler {
|
|||||||
command: 'ctrl-alt-del'
|
command: 'ctrl-alt-del'
|
||||||
});
|
});
|
||||||
this.rdpSocket.write(cadData + '\n');
|
this.rdpSocket.write(cadData + '\n');
|
||||||
logger.info('Sent Ctrl+Alt+Del');
|
console.log(`${new Date().toISOString()} [INFO] Sent Ctrl+Alt+Del`);
|
||||||
break;
|
break;
|
||||||
default:
|
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.rdpSocket = null;
|
||||||
}
|
}
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
|
|
||||||
if (this.session) {
|
|
||||||
this.sessionManager.updateSession(this.session.id, {
|
|
||||||
rdpConnection: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ const express = require('express');
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const net = require('net');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const logger = require('./logger');
|
|
||||||
const SessionManager = require('./sessionManager');
|
|
||||||
const RDPProxyHandler = require('./rdpProxyHandler');
|
const RDPProxyHandler = require('./rdpProxyHandler');
|
||||||
|
|
||||||
class RDPWebGatewayServer {
|
class RDPWebGatewayServer {
|
||||||
@@ -18,7 +17,6 @@ class RDPWebGatewayServer {
|
|||||||
path: '/ws/rdp'
|
path: '/ws/rdp'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sessionManager = new SessionManager();
|
|
||||||
this.port = process.env.PORT || 8080;
|
this.port = process.env.PORT || 8080;
|
||||||
this.rdpBrokerHost = process.env.RDP_BROKER_HOST || 'rdpbroker';
|
this.rdpBrokerHost = process.env.RDP_BROKER_HOST || 'rdpbroker';
|
||||||
this.rdpBrokerPort = process.env.RDP_BROKER_PORT || 3389;
|
this.rdpBrokerPort = process.env.RDP_BROKER_PORT || 3389;
|
||||||
@@ -45,7 +43,7 @@ class RDPWebGatewayServer {
|
|||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
this.app.use((req, res, next) => {
|
this.app.use((req, res, next) => {
|
||||||
logger.info(`${req.method} ${req.url}`);
|
console.log(`${new Date().toISOString()} [INFO] ${req.method} ${req.url}`);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -56,70 +54,44 @@ class RDPWebGatewayServer {
|
|||||||
res.json({
|
res.json({
|
||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
sessions: this.sessionManager.getActiveSessionCount()
|
uptime: process.uptime()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Authentication endpoint
|
// RdpBroker health check endpoint
|
||||||
this.app.post('/api/auth/login', async (req, res) => {
|
this.app.get('/api/broker-status', async (req, res) => {
|
||||||
try {
|
const isAvailable = await this.checkRdpBrokerHealth();
|
||||||
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({
|
res.json({
|
||||||
success: true,
|
available: isAvailable,
|
||||||
sessionId: session.id
|
broker: `${this.rdpBrokerHost}:${this.rdpBrokerPort}`,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
logger.error('Login error:', error);
|
|
||||||
res.status(500).json({ error: 'Authentication failed' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get available targets
|
// Get targets list
|
||||||
this.app.get('/api/targets', async (req, res) => {
|
this.app.get('/api/targets', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const sessionId = req.headers['x-session-id'];
|
const isAvailable = await this.checkRdpBrokerHealth();
|
||||||
|
if (!isAvailable) {
|
||||||
if (!sessionId) {
|
return res.status(503).json({
|
||||||
return res.status(401).json({ error: 'Session ID required' });
|
error: 'RdpBroker service is unavailable. Please contact your administrator.',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = this.sessionManager.getSession(sessionId);
|
// Parse targets from environment variable
|
||||||
if (!session) {
|
const targets = this.parseTargetsFromEnv();
|
||||||
return res.status(401).json({ error: 'Invalid session' });
|
res.json({ targets, timestamp: new Date().toISOString() });
|
||||||
}
|
|
||||||
|
|
||||||
// In a real implementation, this would fetch from RdpBroker
|
|
||||||
// For now, return static list
|
|
||||||
const targets = await this.fetchTargetsFromBroker(session);
|
|
||||||
|
|
||||||
res.json({ targets });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Targets fetch error:', error);
|
console.error(`${new Date().toISOString()} [ERROR] Error fetching targets:`, error);
|
||||||
res.status(500).json({ error: 'Failed to fetch targets' });
|
res.status(500).json({
|
||||||
|
error: 'Failed to fetch targets',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Logout
|
// API endpoints are removed - authentication handled by RdpBroker
|
||||||
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 });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Catch all - serve index.html
|
// Catch all - serve index.html
|
||||||
this.app.get('*', (req, res) => {
|
this.app.get('*', (req, res) => {
|
||||||
@@ -129,11 +101,10 @@ class RDPWebGatewayServer {
|
|||||||
|
|
||||||
setupWebSocket() {
|
setupWebSocket() {
|
||||||
this.wss.on('connection', (ws, req) => {
|
this.wss.on('connection', (ws, req) => {
|
||||||
logger.info('New WebSocket connection');
|
console.log(`${new Date().toISOString()} [INFO] New WebSocket connection`);
|
||||||
|
|
||||||
const proxyHandler = new RDPProxyHandler(
|
const proxyHandler = new RDPProxyHandler(
|
||||||
ws,
|
ws,
|
||||||
this.sessionManager,
|
|
||||||
this.rdpBrokerHost,
|
this.rdpBrokerHost,
|
||||||
this.rdpBrokerPort
|
this.rdpBrokerPort
|
||||||
);
|
);
|
||||||
@@ -143,7 +114,7 @@ class RDPWebGatewayServer {
|
|||||||
const message = JSON.parse(data.toString());
|
const message = JSON.parse(data.toString());
|
||||||
proxyHandler.handleMessage(message);
|
proxyHandler.handleMessage(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('WebSocket message error:', error);
|
console.error(`${new Date().toISOString()} [ERROR] WebSocket message error:`, error);
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
error: 'Invalid message format'
|
error: 'Invalid message format'
|
||||||
@@ -152,50 +123,71 @@ class RDPWebGatewayServer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ws.on('close', () => {
|
ws.on('close', () => {
|
||||||
logger.info('WebSocket connection closed');
|
console.log(`${new Date().toISOString()} [INFO] WebSocket connection closed`);
|
||||||
proxyHandler.cleanup();
|
proxyHandler.cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('error', (error) => {
|
ws.on('error', (error) => {
|
||||||
logger.error('WebSocket error:', error);
|
console.error(`${new Date().toISOString()} [ERROR] WebSocket error:`, error);
|
||||||
proxyHandler.cleanup();
|
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 [
|
// Check if RdpBroker is available
|
||||||
{
|
checkRdpBrokerHealth() {
|
||||||
name: "Windows Server 01",
|
return new Promise((resolve) => {
|
||||||
host: "192.168.1.10",
|
const socket = new net.Socket();
|
||||||
port: 3389,
|
const timeout = 3000; // 3 second timeout
|
||||||
description: "Production Web Server"
|
|
||||||
},
|
socket.setTimeout(timeout);
|
||||||
{
|
|
||||||
name: "Windows Server 02",
|
socket.on('connect', () => {
|
||||||
host: "192.168.1.11",
|
socket.destroy();
|
||||||
port: 3389,
|
resolve(true);
|
||||||
description: "Database Server"
|
});
|
||||||
},
|
|
||||||
{
|
socket.on('timeout', () => {
|
||||||
name: "Development Desktop",
|
socket.destroy();
|
||||||
host: "dev-machine.local",
|
resolve(false);
|
||||||
port: 3389,
|
});
|
||||||
description: "Developer Workstation"
|
|
||||||
|
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: '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() {
|
start() {
|
||||||
this.server.listen(this.port, () => {
|
this.server.listen(this.port, () => {
|
||||||
logger.info(`RDP Web Gateway server running on port ${this.port}`);
|
console.log(`${new Date().toISOString()} [INFO] RDP Web Gateway server running on port ${this.port}`);
|
||||||
logger.info(`RDP Broker: ${this.rdpBrokerHost}:${this.rdpBrokerPort}`);
|
console.log(`${new Date().toISOString()} [INFO] RDP Broker: ${this.rdpBrokerHost}:${this.rdpBrokerPort}`);
|
||||||
logger.info(`WebSocket endpoint: ws://localhost:${this.port}/ws/rdp`);
|
console.log(`${new Date().toISOString()} [INFO] WebSocket endpoint: ws://localhost:${this.port}/ws/rdp`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,9 +198,9 @@ server.start();
|
|||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
logger.info('SIGTERM received, shutting down gracefully...');
|
console.log(`${new Date().toISOString()} [INFO] SIGTERM received, shutting down gracefully...`);
|
||||||
server.server.close(() => {
|
server.server.close(() => {
|
||||||
logger.info('Server closed');
|
console.log(`${new Date().toISOString()} [INFO] Server closed`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
|
||||||
Reference in New Issue
Block a user