Security Improvement
Problem: Direct iframe embedding of MeshCentral exposes:
- MeshCentral server URL to client browser
- Requires separate authentication or exposed credentials
- Vulnerable to credential leakage
Solution: Backend proxy approach:
- Dashboard authenticates to backend only (existing JWT)
- Backend manages MeshCentral sessions server-side
- MeshCentral credentials never exposed to client
- All requests proxied through backend API
Architecture
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Dashboard │ JWT Auth │ Backend │ Credentials │ MeshCentral │
│ (Client) │ ────────────▶ │ Proxy │ ─────────────▶ │ Server │
│ │ │ │ │ │
│ iframe │ ◀──────────── │ Session │ ◀─────────── │ Devices │
│ │ Proxy URL │ Manager │ API Calls │ │
└─────────────┘ └─────────────┘ └──────────────┘
▲ │ │
│ │ │
└──────────────────────────────┴───────────────────────────────┘
All traffic through backend proxy
MeshCentral URL never exposed to client
API Endpoints
1. Create Secure Session
POST /api/meshcentral/session/:agentId
Creates a secure session token for accessing a specific agent's MeshCentral features.
Authentication: Bearer JWT token (existing dashboard auth)
Request Body:
{
"viewMode": 11 // Optional: 10=General, 11=Desktop, 12=Terminal, etc.
}
Response:
{
"success": true,
"sessionToken": "8f3e9c2a1b4d...",
"expiresAt": "2025-01-15T18:30:00Z",
"agent": {
"id": 115,
"uuid": "2c3e2d7a-c521-4844-b44b-a30b4c2ec4d8",
"hostname": "testing",
"platform": "linux",
"nodeId": "sqtYR0SN2DGaJsuSQZsNWxu0H643deE8$Jq9Ymm@UBywhJwXh2sj8ArX4KPoyZK4"
},
"proxyUrl": "/api/meshcentral/proxy/8f3e9c2a1b4d...",
"viewMode": 11
}
Session Expiry: 4 hours (configurable)
Database Storage:
CREATE TABLE meshcentral_sessions (
session_token VARCHAR(64) UNIQUE,
user_id INT,
agent_id INT,
node_id VARCHAR(255),
view_mode INT,
meshcentral_cookie TEXT,
expires_at TIMESTAMP,
created_at TIMESTAMP,
last_used_at TIMESTAMP
);
2. Proxy MeshCentral Requests
ALL /api/meshcentral/proxy/:sessionToken/*
Proxies all HTTP requests to MeshCentral using server-side credentials.
Authentication: Session token (from step 1)
Flow:
- Client makes request to proxy endpoint
- Backend validates session token
- Backend authenticates to MeshCentral using server-side credentials
- Backend forwards request to MeshCentral
- Backend returns response to client
Example:
// Dashboard embeds iframe:
<iframe src="/api/meshcentral/proxy/8f3e9c2a1b4d..." />
// Backend proxies to:
https://rmm-psa-meshcentral-aq48h.ondigitalocean.app/...
// (URL never exposed to client)
Dashboard Integration
Updated MeshCentralEmbed Component
import { useState, useEffect } from 'react';
import api from '../../utils/api';
import './MeshCentralEmbed.css';
export default function MeshCentralEmbed({ agentId, viewMode = 10, title }) {
const [session, setSession] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
createSecureSession();
}, [agentId, viewMode]);
const createSecureSession = async () => {
try {
setLoading(true);
setError(null);
// Request secure session from backend
const response = await api.post(
`/meshcentral/session/${agentId}`,
{ viewMode }
);
setSession(response.data);
setLoading(false);
} catch (err) {
console.error('Failed to create MeshCentral session:', err);
setError(err.response?.data?.message || 'Failed to connect');
setLoading(false);
}
};
if (loading) {
return (
<div className="meshcentral-loading">
<div className="spinner"></div>
<p>Connecting to {title || 'device'}...</p>
</div>
);
}
if (error) {
return (
<div className="meshcentral-error">
<p>❌ {error}</p>
<button onClick={createSecureSession}>Retry</button>
</div>
);
}
return (
<div className="meshcentral-embed-container">
<div className="meshcentral-header">
<h3>{title || 'MeshCentral'}</h3>
<span className="session-info">
Session expires: {new Date(session.expiresAt).toLocaleTimeString()}
</span>
</div>
<iframe
src={session.proxyUrl}
className="meshcentral-iframe"
sandbox="allow-same-origin allow-scripts allow-forms allow-downloads"
title={title || 'MeshCentral'}
/>
</div>
);
}
// View mode constants
export const VIEWMODE = {
GENERAL: 10, // General device info
DESKTOP: 11, // Remote desktop
TERMINAL: 12, // Terminal/command prompt
FILES: 13, // File manager
INTEL_AMT: 14, // Intel AMT features
CONSOLE: 15, // Device console
EVENTS: 16, // Event log
DETAILS: 17, // Detailed hardware info
PLUGINS: 19 // Plugin interface
};
Security Benefits
✅ Credentials Never Exposed
- MeshCentral URL only known by backend
- API credentials stored in server-side .env
- No credentials in client-side code or network requests
✅ Session Management
- Temporary session tokens (4-hour expiry)
- Tokens tied to specific user + agent combination
- Automatic cleanup of expired sessions
✅ Audit Trail
- All access logged in meshcentral_sessions table
- Track which users accessed which agents
- Monitor session usage and patterns
✅ Access Control
- Uses existing JWT authentication
- Respects tenant isolation
- Requires technician role
✅ CORS & CSP Friendly
- No cross-origin issues (same domain)
- No Content-Security-Policy violations
- Works with strict security headers
Migration from Direct Embedding
Before (Insecure):
// ❌ Exposes MeshCentral URL
const meshUrl = `https://rmm-psa-meshcentral-aq48h.ondigitalocean.app/login?gotonode=${nodeId}`;
<iframe src={meshUrl} />
After (Secure):
// ✅ Backend proxy - credentials never exposed
<MeshCentralEmbed agentId={agentId} viewMode={VIEWMODE.DESKTOP} />
Implementation Checklist
- Create meshcentral_sessions database table
- Add POST /api/meshcentral/session/:agentId endpoint
- Add ALL /api/meshcentral/proxy/:sessionToken/* endpoint
- Implement full HTTP/WebSocket proxy forwarding
- Test remote desktop through proxy
- Test terminal through proxy
- Test file manager through proxy
Testing
1. Verify Credentials Not Exposed
# Open browser DevTools Network tab
# Access Remote Desktop in dashboard
# Check all requests:
# ❌ Should NOT see: rmm-psa-meshcentral-aq48h.ondigitalocean.app
# ✅ Should see: rmm-psa-backend-t9f7k.ondigitalocean.app
2. Test Session Expiry
# Create session, note expiry time
# Wait for expiry
# Try to use expired session token
# Should get 401 Unauthorized
3. Test Access Control
# User A creates session for Agent 115
# User B tries to use User A's session token
# Should be rejected (different user_id)
Performance Considerations
Session Pooling
Backend maintains single MeshCentral connection per tenant:
// Reuse MeshCentral API connections
const meshAPIPool = new Map(); // tenantId -> MeshCentralAPI instance
WebSocket Proxying
For real-time features (Desktop, Terminal), use WebSocket proxy:
const WebSocket = require('ws');
// Proxy WebSocket connections
wss.on('connection', (clientWs, req) => {
const serverWs = new WebSocket(MESHCENTRAL_WS_URL);
clientWs.on('message', (data) => serverWs.send(data));
serverWs.on('message', (data) => clientWs.send(data));
});
Caching
Cache MeshCentral API responses for 30 seconds:
const apiCache = new Map(); // endpoint -> { data, timestamp }
Environment Variables
# Backend .env
MESHCENTRAL_URL=https://rmm-psa-meshcentral-aq48h.ondigitalocean.app
MESHCENTRAL_USERNAME=admin
MESHCENTRAL_PASSWORD=<secure-password>
MESHCENTRAL_SESSION_EXPIRY=14400 # 4 hours in seconds
References
- MeshCentral API Documentation
- /rmm-psa-backend/lib/meshcentral-api.js - API client
- /rmm-psa-backend/routes/meshcentral.js - Proxy endpoints
- /rmm-psa-dashboard/MESHCENTRAL_EMBED_GUIDE.md - Original embedding guide