feat(security): Cloudflare-aware IP trust and admin-tunable anti-DDoS
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 1m50s
CI / tests-unit (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m43s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m32s
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 1m50s
CI / tests-unit (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m43s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m32s
- resolveClientIp: trust cf-connecting-ip only behind cf-ray/cdn-loop, use nginx x-real-ip otherwise (anti-spoof) - antiddos-config: Redis-backed live config (antiddos:config) with 30s cache, 13 ANTI_DDOS_* env vars - ddos-guard: consume tunable rates/tiers via getAntiddosConfig - admin panel at /admin/devops/antiddos (save/reset/unban actions, PERMS.SETTINGS_VIEW) - register new admin page in housekeeping migration matrix (146 -> 147)
This commit is contained in:
1 parent
fd4d0fa1cb
commit
f0c27eb815
17 files changed
+1151
-63
No files matched your search
+17
-2
@@ -1,4 +1,5 @@
|
||||
import { isIP } from "node:net";
|
||||
import { isCloudflareProxied } from "@/lib/cloudflare";
|
||||
|
||||
export const UNKNOWN_CLIENT_IP = "0.0.0.0";
|
||||
|
||||
@@ -26,12 +27,26 @@ export function normalizeClientIp(
|
||||
* Forwarded headers must be overwritten by a trusted ingress and the origin must
|
||||
* reject direct public access. Header syntax alone cannot establish peer trust.
|
||||
* Never consume x-real-client-ip: API routes bypass the proxy that once set it.
|
||||
*
|
||||
* Trust order is Cloudflare-aware: only when a request demonstrably arrived via
|
||||
* Cloudflare (CF-Connecting-IP / CF-Ray / CDN-Loop) is `CF-Connecting-IP` used.
|
||||
* Otherwise the client-supplied CF header is ignored and only the ingress-set
|
||||
* `X-Real-IP` (`$remote_addr`) / `X-Forwarded-For` are trusted, so a DDoS that
|
||||
* hits the origin directly cannot re-key itself behind a spoofed header.
|
||||
*/
|
||||
export function resolveClientIp(headers: Pick<Headers, "get">): string {
|
||||
const cf = normalizeClientIp(headers.get("cf-connecting-ip"));
|
||||
if (isCloudflareProxied(headers)) {
|
||||
return (
|
||||
cf ??
|
||||
normalizeClientIp(headers.get("x-forwarded-for")?.split(",", 1)[0]) ??
|
||||
normalizeClientIp(headers.get("x-real-ip")) ??
|
||||
UNKNOWN_CLIENT_IP
|
||||
);
|
||||
}
|
||||
return (
|
||||
normalizeClientIp(headers.get("cf-connecting-ip")) ??
|
||||
normalizeClientIp(headers.get("x-forwarded-for")?.split(",", 1)[0]) ??
|
||||
normalizeClientIp(headers.get("x-real-ip")) ??
|
||||
normalizeClientIp(headers.get("x-forwarded-for")?.split(",", 1)[0]) ??
|
||||
UNKNOWN_CLIENT_IP
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user