File: //bigscoots/wpo/cloudflare/challenge_wplogin.sh
#!/bin/bash
# Usage: ./add_wplogin_challenge.sh <email> <api_key> <zone_id> [extra_paths]
#
# extra_paths: optional comma-separated list of additional paths to challenge
#
# Examples:
# ./add_wplogin_challenge.sh email apikey zoneid
# ./add_wplogin_challenge.sh email apikey zoneid "my-account"
# ./add_wplogin_challenge.sh email apikey zoneid "my-account,wp-admin,checkout"
set -euo pipefail
EMAIL="$1"
API_KEY="$2"
ZONE_ID="$3"
EXTRA_PATHS="${4:-}"
API_BASE="https://api.cloudflare.com/client/v4"
ENDPOINT="$API_BASE/zones/$ZONE_ID/rulesets/phases/http_request_firewall_custom/entrypoint"
# ── Build expression from paths ───────────────────────────────────────────────
# Always starts with wp-login.php, then ORs in any extra paths
PATHS="wp-login.php"
if [[ -n "$EXTRA_PATHS" ]]; then
PATHS="$PATHS,$EXTRA_PATHS"
fi
# Build the path conditions as OR chain
PATH_CONDITIONS=""
IFS=',' read -ra PATH_LIST <<< "$PATHS"
for CF_PATH in "${PATH_LIST[@]}"; do
CF_PATH="${CF_PATH#"${CF_PATH%%[![:space:]]*}"}" # trim leading whitespace
CF_PATH="${CF_PATH%"${CF_PATH##*[![:space:]]}"}" # trim trailing whitespace
CONDITION="http.request.uri.path contains \"$CF_PATH\""
if [[ -z "$PATH_CONDITIONS" ]]; then
PATH_CONDITIONS="$CONDITION"
else
PATH_CONDITIONS="$PATH_CONDITIONS or $CONDITION"
fi
done
# Wrap in parens with logout exclusion
EXPRESSION="($PATH_CONDITIONS) and not http.request.uri.query contains \"action=logout\""
# Build description
if [[ ${#PATH_LIST[@]} -gt 1 ]]; then
DESCRIPTION="Challenge $(IFS=', '; echo "${PATH_LIST[*]}")"
else
DESCRIPTION="Challenge wp-login.php"
fi
echo "Expression: $EXPRESSION"
# Build rule JSON safely with jq
NEW_RULE=$(jq -n \
--arg action "managed_challenge" \
--arg description "$DESCRIPTION" \
--arg expression "$EXPRESSION" \
'{action: $action, description: $description, enabled: true, expression: $expression}')
# ── Step 1: GET current ruleset ───────────────────────────────────────────────
echo "Fetching current rules for zone $ZONE_ID..."
RESPONSE=$(curl -s "$ENDPOINT" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $API_KEY")
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
if [[ "$SUCCESS" != "true" ]]; then
# Zone may not have any custom rules yet — that's fine, start with empty array
ERROR_CODE=$(echo "$RESPONSE" | jq -r '.errors[0].code // ""')
if [[ "$ERROR_CODE" == "10001" ]] || echo "$RESPONSE" | jq -e '.result == null' > /dev/null 2>&1; then
echo "No existing custom ruleset found — will create fresh."
RULES="[]"
else
echo "Error fetching ruleset:"
echo "$RESPONSE" | jq '.errors'
exit 1
fi
else
RULES=$(echo "$RESPONSE" | jq '.result.rules // []')
fi
# ── Step 2: Check if wp-login rule already exists ────────────────────────────
EXISTING_ACTION=$(echo "$RULES" | jq -r '.[] | select(.expression | contains("wp-login.php")) | .action' | head -1)
if [[ "$EXISTING_ACTION" == "managed_challenge" ]]; then
EXISTING_EXPRESSION=$(echo "$RULES" | jq -r '.[] | select(.expression | contains("wp-login.php")) | .expression' | head -1)
if [[ "$EXISTING_EXPRESSION" == "$EXPRESSION" ]]; then
echo "✓ Rule already exists with the correct expression. Nothing to do."
exit 0
else
echo "⚠ A managed_challenge rule for wp-login.php exists but with a different expression."
echo " Existing: $EXISTING_EXPRESSION"
echo " Updating to: $EXPRESSION"
RULES=$(echo "$RULES" | jq --arg expr "wp-login.php" --argjson new "$NEW_RULE" '
map(if (.expression | contains($expr)) then $new + {id: .id, ref: .ref} else . end)
')
fi
elif [[ -n "$EXISTING_ACTION" ]]; then
echo "⚠ A wp-login.php rule already exists with action: $EXISTING_ACTION"
echo " Updating it to managed_challenge..."
RULES=$(echo "$RULES" | jq --arg expr "wp-login.php" --argjson new "$NEW_RULE" '
map(if (.expression | contains($expr)) then $new + {id: .id, ref: .ref} else . end)
')
else
# ── Step 3: Insert after last skip rule, or at top if none ─────────────────
LAST_SKIP_INDEX=$(echo "$RULES" | jq '[to_entries[] | select(.value.action == "skip")] | last | .key // -1')
if [[ "$LAST_SKIP_INDEX" -ge 0 ]]; then
INSERT_AT=$((LAST_SKIP_INDEX + 1))
echo "Inserting rule at position $INSERT_AT (after last skip rule)..."
else
INSERT_AT=0
echo "No skip rules found — inserting rule at top..."
fi
RULES=$(echo "$RULES" | jq --argjson new "$NEW_RULE" --argjson idx "$INSERT_AT" '
.[:$idx] + [$new] + .[$idx:]
')
fi
# ── Step 4: PUT the updated ruleset back ─────────────────────────────────────
echo "Applying updated ruleset..."
PUT_RESPONSE=$(curl -s -X PUT "$ENDPOINT" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"rules\": $RULES}")
PUT_SUCCESS=$(echo "$PUT_RESPONSE" | jq -r '.success')
if [[ "$PUT_SUCCESS" == "true" ]]; then
echo "✓ Done! Final rule order:"
echo "$PUT_RESPONSE" | jq '.result.rules[] | {position: (.id), action, description, expression}'
else
echo "✗ Failed to update ruleset:"
echo "$PUT_RESPONSE" | jq '.errors'
exit 1
fi