@olegkoval/agent-skills 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +3 -2
- package/.cursor-plugin/index.json +5 -0
- package/catalog/skills.json +20 -0
- package/package.json +1 -1
- package/packages/software-development/cloudflare-block-countries/SKILL.md +175 -0
- package/packages/software-development/cloudflare-block-countries/adapters/claude/plugin.json +5 -0
- package/packages/software-development/cloudflare-block-countries/adapters/claude/skills/cloudflare-block-countries/SKILL.md +177 -0
- package/packages/software-development/cloudflare-block-countries/adapters/codex/README.md +3 -0
- package/packages/software-development/cloudflare-block-countries/adapters/cursor/plugin.json +6 -0
- package/packages/software-development/cloudflare-block-countries/adapters/cursor/skills/cloudflare-block-countries/SKILL.md +177 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "olko-agent-skills",
|
|
3
3
|
"description": "Agent-agnostic skill catalog for Codex, Claude, Cursor, and other skill-aware tools.",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Oleg Koval"
|
|
7
7
|
},
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"./packages/software-development/starter-rules",
|
|
24
24
|
"./packages/software-development/open-source-publisher",
|
|
25
25
|
"./packages/marketing/viral-launch",
|
|
26
|
-
"./packages/marketing/search-console-indexing-audit"
|
|
26
|
+
"./packages/marketing/search-console-indexing-audit",
|
|
27
|
+
"./packages/software-development/cloudflare-block-countries"
|
|
27
28
|
]
|
|
28
29
|
}
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
"name": "olko:search-console-indexing-audit",
|
|
76
76
|
"source": "./packages/marketing/search-console-indexing-audit/adapters/cursor",
|
|
77
77
|
"description": "Analyze Google Search Console Coverage CSV exports and correlate them with sitemap, robots, canonical, redirect, and noindex signals."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "olko:cloudflare-block-countries",
|
|
81
|
+
"source": "./packages/software-development/cloudflare-block-countries/adapters/cursor",
|
|
82
|
+
"description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones."
|
|
78
83
|
}
|
|
79
84
|
]
|
|
80
85
|
}
|
package/catalog/skills.json
CHANGED
|
@@ -306,6 +306,26 @@
|
|
|
306
306
|
"cursor",
|
|
307
307
|
"copilot"
|
|
308
308
|
]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "cloudflare-block-countries",
|
|
312
|
+
"lookupName": "olko:cloudflare-block-countries",
|
|
313
|
+
"category": "software-development",
|
|
314
|
+
"path": "packages/software-development/cloudflare-block-countries",
|
|
315
|
+
"description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
|
|
316
|
+
"tags": [
|
|
317
|
+
"cloudflare",
|
|
318
|
+
"waf",
|
|
319
|
+
"security",
|
|
320
|
+
"geo-blocking",
|
|
321
|
+
"firewall"
|
|
322
|
+
],
|
|
323
|
+
"adapters": [
|
|
324
|
+
"codex",
|
|
325
|
+
"claude",
|
|
326
|
+
"cursor",
|
|
327
|
+
"copilot"
|
|
328
|
+
]
|
|
309
329
|
}
|
|
310
330
|
]
|
|
311
331
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloudflare-block-countries
|
|
3
|
+
description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
|
|
7
|
+
metadata:
|
|
8
|
+
author: Oleg Koval
|
|
9
|
+
tags:
|
|
10
|
+
- cloudflare
|
|
11
|
+
- waf
|
|
12
|
+
- security
|
|
13
|
+
- geo-blocking
|
|
14
|
+
- firewall
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Cloudflare Country Block via WAF Custom Rules
|
|
18
|
+
|
|
19
|
+
Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- Cloudflare API Token with `Zone:Rulesets:Edit` permission
|
|
24
|
+
- Zone ID for the target domain
|
|
25
|
+
- `curl` and `jq` installed
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
### 1. Get Zone IDs
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
curl -s -X GET \
|
|
33
|
+
"https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
|
|
34
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
35
|
+
-H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Check Existing Custom Firewall Rulesets
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -s -X GET \
|
|
42
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
43
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
44
|
+
-H "Content-Type: application/json" | \
|
|
45
|
+
jq '.result[] | select(.phase == "http_request_firewall_custom")'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
|
|
49
|
+
|
|
50
|
+
### 3a. Create NEW Ruleset with Block Rule
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
curl -s -X POST \
|
|
54
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
55
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
56
|
+
-H "Content-Type: application/json" \
|
|
57
|
+
--data '{
|
|
58
|
+
"name": "default",
|
|
59
|
+
"description": "WAF Custom Rules",
|
|
60
|
+
"kind": "zone",
|
|
61
|
+
"phase": "http_request_firewall_custom",
|
|
62
|
+
"rules": [
|
|
63
|
+
{
|
|
64
|
+
"action": "block",
|
|
65
|
+
"description": "Block traffic from sanctioned countries",
|
|
66
|
+
"enabled": true,
|
|
67
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}' | jq '.success'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3b. Update EXISTING Ruleset
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl -s -X PUT \
|
|
77
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
78
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
--data '{
|
|
81
|
+
"description": "WAF Custom Rules",
|
|
82
|
+
"rules": [
|
|
83
|
+
{
|
|
84
|
+
"action": "block",
|
|
85
|
+
"description": "Block traffic from sanctioned countries",
|
|
86
|
+
"enabled": true,
|
|
87
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}' | jq '.success'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Verify the Rule is Active
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
curl -s -X GET \
|
|
97
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
98
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
99
|
+
-H "Content-Type: application/json" | jq '.result.rules'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Country Codes Reference
|
|
103
|
+
|
|
104
|
+
| Code | Country |
|
|
105
|
+
|------|---------|
|
|
106
|
+
| `RU` | Russia |
|
|
107
|
+
| `BY` | Belarus |
|
|
108
|
+
| `IR` | Iran |
|
|
109
|
+
| `KP` | North Korea |
|
|
110
|
+
| `CN` | China |
|
|
111
|
+
| `CU` | Cuba |
|
|
112
|
+
| `SY` | Syria |
|
|
113
|
+
| `VE` | Venezuela |
|
|
114
|
+
|
|
115
|
+
## WAF Expression Examples
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# Block multiple countries
|
|
119
|
+
(ip.src.country in {"RU" "BY" "IR" "KP"})
|
|
120
|
+
|
|
121
|
+
# Block country + specific ASN
|
|
122
|
+
(ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
|
|
123
|
+
|
|
124
|
+
# Block all except allowlisted IPs
|
|
125
|
+
(ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Multi-Zone Script
|
|
129
|
+
|
|
130
|
+
To apply the same block rule across multiple zones:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
#!/usr/bin/env bash
|
|
134
|
+
API_TOKEN="YOUR_API_TOKEN"
|
|
135
|
+
ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
|
|
136
|
+
EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
|
|
137
|
+
|
|
138
|
+
for ZONE_ID in "${ZONES[@]}"; do
|
|
139
|
+
EXISTING=$(curl -s -X GET \
|
|
140
|
+
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
|
|
141
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
142
|
+
-H "Content-Type: application/json" | \
|
|
143
|
+
jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
|
|
144
|
+
|
|
145
|
+
if [ -z "$EXISTING" ]; then
|
|
146
|
+
METHOD="POST"
|
|
147
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
|
|
148
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
149
|
+
name: "default", description: "WAF Custom Rules",
|
|
150
|
+
kind: "zone", phase: "http_request_firewall_custom",
|
|
151
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
152
|
+
}')
|
|
153
|
+
else
|
|
154
|
+
METHOD="PUT"
|
|
155
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
|
|
156
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
157
|
+
description: "WAF Custom Rules",
|
|
158
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
159
|
+
}')
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
RESULT=$(curl -s -X "$METHOD" "$URL" \
|
|
163
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
164
|
+
-H "Content-Type: application/json" \
|
|
165
|
+
--data "$DATA" | jq '.success')
|
|
166
|
+
|
|
167
|
+
echo "Zone $ZONE_ID: $RESULT"
|
|
168
|
+
done
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Notes
|
|
172
|
+
|
|
173
|
+
- `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
|
|
174
|
+
- WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
|
|
175
|
+
- Changes are applied globally within seconds; no cache purge needed.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
name: cloudflare-block-countries
|
|
5
|
+
description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
|
|
6
|
+
license: MIT
|
|
7
|
+
allowed-tools: Bash
|
|
8
|
+
compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
|
|
9
|
+
metadata:
|
|
10
|
+
author: Oleg Koval
|
|
11
|
+
tags:
|
|
12
|
+
- cloudflare
|
|
13
|
+
- waf
|
|
14
|
+
- security
|
|
15
|
+
- geo-blocking
|
|
16
|
+
- firewall
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Cloudflare Country Block via WAF Custom Rules
|
|
20
|
+
|
|
21
|
+
Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- Cloudflare API Token with `Zone:Rulesets:Edit` permission
|
|
26
|
+
- Zone ID for the target domain
|
|
27
|
+
- `curl` and `jq` installed
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### 1. Get Zone IDs
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -s -X GET \
|
|
35
|
+
"https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
|
|
36
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
37
|
+
-H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Check Existing Custom Firewall Rulesets
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s -X GET \
|
|
44
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
45
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
46
|
+
-H "Content-Type: application/json" | \
|
|
47
|
+
jq '.result[] | select(.phase == "http_request_firewall_custom")'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
|
|
51
|
+
|
|
52
|
+
### 3a. Create NEW Ruleset with Block Rule
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -s -X POST \
|
|
56
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
57
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
58
|
+
-H "Content-Type: application/json" \
|
|
59
|
+
--data '{
|
|
60
|
+
"name": "default",
|
|
61
|
+
"description": "WAF Custom Rules",
|
|
62
|
+
"kind": "zone",
|
|
63
|
+
"phase": "http_request_firewall_custom",
|
|
64
|
+
"rules": [
|
|
65
|
+
{
|
|
66
|
+
"action": "block",
|
|
67
|
+
"description": "Block traffic from sanctioned countries",
|
|
68
|
+
"enabled": true,
|
|
69
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}' | jq '.success'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3b. Update EXISTING Ruleset
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -s -X PUT \
|
|
79
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
80
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
--data '{
|
|
83
|
+
"description": "WAF Custom Rules",
|
|
84
|
+
"rules": [
|
|
85
|
+
{
|
|
86
|
+
"action": "block",
|
|
87
|
+
"description": "Block traffic from sanctioned countries",
|
|
88
|
+
"enabled": true,
|
|
89
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}' | jq '.success'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Verify the Rule is Active
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -s -X GET \
|
|
99
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
100
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
101
|
+
-H "Content-Type: application/json" | jq '.result.rules'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Country Codes Reference
|
|
105
|
+
|
|
106
|
+
| Code | Country |
|
|
107
|
+
|------|---------|
|
|
108
|
+
| `RU` | Russia |
|
|
109
|
+
| `BY` | Belarus |
|
|
110
|
+
| `IR` | Iran |
|
|
111
|
+
| `KP` | North Korea |
|
|
112
|
+
| `CN` | China |
|
|
113
|
+
| `CU` | Cuba |
|
|
114
|
+
| `SY` | Syria |
|
|
115
|
+
| `VE` | Venezuela |
|
|
116
|
+
|
|
117
|
+
## WAF Expression Examples
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
# Block multiple countries
|
|
121
|
+
(ip.src.country in {"RU" "BY" "IR" "KP"})
|
|
122
|
+
|
|
123
|
+
# Block country + specific ASN
|
|
124
|
+
(ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
|
|
125
|
+
|
|
126
|
+
# Block all except allowlisted IPs
|
|
127
|
+
(ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Multi-Zone Script
|
|
131
|
+
|
|
132
|
+
To apply the same block rule across multiple zones:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
#!/usr/bin/env bash
|
|
136
|
+
API_TOKEN="YOUR_API_TOKEN"
|
|
137
|
+
ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
|
|
138
|
+
EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
|
|
139
|
+
|
|
140
|
+
for ZONE_ID in "${ZONES[@]}"; do
|
|
141
|
+
EXISTING=$(curl -s -X GET \
|
|
142
|
+
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
|
|
143
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
144
|
+
-H "Content-Type: application/json" | \
|
|
145
|
+
jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
|
|
146
|
+
|
|
147
|
+
if [ -z "$EXISTING" ]; then
|
|
148
|
+
METHOD="POST"
|
|
149
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
|
|
150
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
151
|
+
name: "default", description: "WAF Custom Rules",
|
|
152
|
+
kind: "zone", phase: "http_request_firewall_custom",
|
|
153
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
154
|
+
}')
|
|
155
|
+
else
|
|
156
|
+
METHOD="PUT"
|
|
157
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
|
|
158
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
159
|
+
description: "WAF Custom Rules",
|
|
160
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
161
|
+
}')
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
RESULT=$(curl -s -X "$METHOD" "$URL" \
|
|
165
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
--data "$DATA" | jq '.success')
|
|
168
|
+
|
|
169
|
+
echo "Zone $ZONE_ID: $RESULT"
|
|
170
|
+
done
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Notes
|
|
174
|
+
|
|
175
|
+
- `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
|
|
176
|
+
- WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
|
|
177
|
+
- Changes are applied globally within seconds; no cache purge needed.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "olko:cloudflare-block-countries",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
|
|
5
|
+
"skills": "skills/"
|
|
6
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
name: cloudflare-block-countries
|
|
5
|
+
description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
|
|
6
|
+
license: MIT
|
|
7
|
+
allowed-tools: Bash
|
|
8
|
+
compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
|
|
9
|
+
metadata:
|
|
10
|
+
author: Oleg Koval
|
|
11
|
+
tags:
|
|
12
|
+
- cloudflare
|
|
13
|
+
- waf
|
|
14
|
+
- security
|
|
15
|
+
- geo-blocking
|
|
16
|
+
- firewall
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Cloudflare Country Block via WAF Custom Rules
|
|
20
|
+
|
|
21
|
+
Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- Cloudflare API Token with `Zone:Rulesets:Edit` permission
|
|
26
|
+
- Zone ID for the target domain
|
|
27
|
+
- `curl` and `jq` installed
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### 1. Get Zone IDs
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -s -X GET \
|
|
35
|
+
"https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
|
|
36
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
37
|
+
-H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Check Existing Custom Firewall Rulesets
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s -X GET \
|
|
44
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
45
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
46
|
+
-H "Content-Type: application/json" | \
|
|
47
|
+
jq '.result[] | select(.phase == "http_request_firewall_custom")'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
|
|
51
|
+
|
|
52
|
+
### 3a. Create NEW Ruleset with Block Rule
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -s -X POST \
|
|
56
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
57
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
58
|
+
-H "Content-Type: application/json" \
|
|
59
|
+
--data '{
|
|
60
|
+
"name": "default",
|
|
61
|
+
"description": "WAF Custom Rules",
|
|
62
|
+
"kind": "zone",
|
|
63
|
+
"phase": "http_request_firewall_custom",
|
|
64
|
+
"rules": [
|
|
65
|
+
{
|
|
66
|
+
"action": "block",
|
|
67
|
+
"description": "Block traffic from sanctioned countries",
|
|
68
|
+
"enabled": true,
|
|
69
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}' | jq '.success'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3b. Update EXISTING Ruleset
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -s -X PUT \
|
|
79
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
80
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
--data '{
|
|
83
|
+
"description": "WAF Custom Rules",
|
|
84
|
+
"rules": [
|
|
85
|
+
{
|
|
86
|
+
"action": "block",
|
|
87
|
+
"description": "Block traffic from sanctioned countries",
|
|
88
|
+
"enabled": true,
|
|
89
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}' | jq '.success'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Verify the Rule is Active
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -s -X GET \
|
|
99
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
100
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
101
|
+
-H "Content-Type: application/json" | jq '.result.rules'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Country Codes Reference
|
|
105
|
+
|
|
106
|
+
| Code | Country |
|
|
107
|
+
|------|---------|
|
|
108
|
+
| `RU` | Russia |
|
|
109
|
+
| `BY` | Belarus |
|
|
110
|
+
| `IR` | Iran |
|
|
111
|
+
| `KP` | North Korea |
|
|
112
|
+
| `CN` | China |
|
|
113
|
+
| `CU` | Cuba |
|
|
114
|
+
| `SY` | Syria |
|
|
115
|
+
| `VE` | Venezuela |
|
|
116
|
+
|
|
117
|
+
## WAF Expression Examples
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
# Block multiple countries
|
|
121
|
+
(ip.src.country in {"RU" "BY" "IR" "KP"})
|
|
122
|
+
|
|
123
|
+
# Block country + specific ASN
|
|
124
|
+
(ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
|
|
125
|
+
|
|
126
|
+
# Block all except allowlisted IPs
|
|
127
|
+
(ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Multi-Zone Script
|
|
131
|
+
|
|
132
|
+
To apply the same block rule across multiple zones:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
#!/usr/bin/env bash
|
|
136
|
+
API_TOKEN="YOUR_API_TOKEN"
|
|
137
|
+
ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
|
|
138
|
+
EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
|
|
139
|
+
|
|
140
|
+
for ZONE_ID in "${ZONES[@]}"; do
|
|
141
|
+
EXISTING=$(curl -s -X GET \
|
|
142
|
+
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
|
|
143
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
144
|
+
-H "Content-Type: application/json" | \
|
|
145
|
+
jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
|
|
146
|
+
|
|
147
|
+
if [ -z "$EXISTING" ]; then
|
|
148
|
+
METHOD="POST"
|
|
149
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
|
|
150
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
151
|
+
name: "default", description: "WAF Custom Rules",
|
|
152
|
+
kind: "zone", phase: "http_request_firewall_custom",
|
|
153
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
154
|
+
}')
|
|
155
|
+
else
|
|
156
|
+
METHOD="PUT"
|
|
157
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
|
|
158
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
159
|
+
description: "WAF Custom Rules",
|
|
160
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
161
|
+
}')
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
RESULT=$(curl -s -X "$METHOD" "$URL" \
|
|
165
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
--data "$DATA" | jq '.success')
|
|
168
|
+
|
|
169
|
+
echo "Zone $ZONE_ID: $RESULT"
|
|
170
|
+
done
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Notes
|
|
174
|
+
|
|
175
|
+
- `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
|
|
176
|
+
- WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
|
|
177
|
+
- Changes are applied globally within seconds; no cache purge needed.
|