Integrate upstream-compatible read-only MeshCentral transport

This commit is contained in:
Clawdia
2026-09-03 18:35:43 -04:00
parent 765f372c72
commit 589e96ccf2
12 changed files with 369 additions and 189 deletions
+49 -50
View File
@@ -19,9 +19,7 @@ import {
validateCredentials,
} from './contracts';
import { createMockAdapter } from './mock-adapter';
import {
MeshCtrlClient,
} from './meshctrl-client';
import { UpstreamMeshTransport } from './upstream-transport';
import {
createAuditEntry,
AuditAction,
@@ -40,7 +38,7 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
readonly version = '0.1.0';
private config: BridgeConfig;
private client: MeshCtrlClient | null = null;
private client: UpstreamMeshTransport | null = null;
private credentials: { username: string; password: string; totpToken?: string } | null = null;
private connected = false;
private logger: Logger;
@@ -188,12 +186,9 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
);
}
if (this.config.adapterType === 'real' && (!hasUsername || !hasPassword)) {
throw new BridgeError(
ErrorCodes.ConfigurationInvalid,
'Real adapter requires authentication credentials',
);
}
// Real-adapter credentials may be supplied later through setCredentials().
// Keep the constructor usable for protected runtime secret injection;
// _ensureConnected() remains the enforcement point before any connection.
// Validate credential formats if present
if (hasUsername && hasPassword) {
@@ -297,10 +292,7 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
);
}
this.client = new MeshCtrlClient({
url: this.config.meshCentralUrl,
timeoutMs: this.config.timeoutMs,
});
this.client = new UpstreamMeshTransport(this.config.meshCentralUrl);
try {
await this.client.connect(this.credentials);
@@ -402,34 +394,6 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
groupId: request?.groupId,
});
// Get server version first to verify connection
try {
const versionResponse = await this.client!.sendRequest({
action: 'serverversion',
});
if (versionResponse.err) {
this._auditLog(AuditAction.MESHCENTRAL_REQUEST, AuditStatus.FAILURE, {
actor: this._getActorName(),
resource: 'serverversion',
error: versionResponse.err,
});
throw new BridgeError(
ErrorCodes.MeshCentralError,
'Failed to establish connection with MeshCentral server',
);
} else {
this._auditLog(AuditAction.MESHCENTRAL_RESPONSE, AuditStatus.SUCCESS, {
actor: this._getActorName(),
resource: 'serverversion',
});
}
} catch (error) {
await this._disconnect();
throw error;
}
// Get meshes (device groups)
const meshesResponse = await this.client!.sendRequest({
action: 'meshes',
@@ -442,7 +406,10 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
);
}
const meshes = (meshesResponse.meshes ?? []) as Array<{ id: string; name: string }>;
const meshes = ((meshesResponse.meshes ?? []) as Array<{ id?: string; _id?: string; name: string; description?: string; desc?: string }>).map((mesh) => ({
...mesh,
id: mesh.id ?? mesh._id ?? '',
})).filter((mesh) => mesh.id !== '') as Array<{ id: string; name: string }>;
// If group filter provided, find matching mesh
if (request?.groupId) {
@@ -489,7 +456,29 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
);
}
const nodes = (nodesResponse.nodes ?? []) as Array<{ id: string; name: string; online: boolean; meshid?: string; device?: { os?: string; uptime?: number; lastseen?: number; ip?: string[]; agentver?: string } }>;
const groupedNodes = (nodesResponse.nodes ?? {}) as Record<string, Array<{
id?: string;
_id?: string;
name: string;
meshid?: string;
online?: boolean;
conn?: number;
osdesc?: string;
ip?: string[];
device?: { os?: string; uptime?: number; lastseen?: number; ip?: string[]; agentver?: string };
}>>;
const nodes = Object.entries(groupedNodes).flatMap(([groupId, groupNodes]) =>
groupNodes.map((node) => ({
id: node.id ?? node._id ?? '',
name: node.name,
meshid: node.meshid ?? groupId,
online: node.online ?? node.conn === 1,
device: node.device ?? {
os: node.osdesc,
ip: node.ip,
},
})),
).filter((node) => node.id !== '') as Array<{ id: string; name: string; online: boolean; meshid?: string; device?: { os?: string; uptime?: number; lastseen?: number; ip?: string[]; agentver?: string } }>;
if (!this._isGroupAllowed(mesh.id)) {
return [];
@@ -563,7 +552,17 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
);
}
const meshes = (meshesResponse.meshes ?? []) as Array<{ id: string; name: string; description?: string }>;
const meshes = ((meshesResponse.meshes ?? []) as Array<{
id?: string;
_id?: string;
name: string;
description?: string;
desc?: string;
}>).map((mesh) => ({
...mesh,
id: mesh.id ?? mesh._id ?? '',
description: mesh.description ?? mesh.desc,
})).filter((mesh) => mesh.id !== '') as Array<{ id: string; name: string; description?: string }>;
// Filter by group allowlist if configured
const allowedMeshes = this._filterByAllowlist(
@@ -660,11 +659,11 @@ export class MeshCentralRealAdapter implements MeshCentralAdapter {
try {
await this._ensureConnected();
const versionResponse = await this.client!.sendRequest({
action: 'serverversion',
});
const healthy = !versionResponse.err;
// `meshes` is the least-privileged authenticated inventory operation
// and is available without the server-upgrade site permission required
// by MeshCentral's `serverversion` command.
const meshesResponse = await this.client!.sendRequest({ action: 'meshes' });
const healthy = Array.isArray(meshesResponse.meshes);
this._auditLog(AuditAction.SERVICE_START, healthy ? AuditStatus.SUCCESS : AuditStatus.FAILURE, {
actor: this._getActorName(),
resource: 'health-check',