feat(security): give back to CrowdSec and harden the CTI budget
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 29s
CI / tests-integration (push) Successful in 1m34s
CI / tests-unit (push) Successful in 1m36s
CI / tests-ui (push) Successful in 2m22s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m53s

- Bound the in-process verdict cache (FIFO eviction at 2000 entries) so a
  flood of distinct bucket-tripping IPs cannot grow it without limit.
- Record block metadata (reputation, score, behaviors, category, TTL) in
  antiddos:block:meta:{ip}, surfaced as the reason in the admin block list;
  unban now also clears the metadata and report locks.
- Track daily CTI enrichment usage in Redis (crowdsec:usage:{date}); warn
  once at 80% and pause lookups until tomorrow at CROWDSEC_CTI_DAILY_QUOTA
  (default 10000, 0 = unlimited) so a via-spread DDoS cannot burn the plan.
- Add opt-in signal push to the CrowdSec community (CAPI watcher): stable
  auto-generated 48-char machine_id/password pair persisted in Redis (or via
  env), one-time registration, cached JWT login, optional Console enrollment,
  and POST /v3/signals with a ban decision, deduped per IP. Never throws and
  reports last status to the admin panel with a verify action.
- Admin page: quota usage bar, reporting status/verify channel, and CrowdSec
  block reasons in the active-blocks list.
This commit is contained in:
openhands committed 2026-09-23 14:24:44 +02:00
1 parent ee25545b7f
commit 5e4fc9ab59
8 files changed
+1323 -37

No files matched your search

+19
View File
@@ -19,6 +19,10 @@ import {
setLastCrowdsecVerify,
verifyCrowdsecConnection,
} from "@/lib/crowdsec-api";
import {
setLastCrowdsecReport,
verifyCrowdsecReporting,
} from "@/lib/crowdsec-report";
import { db, WebsiteSetting } from "@/lib/db";
import { logger } from "@/lib/logger";
import { PERMS } from "@/lib/permissions";
@@ -223,6 +227,8 @@ export async function unbanAntiddosIp(formData: FormData): Promise<void> {
if (redis) {
await Promise.all([
redis.del(`antiddos:block:${ip}`),
redis.del(`antiddos:block:meta:${ip}`),
redis.del(`crowdsec:report:${ip}`),
redis.del(`antiddos:v:${ip}`),
]);
}
@@ -272,6 +278,19 @@ export async function verifyCrowdsecConfiguration(): Promise<void> {
revalidatePath("/admin/devops/antiddos");
}
/** Test the CrowdSec signal-push (CAPI watcher) channel. */
export async function verifyCrowdsecReportingConfiguration(): Promise<void> {
const staff = await requirePermission(PERMS.SETTINGS_VIEW);
const status = await verifyCrowdsecReporting();
await setLastCrowdsecReport(status);
logger.info("CrowdSec reporting configuration verified", {
staff: staff.username,
ok: status.ok,
message: status.message,
});
revalidatePath("/admin/devops/antiddos");
}
/** Test the configured Cloudflare API credentials against the zone. */
export async function verifyCloudflareConfiguration(): Promise<void> {
const staff = await requirePermission(PERMS.SETTINGS_VIEW);