Skip to content
Notifications
Clear all

Guide: Using the Cloudflare API to automatically disable a rule during deployments.

1 Posts
1 Users
0 Reactions
3 Views
(@chrisk)
Estimable Member
Joined: 1 week ago
Posts: 90
Topic starter   [#18720]

A common operational friction point when deploying new application versions through CI/CD pipelines is the Cloudflare Web Application Firewall (WAF) incorrectly flagging legitimate deployment traffic as malicious. This is particularly prevalent during bulk database migrations, cache warm-up routines, or health check storms from orchestrators, which can trigger rate-limiting or managed challenge rules. Manually toggling WAF rules before and after each deployment is not scalable and introduces human error.

To address this, I've implemented a programmatic method using the Cloudflare API to temporarily disable specific WAF rules during a deployment window and automatically re-enable them post-deployment. The core principle is to modify the rule's action from its standard setting (e.g., `block`, `challenge`, `js_challenge`) to `log` for the duration of the deployment. This maintains auditability while preventing user impact. Below is a detailed workflow and the accompanying script.

**Prerequisites & Methodology:**
* Cloudflare API Token with `Zone.WAF:Edit` permissions.
* Identification of the specific WAF rule ID(s) causing false positives during deployments. This can be found via the Security Events dashboard or the List WAF Rules API call.
* Integration into your existing CI/CD pipeline (e.g., GitHub Actions, GitLab CI, Jenkins).

**Bash Script Implementation:**
This script uses the Cloudflare API v4. It sets a rule to `log`, waits for a defined deployment period, then restores the original action. It employs a state file to reliably revert to the original setting.

```bash
#!/bin/bash
# set_rule_log.sh

ZONE_ID="your_zone_id_here"
RULE_ID="your_waf_rule_id_here"
API_TOKEN="your_api_token_here"
STATE_FILE="/tmp/cf_rule_state_${RULE_ID}.json"
DEPLOY_DURATION=${1:-600} # Default to 600 seconds (10 minutes)

# Function to handle API errors
handle_error() {
echo "API Error: $1"
exit 1
}

# 1. Fetch current rule state and store original action
echo "Fetching current state for rule ${RULE_ID}..."
RULE_DATA=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/firewall/rules/${RULE_ID}"
-H "Authorization: Bearer ${API_TOKEN}"
-H "Content-Type: application/json") || handle_error "Failed to fetch rule."

ORIGINAL_ACTION=$(echo $RULE_DATA | jq -r '.result.action')
if [ "$ORIGINAL_ACTION" = "null" ] || [ -z "$ORIGINAL_ACTION" ]; then
handle_error "Could not parse original action from rule data."
fi

echo "{"original_action": "$ORIGINAL_ACTION"}" > $STATE_FILE
echo "Stored original action: $ORIGINAL_ACTION"

# 2. Update rule action to 'log' for the deployment window
echo "Setting rule ${RULE_ID} action to 'log'..."
UPDATE_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/firewall/rules/${RULE_ID}"
-H "Authorization: Bearer ${API_TOKEN}"
-H "Content-Type: application/json"
--data '{"action": "log"}') || handle_error "Failed to update rule."

if [ "$(echo $UPDATE_RESPONSE | jq -r '.success')" = "true" ]; then
echo "Rule successfully set to log mode."
else
handle_error "API reported failure on update."
fi

# 3. Wait for the deployment duration
echo "Maintaining log mode for ${DEPLOY_DURATION} seconds..."
sleep $DEPLOY_DURATION

# 4. Restore original action from state file
ORIGINAL_ACTION_RESTORE=$(jq -r '.original_action' $STATE_FILE)
echo "Restoring original action: ${ORIGINAL_ACTION_RESTORE}..."

RESTORE_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/firewall/rules/${RULE_ID}"
-H "Authorization: Bearer ${API_TOKEN}"
-H "Content-Type: application/json"
--data "{"action": "$ORIGINAL_ACTION_RESTORE"}") || handle_error "Failed to restore rule."

if [ "$(echo $RESTORE_RESPONSE | jq -r '.success')" = "true" ]; then
echo "Rule successfully restored to ${ORIGINAL_ACTION_RESTORE}."
rm $STATE_FILE
else
handle_error "API reported failure on restore. State file preserved at ${STATE_FILE}."
fi
```

**Integration Notes & Caveats:**
* **Pipeline Integration:** Call this script as a pre-deployment step, passing the estimated deployment duration as an argument. Ensure a post-deployment or cleanup step is configured to run the restore logic even if the deployment fails; the script's built-in wait may not be optimal for all pipelines. A more robust alternative is to split the script into two separate calls: `disable_to_log` and `restore_rule`.
* **Targeted Rules:** Be highly specific. Disabling a broad rule category (e.g., all SQLi rules) is a security risk. Use the rule ID for precision.
* **Concurrency:** If running multiple simultaneous deployments, implement a locking mechanism or idempotency check to prevent race conditions on the rule state.
* **Audit:** All requests made while the rule is in `log` mode will be visible in the Security Events dashboard, allowing for retrospective analysis.

This approach has reduced our deployment-related false positive blocks to zero and eliminated the manual toggling overhead. I encourage others to adapt the methodology and share any improvements, particularly around state management for ephemeral CI/CD runners.

-ck



   
Quote