mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 20:58:04 +00:00
fix(mesh): operator-triggered override regen and boot lifecycle hardening (#1016)
Audit-driven follow-up to the F6+F7 fix. Closes the High-severity findings
from the mesh-dial-path audit without expanding into the architectural
follow-ups (JWT TTL, TLS enforcement, per-stack JWT scope) which need
their own design discussions.
Changes:
1. New `POST /api/mesh/regen-overrides` Admiral-gated endpoint that calls
`MeshService.regenerateAllOverrides()` and returns a structured
`MeshRegenSummary { regenerated, failures, skipped, reason? }`. Lets
the operator rerun boot regen after fixing a remote node that was
offline at central startup, instead of opt-out + opt-in for every
meshed stack on that node. Audit-log row carries the outcome
(success / partial / skipped / error).
2. `regenerateAllOverrides` now aggregates per-stack outcomes into a
single summary log row (`mesh override regen complete: N succeeded,
M failed across K node(s)`) with `failedNodeIds` in `details`. Per-
stack warnings still emitted. When skipped because `senchoIp` is
null, emits an explicit warn instead of silently no-oping.
3. `MeshService.start()` now wraps `refreshAliasCache` and
`syncForwarderListeners` in their own try/catch so a throw from
either step is logged and the boot continues to override regen.
The closing log line surfaces data-plane state ("MeshService started
(data plane ok)" or "(data plane unavailable (<reason>))") so an
operator grepping startup output sees a half-init mesh immediately.
4. `sanitizeForLog` wrap on four previously unsanitized `err.message`
writes (cross-node tcpStream error handler + three probe error
paths). Container IPs and local socket details no longer leak into
the activity buffer or the probe response body.
5. Updated three existing F6 regression tests for the new return shape
and aggregated summary message. Added two new tests: version-skew
404-push handling, and concurrent opt-in during regen.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Router, type Request, type Response } from 'express';
|
||||
import { DatabaseService } from '../services/DatabaseService';
|
||||
import { NodeRegistry } from '../services/NodeRegistry';
|
||||
import { MeshError, MeshService } from '../services/MeshService';
|
||||
import { MeshError, MeshService, type MeshRegenSummary } from '../services/MeshService';
|
||||
import { requireAdmin, requireAdmiral } from '../middleware/tierGates';
|
||||
import { sanitizeForLog } from '../utils/safeLog';
|
||||
import { isValidStackName } from '../utils/validation';
|
||||
@@ -24,6 +24,47 @@ meshRouter.get('/status', async (_req: Request, res: Response): Promise<void> =>
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Operator-triggered rerun of the boot-time override regeneration. Walks
|
||||
* every `mesh_stacks` row across the fleet and re-pushes each override to
|
||||
* its owning node. Useful when a remote node was offline at central boot
|
||||
* and the override files there are stale; previously the only recovery
|
||||
* path was opt-out + opt-in for every meshed stack on that node.
|
||||
*/
|
||||
meshRouter.post('/regen-overrides', async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requireAdmiral(req, res)) return;
|
||||
if (!requireAdmin(req, res)) return;
|
||||
const actor = actorFor(req);
|
||||
let summary: MeshRegenSummary | null = null;
|
||||
let outcome: 'success' | 'skipped' | 'partial' | 'error' = 'error';
|
||||
try {
|
||||
summary = await MeshService.getInstance().regenerateAllOverrides();
|
||||
outcome = summary.skipped ? 'skipped' : (summary.failures.length === 0 ? 'success' : 'partial');
|
||||
res.json(summary);
|
||||
} catch (err) {
|
||||
outcome = 'error';
|
||||
console.warn('[mesh] /regen-overrides failed:', sanitizeForLog((err as Error).message));
|
||||
res.status(500).json({ error: 'Failed to regenerate mesh overrides' });
|
||||
} finally {
|
||||
try {
|
||||
DatabaseService.getInstance().insertAuditLog({
|
||||
timestamp: Date.now(),
|
||||
username: actor,
|
||||
method: 'POST',
|
||||
path: req.path,
|
||||
status_code: res.statusCode,
|
||||
node_id: null,
|
||||
ip_address: req.ip ?? 'unknown',
|
||||
summary: summary
|
||||
? `Mesh override regen ${outcome}: ${summary.regenerated} regenerated, ${summary.failures.length} failed`
|
||||
: `Mesh override regen ${outcome}`,
|
||||
});
|
||||
} catch (auditErr) {
|
||||
console.error('[mesh] Audit log insert failed:', auditErr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
meshRouter.post('/nodes/:nodeId/enable', async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requireAdmiral(req, res)) return;
|
||||
if (!requireAdmin(req, res)) return;
|
||||
|
||||
Reference in New Issue
Block a user