mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 18:11:27 -07:00
update skills
This commit is contained in:
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.
|
||||
Reference in New Issue
Block a user