mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 02:41:14 +00:00
fix(observability): gate global logs to admins, scope to managed containers, harden SSE (#1254)
* fix(observability): gate global logs to admins, scope to managed containers, harden SSE Make the Logs feed an administrator view enforced on both sides (requireAdmin on the /api/logs/global poll and SSE routes; the Logs nav item plus a redirect guard on the frontend), and scope the feed to Sencho-managed containers only via a shared isManagedByComposeDir helper that /stats now reuses. Harden the SSE stream: a stateful frame demuxer that survives chunk boundaries so a Docker frame split across reads is reassembled instead of dropped or garbled; a per-stream error listener so one broken follow stream cannot crash the event loop (it posts a single degraded notice and keeps the others alive); a cap on concurrent follow streams with a truncation notice; a bounded initial tail; and backpressure that pauses the source streams when the client is slow and resumes on drain. Bound the polling snapshot's per-container fan-out with a concurrency limit. Add process-local, in-memory log-stream counters exposed at the admin-only /api/system/log-stream-metrics endpoint (active connections, lines streamed, attach and frame errors). Collapse the view to the local hub and remove the dead remote-node handling. * fix(observability): close remote-proxy bypass of the global-logs admin gate The logs feed's requireAdmin lives in the local route handler, which the remote proxy skips when forwarding a request whose nodeId targets a remote node. A hub user could therefore request /api/logs/global*, /api/logs/global/stream, or /api/system/log-stream-metrics with x-node-id (or ?nodeId= for the SSE transport) pointing at a remote node and have it served as the node-proxy admin on the far side, sidestepping the gate entirely. Add these paths to HUB_ONLY_PREFIXES so hubOnlyGuard rejects a remote nodeId with 403 before the proxy runs, matching the existing protection on audit-log, scheduled-tasks, and notification-routes. Add regression tests covering the collection path, the SSE sub-path (both the x-node-id header and the ?nodeId= query transport), and the stream-metrics endpoint.
This commit is contained in:
@@ -104,8 +104,10 @@ export function useViewNavigationState(options?: UseViewNavigationStateOptions)
|
||||
{ value: 'fleet', label: 'Fleet', icon: Radar },
|
||||
{ value: 'resources', label: 'Resources', icon: HardDrive },
|
||||
{ value: 'templates', label: 'App Store', icon: CloudDownload },
|
||||
{ value: 'global-observability', label: 'Logs', icon: Activity },
|
||||
];
|
||||
// The aggregated Logs feed crosses every managed stack, so it is an
|
||||
// admin-only operator view (the backend gates the same routes on admin).
|
||||
if (isAdmin) items.push({ value: 'global-observability', label: 'Logs', icon: Activity });
|
||||
if (isPaid && isAdmin) {
|
||||
items.push({ value: 'auto-updates', label: 'Auto-Update', icon: RefreshCw });
|
||||
items.push({ value: 'scheduled-ops', label: 'Schedules', icon: Clock });
|
||||
@@ -120,12 +122,17 @@ export function useViewNavigationState(options?: UseViewNavigationStateOptions)
|
||||
}, [isAdmin, isPaid, license?.variant, can, isRemote]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRemote && HUB_ONLY_VIEWS.has(activeView)) {
|
||||
// Redirect off a view the active context can't reach: a hub-only view while
|
||||
// a remote node is active, or the admin-only Logs view as a non-admin (e.g.
|
||||
// arrived via a deep-link event rather than the now-hidden nav item).
|
||||
const blockedByRemote = isRemote && HUB_ONLY_VIEWS.has(activeView);
|
||||
const blockedByRole = !isAdmin && activeView === 'global-observability';
|
||||
if (blockedByRemote || blockedByRole) {
|
||||
onNavigateToDashboard?.();
|
||||
setActiveView('dashboard');
|
||||
setFilterNodeId(null);
|
||||
}
|
||||
}, [isRemote, activeView, onNavigateToDashboard]);
|
||||
}, [isRemote, isAdmin, activeView, onNavigateToDashboard]);
|
||||
|
||||
return {
|
||||
activeView, setActiveView,
|
||||
|
||||
Reference in New Issue
Block a user