@gfargo/doorman 3.9.2 → 3.10.1
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/dist/bin/run.js +89 -88
- package/dist/bin/run.mjs +89 -88
- package/package.json +1 -1
- package/skills/doorman/SKILL.md +19 -16
- package/skills/doorman/references/cicd.md +11 -9
- package/skills/doorman/references/cloudflare.md +42 -44
- package/skills/doorman/references/fastly.md +169 -0
- package/skills/doorman/sync/plugin.json +2 -1
package/package.json
CHANGED
package/skills/doorman/SKILL.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: doorman
|
|
3
|
-
description: Manage Vercel and
|
|
3
|
+
description: Manage Vercel, Cloudflare, and Fastly WAF firewall rules as code with the Doorman CLI. Use for any firewall-as-code task — creating rules, IP blocking, rate limiting, bot protection, geo-blocking, syncing local config to providers, validating configurations, exporting documentation, CI/CD automation, or translating rules between provider formats.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Node.js >= 20. Requires provider API tokens (VERCEL_TOKEN or
|
|
5
|
+
compatibility: Node.js >= 20. Requires provider API tokens (VERCEL_TOKEN, CLOUDFLARE_API_TOKEN, or FASTLY_API_TOKEN). Install globally via npm install -g @gfargo/doorman.
|
|
6
6
|
metadata:
|
|
7
7
|
author: gfargo
|
|
8
|
-
version:
|
|
8
|
+
version: '1.0'
|
|
9
9
|
homepage: https://github.com/gfargo/doorman
|
|
10
|
-
npm:
|
|
10
|
+
npm: '@gfargo/doorman'
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Doorman — Firewall Rules as Code
|
|
14
14
|
|
|
15
|
-
Doorman is a CLI for managing WAF (Web Application Firewall) rules as code across Vercel and
|
|
15
|
+
Doorman is a CLI for managing WAF (Web Application Firewall) rules as code across Vercel, Cloudflare, and Fastly Next-Gen WAF. Configuration lives in `.doorman.json`, syncs bidirectionally with provider APIs, and integrates into CI/CD pipelines.
|
|
16
16
|
|
|
17
17
|
## Command Quick Reference
|
|
18
18
|
|
|
@@ -44,7 +44,7 @@ doorman export --format markdown # Export as markdown|json|yaml|terraform
|
|
|
44
44
|
doorman remove --name "Old Rule" # Remove rules by name/ID
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
All commands accept `--provider vercel|cloudflare` and `--config <path>`.
|
|
47
|
+
All commands accept `--provider vercel|cloudflare|fastly` and `--config <path>`.
|
|
48
48
|
|
|
49
49
|
## Environment Variables
|
|
50
50
|
|
|
@@ -58,6 +58,10 @@ VERCEL_TEAM_ID=team_xxx
|
|
|
58
58
|
CLOUDFLARE_API_TOKEN=your_token
|
|
59
59
|
CLOUDFLARE_ZONE_ID=zone_xxx
|
|
60
60
|
CLOUDFLARE_ACCOUNT_ID=acc_xxx # optional, enables Lists API for bulk IP management
|
|
61
|
+
|
|
62
|
+
# Fastly Next-Gen WAF (beta)
|
|
63
|
+
FASTLY_API_TOKEN=your_token
|
|
64
|
+
FASTLY_WORKSPACE_ID=workspace_xxx
|
|
61
65
|
```
|
|
62
66
|
|
|
63
67
|
## Config Structure
|
|
@@ -72,7 +76,7 @@ CLOUDFLARE_ACCOUNT_ID=acc_xxx # optional, enables Lists API for bulk IP manage
|
|
|
72
76
|
}
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
For Cloudflare, add `provider` and `providers` fields instead of `projectId`/`teamId`.
|
|
79
|
+
For Cloudflare or Fastly, add `provider` and `providers` fields instead of `projectId`/`teamId`.
|
|
76
80
|
|
|
77
81
|
## Core Workflow
|
|
78
82
|
|
|
@@ -93,9 +97,7 @@ doorman backup && doorman validate && doorman diff && doorman sync && doorman st
|
|
|
93
97
|
{
|
|
94
98
|
"name": "Block Admin",
|
|
95
99
|
"active": true,
|
|
96
|
-
"conditionGroup": [
|
|
97
|
-
{ "conditions": [{ "type": "path", "op": "pre", "value": "/admin" }] }
|
|
98
|
-
],
|
|
100
|
+
"conditionGroup": [{ "conditions": [{ "type": "path", "op": "pre", "value": "/admin" }] }],
|
|
99
101
|
"action": { "mitigate": { "action": "deny" } }
|
|
100
102
|
}
|
|
101
103
|
```
|
|
@@ -112,12 +114,13 @@ doorman backup && doorman validate && doorman diff && doorman sync && doorman st
|
|
|
112
114
|
|
|
113
115
|
Load the relevant reference file for detailed documentation:
|
|
114
116
|
|
|
115
|
-
| Task
|
|
116
|
-
|
|
117
|
-
| Writing rules — full field docs, operators, actions, IP blocking, patterns
|
|
118
|
-
| Cloudflare-specific setup, Lists API, expression translation, limitations
|
|
119
|
-
|
|
|
120
|
-
|
|
|
117
|
+
| Task | Reference |
|
|
118
|
+
| ------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
119
|
+
| Writing rules — full field docs, operators, actions, IP blocking, patterns | [references/rules.md](references/rules.md) |
|
|
120
|
+
| Cloudflare-specific setup, Lists API, expression translation, limitations | [references/cloudflare.md](references/cloudflare.md) |
|
|
121
|
+
| Fastly-specific setup, condition/action mapping, rate-limit signal requirement, limitations | [references/fastly.md](references/fastly.md) |
|
|
122
|
+
| Available templates and what they protect against | [references/templates.md](references/templates.md) |
|
|
123
|
+
| CI/CD integration, automation, export formats, validation in pipelines | [references/cicd.md](references/cicd.md) |
|
|
121
124
|
|
|
122
125
|
## Principles
|
|
123
126
|
|
|
@@ -170,6 +170,7 @@ doorman status --format json
|
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
The health score evaluates:
|
|
173
|
+
|
|
173
174
|
- Rule descriptions present
|
|
174
175
|
- ID conventions followed (`rule_` prefix)
|
|
175
176
|
- Operator complexity (penalizes unnecessary regex)
|
|
@@ -202,25 +203,26 @@ doorman sync --provider cloudflare --config .doorman.json
|
|
|
202
203
|
```bash
|
|
203
204
|
# Migrate rules between providers
|
|
204
205
|
doorman download --provider vercel --config .doorman.json
|
|
205
|
-
doorman validate --
|
|
206
|
+
doorman validate --config .doorman.json
|
|
206
207
|
doorman sync --provider cloudflare --config .doorman.json
|
|
207
208
|
```
|
|
208
209
|
|
|
209
210
|
## Command Flags Reference
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
Common flags (availability varies by command):
|
|
212
213
|
|
|
213
|
-
| Flag
|
|
214
|
-
|
|
215
|
-
| `--config <path>`
|
|
216
|
-
| `--provider vercel\|cloudflare`
|
|
217
|
-
| `--format json\|table\|yaml\|markdown\|terraform` | Output format (command-dependent)
|
|
218
|
-
| `--verbose`
|
|
219
|
-
| `--output <path>`
|
|
214
|
+
| Flag | Description |
|
|
215
|
+
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
216
|
+
| `--config <path>` | Path to config file (default: auto-discovered `.doorman.json`) |
|
|
217
|
+
| `--provider vercel\|cloudflare\|fastly` | Override provider detection — `sync`/`diff`/`download`/`list`/`status`/`watch`/`backup`/`export` only |
|
|
218
|
+
| `--format json\|table\|yaml\|markdown\|terraform` | Output format (command-dependent) |
|
|
219
|
+
| `--verbose` | Show detailed output |
|
|
220
|
+
| `--output <path>` | Write output to file instead of stdout |
|
|
220
221
|
|
|
221
222
|
## Error Handling in Automation
|
|
222
223
|
|
|
223
224
|
Doorman exit codes:
|
|
225
|
+
|
|
224
226
|
- `0` — success
|
|
225
227
|
- `1` — error (validation failure, API error, config not found)
|
|
226
228
|
|
|
@@ -15,6 +15,7 @@ CLOUDFLARE_ACCOUNT_ID=acc_xxx # Optional — enables Lists API for bulk IP
|
|
|
15
15
|
### API Token Permissions
|
|
16
16
|
|
|
17
17
|
Create a Custom Token at https://dash.cloudflare.com/profile/api-tokens with:
|
|
18
|
+
|
|
18
19
|
- **Zone > Firewall Services > Edit** — for custom rulesets
|
|
19
20
|
- **Account > Account Filter Lists > Edit** — for Lists API (bulk IP management, requires `CLOUDFLARE_ACCOUNT_ID`)
|
|
20
21
|
|
|
@@ -37,13 +38,14 @@ Multi-provider config with explicit provider declaration:
|
|
|
37
38
|
}
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
Or pass `--provider cloudflare` to any command to override
|
|
41
|
+
Or pass `--provider cloudflare` to any provider-aware command (`sync`, `diff`, `download`, `list`, `status`, `watch`, `backup`, `export`) to override auto-detection.
|
|
41
42
|
|
|
42
43
|
## Usage
|
|
43
44
|
|
|
45
|
+
`doorman init` only supports Vercel today — it has no `--provider` flag and doesn't prompt for Cloudflare credentials. Create `.doorman.json` by hand using the config shape above, then:
|
|
46
|
+
|
|
44
47
|
```bash
|
|
45
|
-
doorman
|
|
46
|
-
doorman validate --provider cloudflare # Validate against Cloudflare constraints
|
|
48
|
+
doorman validate # Validate — auto-detects Cloudflare from the config's `provider` field
|
|
47
49
|
doorman sync --provider cloudflare # Deploy to Cloudflare
|
|
48
50
|
doorman download --provider cloudflare # Pull rules from Cloudflare
|
|
49
51
|
doorman diff --provider cloudflare # Compare local vs live
|
|
@@ -56,33 +58,33 @@ Doorman translates its unified rule format into Cloudflare Wirefilter expression
|
|
|
56
58
|
|
|
57
59
|
### Field Mapping
|
|
58
60
|
|
|
59
|
-
| Doorman Type
|
|
60
|
-
|
|
61
|
-
| `path`
|
|
62
|
-
| `method`
|
|
63
|
-
| `host`
|
|
64
|
-
| `user_agent`
|
|
65
|
-
| `ip_address`
|
|
66
|
-
| `header`
|
|
67
|
-
| `query`
|
|
68
|
-
| `cookie`
|
|
69
|
-
| `geo_country`
|
|
70
|
-
| `geo_city`
|
|
71
|
-
| `geo_continent`
|
|
72
|
-
| `geo_country_region` | `ip.geoip.subdivision_1`
|
|
73
|
-
| `geo_as_number`
|
|
74
|
-
| `scheme`
|
|
61
|
+
| Doorman Type | Cloudflare Field |
|
|
62
|
+
| -------------------- | ----------------------------- |
|
|
63
|
+
| `path` | `http.request.uri.path` |
|
|
64
|
+
| `method` | `http.request.method` |
|
|
65
|
+
| `host` | `http.host` |
|
|
66
|
+
| `user_agent` | `http.user_agent` |
|
|
67
|
+
| `ip_address` | `ip.src` |
|
|
68
|
+
| `header` | `http.request.headers["key"]` |
|
|
69
|
+
| `query` | `http.request.uri.query` |
|
|
70
|
+
| `cookie` | `http.cookie` |
|
|
71
|
+
| `geo_country` | `ip.geoip.country` |
|
|
72
|
+
| `geo_city` | `ip.geoip.city` |
|
|
73
|
+
| `geo_continent` | `ip.geoip.continent` |
|
|
74
|
+
| `geo_country_region` | `ip.geoip.subdivision_1` |
|
|
75
|
+
| `geo_as_number` | `ip.geoip.asnum` |
|
|
76
|
+
| `scheme` | `ssl` (boolean) |
|
|
75
77
|
|
|
76
78
|
### Action Mapping
|
|
77
79
|
|
|
78
|
-
| Doorman Action | Cloudflare Action
|
|
79
|
-
|
|
80
|
-
| `deny`
|
|
81
|
-
| `challenge`
|
|
82
|
-
| `rate_limit`
|
|
83
|
-
| `redirect`
|
|
84
|
-
| `log`
|
|
85
|
-
| `bypass`
|
|
80
|
+
| Doorman Action | Cloudflare Action |
|
|
81
|
+
| -------------- | -------------------------------- |
|
|
82
|
+
| `deny` | `block` |
|
|
83
|
+
| `challenge` | `managed_challenge` |
|
|
84
|
+
| `rate_limit` | `block` + `ratelimit` config |
|
|
85
|
+
| `redirect` | `redirect` + `from_value` params |
|
|
86
|
+
| `log` | `log` |
|
|
87
|
+
| `bypass` | `skip` |
|
|
86
88
|
|
|
87
89
|
## Lists API (Bulk IP Management)
|
|
88
90
|
|
|
@@ -97,16 +99,16 @@ Without `CLOUDFLARE_ACCOUNT_ID`, IP blocking falls back to individual WAF rules
|
|
|
97
99
|
|
|
98
100
|
## Limitations & Differences
|
|
99
101
|
|
|
100
|
-
| Feature
|
|
101
|
-
|
|
102
|
-
| `re` operator
|
|
103
|
-
| `environment` type
|
|
104
|
-
| `ja3_digest`/`ja4_digest` | Yes
|
|
105
|
-
| `region` type
|
|
106
|
-
| IP Lists (bulk)
|
|
107
|
-
| Max custom rules
|
|
108
|
-
| Rate limit
|
|
109
|
-
| Rule order
|
|
102
|
+
| Feature | Vercel | Cloudflare | Notes |
|
|
103
|
+
| ------------------------- | ------------------- | ----------------------------- | ------------------------------------------------- |
|
|
104
|
+
| `re` operator | All plans | Enterprise only | Use `sub`/`pre`/`suf` as alternatives |
|
|
105
|
+
| `environment` type | Yes | No | Vercel-specific concept |
|
|
106
|
+
| `ja3_digest`/`ja4_digest` | Yes | No | Vercel TLS fingerprints |
|
|
107
|
+
| `region` type | Yes | No | Vercel edge region |
|
|
108
|
+
| IP Lists (bulk) | Individual rules | Lists API | Cloudflare needs `accountId` |
|
|
109
|
+
| Max custom rules | ~100 | 5-125 (plan dependent) | Free: 5, Pro: 20, Business: 100, Enterprise: 125+ |
|
|
110
|
+
| Rate limit | Via rule action | Separate phase | Different underlying mechanism |
|
|
111
|
+
| Rule order | Parallel evaluation | Sequential (first match wins) | Ordering matters on Cloudflare |
|
|
110
112
|
|
|
111
113
|
## Translation Warnings
|
|
112
114
|
|
|
@@ -117,7 +119,7 @@ The `RuleTranslator` surfaces warnings when a translation is lossy:
|
|
|
117
119
|
- **Duration differences** — `actionDuration` maps differently between providers
|
|
118
120
|
- **Negation edge cases** — complex negated conditions may produce subtly different behavior in Wirefilter
|
|
119
121
|
|
|
120
|
-
Run `doorman validate
|
|
122
|
+
Run `doorman validate` to surface warnings before deploying — it auto-detects Cloudflare from the config's `provider` field.
|
|
121
123
|
|
|
122
124
|
## Cloudflare-Specific Validation
|
|
123
125
|
|
|
@@ -166,9 +168,7 @@ The `CloudflareOptimizer` consolidates rules for efficient deployment:
|
|
|
166
168
|
"name": "Rate Limit API",
|
|
167
169
|
"description": "Limit API requests to 100/min per IP",
|
|
168
170
|
"active": true,
|
|
169
|
-
"conditionGroup": [
|
|
170
|
-
{ "conditions": [{ "type": "path", "op": "pre", "value": "/api/" }] }
|
|
171
|
-
],
|
|
171
|
+
"conditionGroup": [{ "conditions": [{ "type": "path", "op": "pre", "value": "/api/" }] }],
|
|
172
172
|
"action": {
|
|
173
173
|
"mitigate": {
|
|
174
174
|
"action": "rate_limit",
|
|
@@ -181,8 +181,6 @@ The `CloudflareOptimizer` consolidates rules for efficient deployment:
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
],
|
|
184
|
-
"ips": [
|
|
185
|
-
{ "ip": "203.0.113.0/24", "action": "deny", "notes": "Known attack subnet" }
|
|
186
|
-
]
|
|
184
|
+
"ips": [{ "ip": "203.0.113.0/24", "action": "deny", "notes": "Known attack subnet" }]
|
|
187
185
|
}
|
|
188
186
|
```
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Fastly Next-Gen WAF Provider Reference
|
|
2
|
+
|
|
3
|
+
Fastly Next-Gen WAF support in Doorman (beta). Manage Next-Gen WAF (formerly Signal Sciences) workspace rules through the same config-as-code workflow. Classic Fastly VCL services are **not** supported — only the Next-Gen WAF's structured rules API.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Environment Variables
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
FASTLY_API_TOKEN=your_token # Required — Fastly API token
|
|
11
|
+
FASTLY_WORKSPACE_ID=workspace_xxx # Required — Next-Gen WAF workspace to manage rules for
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### API Token Permissions
|
|
15
|
+
|
|
16
|
+
Create a token at https://manage.fastly.com/account/personal/tokens with access to the Next-Gen WAF product for the target workspace.
|
|
17
|
+
|
|
18
|
+
### Config Shape
|
|
19
|
+
|
|
20
|
+
Multi-provider config with explicit provider declaration:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"$schema": "https://doorman.griffen.codes/schema.json",
|
|
25
|
+
"provider": "fastly",
|
|
26
|
+
"providers": {
|
|
27
|
+
"fastly": {
|
|
28
|
+
"workspaceId": "your_workspace_id"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"rules": [],
|
|
32
|
+
"ips": []
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or pass `--provider fastly` to any provider-aware command (`sync`, `diff`, `download`, `list`, `status`, `watch`, `backup`, `export`) to override auto-detection.
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
`doorman init` only supports Vercel today — it has no `--provider` flag and doesn't prompt for Fastly credentials. Create `.doorman.json` by hand using the config shape above, then:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
doorman validate # Validate — auto-detects Fastly from the config's `provider` field
|
|
44
|
+
doorman sync --provider fastly # Deploy to Fastly
|
|
45
|
+
doorman download --provider fastly # Pull rules from Fastly
|
|
46
|
+
doorman diff --provider fastly # Compare local vs live
|
|
47
|
+
doorman list --provider fastly # Show deployed rules
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Rule Translation
|
|
51
|
+
|
|
52
|
+
Doorman translates its unified rule format into Fastly's structured `request`-type rules automatically. The translation is bidirectional — `doorman download` pulls Fastly rules back into the unified format.
|
|
53
|
+
|
|
54
|
+
### Field Mapping
|
|
55
|
+
|
|
56
|
+
| Doorman Field | Fastly Condition | Notes |
|
|
57
|
+
| ------------- | -------------------------- | ------------------------------------------------------- |
|
|
58
|
+
| `path` | `path` | |
|
|
59
|
+
| `method` | `method` | |
|
|
60
|
+
| `host` | `domain` | |
|
|
61
|
+
| `user_agent` | `user_agent` | |
|
|
62
|
+
| `ip` | `ip` | |
|
|
63
|
+
| `country` | `country` | Country-level only — no region/city/continent condition |
|
|
64
|
+
| `scheme` | `scheme` | |
|
|
65
|
+
| `header` | `request_header` multival | Requires `key` (the header name) |
|
|
66
|
+
| `query` | `query_parameter` multival | Requires `key` (the parameter name) |
|
|
67
|
+
| `cookie` | `request_cookie` multival | Requires `key` (the cookie name) |
|
|
68
|
+
|
|
69
|
+
Fields with no Fastly equivalent (`region`, `city`, `asn`, `referer`, `port`, `target_path`, `environment`, `ja3_digest`, `ja4_digest`) are dropped with a translation warning rather than silently mistranslated — the rule still syncs with its remaining conditions.
|
|
70
|
+
|
|
71
|
+
### Operator Mapping
|
|
72
|
+
|
|
73
|
+
| Doorman Operator | Fastly Operator | Notes |
|
|
74
|
+
| -------------------------- | ------------------------------------------- | -------------------------------------------------------------- |
|
|
75
|
+
| `eq` | `equals` (or `does_not_equal` if `negated`) | |
|
|
76
|
+
| `contains` | `contains` (or `does_not_contain`) | |
|
|
77
|
+
| `matches` | `matches` (or `does_not_match`) | Regex syntax may need adjustment |
|
|
78
|
+
| `starts_with`, `ends_with` | `like` (or `not_like`) | Fastly's wildcard matching isn't a true prefix/suffix operator |
|
|
79
|
+
| `in` | `in_list` (or `not_in_list`) | |
|
|
80
|
+
| `gt`, `lt` | `greater_equal`, `lesser_equal` | No strict `>`/`<` on Fastly — boundary value also matches |
|
|
81
|
+
|
|
82
|
+
### Action Mapping
|
|
83
|
+
|
|
84
|
+
| Doorman Action | Fastly Action | Notes |
|
|
85
|
+
| --------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `block`, `deny` | `block` | |
|
|
87
|
+
| `allow` | `allow` | |
|
|
88
|
+
| `challenge` | `browser_challenge` | |
|
|
89
|
+
| `redirect` | `redirect` | Uses `redirect_url` + `response_code` |
|
|
90
|
+
| `rate_limit` | Rule `type: rate_limit` + `block_signal` action | See Rate Limiting below |
|
|
91
|
+
| `log` | `allow` (with a warning) | Fastly has no dedicated log-only action; `request_logging` is set at the rule level regardless |
|
|
92
|
+
| `bypass` | `allow` (with a warning) | No equivalent action |
|
|
93
|
+
|
|
94
|
+
## Condition Grouping
|
|
95
|
+
|
|
96
|
+
Fastly's condition model is bounded to two levels — a rule's top-level conditions can be plain, or grouped, but a group's own members can never contain another group. This matches Doorman's flat `group` model exactly:
|
|
97
|
+
|
|
98
|
+
- Conditions sharing a `group` index are AND'd together.
|
|
99
|
+
- Distinct `group` values are OR'd against each other.
|
|
100
|
+
- A single implicit group (the common case) becomes flat top-level conditions.
|
|
101
|
+
|
|
102
|
+
## Rate Limiting
|
|
103
|
+
|
|
104
|
+
Fastly rate-limit rules are a distinct rule type (`type: "rate_limit"`) with their own `rate_limit` block (`threshold`, `interval`, `duration`, a counting `signal`). Doorman's `window` (e.g. `"60s"`, `"5m"`) is rounded to the nearest interval Fastly supports (60s / 600s / 3600s).
|
|
105
|
+
|
|
106
|
+
**Fastly rate-limit rules count requests against a named custom signal that must already exist in the workspace** — Doorman does not create it automatically. Create a signal named `doorman-rate-limit-<rule-id>` before syncing a rate-limit rule, or the sync will be rejected. A translation warning is emitted every time to make this easy to miss less easily.
|
|
107
|
+
|
|
108
|
+
## IP Blocking
|
|
109
|
+
|
|
110
|
+
Fastly has no per-IP-rule resource — Doorman manages two workspace **lists** of type `ip` (`doorman-managed-deny`, `doorman-managed-allow`), each replaced wholesale on sync (the same atomic-replace shape as Cloudflare's ruleset write, not Vercel's per-item writes). `hostname`/`notes` on an IP rule have no Fastly equivalent and are dropped with a warning — only the address is kept.
|
|
111
|
+
|
|
112
|
+
## No Draft/Publish Step
|
|
113
|
+
|
|
114
|
+
Unlike classic Fastly VCL services, Next-Gen WAF rule and list writes take effect immediately — there is no draft-then-activate version to manage. A rule's `enabled` flag is the only gate on whether it's live.
|
|
115
|
+
|
|
116
|
+
## Limitations & Differences
|
|
117
|
+
|
|
118
|
+
| Feature | Vercel | Fastly | Notes |
|
|
119
|
+
| -------------------------- | ----------------------------- | ---------------------- | ------------------------------------------------------------------------- |
|
|
120
|
+
| Geo targeting | Country/city/continent/region | Country only | No sub-country condition field |
|
|
121
|
+
| Rule ordering | Best-effort (insertion order) | None | Next-Gen WAF rules are evaluated independently — `priority` has no effect |
|
|
122
|
+
| Managed/vendor rule groups | CRS (enterprise) | Templated signal rules | Not yet configurable through Doorman for either provider (#183) |
|
|
123
|
+
| Signal/exclusion rules | — | `type: signal` | Not managed by Doorman — see below |
|
|
124
|
+
|
|
125
|
+
Fastly rules of type `signal` or `templated_signal` (which tag or exclude WAF signals rather than allow/block/redirect a request) are skipped entirely by `doorman download`/`fetchConfig` rather than forced into a lossy `UnifiedRule` — they simply don't appear in the local config.
|
|
126
|
+
|
|
127
|
+
## Example: Full Fastly Config
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"$schema": "https://doorman.griffen.codes/schema.json",
|
|
132
|
+
"provider": "fastly",
|
|
133
|
+
"providers": {
|
|
134
|
+
"fastly": {
|
|
135
|
+
"workspaceId": "abc123def456"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"rules": [
|
|
139
|
+
{
|
|
140
|
+
"id": "rule_block_bots",
|
|
141
|
+
"name": "Block Bad Bots",
|
|
142
|
+
"enabled": true,
|
|
143
|
+
"conditions": [
|
|
144
|
+
{ "field": "user_agent", "operator": "contains", "value": "AhrefsBot", "group": 0 },
|
|
145
|
+
{ "field": "user_agent", "operator": "contains", "value": "SemrushBot", "group": 1 }
|
|
146
|
+
],
|
|
147
|
+
"action": { "type": "block" }
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"id": "rule_admin_header",
|
|
151
|
+
"name": "Require internal header on admin paths",
|
|
152
|
+
"enabled": true,
|
|
153
|
+
"conditions": [
|
|
154
|
+
{ "field": "path", "operator": "starts_with", "value": "/admin", "group": 0 },
|
|
155
|
+
{
|
|
156
|
+
"field": "header",
|
|
157
|
+
"operator": "eq",
|
|
158
|
+
"value": "internal",
|
|
159
|
+
"key": "X-Access-Level",
|
|
160
|
+
"negated": true,
|
|
161
|
+
"group": 0
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
"action": { "type": "block" }
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"ips": [{ "ip": "203.0.113.0/24", "action": "deny" }]
|
|
168
|
+
}
|
|
169
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "security",
|
|
3
|
-
"description": "Web application firewall management as code with Doorman: create rules, block IPs, rate limit, protect against bots, and deploy WAF configs to Vercel and
|
|
3
|
+
"description": "Web application firewall management as code with Doorman: create rules, block IPs, rate limit, protect against bots, and deploy WAF configs to Vercel, Cloudflare, and Fastly.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Griffen Fargo",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"waf",
|
|
14
14
|
"vercel",
|
|
15
15
|
"cloudflare",
|
|
16
|
+
"fastly",
|
|
16
17
|
"doorman",
|
|
17
18
|
"iac",
|
|
18
19
|
"rules",
|