Interactive list
This commit is contained in:
336
PROTOCOL.md
Normal file
336
PROTOCOL.md
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# RdpBroker Protocol Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the protocol between web-gateway and RdpBroker for user authentication and target management.
|
||||||
|
|
||||||
|
## Connection Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Web-Gateway RdpBroker Samba AD / Target RDP Servers
|
||||||
|
| | |
|
||||||
|
|--AUTH\n{user}\n{pass}\n----->| |
|
||||||
|
| |---LDAP Auth----------------->|
|
||||||
|
| |<----Auth Result--------------|
|
||||||
|
| | |
|
||||||
|
|<---{type:targets,targets:[]}| |
|
||||||
|
| OR | |
|
||||||
|
|<---{type:auth_failed}--------| |
|
||||||
|
| | |
|
||||||
|
|--SELECT\n{target_name}\n---->| |
|
||||||
|
| |---Connect to Target--------->|
|
||||||
|
|<---{type:rdp_ready}----------|<-----------------------------|
|
||||||
|
| | |
|
||||||
|
|<====== RDP Binary Data ======|<===== RDP Session ==========|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol Messages
|
||||||
|
|
||||||
|
### Phase 1: Authentication
|
||||||
|
|
||||||
|
#### Request Format (Web-Gateway → RdpBroker)
|
||||||
|
|
||||||
|
```
|
||||||
|
AUTH\n
|
||||||
|
username\n
|
||||||
|
password\n
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
AUTH
|
||||||
|
user@example.com
|
||||||
|
SecurePassword123
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Success Response (RdpBroker → Web-Gateway)
|
||||||
|
|
||||||
|
**JSON Format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "auth_success",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- The message must end with `\n\n` (double newline) to signal end of JSON message
|
||||||
|
- Targets list is personalized based on user permissions/groups in Samba AD
|
||||||
|
- Empty array means user is authenticated but has no authorized targets
|
||||||
|
|
||||||
|
#### Failure Response (RdpBroker → Web-Gateway)
|
||||||
|
|
||||||
|
**JSON Format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "auth_failed",
|
||||||
|
"message": "Invalid credentials"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Followed by connection close.
|
||||||
|
|
||||||
|
**Possible error messages:**
|
||||||
|
- "Invalid credentials"
|
||||||
|
- "User account disabled"
|
||||||
|
- "LDAP connection failed"
|
||||||
|
- "User not authorized for any targets"
|
||||||
|
|
||||||
|
### Phase 2: Target Selection
|
||||||
|
|
||||||
|
#### Request Format (Web-Gateway → RdpBroker)
|
||||||
|
|
||||||
|
```
|
||||||
|
SELECT\n
|
||||||
|
target_name\n
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
SELECT
|
||||||
|
Windows Server 2022
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Target name must match exactly one of the names from the targets list
|
||||||
|
- Connection should be rejected if target name is invalid or not in user's authorized list
|
||||||
|
|
||||||
|
#### Success Response (RdpBroker → Web-Gateway)
|
||||||
|
|
||||||
|
**JSON Format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "rdp_ready"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Followed by `\n\n`, then RDP binary data stream begins.
|
||||||
|
|
||||||
|
After this message, the connection transitions to raw RDP protocol forwarding mode.
|
||||||
|
|
||||||
|
#### Failure Response (RdpBroker → Web-Gateway)
|
||||||
|
|
||||||
|
**JSON Format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"message": "Target not available"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Followed by connection close.
|
||||||
|
|
||||||
|
**Possible error messages:**
|
||||||
|
- "Target not found"
|
||||||
|
- "Target not authorized for user"
|
||||||
|
- "Failed to connect to target server"
|
||||||
|
- "Target server unreachable"
|
||||||
|
|
||||||
|
### Phase 3: RDP Session
|
||||||
|
|
||||||
|
After `rdp_ready` message, all subsequent data is raw RDP protocol:
|
||||||
|
- Web-Gateway forwards mouse/keyboard events as RDP protocol data
|
||||||
|
- RdpBroker forwards screen updates as RDP protocol data
|
||||||
|
- Connection is bidirectional binary stream
|
||||||
|
|
||||||
|
## Implementation Guidelines for RdpBroker
|
||||||
|
|
||||||
|
### 1. Accept Connection
|
||||||
|
Listen on port 3389 (configurable via RDP_LISTEN_PORT)
|
||||||
|
|
||||||
|
### 2. Read Authentication Message
|
||||||
|
```c
|
||||||
|
char buffer[4096];
|
||||||
|
int bytes = read(client_fd, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
// Parse "AUTH\n{username}\n{password}\n"
|
||||||
|
char *lines[3];
|
||||||
|
int line_count = 0;
|
||||||
|
char *token = strtok(buffer, "\n");
|
||||||
|
while (token != NULL && line_count < 3) {
|
||||||
|
lines[line_count++] = token;
|
||||||
|
token = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(lines[0], "AUTH") != 0) {
|
||||||
|
send_error(client_fd, "Invalid protocol");
|
||||||
|
close(client_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *username = lines[1];
|
||||||
|
char *password = lines[2];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Authenticate with Samba AD
|
||||||
|
```c
|
||||||
|
int auth_result = authenticate_user(username, password,
|
||||||
|
config->samba_server,
|
||||||
|
config->samba_port,
|
||||||
|
config->base_dn);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Get User's Authorized Targets
|
||||||
|
```c
|
||||||
|
rdp_target_t *user_targets[MAX_TARGETS];
|
||||||
|
int target_count = get_user_targets(username, config, user_targets);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recommended approach:**
|
||||||
|
- Query user's groups from LDAP
|
||||||
|
- Filter targets based on group membership or user attributes
|
||||||
|
- Return only targets the user is authorized to access
|
||||||
|
|
||||||
|
**Example YAML configuration:**
|
||||||
|
```yaml
|
||||||
|
targets:
|
||||||
|
- name: "Windows Server 2022"
|
||||||
|
host: "ws2022.example.com"
|
||||||
|
port: 3389
|
||||||
|
description: "Production Windows Server"
|
||||||
|
authorized_groups:
|
||||||
|
- "Domain Admins"
|
||||||
|
- "Server Operators"
|
||||||
|
|
||||||
|
- name: "Development Server"
|
||||||
|
host: "dev.example.com"
|
||||||
|
port: 3389
|
||||||
|
description: "Development environment"
|
||||||
|
authorized_groups:
|
||||||
|
- "Developers"
|
||||||
|
- "Domain Admins"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Send Targets List
|
||||||
|
```c
|
||||||
|
char json_response[8192];
|
||||||
|
snprintf(json_response, sizeof(json_response),
|
||||||
|
"{\"type\":\"auth_success\",\"targets\":[");
|
||||||
|
|
||||||
|
for (int i = 0; i < target_count; i++) {
|
||||||
|
char target_json[512];
|
||||||
|
snprintf(target_json, sizeof(target_json),
|
||||||
|
"%s{\"name\":\"%s\",\"host\":\"%s\",\"port\":%d,\"description\":\"%s\"}",
|
||||||
|
(i > 0 ? "," : ""),
|
||||||
|
user_targets[i]->name,
|
||||||
|
user_targets[i]->host,
|
||||||
|
user_targets[i]->port,
|
||||||
|
user_targets[i]->description);
|
||||||
|
strcat(json_response, target_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
strcat(json_response, "]}\n\n");
|
||||||
|
write(client_fd, json_response, strlen(json_response));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Read Target Selection
|
||||||
|
```c
|
||||||
|
bytes = read(client_fd, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
// Parse "SELECT\n{target_name}\n"
|
||||||
|
if (strncmp(buffer, "SELECT\n", 7) != 0) {
|
||||||
|
send_error(client_fd, "Invalid protocol");
|
||||||
|
close(client_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *target_name = buffer + 7;
|
||||||
|
char *newline = strchr(target_name, '\n');
|
||||||
|
if (newline) *newline = '\0';
|
||||||
|
|
||||||
|
// Verify target is in user's authorized list
|
||||||
|
rdp_target_t *selected_target = find_target_in_list(target_name,
|
||||||
|
user_targets,
|
||||||
|
target_count);
|
||||||
|
if (!selected_target) {
|
||||||
|
send_error(client_fd, "Target not authorized");
|
||||||
|
close(client_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Connect to Target and Start Forwarding
|
||||||
|
```c
|
||||||
|
int target_fd = connect_to_rdp_target(selected_target->host,
|
||||||
|
selected_target->port);
|
||||||
|
|
||||||
|
// Send ready message
|
||||||
|
char *ready_msg = "{\"type\":\"rdp_ready\"}\n\n";
|
||||||
|
write(client_fd, ready_msg, strlen(ready_msg));
|
||||||
|
|
||||||
|
// Start bidirectional forwarding
|
||||||
|
forward_rdp_connection(client_fd, target_fd);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Always validate target selection** - User must be authorized for selected target
|
||||||
|
2. **Close on protocol errors** - Invalid messages should immediately close connection
|
||||||
|
3. **Timeout authentication** - Implement timeout for AUTH phase (e.g., 30 seconds)
|
||||||
|
4. **Rate limiting** - Prevent brute force attacks on authentication
|
||||||
|
5. **Logging** - Log all authentication attempts and target selections
|
||||||
|
6. **TLS/SSL** - Consider wrapping connection in TLS for production
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Authentication Success
|
||||||
|
```bash
|
||||||
|
(echo -e "AUTH\nuser@example.com\nPassword123\n"; sleep 1) | nc rdpbroker 3389
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected response:
|
||||||
|
```json
|
||||||
|
{"type":"auth_success","targets":[...]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Authentication Failure
|
||||||
|
```bash
|
||||||
|
(echo -e "AUTH\nuser@example.com\nWrongPassword\n"; sleep 1) | nc rdpbroker 3389
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected response:
|
||||||
|
```json
|
||||||
|
{"type":"auth_failed","message":"Invalid credentials"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Target Selection
|
||||||
|
```bash
|
||||||
|
(echo -e "AUTH\nuser@example.com\nPassword123\n"; sleep 1; echo -e "SELECT\nWindows Server 2022\n"; sleep 1) | nc rdpbroker 3389
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected response:
|
||||||
|
```json
|
||||||
|
{"type":"auth_success","targets":[...]}
|
||||||
|
|
||||||
|
{"type":"rdp_ready"}
|
||||||
|
|
||||||
|
[RDP binary data follows]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
Existing RdpBroker implementations that present login/menu screens via RDP protocol will need to be refactored to:
|
||||||
|
|
||||||
|
1. Accept the new text-based protocol on initial connection
|
||||||
|
2. Parse AUTH and SELECT commands
|
||||||
|
3. Return JSON responses instead of RDP login screens
|
||||||
|
4. Only start RDP forwarding after receiving SELECT command
|
||||||
|
|
||||||
|
The advantage is that authentication and target selection now happen via structured protocol before RDP session starts, allowing:
|
||||||
|
- Better error handling in web UI
|
||||||
|
- User-specific target lists
|
||||||
|
- Cleaner separation of concerns
|
||||||
|
- Easier debugging and monitoring
|
||||||
@@ -7,11 +7,12 @@ 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
|
||||||
- 🔑 **Simplified Authentication** - Credentials passed directly to RdpBroker
|
- 🔑 **User-Specific Targets** - Each user sees only their authorized RDP servers
|
||||||
- 📊 **Service Health Monitoring** - Automatic RdpBroker availability checks
|
- 📊 **Service Health Monitoring** - Automatic RdpBroker availability checks
|
||||||
- 🎯 **Dynamic Target Loading** - Targets fetched from configuration or RdpBroker
|
- 🎯 **Dynamic Target Loading** - Personalized targets from RdpBroker based on user permissions
|
||||||
- ⚡ **Low Latency** - Optimized for performance
|
- ⚡ **Low Latency** - Optimized for performance
|
||||||
- ☁️ **Kubernetes Native** - Console-only logging for cloud environments
|
- ☁️ **Kubernetes Native** - Console-only logging for cloud environments
|
||||||
|
- 🔐 **Samba AD Integration** - Authentication via RdpBroker with Samba Active Directory
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -19,12 +20,29 @@ HTML5 WebSocket-based gateway for accessing RDP connections through a web browse
|
|||||||
User Browser (HTML5/WebSocket)
|
User Browser (HTML5/WebSocket)
|
||||||
↓
|
↓
|
||||||
RDP Web Gateway (Node.js)
|
RDP Web Gateway (Node.js)
|
||||||
|
↓ [WebSocket Protocol]
|
||||||
|
↓ 1. AUTH → receives user-specific targets
|
||||||
|
↓ 2. SELECT → connects to chosen target
|
||||||
↓
|
↓
|
||||||
RdpBroker (RDP)
|
RdpBroker (C)
|
||||||
|
↓ [Samba AD Auth]
|
||||||
|
↓ [Target Authorization]
|
||||||
|
↓ [RDP Forwarding]
|
||||||
↓
|
↓
|
||||||
Target RDP Servers
|
Target RDP Servers
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Authentication Flow
|
||||||
|
|
||||||
|
1. **User Login** - User enters credentials in web interface
|
||||||
|
2. **Health Check** - Web-gateway verifies RdpBroker is available
|
||||||
|
3. **WebSocket Auth** - Credentials sent via WebSocket to RdpBroker
|
||||||
|
4. **LDAP Authentication** - RdpBroker authenticates against Samba AD
|
||||||
|
5. **Target Authorization** - RdpBroker determines user's authorized targets based on groups/permissions
|
||||||
|
6. **Targets Display** - User-specific target list sent back to web-gateway
|
||||||
|
7. **Target Selection** - User chooses from their authorized servers
|
||||||
|
8. **RDP Session** - RdpBroker establishes connection to selected target
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 18+
|
||||||
@@ -147,22 +165,74 @@ Fetch available RDP targets.
|
|||||||
|
|
||||||
Connect to `ws://localhost:8080/ws/rdp`
|
Connect to `ws://localhost:8080/ws/rdp`
|
||||||
|
|
||||||
#### Client → Server Messages
|
The protocol follows a two-phase approach:
|
||||||
|
1. **Authentication Phase**: User authenticates and receives personalized target list
|
||||||
|
2. **Connection Phase**: User selects target and establishes RDP session
|
||||||
|
|
||||||
**Connect to target:**
|
#### Phase 1: Authentication
|
||||||
|
|
||||||
|
**Client → Server - Authenticate:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "authenticate",
|
||||||
|
"username": "user@domain.com",
|
||||||
|
"password": "password123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Server → Client - Authentication Success with Targets:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "targets",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"name": "Windows Server 2022",
|
||||||
|
"host": "ws2022.example.com",
|
||||||
|
"port": 3389,
|
||||||
|
"description": "Production Windows Server (user-specific)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Development Server",
|
||||||
|
"host": "dev.example.com",
|
||||||
|
"port": 3389,
|
||||||
|
"description": "Development environment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Server → Client - Authentication Failed:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"error": "Invalid credentials"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Phase 2: Connection
|
||||||
|
|
||||||
|
**Client → Server - Connect to Target:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "connect",
|
"type": "connect",
|
||||||
"username": "user@domain.com",
|
|
||||||
"password": "password123",
|
|
||||||
"target": {
|
"target": {
|
||||||
"name": "Server 01",
|
"name": "Windows Server 2022",
|
||||||
"host": "192.168.1.10",
|
"host": "ws2022.example.com",
|
||||||
"port": 3389
|
"port": 3389
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Server → Client - RDP Session Ready:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "connected",
|
||||||
|
"target": "Windows Server 2022"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Client → Server Messages
|
||||||
|
|
||||||
**Mouse event:**
|
**Mouse event:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,41 +72,90 @@ class RDPWebGateway {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store credentials temporarily - will be sent to RdpBroker
|
// Store credentials and authenticate via WebSocket
|
||||||
this.currentUser = username;
|
this.currentUser = username;
|
||||||
this.credentials = { username, password };
|
this.credentials = { username, password };
|
||||||
|
|
||||||
// Load targets and show targets view
|
// Authenticate and get user-specific targets from RdpBroker
|
||||||
await this.loadTargets();
|
await this.authenticateAndLoadTargets();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
this.showError(errorMessage, 'Connection error. Please check your network and try again.');
|
this.showError(errorMessage, 'Connection error. Please check your network and try again.');
|
||||||
} finally {
|
|
||||||
loginBtn.disabled = false;
|
loginBtn.disabled = false;
|
||||||
btnText.style.display = 'block';
|
btnText.style.display = 'block';
|
||||||
spinner.style.display = 'none';
|
spinner.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadTargets() {
|
authenticateAndLoadTargets() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const wsUrl = `${protocol}//${window.location.host}/ws/rdp`;
|
||||||
|
|
||||||
|
// Create WebSocket connection for authentication
|
||||||
|
this.ws = new WebSocket(wsUrl);
|
||||||
|
this.ws.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close();
|
||||||
|
reject(new Error('Authentication timeout'));
|
||||||
|
}
|
||||||
|
}, 10000); // 10 second timeout
|
||||||
|
|
||||||
|
this.ws.onopen = () => {
|
||||||
|
console.log('WebSocket connected for authentication');
|
||||||
|
// Send authentication request to RdpBroker
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
type: 'authenticate',
|
||||||
|
username: this.credentials.username,
|
||||||
|
password: this.credentials.password
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/targets');
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (message.type === 'targets') {
|
||||||
if (response.status === 503) {
|
// Received user-specific targets from RdpBroker
|
||||||
const error = await response.json();
|
clearTimeout(timeout);
|
||||||
throw new Error(error.error || 'Service unavailable');
|
console.log('Received targets from RdpBroker:', message.targets);
|
||||||
}
|
this.showTargetsView(message.targets);
|
||||||
throw new Error('Failed to load targets');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
// Reset login button
|
||||||
this.showTargetsView(data.targets);
|
const loginBtn = document.getElementById('loginBtn');
|
||||||
} catch (error) {
|
const btnText = loginBtn.querySelector('.btn-text');
|
||||||
console.error('Error loading targets:', error);
|
const spinner = loginBtn.querySelector('.spinner');
|
||||||
// Show error in targets view
|
loginBtn.disabled = false;
|
||||||
this.showTargetsView(null, error.message);
|
btnText.style.display = 'block';
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} else if (message.type === 'error') {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = null;
|
||||||
|
reject(new Error(message.error || 'Authentication failed'));
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing WebSocket message:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onerror = (error) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
reject(new Error('WebSocket connection failed'));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (this.ws) {
|
||||||
|
console.log('WebSocket closed during authentication');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showTargetsView(targets = null, errorMsg = null) {
|
showTargetsView(targets = null, errorMsg = null) {
|
||||||
@@ -158,27 +207,17 @@ class RDPWebGateway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializeRDPConnection(target) {
|
initializeRDPConnection(target) {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
// WebSocket already connected from authentication
|
||||||
const wsUrl = `${protocol}//${window.location.host}/ws/rdp`;
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||||
|
console.error('WebSocket not connected');
|
||||||
|
this.showConnectionError('Connection lost. Please login again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.canvas = document.getElementById('rdpCanvas');
|
this.canvas = document.getElementById('rdpCanvas');
|
||||||
this.ctx = this.canvas.getContext('2d');
|
this.ctx = this.canvas.getContext('2d');
|
||||||
|
|
||||||
// Connect WebSocket
|
// Update message handler for RDP session
|
||||||
this.ws = new WebSocket(wsUrl);
|
|
||||||
this.ws.binaryType = 'arraybuffer';
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
console.log('WebSocket connected');
|
|
||||||
// Send credentials and connection request to RdpBroker
|
|
||||||
this.ws.send(JSON.stringify({
|
|
||||||
type: 'connect',
|
|
||||||
username: this.credentials.username,
|
|
||||||
password: this.credentials.password,
|
|
||||||
target: target,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
this.ws.onmessage = (event) => {
|
||||||
this.handleWebSocketMessage(event);
|
this.handleWebSocketMessage(event);
|
||||||
};
|
};
|
||||||
@@ -193,6 +232,13 @@ class RDPWebGateway {
|
|||||||
this.handleDisconnect();
|
this.handleDisconnect();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Send target selection to RdpBroker
|
||||||
|
console.log('Connecting to target:', target.name);
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
type: 'connect',
|
||||||
|
target: target
|
||||||
|
}));
|
||||||
|
|
||||||
// Setup canvas input handlers
|
// Setup canvas input handlers
|
||||||
this.setupCanvasInputHandlers();
|
this.setupCanvasInputHandlers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,17 @@ class RDPProxyHandler {
|
|||||||
this.rdpBrokerPort = rdpBrokerPort;
|
this.rdpBrokerPort = rdpBrokerPort;
|
||||||
this.rdpSocket = null;
|
this.rdpSocket = null;
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
|
this.isAuthenticated = false;
|
||||||
|
this.dataBuffer = '';
|
||||||
|
this.pendingTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMessage(message) {
|
async handleMessage(message) {
|
||||||
try {
|
try {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
|
case 'authenticate':
|
||||||
|
await this.handleAuthenticate(message);
|
||||||
|
break;
|
||||||
case 'connect':
|
case 'connect':
|
||||||
await this.handleConnect(message);
|
await this.handleConnect(message);
|
||||||
break;
|
break;
|
||||||
@@ -33,41 +39,28 @@ class RDPProxyHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConnect(message) {
|
async handleAuthenticate(message) {
|
||||||
const { username, password, target } = message;
|
const { username, password } = message;
|
||||||
|
|
||||||
console.log(`${new Date().toISOString()} [INFO] Connecting to RDP Broker, target: ${target?.name || 'unknown'}`);
|
console.log(`${new Date().toISOString()} [INFO] Authenticating user: ${username}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Connect to RDP Broker
|
// Connect to RDP Broker for authentication
|
||||||
this.rdpSocket = new net.Socket();
|
this.rdpSocket = new net.Socket();
|
||||||
|
|
||||||
this.rdpSocket.connect(this.rdpBrokerPort, this.rdpBrokerHost, () => {
|
this.rdpSocket.connect(this.rdpBrokerPort, this.rdpBrokerHost, () => {
|
||||||
console.log(`${new Date().toISOString()} [INFO] Connected to RDP Broker at ${this.rdpBrokerHost}:${this.rdpBrokerPort}`);
|
console.log(`${new Date().toISOString()} [INFO] Connected to RDP Broker for authentication`);
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
|
|
||||||
// Send credentials to RdpBroker (it will handle authentication)
|
// Send authentication request to RdpBroker
|
||||||
this.sendAuthToBroker(username, password);
|
// Protocol: AUTH\nusername\npassword\n
|
||||||
|
const authMessage = `AUTH\n${username}\n${password}\n`;
|
||||||
this.ws.send(JSON.stringify({
|
this.rdpSocket.write(authMessage);
|
||||||
type: 'connected',
|
|
||||||
target: target?.name || 'RDP Server'
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Set canvas size
|
|
||||||
this.ws.send(JSON.stringify({
|
|
||||||
type: 'resize',
|
|
||||||
width: 1920,
|
|
||||||
height: 1080
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
this.handleBrokerData(data);
|
||||||
if (this.ws.readyState === 1) { // WebSocket.OPEN
|
|
||||||
this.ws.send(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rdpSocket.on('error', (error) => {
|
this.rdpSocket.on('error', (error) => {
|
||||||
@@ -79,28 +72,108 @@ class RDPProxyHandler {
|
|||||||
this.rdpSocket.on('close', () => {
|
this.rdpSocket.on('close', () => {
|
||||||
console.log(`${new Date().toISOString()} [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.isAuthenticated && this.ws.readyState === 1) {
|
||||||
this.ws.close();
|
this.sendError('Authentication failed');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${new Date().toISOString()} [ERROR] Authentication error:`, error);
|
||||||
|
this.sendError('Failed to authenticate with RDP broker');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBrokerData(data) {
|
||||||
|
// Accumulate data in buffer
|
||||||
|
this.dataBuffer += data.toString();
|
||||||
|
|
||||||
|
// Check if we have complete message (ends with \n\n or specific delimiter)
|
||||||
|
if (this.dataBuffer.includes('\n\n')) {
|
||||||
|
const messages = this.dataBuffer.split('\n\n');
|
||||||
|
this.dataBuffer = messages.pop(); // Keep incomplete part in buffer
|
||||||
|
|
||||||
|
messages.forEach(msg => {
|
||||||
|
if (msg.trim()) {
|
||||||
|
this.processBrokerMessage(msg.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already authenticated and in RDP session, forward raw data to WebSocket
|
||||||
|
if (this.isAuthenticated && this.pendingTarget === null) {
|
||||||
|
if (this.ws.readyState === 1) {
|
||||||
|
this.ws.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processBrokerMessage(message) {
|
||||||
|
try {
|
||||||
|
// Try to parse as JSON first (for structured messages)
|
||||||
|
const data = JSON.parse(message);
|
||||||
|
|
||||||
|
if (data.type === 'auth_success') {
|
||||||
|
// Authentication successful, targets list received
|
||||||
|
console.log(`${new Date().toISOString()} [INFO] Authentication successful`);
|
||||||
|
this.isAuthenticated = true;
|
||||||
|
|
||||||
|
// Send targets to client
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
type: 'targets',
|
||||||
|
targets: data.targets || []
|
||||||
|
}));
|
||||||
|
|
||||||
|
} else if (data.type === 'auth_failed') {
|
||||||
|
// Authentication failed
|
||||||
|
console.log(`${new Date().toISOString()} [WARN] Authentication failed: ${data.message}`);
|
||||||
|
this.sendError(data.message || 'Invalid credentials');
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
|
} else if (data.type === 'rdp_ready') {
|
||||||
|
// RDP session ready, start forwarding
|
||||||
|
console.log(`${new Date().toISOString()} [INFO] RDP session ready`);
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
type: 'connected',
|
||||||
|
target: this.pendingTarget
|
||||||
|
}));
|
||||||
|
this.pendingTarget = null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.warn(`${new Date().toISOString()} [WARN] Unknown broker message type:`, data.type);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Not JSON, might be raw RDP data - ignore during auth phase
|
||||||
|
if (this.isAuthenticated) {
|
||||||
|
console.debug(`${new Date().toISOString()} [DEBUG] Non-JSON data from broker (RDP traffic)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleConnect(message) {
|
||||||
|
const { target } = message;
|
||||||
|
|
||||||
|
if (!this.isAuthenticated) {
|
||||||
|
this.sendError('Must authenticate before connecting to target');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${new Date().toISOString()} [INFO] Connecting to target: ${target?.name || 'unknown'}`);
|
||||||
|
this.pendingTarget = target?.name || 'RDP Server';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send target selection to RdpBroker
|
||||||
|
// Protocol: SELECT\ntarget_name\n
|
||||||
|
const selectMessage = `SELECT\n${target?.name}\n`;
|
||||||
|
this.rdpSocket.write(selectMessage);
|
||||||
|
|
||||||
|
// RdpBroker will respond with rdp_ready message
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`${new Date().toISOString()} [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 target');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAuthToBroker(username, password) {
|
|
||||||
// Send credentials directly to RdpBroker
|
|
||||||
// RdpBroker will handle Samba AD authentication
|
|
||||||
|
|
||||||
if (!this.rdpSocket) return;
|
|
||||||
|
|
||||||
// Format: "Username: <username>\nPassword: <password>\n"
|
|
||||||
const authMessage = `${username}\n${password}\n`;
|
|
||||||
this.rdpSocket.write(authMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEvent(message) {
|
handleMouseEvent(message) {
|
||||||
if (!this.isConnected || !this.rdpSocket) return;
|
if (!this.isConnected || !this.rdpSocket) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user