mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-30 02:41:34 +00:00
let the audit log leave the box (#1133)
Retention became configurable in #1132, which only helps if entries can be moved somewhere before they expire. Until now the only way out was two GET endpoints built for the UI. Adds GET /audit-logs/export, taking the same filters as the list endpoint and streaming the whole matching set as CSV or NDJSON in batches, so an export is not bounded by the 200-row page cap and does not buffer the result set. Reading the entire trail is itself recorded as export_audit_logs. CSV fields starting with =, +, - or @ are prefixed with a quote. Audit rows carry attacker-influenced values like resource names, and spreadsheet software treats those as formulas on open. Adds optional live forwarding to a collector via AUDIT_LOG_FORWARD_URL, with an optional bearer token. Delivery goes through safeOutboundFetch so a misconfigured URL cannot be turned into an internal network probe, and it is fire-and-forget: the local write stays the source of truth and a dead SIEM must never delay or fail the operation being audited. Repeated failures are reported five times and then suppressed until delivery recovers, so an outage does not bury the logs it is supposed to appear in.
This commit is contained in:
@@ -86,6 +86,27 @@ export class AuditLogRepository {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads matching entries in ascending time order for export.
|
||||
*
|
||||
* Paged rather than fetched whole so an export cannot pull an unbounded
|
||||
* result set into memory, and ascending so a resumed or appended export
|
||||
* continues where the previous one stopped.
|
||||
*/
|
||||
async listForExport(input: {
|
||||
filters: AuditLogFilters;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}): Promise<AuditLogRecord[]> {
|
||||
return this.context.drizzle
|
||||
.select()
|
||||
.from(auditLogs)
|
||||
.where(this.buildWhere(input.filters))
|
||||
.orderBy(asc(auditLogs.timestamp), asc(auditLogs.id))
|
||||
.limit(input.limit)
|
||||
.offset(input.offset);
|
||||
}
|
||||
|
||||
async listDistinctActions(): Promise<string[]> {
|
||||
const rows = await this.context.drizzle
|
||||
.selectDistinct({ action: auditLogs.action })
|
||||
|
||||
Reference in New Issue
Block a user