Dynamic Proxy Integration Guide

The Machine Broker includes a dynamic reverse proxy feature that allows control applications to register HTTP proxy routes on-the-fly. This enables simplified DNS and SSL management by using a wildcard certificate.

Overview

  • Control apps can register subdomain routes (e.g., hvac192.168.1.50:8080)
  • Requests to hvac.eng.sttark.com are proxied to the registered target
  • Wildcard SSL certificate handles all subdomains automatically
  • Health monitoring tracks the status of proxy targets
  • Authentication: All *.eng.sttark.com requests require Google OAuth (@sttark.com accounts) or a valid API key

Architecture

┌─────────────────┐         ┌──────────────────────┐         ┌─────────────────┐
│   Browser       │────────▶│   Machine Broker     │────────▶│  Target Server  │
│  (@sttark.com   │         │                      │         │  10.0.1.5       │
│   Google login) │◀────────│  *.eng.sttark.com   │◀────────│     :8080       │
│                 │         │  (SSL + OAuth)       │         │  (VPC only)     │
└─────────────────┘         └──────────────────────┘         └─────────────────┘
                                      ▲
                                      │
                            ┌─────────┴─────────┐
                            │   Control App     │
                            │  (registers route)│
                            └───────────────────┘

Authentication

All requests to *.eng.sttark.com are authenticated before being proxied to the target.

Browser Access (Google OAuth)

When accessing any *.eng.sttark.com URL in a browser, you are redirected to Google login. Only @sttark.com Google accounts are accepted. After login, a session cookie is set that works across all subdomains for 30 days.

Programmatic Access (API Key)

For scripts and automated clients, include an X-API-Key header to bypass the OAuth flow:

curl -H "X-API-Key: YOUR_API_KEY" https://hvac.eng.sttark.com/

WebSocket connections can also pass the API key:

const ws = new WebSocket('wss://hvac.eng.sttark.com/ws', {
  headers: { 'X-API-Key': 'YOUR_API_KEY' }
});

Target Machine Security

Target machines are on a VPC (private network) with a default-deny firewall. They are not accessible directly from the internet on any port -- only the broker can reach them via the VPC.

WebSocket Protocol

Registering a Proxy Route

Connect to /control and send a register_proxy message:

const ws = new WebSocket('wss://machinebroker.sttark.com/control?apiKey=YOUR_API_KEY');

ws.onopen = () => {
  // Register a proxy route
  ws.send(JSON.stringify({
    type: 'register_proxy',
    subdomain: 'hvac',
    target: 'http://192.168.1.50:8080'
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  
  if (msg.type === 'proxy_registered') {
    console.log(`Proxy registered: ${msg.url}`);
    // Output: Proxy registered: https://hvac.eng.sttark.com
  }
};

Response

{
  "type": "proxy_registered",
  "action": "proxy_registered",
  "subdomain": "hvac",
  "target": "http://192.168.1.50:8080",
  "url": "https://hvac.eng.sttark.com",
  "replaced": false,
  "timestamp": "2026-02-04T15:30:00.000Z"
}

Removing a Proxy Route

ws.send(JSON.stringify({
  type: 'unregister_proxy',
  subdomain: 'hvac'
}));

Listing Proxy Routes

ws.send(JSON.stringify({
  type: 'list_proxies'
}));

Response:

{
  "type": "success",
  "action": "list_proxies",
  "proxies": [
    {
      "subdomain": "hvac",
      "target": "http://192.168.1.50:8080",
      "url": "https://hvac.eng.sttark.com",
      "health": {
        "status": "healthy",
        "lastCheck": "2026-02-04T15:30:00.000Z",
        "responseTimeMs": 45
      },
      "stats": {
        "requestCount": 1234,
        "lastAccessed": "2026-02-04T15:29:55.000Z",
        "createdAt": "2026-02-04T10:00:00.000Z"
      }
    }
  ],
  "summary": {
    "total": 1,
    "healthy": 1,
    "unhealthy": 0,
    "unknown": 0
  },
  "timestamp": "2026-02-04T15:30:00.000Z"
}

Triggering Health Check

// Check specific subdomain
ws.send(JSON.stringify({
  type: 'check_proxy_health',
  subdomain: 'hvac'
}));

// Check all proxies
ws.send(JSON.stringify({
  type: 'check_proxy_health'
}));

REST API Endpoints

All endpoints require API key authentication via X-API-Key header.

List All Proxies

curl -H "X-API-Key: YOUR_API_KEY" \
  https://machinebroker.sttark.com/api/proxies

Get Single Proxy

curl -H "X-API-Key: YOUR_API_KEY" \
  https://machinebroker.sttark.com/api/proxies/hvac

Get Proxy Health

curl -H "X-API-Key: YOUR_API_KEY" \
  https://machinebroker.sttark.com/api/proxies/hvac/health

Trigger Health Check

curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  https://machinebroker.sttark.com/api/proxies/hvac/check

Health Monitoring

Health Statuses

Status Description
healthy Target responded with 2xx/3xx status
unhealthy Target failed multiple consecutive checks
unknown New route, not yet checked

WebSocket Health Updates

Connect to /health to receive real-time health updates:

const ws = new WebSocket('wss://machinebroker.sttark.com/health');

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  
  if (msg.type === 'proxy_health_update') {
    console.log(`${msg.subdomain} is now ${msg.health.status}`);
  }
};

Health Check Configuration

Environment variables:

PROXY_HEALTH_INTERVAL=30000      # Check every 30 seconds
PROXY_HEALTH_TIMEOUT=5000        # 5 second timeout
PROXY_HEALTH_UNHEALTHY_THRESHOLD=3  # 3 failures = unhealthy

Target URL Formats

The target parameter supports various formats:

// IP with port
{ target: 'http://192.168.1.50:8080' }

// Domain name
{ target: 'http://internal-server.local' }

// HTTPS target
{ target: 'https://secure-backend.example.com' }

// With path (optional)
{ target: 'http://192.168.1.50:8080/api/v1' }

Subdomain Rules

  • Lowercase alphanumeric and hyphens only
  • Cannot start or end with a hyphen
  • Reserved subdomains: www, api, mail, ftp, admin, broker

Valid examples: hvac, plc-1, camera2, building-a

Invalid examples: HVAC, plc_1, -invalid, my.sub

Backward Compatibility

This feature is fully backward compatible:

  • Existing device connections (/devices) work unchanged
  • Existing control connections (/control) work unchanged
  • Proxy messages are optional - controls that don't use them work normally
  • The proxy domain (*.eng.sttark.com) is separate from the broker domain

Deployment

Prerequisites

  1. DNSimple account with API access
  2. DNS records pointing to your server:
    • machinebroker.sttark.com → your server IP
    • eng.sttark.com → your server IP
    • *.eng.sttark.com → your server IP (wildcard A record)
  3. Google Cloud OAuth credentials (project frontier-account):
    • Redirect URI: https://eng.sttark.com/oauth2/callback
    • Authorized JS origins: https://eng.sttark.com, https://machinebroker.sttark.com

Environment Variables

# Required for wildcard SSL
DNSIMPLE_API_KEY=your-api-key
DNSIMPLE_ACCOUNT_ID=your-account-id

# Proxy configuration
PROXY_ENABLED=true
PROXY_DOMAIN=eng.sttark.com

# Google OAuth (for *.eng.sttark.com authentication)
OAUTH2_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
OAUTH2_CLIENT_SECRET=your-google-client-secret
OAUTH2_COOKIE_SECRET=  # Generate with: openssl rand -base64 32 | tr -- '+/' '-_'

Ansible Deployment

The Ansible playbook automatically:

  1. Installs certbot with DNSimple plugin
  2. Obtains wildcard SSL certificate via DNS-01 challenge
  3. Configures nginx for both broker and proxy domains
  4. Installs and configures oauth2-proxy for Google OAuth
  5. Sets up auto-renewal
cd deploy/ansible
ansible-playbook -i inventory.ini site.yml

Target Machine Setup

Target machines should be provisioned with a default-deny firewall:

./deploy/scripts/setup-all.sh --target

This creates a Linode on the VPC, applies firewall rules (SSH + VPC only), and attaches the Linode Cloud Firewall.

If the target machine owner is setting up their Linode manually, without using this repository's scripts, hand them docs/TARGET_SERVER_GUIDE.md. It contains the full VPC, host firewall, Linode Cloud Firewall, broker handoff, and validation steps needed to secure a target server for *.eng.sttark.com.

Example: Complete Integration

const WebSocket = require('ws');

class ProxyManager {
  constructor(brokerUrl, apiKey) {
    this.ws = new WebSocket(`${brokerUrl}/control?apiKey=${apiKey}`);
    this.proxyDomain = 'eng.sttark.com';
    
    this.ws.on('message', (data) => {
      const msg = JSON.parse(data);
      this.handleMessage(msg);
    });
  }

  registerProxy(subdomain, targetIp, targetPort) {
    this.ws.send(JSON.stringify({
      type: 'register_proxy',
      subdomain,
      target: `http://${targetIp}:${targetPort}`
    }));
  }

  handleMessage(msg) {
    switch (msg.type) {
      case 'proxy_registered':
        console.log(`✓ Proxy ready: ${msg.url}`);
        break;
      case 'proxy_health_update':
        const icon = msg.health.status === 'healthy' ? '✓' : '✗';
        console.log(`${icon} ${msg.subdomain}: ${msg.health.status}`);
        break;
    }
  }
}

// Usage
const manager = new ProxyManager('wss://machinebroker.sttark.com', 'your-api-key');
manager.registerProxy('hvac', '192.168.1.50', 8080);
// Now https://hvac.eng.sttark.com proxies to 192.168.1.50:8080