/geoblock

geoblock

21
v1.1.2-beta.3

๐Ÿ›ก๏ธ Traefik Geoblock Plugin

This plugin was forked from nscuro/traefik-plugin-geoblock: traefik plugin to whitelist requests based on geolocation and remains compatible with the original plugin.

Build Status Go Report Card Latest GitHub release License

A Traefik plugin that allows or blocks requests based on IP geolocation using IP2Location database.

๐ŸŒ This project includes IP2Location LITE data available from lite.ip2location.com.

[!Tip]

Traefik Security

The basic middlewares you need to secure your Traefik ingress:

๐ŸŒ Geoblock: david-garcia-garcia/traefik-geoblock - Block or allow requests based on IP geolocation
๐Ÿ›ก๏ธ CrowdSec: maxlerebourg/crowdsec-bouncer-traefik-plugin - Real-time threat intelligence and automated blocking
๐Ÿ”’ ModSecurity CRS: david-garcia-garcia/traefik-modsecurity - Web Application Firewall with OWASP Core Rule Set
๐Ÿšฆ Ratelimit: https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/ratelimit/ - Control request rates and prevent abuse

Performance & Scalability

Designed for high-performance production environments:

  • No external API calls - All geolocation lookups are performed using local IP2Location database files, ensuring zero latency from external services
  • Minimal memory footprint - No internal caching mechanisms; leverages the IP2Location library's efficient binary database format for direct lookups
  • Zero network dependencies - Once configured, operates entirely offline with no external service dependencies
  • Hot-swappable database updates - Database updates occur without middleware restart or service interruption

This architecture ensures consistent response times and eliminates external service bottlenecks, making it ideal for high-traffic environments and air-gapped deployments.

โœจ Features

  • Block or allow requests based on country of origin (using ISO 3166-1 alpha-2 country codes)
  • Whitelist specific IP ranges (CIDR notation) - supports both inline configuration and directory-based files
  • Blacklist specific IP ranges (CIDR notation) - supports both inline configuration and directory-based files
  • Optional bypass using custom headers
  • Configurable handling of private/internal networks
  • Customizable error responses
  • Flexible logging options
  • Hot-swap database updates - automatic IP2Location database updates with zero downtime

๐Ÿ“ฅ Installation

โš ๏ธ IMPORTANT REQUIREMENTS:

  • Traefik v3.5.0 or later is required (unsafe operations support was introduced in v3.5.0)
  • Unsafe operations must be enabled in Traefik configuration

It is possible to install the plugin locally or to install it through Traefik Plugins.

Local Plugin Installation

Create or modify your Traefik static configuration

experimental:
localPlugins:
geoblock:
moduleName: github.com/david-garcia-garcia/traefik-geoblock
settins:
useunsafe: true
# REQUIRED: Enable unsafe operations for this plugin
plugins:
geoblock:
settings:
useunsafe: true

You should clone the plugin into the container, i.e

# Create the directory for the plugins
RUN set -eux; \
mkdir -p /plugins-local/src/github.com/david-garcia-garcia
RUN set -eux && git clone https://github.com/david-garcia-garcia/traefik-geoblock /plugins-local/src/github.com/david-garcia-garcia/traefik-geoblock --branch v1.0.1 --single-branch

Traefik Plugin Registry Installation

Add to your Traefik static configuration:

experimental:
plugins:
geoblock:
moduleName: github.com/david-garcia-garcia/traefik-geoblock
version: v1.0.1
# REQUIRED: Enable unsafe operations for this plugin
settings:
useunsafe: true

Network Requirements

For automatic database updates to function, ensure your firewall allows outbound HTTPS connections to:

  • download.ip2location.com
  • www.ip2location.com

Note: If automatic updates are disabled (databaseAutoUpdate: false), no external network access is required and the plugin operates entirely offline.

๐Ÿงช Testing and development

You can spin up a fully working environment with docker compose:

docker compose up --build

The codebase includes a full set of integration and unit tests:

# Run unit tests
go test
# Run integration tests
.\Test-Integration.ps

โš™๏ธ Configuration

Environment Variables

The plugin supports the following environment variable for configuration:

  • TRAEFIK_PLUGIN_GEOBLOCK_PATH: Directory path used as fallback location for database and HTML files when they are not found in the specified paths or when paths are empty/omitted.

Example usage:

# Docker Compose
environment:
- TRAEFIK_PLUGIN_GEOBLOCK_PATH=/data/geoblock
# Docker run
docker run -e TRAEFIK_PLUGIN_GEOBLOCK_PATH=/data/geoblock traefik:latest
# System environment variable
export TRAEFIK_PLUGIN_GEOBLOCK_PATH=/opt/traefik-plugins/geoblock

When this environment variable is set, the plugin will automatically look for IP2LOCATION-LITE-DB1.IPV6.BIN and geoblockban.html files in the specified directory if they are not found in their configured locations.

Example Docker Compose Setup

version: "3.7"
services:
traefik:
image: traefik:v3.5.3 # v3.5.0 or later required
command:
# REQUIRED: Enable unsafe operations for geoblock plugin
- "--experimental.plugins.geoblock.settings.useunsafe=true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.yml:/etc/traefik/traefik.yml
- ./dynamic-config.yml:/etc/traefik/dynamic-config.yml
- ./IP2LOCATION-LITE-DB1.IPV6.BIN:/plugins-storage/IP2LOCATION-LITE-DB1.IPV6.BIN
ports:
- "80:80"
- "443:443"
whoami:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
- "traefik.http.routers.whoami.middlewares=geoblock@file"

Dynamic Configuration

http:
middlewares:
geoblock:
plugin:
geoblock:
#-------------------------------
# Core Settings
#-------------------------------
enabled: true # Enable/disable the plugin entirely
defaultAllow: false # Default behavior when no rules match (false = block)
#-------------------------------
# Database Configuration
#-------------------------------
databaseFilePath: "/plugins-local/src/github.com/david-garcia-garcia/traefik-geoblock/IP2LOCATION-LITE-DB1.IPV6.BIN"
# Can be:
# - Full path: /path/to/IP2LOCATION-LITE-DB1.IPV6.BIN
# - Directory: /path/to/ (will search for IP2LOCATION-LITE-DB1.IPV6.BIN recursively).
# Use /plugins-storage/sources/ if you are installing from plugin repository.
# - Empty: automatically searches using fallback locations (see below)
#
# Fallback search order when file is not found:
# 1. TRAEFIK_PLUGIN_GEOBLOCK_PATH environment variable directory
#-------------------------------
# Country-based Rules (ISO 3166-1 alpha-2 format)
#-------------------------------
allowedCountries: # Whitelist of countries to allow
- "US" # United States
- "CA" # Canada
- "GB" # United Kingdom
blockedCountries: # Blacklist of countries to block
- "RU" # Russia
- "CN" # China
#-------------------------------
# Network Rules
#-------------------------------
allowPrivate: true # Allow requests from private/internal networks (marked as "PRIVATE")
# This includes RFC 1918 private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
# and loopback addresses (127.0.0.0/8 for IPv4, ::1 for IPv6)
allowedIPBlocks: # CIDR ranges to always allow (highest priority)
- "192.168.0.0/16"
- "10.0.0.0/8"
- "2001:db8::/32"
blockedIPBlocks: # CIDR ranges to always block
- "203.0.113.0/24"
# More specific ranges (longer prefix) take precedence
# Directory-based IP blocks (loaded once during plugin initialization)
# This is useful if you mount configmaps in your traefik plugin
# so that these will be shared among all Geoip middleware instances
allowedIPBlocksDir: "/data/allowed-ips/" # Directory with .txt files containing allowed CIDR blocks
blockedIPBlocksDir: "/data/blocked-ips/" # Directory with .txt files containing blocked CIDR blocks
# All .txt files in the directory are scanned recursively during plugin startup
# Each .txt file should contain one CIDR block per line (comments with # supported)
# Note: Changes to files require plugin restart to take effect
# Example file content:
# # AWS IP ranges
# 172.16.0.0/12
# 203.0.113.0/24
#-------------------------------
# IP Extraction Configuration
#-------------------------------
ipHeaders: # List of headers to check for client IP addresses (required, cannot be empty)
- "x-forwarded-for" # Default: check X-Forwarded-For header first
- "x-real-ip" # Default: check X-Real-IP header second
# Custom examples:
# - "cf-connecting-ip" # Cloudflare
# - "x-client-ip" # Custom proxy
# - "remoteAddress" # SYNTHETIC: Maps to req.RemoteAddr (direct connection IP)
#
# IMPORTANT: Header order matters! IPs are processed in the order headers are defined.
# Within each header, IPs are processed left-to-right (leftmost = original client IP).
# Duplicate IPs are automatically removed, preserving the first occurrence.
#
# SYNTHETIC HEADERS:
# - "remoteAddress": Special synthetic header that maps to req.RemoteAddr field
# This provides access to the actual network connection's remote address
# Useful when you need to check the direct connection IP alongside proxy headers
#
# Example configurations:
# ipHeaders: ["x-forwarded-for", "remoteAddress"] # Check proxy header first, then direct connection
# ipHeaders: ["remoteAddress"] # Only check direct connection IP
# ipHeaders: ["remoteAddress", "x-real-ip"] # Check direct connection first, then proxy header
ipHeaderStrategy: "CheckAll" # Strategy for processing multiple IP addresses (default: CheckAll)
# Options:
# - "CheckAll": Check all IPs found in headers (original behavior)
# - "CheckFirst": Check only the first IP address found
# - "CheckFirstNonePrivate": Check first non-private IP, fallback to first private IP if no public IPs found
ignoreVerbs: # List of HTTP verbs to ignore for blocking (still enriched with GeoIP)
- "OPTIONS" # Common for CORS preflight requests
- "HEAD" # Common for health checks
# Additional examples:
# - "TRACE" # HTTP TRACE method
# - "CONNECT" # HTTP CONNECT method
# Note: Verb matching is case-insensitive
#-------------------------------
# Bypass Configuration
#-------------------------------
bypassHeaders: # Headers that skip geoblocking entirely
X-Internal-Request: "true"
X-Skip-Geoblock: "1"
X-Cdn-Auth: "mysupersecretkey"
#-------------------------------
# Error Handling and ban
#-------------------------------
banIfError: true # Block requests if IP lookup fails
disallowedStatusCode: 403 # HTTP status code for blocked requests. If you are using banHtmlFilePath make sure to set this to a valid code (such as NOT 204).
banHtmlFilePath: "/plugins-local/src/github.com/david-garcia-garcia/traefik-geoblock/geoblockban.html"
# Can be:
# - Full path: /path/to/geoblockban.html
# - Directory: /path/to/ (will search for geoblockban.html recursively). Use /plugins-storage/sources/ if you are installing from plugin repository.
# - Empty: returns only status code
#
# Fallback search order when file is not found:
# 1. TRAEFIK_PLUGIN_GEOBLOCK_PATH environment variable directory
# Template variables available: {{.IP}} and {{.Country}}
#-------------------------------
# Logging Configuration
#-------------------------------
logLevel: "info" # Available: debug, info, warn, error
logFormat: "json" # Available: json, text
logPath: "/var/log/geoblock.log" # Empty for Traefik's standard output
logBannedRequests: true # Log blocked requests. They will be logged at info level.
fileLogBufferSizeBytes: 1024 # Buffer size for file logging in bytes (default: 1024)
fileLogBufferTimeoutSeconds: 2 # Buffer timeout for file logging in seconds (default: 2)
# File logging uses buffered writes for better performance. The buffer is flushed when:
# - The buffer reaches fileLogBufferSizeBytes size
# - fileLogBufferTimeoutSeconds seconds have passed since the last flush
# - The logger is closed/shutdown
#-------------------------------
# Database Auto-Update Settings
#-------------------------------
databaseAutoUpdate: true
# Enable automatic database updates with hot-swapping. Updates check every 24 hours
# and immediately on startup if the current database is older than 1 month.
# Updated databases are hot-swapped without requiring middleware restart.
# Make sure you whitelist in your FW domains ["download.ip2location.com", "www.ip2location.com"]
databaseAutoUpdateDir: "/data/ip2database"
# Directory to store updated databases. This must be a persistent volume in the traefik pod.
# The plugin uses a singleton pattern - multiple middlewares with identical configurations
# share the same database factory and hot-swap operations.
databaseAutoUpdateToken: "" # IP2Location download token (if using premium)
databaseAutoUpdateCode: "DB1" # Database product code to download (if using premium)
#-------------------------------
# Response header settings
#-------------------------------
countryHeader: "X-IPCountry"
# Optional header to add the country code to the REQUEST (available in Traefik access logs)
# This header is added to the request that gets forwarded to your backend service
# You can use this to see where all your traffic is coming from in access logs
# Example access log config: accesslog.fields.headers.names.X-IPCountry=keep
# Note: Header is initially set to "PRIVATE" and only overridden by the first real country found
# This ensures private IPs processed later cannot override legitimate country information
remediationHeadersCustomName: "X-Geoblock-Action"
# Optional header to add the blocking phase/reason to the RESPONSE when request is blocked
# This header is added to the HTTP response sent back to the client (available in Traefik access logs)
# Possible values: "allow_private", "blocked_ip_block", "allowed_ip_block",
# "blocked_country", "allowed_country", "default_allow", "error"
# Example access log config: accesslog.fields.headers.names.X-Geoblock-Action=keep
# When empty, no header is added to blocked responses

๐Ÿ”„ Processing Order

The plugin processes requests in the following order:

  1. Check if plugin is enabled
  2. Check bypass headers
  3. Check if HTTP verb is in ignoreVerbs list (skip blocking but continue enrichment)
  4. Extract IP addresses from configured IP headers (ipHeaders) in the order they are defined
  5. Apply IP header strategy (ipHeaderStrategy) to determine which IPs to process:
    • CheckAll: Process all found IP addresses (original behavior)
    • CheckFirst: Process only the first IP address found
    • CheckFirstNonePrivate: Process first non-private IP, fallback to first private IP if no public IPs found
  6. For each selected IP:
    • Check if it's in private network range [allowPrivate]
    • Check allowed/blocked IP blocks [allowedIPBlocks + allowedIPBlocksDir, blockedIPBlocks + blockedIPBlocksDir] (most specific match wins)
    • Look up country code
    • Check allowed/blocked countries [allowedCountries, blockedCountries]
    • Apply default allow/deny if no rules match [defaultAllow]

Important Notes:

  • With CheckAll strategy: If any IP in the chain is blocked, the request is denied
  • With CheckFirst or CheckFirstNonePrivate strategies: Only the selected IP(s) are evaluated; the request is denied only if the selected IP is blocked
  • Country header behavior: Header is initially set to "PRIVATE" and only overridden by the first real country found, preventing private IPs from overriding legitimate geolocation information
  • Ignored HTTP verbs: Requests using verbs in ignoreVerbs skip all blocking logic but still receive GeoIP enrichment

๐Ÿ“ Log Format

When using JSON logging, the following fields are included in blocked request log entries (note: allowed requests are not logged):

  • time: Timestamp of the request in ISO 8601 format
  • level: Log level (debug, info, warn, error)
  • msg: Log message describing the action
  • plugin: Plugin identifier
  • ip: The IP address that triggered the action
  • ip_chain: Full chain of IP addresses from X-Forwarded-For header
  • country: Country code or "PRIVATE" for internal networks
  • host: Request host header
  • method: HTTP method used
  • phase: Processing phase where the action occurred:
    • allow_private: Private network check
    • blocked_ip_block: IP block rules check (blocked)
    • allowed_ip_block: IP block rules check (allowed)
    • blocked_country: Country rules check (blocked)
    • allowed_country: Country rules check (allowed)
    • default_allow: Default allow/deny rule
  • path: Request path

Example log entry:

{
"time": "2025-03-01T19:24:04.414051815Z",
"level": "INFO",
"msg": "blocked request",
"plugin": "geoblock@docker",
"ip": "172.18.0.1",
"ip_chain": "",
"country": "PRIVATE",
"host": "localhost:8000",
"method": "GET",
"phase": "allow_private",
"path": "/bar"
}

๐Ÿ“„ This project is licensed under the Apache License 2.0 - see the LICENSE file for details.