# πŸ—οΈ Guacamole + Python ASGI Remote Access Architecture ## Overview This document outlines the proposed architecture for replacing the problematic 38MB WASM SSH console with a professional, secure, and maintainable remote access solution using Apache Guacamole, Python ASGI backend, and a custom SPA frontend. ## 🎯 Architecture Goals ### Security First - **Server-side connections**: All SSH/RDP/VNC handled on trusted infrastructure - **No client-side crypto**: Private keys never leave the server - **Role-based access control**: Integrate with existing OIDC role mapping - **Audit trails**: Complete session logging and recording capabilities - **Standard protocols**: Use proven, auditable technologies ### Performance & UX - **Lightweight frontend**: <1MB custom SPA vs 38MB WASM blob - **Real-time communication**: WebSocket-based terminal streaming - **Responsive design**: Mobile-friendly remote access interface - **Fast deployment**: Standard containerization, no Go build complexity ## 🏒 System Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Custom SPA │◄──►│ Python ASGI │◄──►│ Guacd Daemon β”‚ β”‚ (TypeScript) β”‚ β”‚ Backend β”‚ β”‚ (C/Docker) β”‚ β”‚ β”‚ β”‚ (FastAPI/Sanic) β”‚ β”‚ β”‚ β”‚ β€’ Terminal UI β”‚ β”‚ β€’ WebSocket β”‚ β”‚ β€’ SSH Client β”‚ β”‚ β€’ Session Mgmt β”‚ β”‚ β€’ Auth Checks β”‚ β”‚ β€’ RDP Client β”‚ β”‚ β€’ Role Display β”‚ β”‚ β€’ Protocol Bridgeβ”‚ β”‚ β€’ VNC Client β”‚ β”‚ β€’ File Transfer β”‚ β”‚ β€’ Session Logs β”‚ β”‚ β€’ Protocol Mux β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ └──────────────►│ Headplane β”‚β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Main App β”‚ β”‚ β€’ OIDC Roles β”‚ β”‚ β€’ User Sessions β”‚ β”‚ β€’ Node Discovery β”‚ β”‚ β€’ Audit Logs β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## πŸ”§ Component Design ### 1. Apache Guacamole Daemon (guacd) **Role**: Protocol handling and connection management ```dockerfile # Lightweight guacd container FROM guacamole/guacd:latest EXPOSE 4822 # Custom configuration for Headplane integration COPY guacd.conf /etc/guacamole/ ``` **Configuration**: - **SSH connections**: Direct to Tailscale nodes - **Connection pooling**: Efficient resource usage - **Protocol support**: SSH, RDP, VNC as needed - **Security**: Connection isolation and timeout handling ### 2. Python ASGI Backend **Role**: WebSocket bridge and authentication/authorization ```python # Core FastAPI application from fastapi import FastAPI, WebSocket, Depends from fastapi.security import HTTPBearer import asyncio import websockets app = FastAPI() class GuacamoleProxy: def __init__(self): self.guacd_host = "guacd" self.guacd_port = 4822 async def create_connection(self, protocol: str, params: dict): """Create guacamole connection with protocol-specific params""" pass async def stream_session(self, websocket: WebSocket, connection_id: str): """Proxy guacamole protocol over WebSocket""" pass @app.websocket("/terminal/{node_name}") async def terminal_endpoint( websocket: WebSocket, node_name: str, user: User = Depends(get_current_user) ): # Check role-based permissions permissions = get_terminal_permissions(user.groups) if not permissions['ssh']: await websocket.close(4003, "Insufficient permissions") return # Create guacamole connection proxy = GuacamoleProxy() connection = await proxy.create_connection("ssh", { "hostname": node_name, "username": user.preferred_username, "port": "22" }) # Stream session await proxy.stream_session(websocket, connection.id) ``` **Key Features**: - **Authentication**: Validate user sessions from Headplane - **Authorization**: Role-based access control using OIDC groups - **WebSocket proxy**: Bridge between SPA and guacd protocol - **Session management**: Track active connections and resources - **Audit logging**: Record all connection attempts and activities ### 3. Custom SPA Frontend **Role**: User interface and terminal rendering ```typescript // Terminal component using xterm.js import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { WebLinksAddon } from 'xterm-addon-web-links'; class GuacamoleTerminal { private terminal: Terminal; private socket: WebSocket; constructor(private nodeHostname: string) { this.terminal = new Terminal({ theme: { background: '#1a1a1a' }, fontSize: 14, fontFamily: 'JetBrains Mono, monospace' }); this.terminal.loadAddon(new FitAddon()); this.terminal.loadAddon(new WebLinksAddon()); } async connect() { const wsUrl = `wss://${location.host}/api/terminal/${this.nodeHostname}`; this.socket = new WebSocket(wsUrl); this.socket.onmessage = (event) => { this.terminal.write(event.data); }; this.terminal.onData((data) => { this.socket.send(data); }); } } ``` **UI Features**: - **Modern terminal**: xterm.js with full VT100 compatibility - **Responsive design**: Works on desktop, tablet, mobile - **File transfer**: Drag-and-drop file uploads via SFTP - **Session tabs**: Multiple concurrent connections - **Role indicators**: Clear display of user permissions ## πŸ” OIDC Role Integration ### Permission Matrix Integration with existing OIDC role mapping system: ```python def get_terminal_permissions(user_groups: list[str]) -> dict: """Get terminal access permissions based on OIDC groups""" role = map_oidc_groups_to_role(user_groups) # Use existing mapping permissions = { 'owner': { 'ssh': True, 'rdp': True, 'vnc': True, 'file_transfer': True, 'session_recording': False, # Owners don't need to be recorded 'connection_sharing': True, 'admin_nodes': True }, 'admin': { 'ssh': True, 'rdp': True, 'vnc': True, 'file_transfer': True, 'session_recording': True, # Record admin sessions 'connection_sharing': True, 'admin_nodes': True }, 'network_admin': { 'ssh': True, 'rdp': False, 'vnc': False, 'file_transfer': True, 'session_recording': True, 'connection_sharing': False, 'admin_nodes': False # Only access to regular nodes }, 'it_admin': { 'ssh': True, 'rdp': True, # IT needs RDP for Windows support 'vnc': False, 'file_transfer': True, 'session_recording': True, 'connection_sharing': False, 'admin_nodes': False }, 'auditor': { 'ssh': False, # Read-only access 'rdp': False, 'vnc': False, 'file_transfer': False, 'session_recording': False, 'connection_sharing': False, 'admin_nodes': False, 'view_sessions': True, # Can view active sessions 'replay_sessions': True # Can replay recorded sessions }, 'member': { 'ssh': False, 'rdp': False, 'vnc': False, 'file_transfer': False, 'session_recording': False, 'connection_sharing': False, 'admin_nodes': False } } return permissions.get(role, permissions['member']) ``` ### Environment Variable Configuration Extend existing environment variable system for remote access: ```bash # Existing OIDC role mapping HEADPLANE_ADMIN_GROUPS="vp,director,manager" HEADPLANE_NETWORK_ADMIN_GROUPS="devops,network,sre" # New remote access permissions HEADPLANE_SSH_ALLOWED_GROUPS="admin,network_admin,it_admin" HEADPLANE_RDP_ALLOWED_GROUPS="admin,it_admin" HEADPLANE_SESSION_RECORDING_REQUIRED="admin,network_admin,it_admin" HEADPLANE_FILE_TRANSFER_ALLOWED="admin,network_admin,it_admin" ``` ## πŸš€ Deployment Strategy ### Docker Compose Setup ```yaml services: headplane: # Existing Headplane service environment: - HEADPLANE_REMOTE_ACCESS_ENABLED=true - HEADPLANE_GUACD_HOST=guacd guacd: image: guacamole/guacd:latest container_name: headplane-guacd restart: unless-stopped volumes: - ./guacd.conf:/etc/guacamole/guacd.conf:ro expose: - "4822" remote-access-backend: build: ./remote-access container_name: headplane-remote-access restart: unless-stopped environment: - GUACD_HOST=guacd - GUACD_PORT=4822 - HEADPLANE_API_URL=http://headplane:3000 expose: - "8000" depends_on: - guacd - headplane ``` ### Integration Points - **Authentication**: Validate JWT tokens from Headplane - **Node discovery**: Query Headplane API for available Tailscale nodes - **Audit integration**: Send session logs to Headplane audit system - **Role sync**: Real-time role updates from OIDC changes ## πŸ“Š Benefits Over WASM Approach | **Metric** | **38MB WASM SSH** | **Guacamole Architecture** | |------------|-------------------|-----------------------------| | **Bundle Size** | 38MB | <1MB SPA | | **Security Model** | Client-side crypto | Server-side only | | **Build Complexity** | Go toolchain + deps | Standard containers | | **Protocol Support** | SSH only | SSH + RDP + VNC | | **Audit Capability** | Limited | Full session recording | | **Mobile Support** | Poor | Responsive design | | **Enterprise Ready** | No | Yes | | **Maintenance** | High complexity | Standard stack | ## 🎯 Implementation Phases ### Phase 1: Core Infrastructure - [ ] Deploy guacd container - [ ] Build Python ASGI WebSocket proxy - [ ] Create basic terminal SPA interface - [ ] Integrate with existing OIDC authentication ### Phase 2: Role Integration - [ ] Implement permission matrix with OIDC roles - [ ] Add environment variable configuration - [ ] Create role-based UI elements - [ ] Add audit logging for connections ### Phase 3: Advanced Features - [ ] Session recording and playback - [ ] File transfer capabilities - [ ] Multi-protocol support (RDP/VNC) - [ ] Connection sharing for collaboration ### Phase 4: Enterprise Features - [ ] Session timeout policies - [ ] Connection quotas per role - [ ] Advanced audit reporting - [ ] Integration with external SIEM systems ## πŸ”’ Security Considerations ### Network Security - **TLS encryption**: All WebSocket connections over WSS - **Network isolation**: guacd in separate container network - **Firewall rules**: Restrict guacd access to ASGI backend only - **Connection limits**: Per-user and per-role connection quotas ### Authentication & Authorization - **JWT validation**: Verify tokens against Headplane API - **Role-based access**: Fine-grained permissions per protocol - **Session management**: Automatic timeout and cleanup - **Audit trails**: Complete logging of all access attempts ### Data Protection - **No credential storage**: Credentials never stored in frontend - **Session isolation**: Each connection in separate context - **Memory protection**: Clear sensitive data from memory - **Log sanitization**: Remove sensitive data from audit logs ## πŸŽ‰ Conclusion This guacamole-based architecture provides a secure, scalable, and maintainable solution for remote access that: 1. **Eliminates security risks** of client-side crypto and massive WASM downloads 2. **Integrates seamlessly** with existing OIDC role mapping improvements 3. **Provides enterprise-grade** audit, recording, and management capabilities 4. **Uses proven technologies** that security teams understand and trust 5. **Scales efficiently** with standard container orchestration The architecture leverages our OIDC improvements to provide fine-grained access control while maintaining the security posture required for VPN infrastructure management.