feat(security): ops alerts, shared backoff, atomic quota and daily stats for CrowdSec
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-integration (push) Successful in 1m36s
CI / tests-unit (push) Successful in 1m40s
CI / tests-ui (push) Successful in 2m28s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m3s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-integration (push) Successful in 1m36s
CI / tests-unit (push) Successful in 1m40s
CI / tests-ui (push) Successful in 2m28s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m3s
Add an alerting/stats layer over the existing CrowdSec integration: - New crowdsec-alerts.ts: cooldown-gated ops alerts (Redis NX lock, TTL from HEALTH_ALERT_COOLDOWN_MIN) fanning out through the app's sendAlert service. Raised for daily quota exhaustion, block bursts (5-min window past CROWDSEC_ALERT_BLOCK_BURST), and signal-push failures. - New crowdsec-stats.ts: daily counters (lookups/blocks/reports/report_fail) in Redis with a 14-day reader for the admin panel. - Shared 403/429 backoff: the pause marker now lives in Redis (crowdsec:backoff-until) so every instance honours it, not just the process that hit the limit. - Atomic quota reservation: INCR-before-call with self-rollback on overshoot, so concurrent instances can never slip calls past the daily ceiling. - Admin anti-DDoS page gains a last-14-days activity table next to the quota bar.
This commit is contained in:
1 parent
5e4fc9ab59
commit
301edd2c9a
9 files changed
+620
-28
No files matched your search
@@ -45,6 +45,7 @@ import {
|
||||
crowdsecReportEnabled,
|
||||
getLastCrowdsecReport,
|
||||
} from "@/lib/crowdsec-report";
|
||||
import { getCrowdsecStats } from "@/lib/crowdsec-stats";
|
||||
import { db, WebsiteSetting } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { redis } from "@/lib/redis";
|
||||
@@ -147,6 +148,7 @@ export default async function AdminAntiDdosPage() {
|
||||
const crowdsecUsage = redisOk ? await getCrowdsecQuotaUsage() : null;
|
||||
const reportingEnabled = await crowdsecReportEnabled();
|
||||
const lastReport = await getLastCrowdsecReport();
|
||||
const crowdsecStats = redisOk ? await getCrowdsecStats(14) : [];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -723,6 +725,62 @@ export default async function AdminAntiDdosPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{crowdsecStats.length > 0 && (
|
||||
<div className="rounded-md border p-3">
|
||||
<p className="text-xs font-medium mb-2">
|
||||
Daily activity (last {crowdsecStats.length} days)
|
||||
</p>
|
||||
<div className="max-h-40 overflow-y-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="text-left text-muted-foreground">
|
||||
<th className="pb-1 pr-2 font-medium">Date</th>
|
||||
<th className="pb-1 pr-2 font-medium text-right">
|
||||
Lookups
|
||||
</th>
|
||||
<th className="pb-1 pr-2 font-medium text-right">
|
||||
Blocks
|
||||
</th>
|
||||
<th className="pb-1 pr-2 font-medium text-right">
|
||||
Reports
|
||||
</th>
|
||||
<th className="pb-1 font-medium text-right">Failures</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{crowdsecStats.map((row) => (
|
||||
<tr key={row.date} className="border-t">
|
||||
<td className="py-1 pr-2 text-muted-foreground">
|
||||
{row.date === new Date().toISOString().slice(0, 10)
|
||||
? "Today"
|
||||
: row.date.slice(5)}
|
||||
</td>
|
||||
<td className="py-1 pr-2 text-right">
|
||||
{row.lookups.toLocaleString()}
|
||||
</td>
|
||||
<td className="py-1 pr-2 text-right">
|
||||
{row.blocks.toLocaleString()}
|
||||
</td>
|
||||
<td className="py-1 pr-2 text-right">
|
||||
{row.reports.toLocaleString()}
|
||||
</td>
|
||||
<td className="py-1 text-right">
|
||||
{row.reportFailures > 0 ? (
|
||||
<span className="text-destructive">
|
||||
{row.reportFailures.toLocaleString()}
|
||||
</span>
|
||||
) : (
|
||||
"–"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-md border p-3">
|
||||
<p className="text-xs font-medium mb-1">Community signal push</p>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
|
||||
Reference in new issue
Block a user