feat(security): mirror anti-DDoS blocks to Cloudflare edge via API
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Failing after 1m40s
CI / tests-ui (push) Successful in 2m28s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Failing after 1m40s
CI / tests-ui (push) Successful in 2m28s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
- gate creates a zone IP Access Rule (block) for proxied offenders that hit the block threshold, deduped until the tiered block expires - cloudflare-api lib: verified endpoints, create/delete/verify/list helpers, Redis-backed tracking + 30s TTL sweep (instrumentation worker + admin render) - runtime toggle cloudflareAutoBlock in antiddos config; boot default CLOUDFLARE_AUTO_BLOCK_ENABLED - admin panel: Cloudflare edge-blocks card with verify + remove-rule actions; unban also lifts the edge block - credentials live in env only (CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID)
This commit is contained in:
1 parent
f0c27eb815
commit
4479753160
9 files changed
+1157
-1
No files matched your search
@@ -10,6 +10,11 @@ import {
|
||||
antiddosDefaultsFromEnv,
|
||||
invalidateAntiddosConfig,
|
||||
} from "@/lib/antiddos-config";
|
||||
import {
|
||||
removeCloudflareBlock,
|
||||
setLastCloudflareVerify,
|
||||
verifyCloudflareConnection,
|
||||
} from "@/lib/cloudflare-api";
|
||||
import { db, WebsiteSetting } from "@/lib/db";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
@@ -93,6 +98,7 @@ function configFromForm(formData: FormData): AntiddosConfig {
|
||||
formData.get("global_halt_ms"),
|
||||
defaults.globalHaltMs,
|
||||
),
|
||||
cloudflareAutoBlock: str(formData.get("cfa_auto_block")) === "1",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -116,6 +122,7 @@ async function persistSettings(config: AntiddosConfig): Promise<void> {
|
||||
.join(","),
|
||||
],
|
||||
["antiddos_global_halt_ms", String(config.globalHaltMs)],
|
||||
["antiddos_cfa_auto_block", config.cloudflareAutoBlock ? "1" : "0"],
|
||||
];
|
||||
await Promise.all(
|
||||
entries.map(([key, value]) =>
|
||||
@@ -190,12 +197,49 @@ export async function unbanAntiddosIp(formData: FormData): Promise<void> {
|
||||
redis.del(`antiddos:v:${ip}`),
|
||||
]);
|
||||
}
|
||||
// Also lift a matching Cloudflare edge block (best effort).
|
||||
const cloudflare = await removeCloudflareBlock(ip);
|
||||
logger.info("Anti-DDoS block manually removed", {
|
||||
staff: staff.username,
|
||||
ip,
|
||||
cloudflareCleared: cloudflare.removed,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Failed to remove anti-DDoS block", { err, ip });
|
||||
}
|
||||
revalidatePath("/admin/devops/antiddos");
|
||||
}
|
||||
|
||||
/** Remove an automatic Cloudflare edge block for a tracked IP. */
|
||||
export async function removeCloudflareRule(formData: FormData): Promise<void> {
|
||||
const staff = await requirePermission(PERMS.SETTINGS_VIEW);
|
||||
const ip = str(formData.get("ip")).trim();
|
||||
if (!ip) return;
|
||||
|
||||
const result = await removeCloudflareBlock(ip);
|
||||
logger.info(
|
||||
result.removed
|
||||
? "Cloudflare automatic block removed"
|
||||
: "Cloudflare automatic block removal skipped",
|
||||
{
|
||||
staff: staff.username,
|
||||
ip,
|
||||
message: result.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);
|
||||
const status = await verifyCloudflareConnection();
|
||||
await setLastCloudflareVerify(status);
|
||||
logger.info("Cloudflare API configuration verified", {
|
||||
staff: staff.username,
|
||||
ok: status.ok,
|
||||
zoneName: status.zoneName,
|
||||
message: status.message,
|
||||
});
|
||||
revalidatePath("/admin/devops/antiddos");
|
||||
}
|
||||
Reference in new issue
Block a user