/State Geo Block Middleware

State Geo Block Middleware

1
v1.2.0-rc.1

State Geo Block Middleware

State Geo Block is a Traefik HTTP middleware that blocks non-US traffic and selected US subdivisions using a mounted compatible City-schema MMDB. It supports MaxMind GeoLite2-City and GeoIP2-City directly, as well as compact stategeodb compliance artifacts. It also supports trusted Cloudflare and proxy headers, direct connections, IPv4 and IPv6, explicit failure policies, database hot reload, and custom block pages.

CI CodeQL License

What it does

  • resolves clients from trusted CF-Connecting-IP, True-Client-IP, X-Forwarded-For, RFC Forwarded, X-Real-IP, custom headers, or the direct socket peer;
  • parses IPv4, IPv6, mapped IPv4, bracketed IPv6, and forwarding chains;
  • blocks every non-US country when blockNonUS is enabled;
  • blocks configured two-letter US subdivision codes when blockUSStates is enabled;
  • supports exact IPs, CIDRs, exact path bypasses, and segment-safe path-prefix bypasses;
  • shares one in-memory MMDB reader per database path and reloads atomic file replacements without restarting Traefik;
  • provides bounded TTL/LRU decisions, escaped HTML templates, and structured privacy-conscious logs;
  • gives database, lookup, invalid-IP, unknown-country, unknown-subdivision, and private-IP cases independent policies.

Trust boundary

Forwarding headers are ignored unless the immediate RemoteAddr peer matches trustedProxyCIDRs. This prevents an ordinary direct client from selecting its own geography with a forged header.

Traefik also has a separate entry-point setting, forwardedHeaders.trustedIPs, which controls whether it preserves incoming X-Forwarded-* values. Configure both layers. With Kubernetes externalTrafficPolicy: Cluster, the plugin usually sees a node-SNAT address, so the node CIDR must be trusted and upstream header sanitization remains part of the security boundary.

The safe default config trusts no proxy, enables only X-Forwarded-For, rejects malformed trusted headers, and denies unresolved or non-public client addresses. Provider-specific headers are opt-in. See Client IP resolution for the full algorithm and the Traefik-specific X-Real-IP behavior.

Install a versioned plugin

State Geo Block is listed in the Traefik Plugin Catalog. Configure a released tag in Traefik's static configuration; do not use a branch name in production.

experimental:
plugins:
stateGeoBlock:
moduleName: github.com/vikewoods/traefik-plugin-state-geo
version: v1.2.0-rc.1

The alias stateGeoBlock is operator-selected but must be used consistently in the dynamic Middleware configuration.

Kubernetes quick start

Mount a current City MMDB into every Traefik pod, configure Traefik's entry-point trusted IPs, and then create the Middleware:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: state-geo-block
namespace: traefik-system
spec:
plugin:
stateGeoBlock:
dbPath: /data/geolite/GeoLite2-City.mmdb
databaseReloadInterval: 1m
cacheSize: 50000
cacheTTL: 15m
trustedProxyCIDRs:
- 10.17.1.0/24
clientIPHeaders:
- X-Forwarded-For
rejectInvalidClientIPHeaders: true
blockNonUS: true
blockUSStates: true
blockedStates: [CA, NY]
databaseFailurePolicy: deny
lookupFailurePolicy: deny
invalidClientIPPolicy: deny
unknownCountryPolicy: deny
unknownSubdivisionPolicy: deny
privateIPPolicy: deny
logLevel: info
logClientIP: false
whitelistedPaths:
- /health
whitelistedPathPrefixes:
- /.well-known

10.17.1.0/24, /data/geolite/GeoLite2-City.mmdb, and the namespace above are specific to the audited cluster and must be changed for other installations. The complete Helm/PVC, Middleware, IngressRoute, standard Ingress, update, and canary instructions are in Kubernetes deployment.

Configuration

FieldDefaultDescription
dbPathemptyPath inside the Traefik container to a compatible City-schema MMDB.
databaseReloadInterval1mMinimum interval between request-driven file replacement checks; minimum 1s.
cacheSize1000Per-Middleware exact-IP LRU entry bound; 0 disables, maximum 100000. High-cardinality ingress can start near 50000 and tune from memory/hit-rate evidence.
cacheTTL15mPositive decision TTL when caching is enabled.
clientIPHeadersXFFOrdered trusted client-IP sources. Cloudflare, True Client IP, Forwarded, X-Real-IP, and custom headers are opt-in.
trustedProxyCIDRsemptyImmediate peers permitted to supply client-IP headers.
rejectInvalidClientIPHeaderstrueSend a present-but-invalid trusted header to invalidClientIPPolicy; false warns and tries the next source.
blockNonUStrueDeny known non-US countries.
blockUSStatestrueApply blockedStates and unknown-subdivision policy to US records.
blockedStatesemptyTwo-letter US subdivision codes to deny, normalized to uppercase.
whitelistedIPsemptyExact IPv4/IPv6 addresses and CIDRs that bypass geography decisions.
whitelistedPathsemptyNormalized exact request paths that bypass all other decisions.
whitelistedPathPrefixesemptyNormalized, segment-safe path prefixes that bypass all other decisions.
templateHTMLbuilt-inInline escaped HTML template; supports {{STATE}} or {{.State}}.
templatePathemptyMounted HTML template path; mutually exclusive with templateHTML.
databaseFailurePolicylegacyallow, deny, error, or deprecated compatibility mode legacy.
lookupFailurePolicyallowallow or deny when an MMDB lookup returns an error.
invalidClientIPPolicydenyallow or deny when RemoteAddr or a strict trusted header is unusable.
unknownCountryPolicyallowallow or deny when lookup returns no country.
unknownSubdivisionPolicydenyallow or deny for a US record without a subdivision.
privateIPPolicydenyallow, lookup, or deny for private, loopback, link-local, or unspecified clients.
logLevelinfooff, error, warn, info, or debug; info records deny decisions without client IPs by default.
logClientIPfalseInclude resolved client IPs in structured logs.
failOpentrueDeprecated bridge used only by databaseFailurePolicy: legacy.

Invalid state, IP/CIDR, path, duration, policy, template, and header configuration fails Middleware construction instead of being silently skipped. See Failure policies, Database lifecycle, and Middleware hardening for exact behavior.

Database requirements

The repository does not ship a production database. Operators may mount a MaxMind GeoLite2-City or GeoIP2-City database, or a compact stategeodb compliance artifact. The compact format supplies country data globally and the first subdivision only for US records, matching this middleware's country-first, US-state-only policy inputs; all allow, deny, unknown-data, and failure behavior still comes entirely from plugin configuration.

Mount the selected artifact read-only into Traefik and replace it atomically. The plugin detects a size or modification-time change, validates the replacement, retains the last known-good reader if reload fails, and invalidates cached decisions after a successful swap. See MMDB compatibility and Database lifecycle for the exact schema, validation evidence, cache generation behavior, and memory model. GeoLite2 users remain responsible for the MaxMind GeoLite terms.

Upgrade and development

Published v1.1 configurations require migration because trusted headers, path prefixes, invalid rules, failure policies, and bundled data behavior changed. The release candidate also hardens defaults from the withdrawn v2 prereleases. Read Migration from v1 before upgrading.

go test ./...
go test -race ./...
go vet ./...
golangci-lint run ./...
govulncheck ./...
gosec -quiet ./...
./scripts/traefik-smoke-test.sh

The smoke test loads the source through Traefik v3.7.1 and v3.7.6 interpreted local-plugin runtimes and verifies real IPv4/IPv6 decisions. See Contributing and the release checklist.

License

State Geo Block is licensed under the Apache License 2.0. Third-party fixture and dependency licenses are documented in THIRD_PARTY_NOTICES.md.