mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-20 06:11:27 -07:00
update skills
This commit is contained in:
113
.agents/skills/cloudflare-deploy/references/waf/README.md
Normal file
113
.agents/skills/cloudflare-deploy/references/waf/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Cloudflare WAF Expert Skill Reference
|
||||
|
||||
**Expertise**: Cloudflare Web Application Firewall (WAF) configuration, custom rules, managed rulesets, rate limiting, attack detection, and API integration
|
||||
|
||||
## Overview
|
||||
|
||||
Cloudflare WAF protects web applications from attacks through managed rulesets and custom rules.
|
||||
|
||||
**Detection (Managed Rulesets)**
|
||||
- Pre-configured rules maintained by Cloudflare
|
||||
- CVE-based rules, OWASP Top 10 coverage
|
||||
- Three main rulesets: Cloudflare Managed, OWASP CRS, Exposed Credentials
|
||||
- Actions: log, block, challenge, js_challenge, managed_challenge
|
||||
|
||||
**Mitigation (Custom Rules & Rate Limiting)**
|
||||
- Custom expressions using Wirefilter syntax
|
||||
- Attack score-based blocking (`cf.waf.score`)
|
||||
- Rate limiting with per-IP, per-user, or custom characteristics
|
||||
- Actions: block, challenge, js_challenge, managed_challenge, log, skip
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Deploy Cloudflare Managed Ruleset
|
||||
```typescript
|
||||
import Cloudflare from 'cloudflare';
|
||||
|
||||
const client = new Cloudflare({ apiToken: process.env.CF_API_TOKEN });
|
||||
|
||||
// Deploy managed ruleset to zone
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_managed',
|
||||
name: 'Deploy Cloudflare Managed Ruleset',
|
||||
rules: [{
|
||||
action: 'execute',
|
||||
action_parameters: {
|
||||
id: 'efb7b8c949ac4650a09736fc376e9aee', // Cloudflare Managed Ruleset
|
||||
},
|
||||
expression: 'true',
|
||||
enabled: true,
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
### Create Custom Rule
|
||||
```typescript
|
||||
// Block requests with attack score >= 40
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_custom',
|
||||
name: 'Custom WAF Rules',
|
||||
rules: [{
|
||||
action: 'block',
|
||||
expression: 'cf.waf.score gt 40',
|
||||
description: 'Block high attack scores',
|
||||
enabled: true,
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
### Create Rate Limit
|
||||
```typescript
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_ratelimit',
|
||||
name: 'API Rate Limits',
|
||||
rules: [{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path eq "/api/login"',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
characteristics: ['cf.colo.id', 'ip.src'],
|
||||
period: 60,
|
||||
requests_per_period: 10,
|
||||
mitigation_timeout: 600,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
## Managed Ruleset Quick Reference
|
||||
|
||||
| Ruleset Name | ID | Coverage |
|
||||
|--------------|----|---------|
|
||||
| Cloudflare Managed | `efb7b8c949ac4650a09736fc376e9aee` | OWASP Top 10, CVEs |
|
||||
| OWASP Core Ruleset | `4814384a9e5d4991b9815dcfc25d2f1f` | OWASP ModSecurity CRS |
|
||||
| Exposed Credentials Check | `c2e184081120413c86c3ab7e14069605` | Credential stuffing |
|
||||
|
||||
## Phases
|
||||
|
||||
WAF rules execute in specific phases:
|
||||
- `http_request_firewall_managed` - Managed rulesets
|
||||
- `http_request_firewall_custom` - Custom rules
|
||||
- `http_ratelimit` - Rate limiting rules
|
||||
- `http_request_sbfm` - Super Bot Fight Mode (Pro+)
|
||||
|
||||
## Reading Order
|
||||
|
||||
1. **[api.md](api.md)** - SDK methods, expressions, actions, parameters
|
||||
2. **[configuration.md](configuration.md)** - Setup with Wrangler, Terraform, Pulumi
|
||||
3. **[patterns.md](patterns.md)** - Common patterns: deploy managed, rate limiting, skip, override
|
||||
4. **[gotchas.md](gotchas.md)** - Execution order, limits, expression errors
|
||||
|
||||
## See Also
|
||||
|
||||
- [Cloudflare WAF Docs](https://developers.cloudflare.com/waf/)
|
||||
- [Ruleset Engine](https://developers.cloudflare.com/ruleset-engine/)
|
||||
- [Expression Reference](https://developers.cloudflare.com/ruleset-engine/rules-language/)
|
||||
202
.agents/skills/cloudflare-deploy/references/waf/api.md
Normal file
202
.agents/skills/cloudflare-deploy/references/waf/api.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# API Reference
|
||||
|
||||
## SDK Setup
|
||||
|
||||
```typescript
|
||||
import Cloudflare from 'cloudflare';
|
||||
|
||||
const client = new Cloudflare({
|
||||
apiToken: process.env.CF_API_TOKEN,
|
||||
});
|
||||
```
|
||||
|
||||
## Core Methods
|
||||
|
||||
```typescript
|
||||
// List rulesets
|
||||
await client.rulesets.list({ zone_id: 'zone_id', phase: 'http_request_firewall_managed' });
|
||||
|
||||
// Get ruleset
|
||||
await client.rulesets.get({ zone_id: 'zone_id', ruleset_id: 'ruleset_id' });
|
||||
|
||||
// Create ruleset
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_custom',
|
||||
name: 'Custom WAF Rules',
|
||||
rules: [{ action: 'block', expression: 'cf.waf.score gt 40', enabled: true }],
|
||||
});
|
||||
|
||||
// Update ruleset (include rule id to keep existing, omit id for new rules)
|
||||
await client.rulesets.update({
|
||||
zone_id: 'zone_id',
|
||||
ruleset_id: 'ruleset_id',
|
||||
rules: [
|
||||
{ id: 'rule_id', action: 'block', expression: 'cf.waf.score gt 40', enabled: true },
|
||||
{ action: 'challenge', expression: 'http.request.uri.path contains "/admin"', enabled: true },
|
||||
],
|
||||
});
|
||||
|
||||
// Delete ruleset
|
||||
await client.rulesets.delete({ zone_id: 'zone_id', ruleset_id: 'ruleset_id' });
|
||||
```
|
||||
|
||||
## Actions & Phases
|
||||
|
||||
### Actions by Phase
|
||||
|
||||
| Action | Custom | Managed | Rate Limit | Description |
|
||||
|--------|--------|---------|------------|-------------|
|
||||
| `block` | ✅ | ❌ | ✅ | Block request with 403 |
|
||||
| `challenge` | ✅ | ❌ | ✅ | Show CAPTCHA challenge |
|
||||
| `js_challenge` | ✅ | ❌ | ✅ | JS-based challenge |
|
||||
| `managed_challenge` | ✅ | ❌ | ✅ | Smart challenge (recommended) |
|
||||
| `log` | ✅ | ❌ | ✅ | Log only, don't block |
|
||||
| `skip` | ✅ | ❌ | ❌ | Skip rule evaluation |
|
||||
| `execute` | ❌ | ✅ | ❌ | Deploy managed ruleset |
|
||||
|
||||
### Phases (Execution Order)
|
||||
|
||||
1. `http_request_firewall_custom` - Custom rules (first line of defense)
|
||||
2. `http_request_firewall_managed` - Managed rulesets (pre-configured protection)
|
||||
3. `http_ratelimit` - Rate limiting (request throttling)
|
||||
4. `http_request_sbfm` - Super Bot Fight Mode (Pro+ only)
|
||||
|
||||
## Expression Syntax
|
||||
|
||||
### Fields
|
||||
|
||||
```typescript
|
||||
// Request properties
|
||||
http.request.method // GET, POST, etc.
|
||||
http.request.uri.path // /api/users
|
||||
http.host // example.com
|
||||
|
||||
// IP and Geolocation
|
||||
ip.src // 192.0.2.1
|
||||
ip.geoip.country // US, GB, etc.
|
||||
ip.geoip.continent // NA, EU, etc.
|
||||
|
||||
// Attack detection
|
||||
cf.waf.score // 0-100 attack score
|
||||
cf.waf.score.sqli // SQL injection score
|
||||
cf.waf.score.xss // XSS score
|
||||
|
||||
// Headers & Cookies
|
||||
http.request.headers["authorization"][0]
|
||||
http.request.cookies["session"][0]
|
||||
lower(http.user_agent) // Lowercase user agent
|
||||
```
|
||||
|
||||
### Operators
|
||||
|
||||
```typescript
|
||||
// Comparison
|
||||
eq // Equal
|
||||
ne // Not equal
|
||||
lt // Less than
|
||||
le // Less than or equal
|
||||
gt // Greater than
|
||||
ge // Greater than or equal
|
||||
|
||||
// String matching
|
||||
contains // Substring match
|
||||
matches // Regex match (use carefully)
|
||||
starts_with // Prefix match
|
||||
ends_with // Suffix match
|
||||
|
||||
// List operations
|
||||
in // Value in list
|
||||
not // Logical NOT
|
||||
and // Logical AND
|
||||
or // Logical OR
|
||||
```
|
||||
|
||||
### Expression Examples
|
||||
|
||||
```typescript
|
||||
'cf.waf.score gt 40' // Attack score
|
||||
'http.request.uri.path eq "/api/login" and http.request.method eq "POST"' // Path + method
|
||||
'ip.src in {192.0.2.0/24 203.0.113.0/24}' // IP blocking
|
||||
'ip.geoip.country in {"CN" "RU" "KP"}' // Country blocking
|
||||
'http.user_agent contains "bot"' // User agent
|
||||
'not http.request.headers["authorization"][0]' // Header check
|
||||
'(cf.waf.score.sqli gt 20 or cf.waf.score.xss gt 20) and http.request.uri.path starts_with "/api"' // Complex
|
||||
```
|
||||
|
||||
## Rate Limiting Configuration
|
||||
|
||||
```typescript
|
||||
{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path starts_with "/api"',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
// Characteristics define uniqueness: 'ip.src', 'cf.colo.id',
|
||||
// 'http.request.headers["key"][0]', 'http.request.cookies["session"][0]'
|
||||
characteristics: ['cf.colo.id', 'ip.src'], // Recommended: per-IP per-datacenter
|
||||
period: 60, // Time window in seconds
|
||||
requests_per_period: 100, // Max requests in period
|
||||
mitigation_timeout: 600, // Block duration in seconds
|
||||
counting_expression: 'http.request.method ne "GET"', // Optional: filter counted requests
|
||||
requests_to_origin: false, // Count all requests (not just origin hits)
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
## Managed Ruleset Deployment
|
||||
|
||||
```typescript
|
||||
{
|
||||
action: 'execute',
|
||||
action_parameters: {
|
||||
id: 'efb7b8c949ac4650a09736fc376e9aee', // Cloudflare Managed
|
||||
overrides: {
|
||||
// Override specific rules
|
||||
rules: [
|
||||
{ id: '5de7edfa648c4d6891dc3e7f84534ffa', action: 'log', enabled: true },
|
||||
],
|
||||
// Override categories: 'wordpress', 'sqli', 'xss', 'rce', etc.
|
||||
categories: [
|
||||
{ category: 'wordpress', enabled: false },
|
||||
{ category: 'sqli', action: 'log' },
|
||||
],
|
||||
},
|
||||
},
|
||||
expression: 'true',
|
||||
enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
## Skip Rules
|
||||
|
||||
Skip rules bypass subsequent rule evaluation. Two skip types:
|
||||
|
||||
**Skip current ruleset**: Skip remaining rules in current phase only
|
||||
```typescript
|
||||
{
|
||||
action: 'skip',
|
||||
action_parameters: {
|
||||
ruleset: 'current', // Skip rest of current ruleset
|
||||
},
|
||||
expression: 'http.request.uri.path ends_with ".jpg" or http.request.uri.path ends_with ".css"',
|
||||
enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
**Skip entire phases**: Skip one or more phases completely
|
||||
```typescript
|
||||
{
|
||||
action: 'skip',
|
||||
action_parameters: {
|
||||
phases: ['http_request_firewall_managed', 'http_ratelimit'], // Skip multiple phases
|
||||
},
|
||||
expression: 'ip.src in {192.0.2.0/24 203.0.113.0/24}',
|
||||
enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Skip rules in custom phase can skip managed/ratelimit phases, but not vice versa (execution order).
|
||||
203
.agents/skills/cloudflare-deploy/references/waf/configuration.md
Normal file
203
.agents/skills/cloudflare-deploy/references/waf/configuration.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Configuration
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**API Token**: Create at https://dash.cloudflare.com/profile/api-tokens
|
||||
- Permission: `Zone.WAF Edit` or `Zone.Firewall Services Edit`
|
||||
- Zone Resources: Include specific zones or all zones
|
||||
|
||||
**Zone ID**: Found in dashboard > Overview > API section (right sidebar)
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export CF_API_TOKEN="your_api_token_here"
|
||||
export ZONE_ID="your_zone_id_here"
|
||||
```
|
||||
|
||||
## TypeScript SDK Usage
|
||||
|
||||
```bash
|
||||
npm install cloudflare
|
||||
```
|
||||
|
||||
```typescript
|
||||
import Cloudflare from 'cloudflare';
|
||||
|
||||
const client = new Cloudflare({ apiToken: process.env.CF_API_TOKEN });
|
||||
|
||||
// Custom rules
|
||||
await client.rulesets.create({
|
||||
zone_id: process.env.ZONE_ID,
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_custom',
|
||||
name: 'Custom WAF',
|
||||
rules: [
|
||||
{ action: 'block', expression: 'cf.waf.score gt 50', enabled: true },
|
||||
{ action: 'challenge', expression: 'http.request.uri.path eq "/admin"', enabled: true },
|
||||
],
|
||||
});
|
||||
|
||||
// Managed ruleset
|
||||
await client.rulesets.create({
|
||||
zone_id: process.env.ZONE_ID,
|
||||
phase: 'http_request_firewall_managed',
|
||||
rules: [{
|
||||
action: 'execute',
|
||||
action_parameters: { id: 'efb7b8c949ac4650a09736fc376e9aee' },
|
||||
expression: 'true',
|
||||
}],
|
||||
});
|
||||
|
||||
// Rate limiting
|
||||
await client.rulesets.create({
|
||||
zone_id: process.env.ZONE_ID,
|
||||
phase: 'http_ratelimit',
|
||||
rules: [{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path starts_with "/api"',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
characteristics: ['cf.colo.id', 'ip.src'],
|
||||
period: 60,
|
||||
requests_per_period: 100,
|
||||
mitigation_timeout: 600,
|
||||
},
|
||||
},
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
## Terraform Configuration
|
||||
|
||||
```hcl
|
||||
provider "cloudflare" {
|
||||
api_token = var.cloudflare_api_token
|
||||
}
|
||||
|
||||
resource "cloudflare_ruleset" "waf_custom" {
|
||||
zone_id = var.zone_id
|
||||
kind = "zone"
|
||||
phase = "http_request_firewall_custom"
|
||||
|
||||
rules {
|
||||
action = "block"
|
||||
expression = "cf.waf.score gt 50"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Managed Ruleset & Rate Limiting**:
|
||||
```hcl
|
||||
resource "cloudflare_ruleset" "waf_managed" {
|
||||
zone_id = var.zone_id
|
||||
name = "Managed Ruleset"
|
||||
kind = "zone"
|
||||
phase = "http_request_firewall_managed"
|
||||
|
||||
rules {
|
||||
action = "execute"
|
||||
action_parameters {
|
||||
id = "efb7b8c949ac4650a09736fc376e9aee"
|
||||
overrides {
|
||||
rules {
|
||||
id = "5de7edfa648c4d6891dc3e7f84534ffa"
|
||||
action = "log"
|
||||
}
|
||||
}
|
||||
}
|
||||
expression = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "cloudflare_ruleset" "rate_limiting" {
|
||||
zone_id = var.zone_id
|
||||
phase = "http_ratelimit"
|
||||
|
||||
rules {
|
||||
action = "block"
|
||||
expression = "http.request.uri.path starts_with \"/api\""
|
||||
ratelimit {
|
||||
characteristics = ["cf.colo.id", "ip.src"]
|
||||
period = 60
|
||||
requests_per_period = 100
|
||||
mitigation_timeout = 600
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Pulumi Configuration
|
||||
|
||||
```typescript
|
||||
import * as cloudflare from '@pulumi/cloudflare';
|
||||
|
||||
const zoneId = 'zone_id';
|
||||
|
||||
// Custom rules
|
||||
const wafCustom = new cloudflare.Ruleset('waf-custom', {
|
||||
zoneId,
|
||||
phase: 'http_request_firewall_custom',
|
||||
rules: [
|
||||
{ action: 'block', expression: 'cf.waf.score gt 50', enabled: true },
|
||||
{ action: 'challenge', expression: 'http.request.uri.path eq "/admin"', enabled: true },
|
||||
],
|
||||
});
|
||||
|
||||
// Managed ruleset
|
||||
const wafManaged = new cloudflare.Ruleset('waf-managed', {
|
||||
zoneId,
|
||||
phase: 'http_request_firewall_managed',
|
||||
rules: [{
|
||||
action: 'execute',
|
||||
actionParameters: { id: 'efb7b8c949ac4650a09736fc376e9aee' },
|
||||
expression: 'true',
|
||||
}],
|
||||
});
|
||||
|
||||
// Rate limiting
|
||||
const rateLimiting = new cloudflare.Ruleset('rate-limiting', {
|
||||
zoneId,
|
||||
phase: 'http_ratelimit',
|
||||
rules: [{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path starts_with "/api"',
|
||||
ratelimit: {
|
||||
characteristics: ['cf.colo.id', 'ip.src'],
|
||||
period: 60,
|
||||
requestsPerPeriod: 100,
|
||||
mitigationTimeout: 600,
|
||||
},
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
## Dashboard Configuration
|
||||
|
||||
1. Navigate to: **Security** > **WAF**
|
||||
2. Select tab:
|
||||
- **Managed rules** - Deploy/configure managed rulesets
|
||||
- **Custom rules** - Create custom rules
|
||||
- **Rate limiting rules** - Configure rate limits
|
||||
3. Click **Deploy** or **Create rule**
|
||||
|
||||
**Testing**: Use Security Events to test expressions before deploying.
|
||||
|
||||
## Wrangler Integration
|
||||
|
||||
WAF configuration is zone-level (not Worker-specific). Configuration methods:
|
||||
- Dashboard UI
|
||||
- Cloudflare API via SDK
|
||||
- Terraform/Pulumi (IaC)
|
||||
|
||||
**Workers benefit from WAF automatically** - no Worker code changes needed.
|
||||
|
||||
**Example: Query WAF API from Worker**:
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
return fetch(`https://api.cloudflare.com/client/v4/zones/${env.ZONE_ID}/rulesets`, {
|
||||
headers: { 'Authorization': `Bearer ${env.CF_API_TOKEN}` },
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
204
.agents/skills/cloudflare-deploy/references/waf/gotchas.md
Normal file
204
.agents/skills/cloudflare-deploy/references/waf/gotchas.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Gotchas & Troubleshooting
|
||||
|
||||
## Execution Order
|
||||
|
||||
**Problem:** Rules execute in unexpected order
|
||||
**Cause:** Misunderstanding phase execution
|
||||
**Solution:**
|
||||
|
||||
Phases execute sequentially (can't be changed):
|
||||
1. `http_request_firewall_custom` - Custom rules
|
||||
2. `http_request_firewall_managed` - Managed rulesets
|
||||
3. `http_ratelimit` - Rate limiting
|
||||
|
||||
Within phase: top-to-bottom, first match wins (unless `skip`)
|
||||
|
||||
```typescript
|
||||
// WRONG: Can't mix phase-specific actions
|
||||
await client.rulesets.create({
|
||||
phase: 'http_request_firewall_custom',
|
||||
rules: [
|
||||
{ action: 'block', expression: 'cf.waf.score gt 50' },
|
||||
{ action: 'execute', action_parameters: { id: 'managed_id' } }, // WRONG
|
||||
],
|
||||
});
|
||||
|
||||
// CORRECT: Separate rulesets per phase
|
||||
await client.rulesets.create({ phase: 'http_request_firewall_custom', rules: [...] });
|
||||
await client.rulesets.create({ phase: 'http_request_firewall_managed', rules: [...] });
|
||||
```
|
||||
|
||||
## Expression Errors
|
||||
|
||||
**Problem:** Syntax errors prevent deployment
|
||||
**Cause:** Invalid field/operator/syntax
|
||||
**Solution:**
|
||||
|
||||
```typescript
|
||||
// Common mistakes
|
||||
'http.request.path' → 'http.request.uri.path' // Correct field
|
||||
'ip.geoip.country eq US' → 'ip.geoip.country eq "US"' // Quote strings
|
||||
'http.user_agent eq "Mozilla"' → 'lower(http.user_agent) contains "mozilla"' // Case sensitivity
|
||||
'matches ".*[.jpg"' → 'matches ".*\\.jpg$"' // Valid regex
|
||||
```
|
||||
|
||||
Test expressions in Security Events before deploying.
|
||||
|
||||
## Skip Rule Pitfalls
|
||||
|
||||
**Problem:** Skip rules don't work as expected
|
||||
**Cause:** Misunderstanding skip scope
|
||||
**Solution:**
|
||||
|
||||
Skip types:
|
||||
- `ruleset: 'current'` - Skip remaining rules in current ruleset only
|
||||
- `phases: ['phase_name']` - Skip entire phases
|
||||
|
||||
```typescript
|
||||
// WRONG: Trying to skip managed rules from custom phase
|
||||
// In http_request_firewall_custom:
|
||||
{
|
||||
action: 'skip',
|
||||
action_parameters: { ruleset: 'current' },
|
||||
expression: 'ip.src in {192.0.2.0/24}',
|
||||
}
|
||||
// This only skips remaining custom rules, not managed rules
|
||||
|
||||
// CORRECT: Skip specific phases
|
||||
{
|
||||
action: 'skip',
|
||||
action_parameters: {
|
||||
phases: ['http_request_firewall_managed', 'http_ratelimit'],
|
||||
},
|
||||
expression: 'ip.src in {192.0.2.0/24}',
|
||||
}
|
||||
```
|
||||
|
||||
## Update Replaces All Rules
|
||||
|
||||
**Problem:** Updating ruleset deletes other rules
|
||||
**Cause:** `update()` replaces entire rule list
|
||||
**Solution:**
|
||||
|
||||
```typescript
|
||||
// WRONG: This deletes all existing rules!
|
||||
await client.rulesets.update({
|
||||
zone_id: 'zone_id',
|
||||
ruleset_id: 'ruleset_id',
|
||||
rules: [{ action: 'block', expression: 'cf.waf.score gt 50' }],
|
||||
});
|
||||
|
||||
// CORRECT: Get existing rules first
|
||||
const ruleset = await client.rulesets.get({ zone_id: 'zone_id', ruleset_id: 'ruleset_id' });
|
||||
await client.rulesets.update({
|
||||
zone_id: 'zone_id',
|
||||
ruleset_id: 'ruleset_id',
|
||||
rules: [...ruleset.rules, { action: 'block', expression: 'cf.waf.score gt 50' }],
|
||||
});
|
||||
```
|
||||
|
||||
## Override Conflicts
|
||||
|
||||
**Problem:** Managed ruleset overrides don't apply
|
||||
**Cause:** Rule ID doesn't exist or category name incorrect
|
||||
**Solution:**
|
||||
|
||||
```typescript
|
||||
// List managed ruleset rules to find IDs
|
||||
const ruleset = await client.rulesets.get({
|
||||
zone_id: 'zone_id',
|
||||
ruleset_id: 'efb7b8c949ac4650a09736fc376e9aee',
|
||||
});
|
||||
console.log(ruleset.rules.map(r => ({ id: r.id, description: r.description })));
|
||||
|
||||
// Use correct IDs in overrides
|
||||
{ action: 'execute', action_parameters: { id: 'efb7b8c949ac4650a09736fc376e9aee',
|
||||
overrides: { rules: [{ id: '5de7edfa648c4d6891dc3e7f84534ffa', action: 'log' }] } } }
|
||||
```
|
||||
|
||||
## False Positives
|
||||
|
||||
**Problem:** Legitimate traffic blocked
|
||||
**Cause:** Aggressive rules/thresholds
|
||||
**Solution:**
|
||||
|
||||
1. Start with log mode: `overrides: { action: 'log' }`
|
||||
2. Review Security Events to identify false positives
|
||||
3. Override specific rules: `overrides: { rules: [{ id: 'rule_id', action: 'log' }] }`
|
||||
|
||||
## Rate Limiting NAT Issues
|
||||
|
||||
**Problem:** Users behind NAT hit rate limits too quickly
|
||||
**Cause:** Multiple users sharing single IP
|
||||
**Solution:**
|
||||
|
||||
Add more characteristics: User-Agent, session cookie, or authorization header
|
||||
```typescript
|
||||
{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path starts_with "/api"',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
characteristics: ['cf.colo.id', 'ip.src', 'http.request.cookies["session"][0]'],
|
||||
period: 60,
|
||||
requests_per_period: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Issues
|
||||
|
||||
**Problem:** Increased latency
|
||||
**Cause:** Complex expressions, excessive rules
|
||||
**Solution:**
|
||||
|
||||
1. Skip static assets early: `action: 'skip'` for `\\.(jpg|css|js)$`
|
||||
2. Path-based deployment: Only run managed on `/api` or `/admin`
|
||||
3. Disable unused categories: `{ category: 'wordpress', enabled: false }`
|
||||
4. Prefer string operators over regex: `starts_with` vs `matches`
|
||||
|
||||
## Limits & Quotas
|
||||
|
||||
| Resource | Free | Pro | Business | Enterprise |
|
||||
|----------|------|-----|----------|------------|
|
||||
| Custom rules | 5 | 20 | 100 | 1000 |
|
||||
| Rate limiting rules | 1 | 10 | 25 | 100 |
|
||||
| Rule expression length | 4096 chars | 4096 chars | 4096 chars | 4096 chars |
|
||||
| Rules per ruleset | 75 | 75 | 400 | 1000 |
|
||||
| Managed rulesets | Yes | Yes | Yes | Yes |
|
||||
| Rate limit characteristics | 2 | 3 | 5 | 5 |
|
||||
|
||||
**Important Notes:**
|
||||
- Rules execute in order; first match wins (except skip rules)
|
||||
- Expression evaluation stops at first `false` in AND chains
|
||||
- `matches` regex operator is slower than string operators
|
||||
- Rate limit counting happens before mitigation
|
||||
|
||||
## API Errors
|
||||
|
||||
**Problem:** API calls fail with cryptic errors
|
||||
**Cause:** Invalid parameters or permissions
|
||||
**Solution:**
|
||||
|
||||
```typescript
|
||||
// Error: "Invalid phase" → Use exact phase name
|
||||
phase: 'http_request_firewall_custom'
|
||||
|
||||
// Error: "Ruleset already exists" → Use update() or list first
|
||||
const rulesets = await client.rulesets.list({ zone_id, phase: 'http_request_firewall_custom' });
|
||||
if (rulesets.result.length > 0) {
|
||||
await client.rulesets.update({ zone_id, ruleset_id: rulesets.result[0].id, rules: [...] });
|
||||
}
|
||||
|
||||
// Error: "Action not supported" → Check phase/action compatibility
|
||||
// 'execute' only in http_request_firewall_managed
|
||||
// Rate limit config only in http_ratelimit phase
|
||||
|
||||
// Error: "Expression parse error" → Common fixes:
|
||||
'ip.geoip.country eq "US"' // Quote strings
|
||||
'cf.waf.score gt 40' // Use 'gt' not '>'
|
||||
'http.request.uri.path' // Not 'http.request.path'
|
||||
```
|
||||
|
||||
**Tip**: Test expressions in dashboard Security Events before deploying.
|
||||
197
.agents/skills/cloudflare-deploy/references/waf/patterns.md
Normal file
197
.agents/skills/cloudflare-deploy/references/waf/patterns.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Common Patterns
|
||||
|
||||
## Deploy Managed Rulesets
|
||||
|
||||
```typescript
|
||||
// Deploy Cloudflare Managed Ruleset (default)
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_managed',
|
||||
name: 'Cloudflare Managed Ruleset',
|
||||
rules: [{
|
||||
action: 'execute',
|
||||
action_parameters: {
|
||||
id: 'efb7b8c949ac4650a09736fc376e9aee', // Cloudflare Managed
|
||||
// Or: '4814384a9e5d4991b9815dcfc25d2f1f' for OWASP CRS
|
||||
// Or: 'c2e184081120413c86c3ab7e14069605' for Exposed Credentials
|
||||
},
|
||||
expression: 'true', // All requests
|
||||
// Or: 'http.request.uri.path starts_with "/api"' for specific paths
|
||||
enabled: true,
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
## Override Managed Ruleset
|
||||
|
||||
```typescript
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
phase: 'http_request_firewall_managed',
|
||||
rules: [{
|
||||
action: 'execute',
|
||||
action_parameters: {
|
||||
id: 'efb7b8c949ac4650a09736fc376e9aee',
|
||||
overrides: {
|
||||
// Override specific rules
|
||||
rules: [
|
||||
{ id: '5de7edfa648c4d6891dc3e7f84534ffa', action: 'log' },
|
||||
{ id: '75a0060762034b9dad4e883afc121b4c', enabled: false },
|
||||
],
|
||||
// Override categories: wordpress, sqli, xss, rce, etc.
|
||||
categories: [
|
||||
{ category: 'wordpress', enabled: false },
|
||||
{ category: 'sqli', action: 'log' },
|
||||
],
|
||||
},
|
||||
},
|
||||
expression: 'true',
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Rules
|
||||
|
||||
```typescript
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_custom',
|
||||
name: 'Custom WAF Rules',
|
||||
rules: [
|
||||
// Attack score-based
|
||||
{ action: 'block', expression: 'cf.waf.score gt 50', enabled: true },
|
||||
{ action: 'challenge', expression: 'cf.waf.score gt 20', enabled: true },
|
||||
|
||||
// Specific attack types
|
||||
{ action: 'block', expression: 'cf.waf.score.sqli gt 30 or cf.waf.score.xss gt 30', enabled: true },
|
||||
|
||||
// Geographic blocking
|
||||
{ action: 'block', expression: 'ip.geoip.country in {"CN" "RU"}', enabled: true },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
```typescript
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_ratelimit',
|
||||
name: 'Rate Limits',
|
||||
rules: [
|
||||
// Per-IP global limit
|
||||
{
|
||||
action: 'block',
|
||||
expression: 'true',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
characteristics: ['cf.colo.id', 'ip.src'],
|
||||
period: 60,
|
||||
requests_per_period: 100,
|
||||
mitigation_timeout: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Login endpoint (stricter)
|
||||
{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path eq "/api/login"',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
characteristics: ['ip.src'],
|
||||
period: 60,
|
||||
requests_per_period: 5,
|
||||
mitigation_timeout: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// API writes only (using counting_expression)
|
||||
{
|
||||
action: 'block',
|
||||
expression: 'http.request.uri.path starts_with "/api"',
|
||||
action_parameters: {
|
||||
ratelimit: {
|
||||
characteristics: ['cf.colo.id', 'ip.src'],
|
||||
period: 60,
|
||||
requests_per_period: 50,
|
||||
counting_expression: 'http.request.method ne "GET"',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Skip Rules
|
||||
|
||||
```typescript
|
||||
await client.rulesets.create({
|
||||
zone_id: 'zone_id',
|
||||
kind: 'zone',
|
||||
phase: 'http_request_firewall_custom',
|
||||
name: 'Skip Rules',
|
||||
rules: [
|
||||
// Skip static assets (current ruleset only)
|
||||
{
|
||||
action: 'skip',
|
||||
action_parameters: { ruleset: 'current' },
|
||||
expression: 'http.request.uri.path matches "\\.(jpg|css|js|woff2?)$"',
|
||||
},
|
||||
|
||||
// Skip all WAF phases for trusted IPs
|
||||
{
|
||||
action: 'skip',
|
||||
action_parameters: {
|
||||
phases: ['http_request_firewall_managed', 'http_ratelimit'],
|
||||
},
|
||||
expression: 'ip.src in {192.0.2.0/24}',
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Complete Setup Example
|
||||
|
||||
Combine all three phases for comprehensive protection:
|
||||
|
||||
```typescript
|
||||
const client = new Cloudflare({ apiToken: process.env.CF_API_TOKEN });
|
||||
const zoneId = process.env.ZONE_ID;
|
||||
|
||||
// 1. Custom rules (execute first)
|
||||
await client.rulesets.create({
|
||||
zone_id: zoneId,
|
||||
phase: 'http_request_firewall_custom',
|
||||
rules: [
|
||||
{ action: 'skip', action_parameters: { phases: ['http_request_firewall_managed', 'http_ratelimit'] }, expression: 'ip.src in {192.0.2.0/24}' },
|
||||
{ action: 'block', expression: 'cf.waf.score gt 50' },
|
||||
{ action: 'managed_challenge', expression: 'cf.waf.score gt 20' },
|
||||
],
|
||||
});
|
||||
|
||||
// 2. Managed ruleset (execute second)
|
||||
await client.rulesets.create({
|
||||
zone_id: zoneId,
|
||||
phase: 'http_request_firewall_managed',
|
||||
rules: [{
|
||||
action: 'execute',
|
||||
action_parameters: { id: 'efb7b8c949ac4650a09736fc376e9aee', overrides: { categories: [{ category: 'wordpress', enabled: false }] } },
|
||||
expression: 'true',
|
||||
}],
|
||||
});
|
||||
|
||||
// 3. Rate limiting (execute third)
|
||||
await client.rulesets.create({
|
||||
zone_id: zoneId,
|
||||
phase: 'http_ratelimit',
|
||||
rules: [
|
||||
{ action: 'block', expression: 'true', action_parameters: { ratelimit: { characteristics: ['cf.colo.id', 'ip.src'], period: 60, requests_per_period: 100, mitigation_timeout: 600 } } },
|
||||
{ action: 'block', expression: 'http.request.uri.path eq "/api/login"', action_parameters: { ratelimit: { characteristics: ['ip.src'], period: 60, requests_per_period: 5, mitigation_timeout: 600 } } },
|
||||
],
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user