Overview
Our MeshCentral integration now implements true multi-tenant isolation at every level:
- Each tenant gets their own user account in MeshCentral
- Each tenant gets their own device group (mesh) in MeshCentral
- Agents are assigned to their tenant's group automatically
- Tenant users can only see and manage their own devices
- Root tenant (MSP) maintains administrative access to all tenants
This architecture ensures complete separation of tenant data and prevents cross-tenant access at the MeshCentral level, not just our database level.
Architecture Components
1. Database Schema
Tenant Table Extensions:
ALTER TABLE tenants ADD COLUMN
meshcentral_username VARCHAR(100),
meshcentral_password_encrypted TEXT,
meshcentral_group_id VARCHAR(255),
meshcentral_group_name VARCHAR(255),
meshcentral_setup_complete BOOLEAN DEFAULT false;
2. Tenant Setup Workflow
When a new tenant is created:
- Admin creates user account in MeshCentral for tenant
- Username: subdomain or tenant_{uuid}
- Password: Randomly generated 64-char hex
- Email: {username}@meshcentral.local
- Permissions: Regular user (no site admin)
- Admin creates device group (mesh) for tenant
- Name: {TenantName} Devices
- Type: Agent mesh (not Intel AMT)
- Description: Device group for {TenantName}
- Admin links user to mesh with full permissions
- Permissions: 0xFFFFFFFF (full access)
- Includes: Remote Desktop, Terminal, Files, Events, etc.
- Backend stores credentials in database
- Password encrypted with AES-256-GCM
- Encryption key from process.env.ENCRYPTION_KEY
- Stored in meshcentral_password_encrypted column
- Backend marks tenant as setup complete
- Sets meshcentral_setup_complete = true
- Future sessions use tenant's own credentials
3. Session Management
When user accesses Remote Desktop:
// 1. Determine which tenant owns the agent
const agent = await getAgentWithTenantId(agentId);
// 2. Get tenant-specific MeshCentral API client
const api = await getTenantMeshAPI(agent.tenant_id);
// - Root tenant (MSP): Uses admin credentials
// - Regular tenant: Uses tenant-specific credentials from database
// 3. Login with tenant credentials
await api.login(); // Uses tenant username/password
// 4. Generate authenticated iframe URL
const iframeUrl = `${MESHCENTRAL_URL}/?login=${api.cookie}&gotonode=${nodeId}`;
// 5. Return pre-authenticated URL to dashboard
// User sees only their tenant's devices in MeshCentral UI
4. Tenant Isolation Layers
Layer 1: MeshCentral User Accounts
- Each tenant has separate MeshCentral user
- User can only see meshes they are assigned to
- No cross-tenant visibility at MeshCentral level
Layer 2: MeshCentral Device Groups
- Each tenant has separate mesh (device group)
- Agents register to tenant-specific mesh
- Devices physically isolated in MeshCentral
Layer 3: Database Tenant Filtering
- All queries include WHERE tenant_id = $userTenant
- Root tenant (tenant_id=1) can access all
- Regular tenants see only their records
Layer 4: JWT Authentication
- User JWT contains tenant_id claim
- Backend validates tenant ownership on every request
- Session tokens linked to specific user + tenant
API Endpoints
Setup Tenant MeshCentral (MSP Only)
POST /api/meshcentral/setup-tenant/:tenantId
Creates user account and device group for tenant.
Request:
curl -X POST https://backend/api/meshcentral/setup-tenant/abc-123 \
-H "Authorization: Bearer {MSP_JWT}"
Response:
{
"success": true,
"message": "Tenant MeshCentral setup complete",
"username": "client_abc",
"meshId": "mesh//abc123...",
"meshName": "Client ABC Devices"
}
Get Agent Installer URL
GET /api/meshcentral/installer/:tenantId?platform=win32
Returns download URL for MeshAgent configured for tenant's mesh.
Request:
curl https://backend/api/meshcentral/installer/abc-123?platform=win32 \
-H "Authorization: Bearer {TENANT_JWT}"
Response:
{
"success": true,
"installerUrl": "https://mesh.example.com/meshagents?id=mesh//abc123...&installflags=0&meshinstall=meshagent.exe",
"meshId": "mesh//abc123...",
"platform": "win32",
"instructions": {
"windows": "Download and run: https://...",
"linux": "wget https://... && chmod +x meshagent && sudo ./meshagent -install",
"macos": "curl -O https://... && chmod +x meshagent && sudo ./meshagent -install"
}
}
Create Authenticated Session
POST /api/meshcentral/session/:agentId
Creates authenticated MeshCentral session using tenant credentials.
Flow:
- Get agent from database (with tenant_id)
- Get tenant's MeshCentral credentials
- Login to MeshCentral with tenant username/password
- Return iframe URL with tenant's session cookie
- User sees only their tenant's devices
Request:
curl -X POST https://backend/api/meshcentral/session/115 \
-H "Authorization: Bearer {USER_JWT}" \
-H "Content-Type: application/json" \
-d '{"viewMode": 11}'
Response:
{
"success": true,
"sessionToken": "abc123...",
"expiresAt": "2026-02-13T14:30:00Z",
"agent": {
"id": 115,
"uuid": "2c3e2d7a-...",
"hostname": "testing",
"platform": "win32"
},
"iframeUrl": "https://mesh.example.com/?login={TENANT_COOKIE}&gotonode=...",
"viewMode": 11
}
Implementation Details
Password Encryption
// lib/encryption.js
const { encrypt, decrypt } = require('./lib/encryption');
// Encrypt before storing
const encryptedPassword = encrypt(plainPassword);
await db.query(
'UPDATE tenants SET meshcentral_password_encrypted = $1 WHERE tenant_id = $2',
[encryptedPassword, tenantId]
);
// Decrypt when needed
const encryptedPassword = row.meshcentral_password_encrypted;
const plainPassword = decrypt(encryptedPassword);
Encryption Method: AES-256-GCM Key Derivation: scrypt with salt Format: Base64-encoded JSON: { iv, authTag, data }
API Client Caching
// Cache tenant-specific API clients to avoid repeated logins
const tenantAPIClients = new Map();
async function getTenantMeshAPI(tenantId) {
const cacheKey = `tenant_${tenantId}`;
if (tenantAPIClients.has(cacheKey)) {
return tenantAPIClients.get(cacheKey);
}
// Get credentials, create client, login, cache
const api = new MeshCentralAPI(url, username, password);
await api.login();
tenantAPIClients.set(cacheKey, api);
return api;
}
Automatic Setup
Tenants are automatically setup when first accessed:
if (!tenant.meshcentral_setup_complete) {
console.log(`Auto-setting up MeshCentral for tenant ${tenantId}...`);
await setupTenantMeshCentral(tenantId);
}
Agent Installation
For Tenant Users
- Get installer URL:
GET /api/meshcentral/installer/{tenantId}?platform=win32
- Download and install agent:
- Windows: Run meshagent.exe
- Linux: wget {url} && chmod +x meshagent && sudo ./meshagent -install
- macOS: curl -O {url} && chmod +x meshagent && sudo ./meshagent -install
- Agent registers to tenant's mesh automatically
- Uses mesh ID embedded in installer URL
- Appears only in tenant's device group
- Other tenants cannot see the device
- Backend links MeshCentral node to our agent:
POST /api/meshcentral/auto-link
{
"agentUuid": "2c3e2d7a-...",
"tenantId": "abc-123"
}
Security Benefits
1. True Isolation
- Tenants use different MeshCentral accounts
- Physical separation at MeshCentral level
- No shared credentials between tenants
2. Credential Security
- Passwords encrypted at rest (AES-256-GCM)
- Never transmitted to client browser
- Admin password only used for tenant setup
3. Audit Trail
- Every session logged with tenant_id
- Session tokens track which tenant accessed which agent
- MeshCentral logs show per-tenant activity
4. Principle of Least Privilege
- Tenant users have no site admin rights
- Can only see devices in their mesh
- Cannot create additional meshes or users
5. MSP Administrative Access
- Root tenant (MSP) uses admin credentials
- Can access all tenants' devices for support
- Maintains oversight without breaking isolation
Testing Procedures
1. Setup New Tenant
# As MSP admin
curl -X POST https://backend/api/meshcentral/setup-tenant/{TENANT_ID} \
-H "Authorization: Bearer {MSP_JWT}"
# Verify in database
SELECT meshcentral_username, meshcentral_group_id, meshcentral_setup_complete
FROM tenants WHERE tenant_id = '{TENANT_ID}';
2. Install Agent on Tenant Device
# Get installer URL
curl https://backend/api/meshcentral/installer/{TENANT_ID}?platform=win32 \
-H "Authorization: Bearer {TENANT_JWT}"
# Download and run installer on target device
# Agent should appear in tenant's mesh in MeshCentral
3. Verify Isolation
# Login as Tenant A user
# Access Remote Desktop tab for Tenant A agent
# Should see device and can connect
# Login as Tenant B user
# Try to access Tenant A agent UUID
# Should get 404: Agent not found
# Login as MSP admin
# Can access both Tenant A and Tenant B agents
4. Verify Session Authentication
# Check that iframe loads without login screen
# Verify tenant can control their device
# Check that only tenant's devices visible in MeshCentral UI
Migration Path
For Existing Tenants
Run setup for all existing tenants:
SELECT tenant_id FROM tenants WHERE meshcentral_setup_complete = false;
for tenant in $tenants; do
curl -X POST https://backend/api/meshcentral/setup-tenant/$tenant \
-H "Authorization: Bearer {MSP_JWT}"
done
- Get new installer for each tenant:
- Each tenant needs tenant-specific installer
- Old agents registered to admin's mesh need reinstalling
- Or bulk-move agents to tenant meshes via MeshCentral API
- Update agent installations:
- Uninstall old MeshAgent
- Install new tenant-specific agent
- Backend auto-links on first connection
For New Tenants
- Automatic setup on first Remote Desktop access
- Or manually trigger via /setup-tenant/:id endpoint
- Provide installer URL to customer
- Agent auto-registers to correct mesh
Troubleshooting
Tenant Can't See Their Devices
# Check setup status
SELECT meshcentral_setup_complete, meshcentral_group_id
FROM tenants WHERE tenant_id = '{TENANT_ID}';
# If not complete, run setup
POST /api/meshcentral/setup-tenant/{TENANT_ID}
# Verify agent is in correct mesh
# Check agent's meshcentral_nodeid matches tenant's mesh
Session Cookie Not Working
# Verify tenant credentials can login
# Check encryption/decryption working
# Verify MeshCentral URL accessible
# Check session not expired (4 hour TTL)
Agent Not Auto-Linking
# Verify hostname match between agent and MeshCentral node
# Check tenant_id matches between agent and mesh
# Run manual link:
POST /api/meshcentral/auto-link
{
"agentUuid": "...",
"tenantId": "..."
}
Performance Considerations
- API clients cached per tenant (avoid repeated logins)
- Sessions expire after 4 hours (reduce MeshCentral load)
- Password decryption only happens once per session
- WebSocket connections reused within same client session
Future Enhancements
- Session Refresh: Auto-refresh before 4-hour expiry
- WebSocket Proxy: Full bidirectional proxy for real-time features
- Bulk Agent Migration: Move existing agents to tenant meshes
- User Self-Service: Let tenants download their own installer
- Monitoring Integration: Track MeshCentral connection status per tenant
- Automated Cleanup: Remove unused tenants from MeshCentral
- Permission Customization: Fine-tune rights per tenant (e.g., no terminal access)
References