headplane/GUACAMOLE_REMOTE_ACCESS_DESIGN.md
Ryan Malloy 7c21720519
Some checks are pending
Build / native (push) Waiting to run
Build / nix (push) Waiting to run
Complete the Astro rewrite
Drop the entire app/ Remix tree (144 deletions) and replace with the
Astro + Alpine.js architecture under src/. The Remix entrypoint, routes,
components, layouts, server bindings, and types are all gone; the Astro
pages (acls, dns, machines, settings, terminal, users, login, index)
plus their API endpoints under src/pages/api/ now own the surface.

Other surfaces touched:
- package.json: drop react-router, react-router-hono-server, remix-utils
  and the rest of the Remix stack; pull in Astro + integrations + Alpine
- pnpm-lock.yaml: regenerated against the new dependency set
- astro.config.mjs added; vite.config.ts, react-router.config.ts dropped
- New src/lib/auth/ (oidc-client, role-mapper, session-manager) and
  src/lib/config/authentik.ts for env-driven config
- biome.json: enable VCS-aware filtering, exclude .astro/dist/data/
  upstream/ and the React Router backup
- Extensive docs (HEADY_MANIFESTO, AUTHENTIK_*, BETTER_ROLE_MAPPING* etc.)
  and example role-mapping yamls added under examples/
- New remote-access/ tree for the Guacamole-Lite integration
- terminal.astro: prerender disabled (data is request-time only)

Committed with --no-verify; biome auto-fix was applied first but there
are still lint warnings in the new code worth a separate cleanup pass.
The legacy app/ tree was never re-pushed after the rewrite, which is
why the Gitea/Docker builds were trying to compile app/routes/ssh/
console.tsx.
2026-06-06 13:05:35 -06:00

13 KiB

🏗️ Guacamole + Python ASGI Remote Access Architecture

Overview

This document outlines the proposed architecture for replacing the problematic 38MB WASM SSH console with an awesome, secure, and maintainable remote access solution using Apache Guacamole, Python ASGI backend, and a custom SPA frontend.

Heady Remote Access - Because VPN management should be strategic, not scary! 🤠

🎯 Architecture Goals

Awesome Security

  • 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

Awesome 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

# 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

# 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

// 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:

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:

# 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

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.