Add Security Center — multi-source event pipeline with dashboard UI
Introduces a unified security event store and HTTP API that ingests events from any of the configured sources (API audit, Caddy access log, fail2ban, shared_bans, future remote agents) and surfaces them in the dashboard. New files: src/security/event-store.js JSONL-backed store + in-memory query index src/security/host-registry.js Registered hosts with per-host API keys src/security/event-workers.js Tail-followers for Caddy/fail2ban/shared_bans logs routes/security.js Events, hosts, ingest, SSE stream endpoints status/js/security-center.js Dashboard modal with Overview/Events/Hosts tabs SECURITY-FEATURE.md Full feature documentation DEAD-CODE.md, DUP-CODE.md, HARDENING.md Prior audits Modified: src/app.js Mount /api/v1/security/* src/utilities/middleware.js Add ingest endpoints to PUBLIC_ROUTES src/security/audit-logger.js Mirror audit events into security store server.js Start security workers on boot status/build.js Bundle security-center.js status/index.html Add Security button to nav
This commit is contained in:
@@ -119,6 +119,25 @@ class AuditLogger {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a (action, outcome) pair to a severity level for the security event store.
|
||||
* Most actions are 'info', but security-sensitive ones get escalated.
|
||||
*/
|
||||
resolveSeverity(action, outcome) {
|
||||
// Failed auth + sensitive actions are warnings at minimum
|
||||
if (outcome === 'failure' || outcome === 'denied' || outcome === 'error') {
|
||||
if (action?.startsWith('auth.')) return 'warn';
|
||||
if (action?.includes('credential')) return 'warn';
|
||||
if (action?.includes('delete') || action?.includes('disable')) return 'warn';
|
||||
return 'notice';
|
||||
}
|
||||
// Successful sensitive actions (key generation, TOTP setup, config changes)
|
||||
if (action?.startsWith('auth.totp-') || action?.includes('rotate-key')) return 'notice';
|
||||
if (action?.includes('delete') || action?.includes('disable')) return 'notice';
|
||||
if (action?.startsWith('config.')) return 'notice';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
async log({ action, resource, details, outcome, ip }) {
|
||||
try {
|
||||
const entry = {
|
||||
@@ -138,6 +157,34 @@ class AuditLogger {
|
||||
}
|
||||
return entries;
|
||||
});
|
||||
|
||||
// ALSO emit to the unified security event store so security events from
|
||||
// the API show up alongside Caddy access logs, fail2ban events, and any
|
||||
// future remote-agent events in one timeline. This is best-effort —
|
||||
// failure here MUST NOT block the audit log write.
|
||||
try {
|
||||
const { getStore } = require('./event-store');
|
||||
const store = getStore();
|
||||
const severity = this.resolveSeverity(action, outcome);
|
||||
const hostname = require('os').hostname();
|
||||
store.append({
|
||||
source_host: hostname,
|
||||
source_type: 'api',
|
||||
actor: ip || null,
|
||||
target: resource || null,
|
||||
action: action || 'unknown',
|
||||
outcome: outcome || 'unknown',
|
||||
severity,
|
||||
message: `${action} ${outcome} on ${resource}`.trim(),
|
||||
metadata: {
|
||||
method: details?.body && Object.keys(details.body)[0] ? '(see audit-log)' : undefined,
|
||||
audit_id: entry.id,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
// Non-fatal — security store is a best-effort mirror
|
||||
console.error('[AuditLogger] Security event emit failed:', e.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[AuditLogger] Failed to write entry:', e.message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user