Caddy MaxMind GeoIP 地理访问限制:白名单与黑名单模式

为什么需要 GeoIP 访问限制

如果服务主要面向特定地区的用户,其他地区来的流量里常会混进恶意请求。比如:

  • 你的博客主要面向中文读者,欧洲 IP 的访问可能来自扫描器
  • 管理后台只需要从你所在的国家访问;API 服务也可能要限制特定区域的滥用

GeoIP 限制不能当成唯一的防护手段,VPN 可以绕过 IP 地理位置判断。它更适合拿来减少恶意流量和日志噪音,也能顺带减轻一点服务器压力。

caddy-maxmind-geolocation 插件

我用的自定义构建里已经带上这个插件(参考 xcaddy 构建文章)。它基于 MaxMind 的 GeoLite2 数据库做 IP 地理位置查询。

数据库中每条记录包含:

  • IP 段起止范围,以及所属国家代码(ISO 3166-1 alpha-2,如 CNUSJP
  • 可选的地理区域和城市信息(City 版本,本文用的是 Country 版本)

两种限制模式

我写了两个 snippet,对应不同场景:

白名单模式:只允许指定国家

(geo_only_allow_countries) {
    @only_allow_countries_geofilter {
        not {
            maxmind_geolocation {
                db_path "/config/geodb/GeoLite2-Country.mmdb"
                allow_countries {args[:]}
            }
        }
    }

    respond @only_allow_countries_geofilter "You are blocked" 403
}

这里先定义名为 @only_allow_countries_geofilter 的命名匹配器,专门匹配不在指定国家列表里的请求,再对这些请求返回 403。

使用方式:

example.com {
    import geo_only_allow_countries CN JP
    reverse_proxy 127.0.0.1:3004
}

这样就只放行中国和日本的 IP。

黑名单模式:禁止指定国家

(geo_not_allow_countries) {
    @not_allow_countries_geofilter {
        maxmind_geolocation {
            db_path "/config/geodb/GeoLite2-Country.mmdb"
            allow_countries {args[:]}
        }
    }

    respond @not_allow_countries_geofilter "You are blocked" 403
}

这里直接匹配指定国家列表里的请求,然后返回 403。

使用方式:

example.com {
    import geo_not_allow_countries RU
    reverse_proxy 127.0.0.1:8080
}

这样会拦掉来自俄罗斯的 IP。

GeoLite2 数据库自动更新

MaxMind 的 GeoLite2 数据库会跟着 IP 分配变化更新,大概每月 1-2 次。数据库太旧时,部分 IP 的地理位置判断会出错。

我的容器里通过 update_geodb.sh 自动管理,xcaddy 文章里已经写过。

局限性

  1. VPN/代理绕过:用户可以通过 VPN 切换出口 IP,GeoIP 挡不住这个
  2. 数据库时效性和 CDN 回源:IP 段会重新分配,老数据库可能导致误判;如果请求经过 Cloudflare,Caddy 收到的 IP 是 Cloudflare 的回源 IP,需要正确配置 trusted_proxies 才能获取真实客户端 IP

GeoIP 更适合拿来收窄攻击面,不适合单独扛安全防护。

实际配置就是几行 Caddyfile。管理后台这类场景更适合白名单模式;要挡掉特定地区的已知恶意流量,用黑名单模式更顺手。把 GeoIP 数据库自动更新接上后,后续维护也省事。

参考链接