Add country-based IP blocking for Caddy via Ansible
- Introduce generate_country_blocks.py to fetch IP ranges by country - Update group_vars/servers.yml with country blocking settings - Add country_block snippet to Caddyfile and apply to all sites - Create Ansible tasks for automated IP range generation and integration - Add documentation for configuring and managing country blocking
This commit is contained in:
162
config/ansible/files/generate_country_blocks.py
Normal file
162
config/ansible/files/generate_country_blocks.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate country IP ranges for Caddy blocking configuration.
|
||||||
|
Downloads the latest country IP ranges and formats them for use in Caddyfile.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from typing import List, Dict
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
|
||||||
|
def download_country_ranges(countries: List[str]) -> Dict[str, List[str]]:
|
||||||
|
"""
|
||||||
|
Download IP ranges for specified countries from ip2location.com free database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
countries: List of ISO 3166-1 alpha-2 country codes (e.g., ['CN', 'RU'])
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping country codes to lists of IP ranges
|
||||||
|
"""
|
||||||
|
country_ranges = {}
|
||||||
|
|
||||||
|
for country in countries:
|
||||||
|
print(f"Downloading IP ranges for {country}...", file=sys.stderr)
|
||||||
|
|
||||||
|
# Use ipinfo.io's free API for country IP ranges
|
||||||
|
try:
|
||||||
|
url = f"https://ipinfo.io/countries/{country.lower()}"
|
||||||
|
headers = {'User-Agent': 'Mozilla/5.0 (compatible; country-blocker/1.0)'}
|
||||||
|
response = requests.get(url, headers=headers, timeout=30)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
# The response contains IP ranges in CIDR format, one per line
|
||||||
|
ranges = []
|
||||||
|
for line in response.text.strip().split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#'):
|
||||||
|
try:
|
||||||
|
# Validate the IP range
|
||||||
|
ipaddress.ip_network(line, strict=False)
|
||||||
|
ranges.append(line)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
country_ranges[country] = ranges
|
||||||
|
print(f"Found {len(ranges)} IP ranges for {country}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f"Failed to download ranges for {country}: HTTP {response.status_code}", file=sys.stderr)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error downloading ranges for {country}: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
return country_ranges
|
||||||
|
|
||||||
|
|
||||||
|
def get_alternative_ranges(countries: List[str]) -> Dict[str, List[str]]:
|
||||||
|
"""
|
||||||
|
Alternative method using a different data source.
|
||||||
|
Falls back to manually curated ranges for common blocking scenarios.
|
||||||
|
"""
|
||||||
|
# Common IP ranges for frequently blocked countries
|
||||||
|
# These are examples - you should update with current ranges
|
||||||
|
manual_ranges = {
|
||||||
|
'CN': [
|
||||||
|
'1.0.1.0/24', '1.0.2.0/23', '1.0.8.0/21', '1.0.32.0/19',
|
||||||
|
'14.0.12.0/22', '14.0.36.0/22', '14.1.0.0/18', '14.16.0.0/12',
|
||||||
|
'27.0.0.0/11', '27.50.40.0/21', '27.54.192.0/18', '27.98.208.0/20',
|
||||||
|
# Add more ranges as needed
|
||||||
|
],
|
||||||
|
'RU': [
|
||||||
|
'2.56.96.0/19', '2.58.56.0/21', '5.8.0.0/19', '5.34.96.0/19',
|
||||||
|
'5.42.192.0/19', '5.44.64.0/18', '5.53.96.0/19', '5.61.24.0/21',
|
||||||
|
# Add more ranges as needed
|
||||||
|
],
|
||||||
|
'KP': [
|
||||||
|
'175.45.176.0/22', '210.52.109.0/24', '77.94.35.0/24'
|
||||||
|
],
|
||||||
|
'IR': [
|
||||||
|
'2.176.0.0/12', '5.22.0.0/16', '5.23.0.0/16', '5.56.128.0/17',
|
||||||
|
# Add more ranges as needed
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for country in countries:
|
||||||
|
if country in manual_ranges:
|
||||||
|
result[country] = manual_ranges[country]
|
||||||
|
print(f"Using manual ranges for {country}: {len(manual_ranges[country])} ranges", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f"No manual ranges available for {country}", file=sys.stderr)
|
||||||
|
result[country] = []
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def format_for_caddy(country_ranges: Dict[str, List[str]]) -> List[str]:
|
||||||
|
"""
|
||||||
|
Format IP ranges for use in Caddyfile remote_ip matcher.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
country_ranges: Dictionary of country codes to IP ranges
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of IP ranges formatted for Caddy
|
||||||
|
"""
|
||||||
|
all_ranges = []
|
||||||
|
for country, ranges in country_ranges.items():
|
||||||
|
all_ranges.extend(ranges)
|
||||||
|
|
||||||
|
return all_ranges
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Generate country IP ranges for blocking')
|
||||||
|
parser.add_argument('countries', nargs='+',
|
||||||
|
help='Country codes to block (e.g., CN RU KP IR)')
|
||||||
|
parser.add_argument('--format', choices=['caddy', 'json', 'list'],
|
||||||
|
default='caddy', help='Output format')
|
||||||
|
parser.add_argument('--fallback', action='store_true',
|
||||||
|
help='Use manual fallback ranges instead of downloading')
|
||||||
|
parser.add_argument('--output', '-o', help='Output file (default: stdout)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Normalize country codes to uppercase
|
||||||
|
countries = [c.upper() for c in args.countries]
|
||||||
|
|
||||||
|
# Download or use fallback ranges
|
||||||
|
if args.fallback:
|
||||||
|
country_ranges = get_alternative_ranges(countries)
|
||||||
|
else:
|
||||||
|
country_ranges = download_country_ranges(countries)
|
||||||
|
# If download failed, try fallback
|
||||||
|
if not any(country_ranges.values()):
|
||||||
|
print("Download failed, using fallback ranges...", file=sys.stderr)
|
||||||
|
country_ranges = get_alternative_ranges(countries)
|
||||||
|
|
||||||
|
# Format output
|
||||||
|
if args.format == 'caddy':
|
||||||
|
ranges = format_for_caddy(country_ranges)
|
||||||
|
output = ' '.join(ranges)
|
||||||
|
elif args.format == 'json':
|
||||||
|
output = json.dumps(country_ranges, indent=2)
|
||||||
|
else: # list format
|
||||||
|
ranges = format_for_caddy(country_ranges)
|
||||||
|
output = '\n'.join(ranges)
|
||||||
|
|
||||||
|
# Write output
|
||||||
|
if args.output:
|
||||||
|
with open(args.output, 'w') as f:
|
||||||
|
f.write(output)
|
||||||
|
print(f"Output written to {args.output}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@@ -1,3 +1,19 @@
|
|||||||
---
|
---
|
||||||
flatpaks: false
|
flatpaks: false
|
||||||
install_ui_apps: false
|
install_ui_apps: false
|
||||||
|
|
||||||
|
# Country blocking configuration for Caddy
|
||||||
|
# List of countries to block by ISO 3166-1 alpha-2 country codes
|
||||||
|
# Common examples: CN (China), RU (Russia), KP (North Korea), IR (Iran), BY (Belarus)
|
||||||
|
blocked_countries_codes:
|
||||||
|
- CN # China
|
||||||
|
- RU # Russia
|
||||||
|
- KP # North Korea
|
||||||
|
- IR # Iran
|
||||||
|
|
||||||
|
# IP ranges for blocked countries (generated automatically)
|
||||||
|
# This will be populated by the country blocking script
|
||||||
|
blocked_countries: []
|
||||||
|
|
||||||
|
# Enable/disable country blocking globally
|
||||||
|
enable_country_blocking: true
|
||||||
|
@@ -37,11 +37,11 @@
|
|||||||
enabled: true
|
enabled: true
|
||||||
- name: beszel
|
- name: beszel
|
||||||
enabled: true
|
enabled: true
|
||||||
- name: arr-stack
|
|
||||||
enabled: false
|
|
||||||
- name: downloaders
|
- name: downloaders
|
||||||
enabled: true
|
enabled: true
|
||||||
- name: wireguard
|
- name: wireguard
|
||||||
enabled: true
|
enabled: true
|
||||||
- name: echoip
|
- name: echoip
|
||||||
enabled: true
|
enabled: true
|
||||||
|
- name: arr-stack
|
||||||
|
enabled: false
|
||||||
|
@@ -1,43 +1,74 @@
|
|||||||
|
# Global configuration for country blocking
|
||||||
|
{
|
||||||
|
# Block specific countries (add ISO country codes as needed)
|
||||||
|
# Examples: CN (China), RU (Russia), KP (North Korea), IR (Iran)
|
||||||
|
servers {
|
||||||
|
protocols h1 h2 h3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Country blocking snippet - reusable across all sites
|
||||||
|
{% if enable_country_blocking | default(false) and blocked_countries | default([]) | length > 0 %}
|
||||||
|
(country_block) {
|
||||||
|
@blocked_countries {
|
||||||
|
remote_ip {{ blocked_countries | join(' ') }}
|
||||||
|
}
|
||||||
|
respond @blocked_countries "Access denied from your country" 403
|
||||||
|
}
|
||||||
|
{% else %}
|
||||||
|
(country_block) {
|
||||||
|
# Country blocking disabled
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
photos.mvl.sh {
|
photos.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy immich:2283
|
reverse_proxy immich:2283
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
photos.vleeuwen.me {
|
photos.vleeuwen.me {
|
||||||
|
import country_block
|
||||||
redir https://photos.mvl.sh{uri}
|
redir https://photos.mvl.sh{uri}
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
karakeep.mvl.sh {
|
karakeep.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy karakeep:3000
|
reverse_proxy karakeep:3000
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
hoarder.mvl.sh {
|
hoarder.mvl.sh {
|
||||||
|
import country_block
|
||||||
redir https://karakeep.mvl.sh{uri}
|
redir https://karakeep.mvl.sh{uri}
|
||||||
}
|
}
|
||||||
|
|
||||||
git.vleeuwen.me git.mvl.sh {
|
git.vleeuwen.me git.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy gitea:3000
|
reverse_proxy gitea:3000
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
status.vleeuwen.me status.mvl.sh {
|
status.vleeuwen.me status.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy uptime-kuma:3001
|
reverse_proxy uptime-kuma:3001
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
sf.mvl.sh {
|
sf.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy seafile:80
|
reverse_proxy seafile:80
|
||||||
|
|
||||||
handle /seafdav* {
|
handle /seafdav* {
|
||||||
reverse_proxy seafile:8080
|
reverse_proxy seafile:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
of.mvl.sh {
|
of.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy onlyoffice:80 {
|
reverse_proxy onlyoffice:80 {
|
||||||
header_up Host {host}
|
header_up Host {host}
|
||||||
header_up X-Real-IP {remote}
|
header_up X-Real-IP {remote}
|
||||||
@@ -48,31 +79,37 @@ of.mvl.sh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fsm.mvl.sh {
|
fsm.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy factorio-server-manager:80
|
reverse_proxy factorio-server-manager:80
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
df.mvl.sh {
|
df.mvl.sh {
|
||||||
|
import country_block
|
||||||
redir / https://git.mvl.sh/vleeuwenmenno/dotfiles/raw/branch/master/setup.sh
|
redir / https://git.mvl.sh/vleeuwenmenno/dotfiles/raw/branch/master/setup.sh
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
overseerr.mvl.sh jellyseerr.mvl.sh overseerr.vleeuwen.me jellyseerr.vleeuwen.me {
|
overseerr.mvl.sh jellyseerr.mvl.sh overseerr.vleeuwen.me jellyseerr.vleeuwen.me {
|
||||||
|
import country_block
|
||||||
reverse_proxy mennos-server:5555
|
reverse_proxy mennos-server:5555
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
jellyfin.mvl.sh jellyfin.vleeuwen.me {
|
jellyfin.mvl.sh jellyfin.vleeuwen.me {
|
||||||
|
import country_block
|
||||||
reverse_proxy jellyfin:8096
|
reverse_proxy jellyfin:8096
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
fladder.mvl.sh {
|
fladder.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy fladder:80
|
reverse_proxy fladder:80
|
||||||
tls {{ caddy_email }}
|
tls {{ caddy_email }}
|
||||||
}
|
}
|
||||||
|
|
||||||
ip.mvl.sh {
|
ip.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy echoip:8080 {
|
reverse_proxy echoip:8080 {
|
||||||
header_up X-Real-IP {http.request.remote.host}
|
header_up X-Real-IP {http.request.remote.host}
|
||||||
header_up X-Forwarded-For {http.request.remote.host}
|
header_up X-Forwarded-For {http.request.remote.host}
|
||||||
@@ -84,6 +121,7 @@ ip.mvl.sh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
http://ip.mvl.sh {
|
http://ip.mvl.sh {
|
||||||
|
import country_block
|
||||||
reverse_proxy echoip:8080 {
|
reverse_proxy echoip:8080 {
|
||||||
header_up X-Real-IP {http.request.remote.host}
|
header_up X-Real-IP {http.request.remote.host}
|
||||||
header_up X-Forwarded-For {http.request.remote.host}
|
header_up X-Forwarded-For {http.request.remote.host}
|
||||||
|
@@ -0,0 +1,214 @@
|
|||||||
|
# Caddy Country Blocking Configuration
|
||||||
|
|
||||||
|
This directory contains configuration for implementing country-based IP blocking in Caddy reverse proxy.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The country blocking feature allows you to block traffic from specific countries by their IP address ranges. This is useful for:
|
||||||
|
|
||||||
|
- Reducing spam and malicious traffic
|
||||||
|
- Complying with regional restrictions
|
||||||
|
- Improving security posture
|
||||||
|
- Reducing server load from unwanted traffic
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **IP Range Generation**: The `generate_country_blocks.py` script downloads current IP ranges for specified countries
|
||||||
|
2. **Caddy Configuration**: The Caddyfile template includes a reusable snippet that blocks IPs from those ranges
|
||||||
|
3. **Automatic Updates**: Ansible task generates fresh IP ranges on each deployment
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Enable/Disable Country Blocking
|
||||||
|
|
||||||
|
In `group_vars/servers.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Enable or disable country blocking
|
||||||
|
enable_country_blocking: true # Set to false to disable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specify Countries to Block
|
||||||
|
|
||||||
|
Add country codes (ISO 3166-1 alpha-2) to the `blocked_countries_codes` list:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
blocked_countries_codes:
|
||||||
|
- CN # China
|
||||||
|
- RU # Russia
|
||||||
|
- KP # North Korea
|
||||||
|
- IR # Iran
|
||||||
|
- BY # Belarus
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Country Codes
|
||||||
|
|
||||||
|
| Country | Code | | Country | Code |
|
||||||
|
|---------|------|-|---------|------|
|
||||||
|
| China | CN | | Russia | RU |
|
||||||
|
| North Korea | KP | | Iran | IR |
|
||||||
|
| Belarus | BY | | Syria | SY |
|
||||||
|
| Myanmar | MM | | Afghanistan | AF |
|
||||||
|
| Cuba | CU | | Venezuela | VE |
|
||||||
|
|
||||||
|
## Files Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
caddy/
|
||||||
|
├── Caddyfile.j2 # Main Caddy configuration with country blocking
|
||||||
|
├── caddy.yml # Ansible task for Caddy deployment
|
||||||
|
├── country-blocking.yml # Ansible task for country blocking setup
|
||||||
|
├── docker-compose.yml.j2 # Docker Compose configuration
|
||||||
|
├── generate_country_blocks.py # Script to generate IP ranges
|
||||||
|
└── README-country-blocking.md # This documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Usage
|
||||||
|
|
||||||
|
### Generate IP Ranges Manually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate ranges for specific countries
|
||||||
|
python3 generate_country_blocks.py CN RU --format=list
|
||||||
|
|
||||||
|
# Output to file
|
||||||
|
python3 generate_country_blocks.py CN RU KP --output=blocked_ranges.txt
|
||||||
|
|
||||||
|
# Use fallback/manual ranges if download fails
|
||||||
|
python3 generate_country_blocks.py CN RU --fallback
|
||||||
|
|
||||||
|
# JSON format for inspection
|
||||||
|
python3 generate_country_blocks.py CN RU --format=json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Country Blocking
|
||||||
|
|
||||||
|
After deployment, test blocking by checking access from different locations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test from a VPN/proxy in a blocked country
|
||||||
|
curl -I https://your-domain.com
|
||||||
|
|
||||||
|
# Should return HTTP 403 with "Access denied from your country"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
The country blocking is automatically configured when you run the Caddy Ansible task:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory.ini playbook.yml --tags caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
Or specifically for country blocking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory.ini playbook.yml --tags country-blocking
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### No IP Ranges Generated
|
||||||
|
|
||||||
|
If the script fails to download ranges:
|
||||||
|
|
||||||
|
1. Check internet connectivity on the Ansible control machine
|
||||||
|
2. Verify the country codes are valid (2-letter ISO codes)
|
||||||
|
3. Use fallback ranges: set `--fallback` flag in the script
|
||||||
|
4. Check the script output for error messages
|
||||||
|
|
||||||
|
### Legitimate Traffic Blocked
|
||||||
|
|
||||||
|
If legitimate users are blocked:
|
||||||
|
|
||||||
|
1. Verify their country isn't in your blocked list
|
||||||
|
2. Check if they're using a VPN/proxy from a blocked country
|
||||||
|
3. Consider whitelisting specific IP ranges for known users
|
||||||
|
4. Review your country blocking list for overly broad restrictions
|
||||||
|
|
||||||
|
### Service Not Starting
|
||||||
|
|
||||||
|
If Caddy fails to start after enabling country blocking:
|
||||||
|
|
||||||
|
1. Check the generated Caddyfile syntax: `caddy validate --config /path/to/Caddyfile`
|
||||||
|
2. Verify the IP ranges are valid CIDR notation
|
||||||
|
3. Check Docker logs: `docker compose logs caddy`
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
- **Reduced Attack Surface**: Blocks traffic from high-risk regions
|
||||||
|
- **Lower Server Load**: Reduces processing of malicious requests
|
||||||
|
- **Compliance**: Helps meet regional access restrictions
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
- **VPN Bypass**: Users can circumvent blocking using VPNs
|
||||||
|
- **Legitimate Users**: May block legitimate traffic from blocked countries
|
||||||
|
- **Maintenance**: IP ranges change over time and need updates
|
||||||
|
- **False Positives**: Geolocation isn't 100% accurate
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
1. **Whitelist Critical Services**: Don't block access to monitoring/health endpoints
|
||||||
|
2. **Regular Updates**: Update IP ranges regularly (automated with Ansible)
|
||||||
|
3. **Monitor Logs**: Watch for legitimate users being blocked
|
||||||
|
4. **Gradual Implementation**: Start with high-risk countries, expand cautiously
|
||||||
|
5. **Document Decisions**: Keep records of why specific countries are blocked
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Custom Block Message
|
||||||
|
|
||||||
|
Modify the response in `Caddyfile.j2`:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
respond @blocked_countries "Custom block message" 403
|
||||||
|
```
|
||||||
|
|
||||||
|
### Whitelist Specific IPs
|
||||||
|
|
||||||
|
Add to the country_block snippet:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
(country_block) {
|
||||||
|
@blocked_countries {
|
||||||
|
remote_ip {{ blocked_countries | join(' ') }}
|
||||||
|
not remote_ip 1.2.3.4 5.6.7.8/24 # Whitelist specific IPs
|
||||||
|
}
|
||||||
|
respond @blocked_countries "Access denied from your country" 403
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Blocked Requests
|
||||||
|
|
||||||
|
Add logging to track blocked requests:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
(country_block) {
|
||||||
|
@blocked_countries {
|
||||||
|
remote_ip {{ blocked_countries | join(' ') }}
|
||||||
|
}
|
||||||
|
log @blocked_countries {
|
||||||
|
output file /var/log/caddy/blocked_countries.log
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
respond @blocked_countries "Access denied from your country" 403
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Sources
|
||||||
|
|
||||||
|
The script uses multiple data sources for IP ranges:
|
||||||
|
|
||||||
|
1. **Primary**: ipinfo.io free API
|
||||||
|
2. **Fallback**: Manually curated ranges for common countries
|
||||||
|
3. **Alternative**: Can be extended to use other sources like MaxMind, IP2Location
|
||||||
|
|
||||||
|
## License and Legal
|
||||||
|
|
||||||
|
- Ensure compliance with local laws regarding geographic blocking
|
||||||
|
- Some regions have regulations about access restrictions
|
||||||
|
- Consider accessibility requirements for your services
|
||||||
|
- Review terms of service for IP geolocation data providers
|
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy Caddy service
|
- name: Deploy Caddy service
|
||||||
block:
|
block:
|
||||||
|
- name: Setup country blocking
|
||||||
|
ansible.builtin.include_tasks: country-blocking.yml
|
||||||
- name: Set Caddy directories
|
- name: Set Caddy directories
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
caddy_service_dir: "{{ ansible_env.HOME }}/services/caddy"
|
caddy_service_dir: "{{ ansible_env.HOME }}/services/caddy"
|
||||||
|
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
- name: Country blocking setup for Caddy
|
||||||
|
block:
|
||||||
|
- name: Ensure Python requests module is installed
|
||||||
|
ansible.builtin.pip:
|
||||||
|
name: requests
|
||||||
|
state: present
|
||||||
|
when: enable_country_blocking | default(false)
|
||||||
|
|
||||||
|
- name: Copy country blocking script
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: generate_country_blocks.py
|
||||||
|
dest: "{{ caddy_service_dir }}/generate_country_blocks.py"
|
||||||
|
mode: '0755'
|
||||||
|
when: enable_country_blocking | default(false)
|
||||||
|
|
||||||
|
- name: Generate country IP ranges
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "python3 {{ caddy_service_dir }}/generate_country_blocks.py {{ blocked_countries_codes | join(' ') }} --format=list"
|
||||||
|
register: country_ranges_result
|
||||||
|
when:
|
||||||
|
- enable_country_blocking | default(false)
|
||||||
|
- blocked_countries_codes | default([]) | length > 0
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Set country IP ranges fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
blocked_countries: "{{ country_ranges_result.stdout.split('\n') | select('match', '^[0-9]') | list }}"
|
||||||
|
when:
|
||||||
|
- enable_country_blocking | default(false)
|
||||||
|
- country_ranges_result is defined
|
||||||
|
- country_ranges_result.stdout is defined
|
||||||
|
|
||||||
|
- name: Display blocked countries info
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Country blocking enabled: {{ enable_country_blocking | default(false) }}"
|
||||||
|
- "Countries to block: {{ blocked_countries_codes | default([]) | join(', ') }}"
|
||||||
|
- "IP ranges found: {{ blocked_countries | default([]) | length }}"
|
||||||
|
when: enable_country_blocking | default(false)
|
||||||
|
|
||||||
|
- name: Fallback to empty list if no ranges generated
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
blocked_countries: []
|
||||||
|
when:
|
||||||
|
- enable_country_blocking | default(false)
|
||||||
|
- blocked_countries is not defined
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- caddy
|
||||||
|
- security
|
||||||
|
- country-blocking
|
Reference in New Issue
Block a user