Skip to content

Commit 05a6dc8

Browse files
CopilotGrantBirki
andcommitted
Fix test coverage to 100% and add comprehensive IP filtering documentation
Co-authored-by: GrantBirki <[email protected]>
1 parent 37d86af commit 05a6dc8

File tree

2 files changed

+316
-1
lines changed

2 files changed

+316
-1
lines changed

docs/ip_filtering.md

Lines changed: 296 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,298 @@
11
# IP Filtering
22

3-
TODO
3+
The Hooks service provides comprehensive application-level IP filtering functionality that allows you to control access to your webhooks based on client IP addresses. This feature supports both allowlist (whitelist) and blocklist (blacklist) configurations with CIDR notation support.
4+
5+
## Overview
6+
7+
IP filtering operates as a "pre-flight" check in the request processing pipeline, validating incoming requests before they reach your webhook handlers. The filtering can be configured both globally (for all endpoints) and at the individual endpoint level.
8+
9+
## ⚠️ Security Considerations
10+
11+
**Important**: This IP filtering operates at the application layer and relies on HTTP headers (like `X-Forwarded-For`) to determine client IP addresses. This approach has important security implications:
12+
13+
1. **Header Trust**: The service trusts proxy headers, which can be spoofed by malicious clients
14+
2. **Network-Level Protection**: For production security, consider implementing IP filtering at the network or load balancer level
15+
3. **Proper Proxy Configuration**: Ensure your reverse proxy/load balancer is properly configured to set accurate IP headers
16+
4. **Defense in Depth**: Use this feature as part of a broader security strategy, not as the sole protection mechanism
17+
18+
## Configuration
19+
20+
### Global Configuration
21+
22+
Configure IP filtering globally to apply rules to all endpoints:
23+
24+
```yaml
25+
# hooks.yml or your main configuration file
26+
ip_filtering:
27+
ip_header: X-Forwarded-For # Optional, defaults to X-Forwarded-For
28+
allowlist:
29+
- "10.0.0.0/8" # Allow entire private network
30+
- "172.16.0.0/12" # Allow another private range
31+
- "192.168.1.100" # Allow specific IP
32+
blocklist:
33+
- "192.168.1.200" # Block specific IP even if in allowlist
34+
- "203.0.113.0/24" # Block entire subnet
35+
```
36+
37+
### Endpoint-Level Configuration
38+
39+
Configure IP filtering for specific endpoints:
40+
41+
```yaml
42+
# config/endpoints/secure-endpoint.yml
43+
path: /secure-webhook
44+
handler: my_secure_handler
45+
46+
ip_filtering:
47+
ip_header: X-Real-IP # Optional, defaults to X-Forwarded-For
48+
allowlist:
49+
- "127.0.0.1" # Allow localhost
50+
- "192.168.1.0/24" # Allow local network
51+
blocklist:
52+
- "192.168.1.100" # Block specific IP in the allowed range
53+
```
54+
55+
## Configuration Options
56+
57+
### `ip_header` (optional)
58+
- **Default**: `X-Forwarded-For`
59+
- **Description**: HTTP header to check for the client IP address
60+
- **Common alternatives**: `X-Real-IP`, `CF-Connecting-IP`, `X-Client-IP`
61+
62+
### `allowlist` (optional)
63+
- **Type**: Array of strings
64+
- **Description**: List of allowed IP addresses or CIDR ranges
65+
- **Behavior**: If specified, only IPs in this list are allowed access
66+
- **Format**: Individual IPs (`192.168.1.1`) or CIDR notation (`192.168.1.0/24`)
67+
68+
### `blocklist` (optional)
69+
- **Type**: Array of strings
70+
- **Description**: List of blocked IP addresses or CIDR ranges
71+
- **Behavior**: IPs in this list are denied access, even if they appear in the allowlist
72+
- **Format**: Individual IPs (`192.168.1.1`) or CIDR notation (`192.168.1.0/24`)
73+
74+
## Filtering Logic
75+
76+
The IP filtering follows this precedence order:
77+
78+
1. **Extract Client IP**: Get the client IP from the configured header (case-insensitive lookup)
79+
2. **Check Blocklist**: If the IP matches any entry in the blocklist, deny immediately
80+
3. **Check Allowlist**: If an allowlist is configured, the IP must match an entry to be allowed
81+
4. **Default Allow**: If no allowlist is configured and IP is not blocked, allow the request
82+
83+
### Precedence Rules
84+
85+
- **Endpoint-level configuration** takes precedence over global configuration
86+
- **Blocklist rules** take precedence over allowlist rules
87+
- **First IP in comma-separated list** is used (e.g., in `X-Forwarded-For: 192.168.1.1, 10.0.0.1`, only `192.168.1.1` is checked)
88+
89+
## CIDR Notation Support
90+
91+
The service supports CIDR (Classless Inter-Domain Routing) notation for specifying IP ranges:
92+
93+
```yaml
94+
ip_filtering:
95+
allowlist:
96+
- "192.168.1.0/24" # Allows 192.168.1.1 through 192.168.1.254
97+
- "10.0.0.0/8" # Allows 10.0.0.1 through 10.255.255.254
98+
- "172.16.0.0/12" # Allows 172.16.0.1 through 172.31.255.254
99+
blocklist:
100+
- "192.168.1.100/32" # Blocks specific IP (equivalent to 192.168.1.100)
101+
- "203.0.113.0/24" # Blocks entire test network range
102+
```
103+
104+
## Examples
105+
106+
### Example 1: Basic Allowlist
107+
108+
```yaml
109+
# Allow only specific IPs
110+
path: /secure-webhook
111+
handler: secure_handler
112+
113+
ip_filtering:
114+
allowlist:
115+
- "127.0.0.1"
116+
- "192.168.1.50"
117+
```
118+
119+
### Example 2: CIDR Range with Exceptions
120+
121+
```yaml
122+
# Allow local network but block specific troublemaker
123+
path: /internal-webhook
124+
handler: internal_handler
125+
126+
ip_filtering:
127+
allowlist:
128+
- "192.168.1.0/24"
129+
blocklist:
130+
- "192.168.1.100" # Block this specific IP
131+
```
132+
133+
### Example 3: Custom IP Header
134+
135+
```yaml
136+
# Use Cloudflare's connecting IP header
137+
path: /cloudflare-webhook
138+
handler: cf_handler
139+
140+
ip_filtering:
141+
ip_header: CF-Connecting-IP
142+
allowlist:
143+
- "203.0.113.0/24"
144+
```
145+
146+
### Example 4: Multiple CIDR Ranges
147+
148+
```yaml
149+
# Allow multiple office networks
150+
path: /office-webhook
151+
handler: office_handler
152+
153+
ip_filtering:
154+
allowlist:
155+
- "192.168.1.0/24" # Main office
156+
- "192.168.2.0/24" # Branch office
157+
- "10.0.100.0/24" # VPN range
158+
blocklist:
159+
- "192.168.1.200" # Compromised machine
160+
```
161+
162+
## Error Responses
163+
164+
When IP filtering fails, the service returns an HTTP 403 Forbidden response:
165+
166+
```json
167+
{
168+
"error": "ip_filtering_failed",
169+
"message": "IP address not allowed",
170+
"request_id": "550e8400-e29b-41d4-a716-446655440000"
171+
}
172+
```
173+
174+
## Testing Your Configuration
175+
176+
You can test your IP filtering configuration using curl:
177+
178+
```bash
179+
# Test with allowed IP
180+
curl -H "X-Forwarded-For: 192.168.1.50" \
181+
-H "Content-Type: application/json" \
182+
-d '{"test": "data"}' \
183+
http://localhost:8080/webhooks/secure-endpoint
184+
185+
# Test with blocked IP
186+
curl -H "X-Forwarded-For: 192.168.1.100" \
187+
-H "Content-Type: application/json" \
188+
-d '{"test": "data"}' \
189+
http://localhost:8080/webhooks/secure-endpoint
190+
```
191+
192+
## Common Use Cases
193+
194+
### 1. Restrict to Corporate Network
195+
196+
```yaml
197+
ip_filtering:
198+
allowlist:
199+
- "10.0.0.0/8" # Private network
200+
- "172.16.0.0/12" # Private network
201+
- "192.168.0.0/16" # Private network
202+
```
203+
204+
### 2. Allow Specific Service Providers
205+
206+
```yaml
207+
ip_filtering:
208+
allowlist:
209+
- "140.82.112.0/20" # GitHub webhook IPs (example)
210+
- "192.30.252.0/22" # GitHub webhook IPs (example)
211+
```
212+
213+
### 3. Block Known Bad Actors
214+
215+
```yaml
216+
ip_filtering:
217+
blocklist:
218+
- "203.0.113.0/24" # Example bad network
219+
- "198.51.100.50" # Specific bad IP
220+
```
221+
222+
### 4. Development vs Production
223+
224+
```yaml
225+
# Development - allow local testing
226+
ip_filtering:
227+
allowlist:
228+
- "127.0.0.1"
229+
- "192.168.1.0/24"
230+
231+
# Production - restrict to known sources
232+
ip_filtering:
233+
allowlist:
234+
- "10.0.0.0/8"
235+
blocklist:
236+
- "10.0.0.100" # Compromised internal host
237+
```
238+
239+
## Best Practices
240+
241+
1. **Start Restrictive**: Begin with a narrow allowlist and expand as needed
242+
2. **Monitor Logs**: Watch for legitimate traffic being blocked
243+
3. **Layer Security**: Use IP filtering alongside other security measures
244+
4. **Document Changes**: Keep track of why specific IPs or ranges are allowed/blocked
245+
5. **Regular Review**: Periodically review and update your IP filtering rules
246+
6. **Test Thoroughly**: Always test configuration changes in a non-production environment first
247+
7. **Consider Automation**: For dynamic environments, consider using network-level controls instead
248+
249+
## Troubleshooting
250+
251+
### Common Issues
252+
253+
1. **Wrong IP Header**: Verify your proxy/load balancer sets the correct IP header
254+
2. **CIDR Notation**: Ensure CIDR ranges are correctly formatted
255+
3. **Header Case**: The service performs case-insensitive header lookup
256+
4. **Multiple IPs**: Only the first IP in comma-separated headers is checked
257+
5. **IPv6 Support**: The service supports IPv6 addresses and ranges
258+
259+
### Debug Steps
260+
261+
1. Check server logs for IP filtering messages
262+
2. Verify the client IP being detected matches expectations
263+
3. Test with known good/bad IPs
264+
4. Confirm endpoint vs global configuration precedence
265+
5. Validate CIDR range calculations
266+
267+
## Performance Considerations
268+
269+
- IP filtering adds minimal overhead to request processing
270+
- CIDR matching is optimized for common use cases
271+
- Consider the number of rules - hundreds of rules may impact performance
272+
- Invalid IP patterns in configuration are silently skipped
273+
274+
## Migration from Auth Plugins
275+
276+
If you're currently using custom auth plugins for IP filtering, you can migrate to the built-in IP filtering feature:
277+
278+
**Before (Custom Auth Plugin):**
279+
```yaml
280+
path: /webhook
281+
handler: my_handler
282+
auth:
283+
type: my_ip_filtering_plugin
284+
opts:
285+
allowed_ips:
286+
- "192.168.1.1"
287+
```
288+
289+
**After (Built-in IP Filtering):**
290+
```yaml
291+
path: /webhook
292+
handler: my_handler
293+
ip_filtering:
294+
allowlist:
295+
- "192.168.1.1"
296+
```
297+
298+
The built-in IP filtering provides better performance, more features (CIDR support, blocklists), and consistent error handling across all endpoints.

spec/unit/lib/hooks/app/helpers_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,24 @@ def error!(message, code)
345345
expect(helper.send(:determine_error_code, error)).to eq(500)
346346
end
347347
end
348+
349+
describe "#ip_filtering!" do
350+
let(:headers) { { "X-Forwarded-For" => "192.168.1.1" } }
351+
let(:endpoint_config) { {} }
352+
let(:global_config) { {} }
353+
let(:request_context) { { request_id: "test-request-id" } }
354+
let(:env) { {} }
355+
356+
it "delegates to Network::IpFiltering.ip_filtering!" do
357+
expect(Hooks::App::Network::IpFiltering).to receive(:ip_filtering!).with(
358+
headers,
359+
endpoint_config,
360+
global_config,
361+
request_context,
362+
env
363+
)
364+
365+
helper.ip_filtering!(headers, endpoint_config, global_config, request_context, env)
366+
end
367+
end
348368
end

0 commit comments

Comments
 (0)