feat(security): auto-block repeat offenders via CrowdSec community reputation
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Failing after 17s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Failing after 17s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
- new crowdsec-api lib: CTI lookup (GET /smoke/{ip}, freemium x-api-key), verdict parser with false-positive veto, 1h Redis + in-memory verdict cache, NX lock dedupe, 403/429 backoff; writes only the shared antiddos:block:{ip} key (value "crowdsec") and never touches Cloudflare
- gate fires it fire-and-forget for IPs that already tripped a rate bucket, so known-bad IPs are hard-blocked before the local maxViolations threshold
- runtime config: crowdsecAutoBlock toggle, score threshold (0-5, default 4), block TTL (default 24h); boot defaults CROWDSEC_AUTO_BLOCK_ENABLED / CROWDSEC_BLOCK_SCORE / CROWDSEC_BLOCK_TTL_SECONDS
- admin panel: CrowdSec stat card, verify-connection action, score/TTL settings, CrowdSec source badge in the blocked-IPs list
- credentials live in env only (CROWDSEC_API_KEY); block is enforced per-request via proxy on the resolved X-Forwarded-For / CF-Connecting-IP
- tests: crowdsec-api unit suite + ddos-guard integration suite (early-block, threshold, cache dedupe, backoff)
This commit is contained in:
1 parent
64edb81ab7
commit
f32a6dadd0
9 files changed
+1414
-5
No files matched your search
@@ -24,6 +24,11 @@ export interface AntiddosConfig {
|
||||
blockTiers: AntiddosBlockTier[];
|
||||
globalHaltMs: number;
|
||||
cloudflareAutoBlock: boolean;
|
||||
crowdsecAutoBlock: boolean;
|
||||
/** Minimum CrowdSec malevolence score (0-5) treated as known-bad. */
|
||||
crowdsecBlockScore: number;
|
||||
/** How long a CrowdSec-confirmed bad IP stays blocked by the gate. */
|
||||
crowdsecBlockTtlSeconds: number;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: AntiddosConfig = {
|
||||
@@ -41,6 +46,9 @@ const DEFAULT_CONFIG: AntiddosConfig = {
|
||||
],
|
||||
globalHaltMs: 10_000,
|
||||
cloudflareAutoBlock: true,
|
||||
crowdsecAutoBlock: true,
|
||||
crowdsecBlockScore: 4,
|
||||
crowdsecBlockTtlSeconds: 86_400,
|
||||
};
|
||||
|
||||
function positiveInt(value: number | undefined, fallback: number): number {
|
||||
@@ -49,6 +57,17 @@ function positiveInt(value: number | undefined, fallback: number): number {
|
||||
return Math.floor(n);
|
||||
}
|
||||
|
||||
function clampInt(
|
||||
value: number | undefined,
|
||||
fallback: number,
|
||||
min: number,
|
||||
max: number,
|
||||
): number {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n)) return fallback;
|
||||
return Math.min(max, Math.max(min, Math.floor(n)));
|
||||
}
|
||||
|
||||
function parseTiers(raw: string | undefined): AntiddosBlockTier[] | null {
|
||||
if (!raw?.trim()) return null;
|
||||
const tiers: AntiddosBlockTier[] = [];
|
||||
@@ -125,6 +144,17 @@ export function antiddosDefaultsFromEnv(): AntiddosConfig {
|
||||
DEFAULT_CONFIG.globalHaltMs,
|
||||
),
|
||||
cloudflareAutoBlock: isTruthyFlag(env.CLOUDFLARE_AUTO_BLOCK_ENABLED),
|
||||
crowdsecAutoBlock: isTruthyFlag(env.CROWDSEC_AUTO_BLOCK_ENABLED),
|
||||
crowdsecBlockScore: clampInt(
|
||||
env.CROWDSEC_BLOCK_SCORE,
|
||||
DEFAULT_CONFIG.crowdsecBlockScore,
|
||||
0,
|
||||
5,
|
||||
),
|
||||
crowdsecBlockTtlSeconds: positiveInt(
|
||||
env.CROWDSEC_BLOCK_TTL_SECONDS,
|
||||
DEFAULT_CONFIG.crowdsecBlockTtlSeconds,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -167,6 +197,17 @@ function sanitize(config: AntiddosConfig): AntiddosConfig {
|
||||
: base.blockTiers,
|
||||
globalHaltMs: positiveInt(config?.globalHaltMs, base.globalHaltMs),
|
||||
cloudflareAutoBlock: config?.cloudflareAutoBlock !== false,
|
||||
crowdsecAutoBlock: config?.crowdsecAutoBlock !== false,
|
||||
crowdsecBlockScore: clampInt(
|
||||
config?.crowdsecBlockScore,
|
||||
base.crowdsecBlockScore,
|
||||
0,
|
||||
5,
|
||||
),
|
||||
crowdsecBlockTtlSeconds: positiveInt(
|
||||
config?.crowdsecBlockTtlSeconds,
|
||||
base.crowdsecBlockTtlSeconds,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user