cf-cli-tools/cf-fw
feibisi 1be3d8ef8d 初始提交:Cloudflare CLI 工具集
debian 开发,modiqi 修复 cf_load_profile 缺失问题。
9 个命令:cf-api/cf-zone/cf-dns/cf-cache/cf-ssl/cf-fw/cf-pagerule/cf-analytics/cf-hostname
支持多账号、域名自动解析 zone_id。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-24 02:39:31 +08:00

175 lines
6.7 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
# cf-fw — Cloudflare 防火墙/安全规则管理
# 用法: cf-fw rules <domain> 列出防火墙规则
# cf-fw waf <domain> WAF 托管规则状态
# cf-fw access-rules <domain> IP 访问规则
# cf-fw block <domain> <ip> [note] 封禁 IP
# cf-fw unblock <domain> <rule_id> 解封
# cf-fw whitelist <domain> <ip> [note] 白名单 IP
# cf-fw challenge <domain> <ip> [note] JS Challenge
# cf-fw security-level <domain> [level] 安全级别
# cf-fw ua-rules <domain> UA 规则列表
# cf-fw lockdown <domain> Zone Lockdown 规则
# 选项: -p <profile> 指定 profile
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/cf-lib.sh"
cf_parse_profile "$@"
set -- "${CF_REMAINING_ARGS[@]}"
cf_load_profile
usage() {
cat >&2 <<'EOF'
用法: cf-fw <command> <domain> [args...]
命令:
rules <domain> 防火墙规则列表
access-rules <domain> IP 访问规则列表
block <domain> <ip> [note] 封禁 IP
unblock <domain> <rule_id> 解封(删除访问规则)
whitelist <domain> <ip> [note] 白名单 IP
challenge <domain> <ip> [note] JS Challenge
security-level <domain> [level] 安全级别off/essentially_off/low/medium/high/under_attack
ua-rules <domain> User-Agent 规则
lockdown <domain> Zone Lockdown 规则
rate-limits <domain> 速率限制规则
EOF
exit 1
}
[[ $# -lt 1 ]] && usage
CMD="$1"; shift
# 创建 IP 访问规则的通用函数
create_access_rule() {
local zone_id="$1" mode="$2" ip="$3" note="${4:-}"
local target_type="ip"
# 检测是否为 CIDR
[[ "$ip" == *"/"* ]] && target_type="ip_range"
local data
data=$(jq -n --arg m "$mode" --arg t "$target_type" --arg v "$ip" --arg n "$note" \
'{mode:$m, configuration:{target:$t, value:$v}, notes:$n}')
cf_call POST "/zones/$zone_id/firewall/access_rules/rules" -d "$data"
}
case "$CMD" in
rules)
[[ $# -lt 1 ]] && { echo "用法: cf-fw rules <domain>" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(cf_call GET "/zones/$ZONE_ID/firewall/rules?per_page=50")
cf_check_error "$resp"
count=$(echo "$resp" | jq '.result | length')
if [[ "$count" == "0" ]]; then
echo "无防火墙规则"
else
echo "$resp" | jq -r '
["描述", "动作", "优先级", "启用", "ID"],
(.result[] | [.description[:40], .action, (.priority|tostring), (if .paused then "✗" else "✓" end), .id]) |
@tsv' | column -t -s $'\t'
fi
;;
access-rules)
[[ $# -lt 1 ]] && { echo "用法: cf-fw access-rules <domain>" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(cf_call GET "/zones/$ZONE_ID/firewall/access_rules/rules?per_page=50")
cf_check_error "$resp"
count=$(echo "$resp" | jq '.result | length')
if [[ "$count" == "0" ]]; then
echo "无 IP 访问规则"
else
echo "$resp" | jq -r '
["模式", "目标", "值", "备注", "创建时间", "ID"],
(.result[] | [.mode, .configuration.target, .configuration.value, .notes[:20], .created_on[:10], .id]) |
@tsv' | column -t -s $'\t'
fi
;;
block)
[[ $# -lt 2 ]] && { echo "用法: cf-fw block <domain> <ip> [note]" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(create_access_rule "$ZONE_ID" "block" "$2" "${3:-blocked by cf-fw}")
cf_check_error "$resp"
echo "已封禁: $2"
;;
unblock)
[[ $# -lt 2 ]] && { echo "用法: cf-fw unblock <domain> <rule_id>" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(cf_call DELETE "/zones/$ZONE_ID/firewall/access_rules/rules/$2")
cf_check_error "$resp"
echo "已解封: $2"
;;
whitelist)
[[ $# -lt 2 ]] && { echo "用法: cf-fw whitelist <domain> <ip> [note]" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(create_access_rule "$ZONE_ID" "whitelist" "$2" "${3:-whitelisted by cf-fw}")
cf_check_error "$resp"
echo "已加白名单: $2"
;;
challenge)
[[ $# -lt 2 ]] && { echo "用法: cf-fw challenge <domain> <ip> [note]" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(create_access_rule "$ZONE_ID" "js_challenge" "$2" "${3:-challenged by cf-fw}")
cf_check_error "$resp"
echo "已设置 JS Challenge: $2"
;;
security-level)
[[ $# -lt 1 ]] && { echo "用法: cf-fw security-level <domain> [level]" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
if [[ $# -lt 2 ]]; then
resp=$(cf_call GET "/zones/$ZONE_ID/settings/security_level")
echo "$resp" | jq -r '.result.value'
else
resp=$(cf_call PATCH "/zones/$ZONE_ID/settings/security_level" -d "{\"value\":\"$2\"}")
cf_check_error "$resp"
echo "安全级别已设为: $2"
fi
;;
ua-rules)
[[ $# -lt 1 ]] && { echo "用法: cf-fw ua-rules <domain>" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(cf_call GET "/zones/$ZONE_ID/firewall/ua_rules?per_page=50")
cf_check_error "$resp"
count=$(echo "$resp" | jq '.result | length')
if [[ "$count" == "0" ]]; then
echo "无 UA 规则"
else
echo "$resp" | jq -r '.result[] | "\(.mode) | \(.configuration.value[:50]) | \(.description)"'
fi
;;
lockdown)
[[ $# -lt 1 ]] && { echo "用法: cf-fw lockdown <domain>" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(cf_call GET "/zones/$ZONE_ID/firewall/lockdowns?per_page=50")
cf_check_error "$resp"
count=$(echo "$resp" | jq '.result | length')
if [[ "$count" == "0" ]]; then
echo "无 Zone Lockdown 规则"
else
echo "$resp" | jq '.result[] | {id, description, urls, configurations}'
fi
;;
rate-limits)
[[ $# -lt 1 ]] && { echo "用法: cf-fw rate-limits <domain>" >&2; exit 1; }
ZONE_ID=$(cf_resolve_zone "$1")
resp=$(cf_call GET "/zones/$ZONE_ID/rate_limits?per_page=50")
cf_check_error "$resp"
count=$(echo "$resp" | jq '.result | length')
if [[ "$count" == "0" ]]; then
echo "无速率限制规则"
else
echo "$resp" | jq '.result[] | {id, description, match, threshold, period, action: .action.mode}'
fi
;;
*) usage ;;
esac