git.nju.edu.cn 从2015年11月开始得到了 AI bot 的热爱,CPU 根本扛不住,临时限制了校外的网络带宽,但这导致校外慢的几乎无法使用。所以趁着元旦,用 Anubis 对网站进行保护,把 bot 拒之门外。
反向代理模式
Anubis 默认以反向代理的模式运行,所有的流量都经过 Anubis ,Anubis 再将流量反代给后端服务。这种情况下前面还要有一个 HTTPd 如 nginx 实现 TLS 终结再将流量反向代理给 Anubis。在这种情况下怎么将客户端的真实IP传递给最后的应用尤为关键。
当正常访问者的IP有可能为 RFC 1918 私网IP,如校园网环境,校园网内用户使用私网IP访问校园网内服务器是非常正常的行为。但是当这种情况下,Anubis 的处理就有点不尽如人意了。
Anubis 使用 X-Real-IP 和 X-Forwarded-For 处理客户端真实IP,与之相关的环境变量有两个 CUSTOM_REAL_IP_HEADER 和 XFF_STRIP_PRIVATE。
Anubis 会对 X-Forwarded-For 进行处理
- 当 XFF_STRIP_PRIVATE=true (默认值)时从右向左扫描将第一个公网IP配置为新的 X-Forwarded-For ,如果所有的客户端都是公网 IP 这没有问题,但是如果客户端是公网和私网 IP 混合的模式就是麻烦了。对于私网 IP 的客户端,Anubis 的日志中 X-Forwarded-For 就变空了,如果后端服务使用 X-Forwarded-For 识别客户端真实IP( GitLab 中配置 nginx[‘real_ip_header’] = ‘X-Forwarded-For’ ),结果后端记录的都是前端 nginx 的 IP,这里的 nginx IP 也是私网IP。
- 当 XFF_STRIP_PRIVATE=false 时从右向左扫描将第一个公网或私网IP配置为新的 X-Forwarded-For ,结果 Anubis 的日志中 X-Forwarded-For 均为前端 nginx 的IP,后端记录的也都是前端 nginx 的 IP。
Anubis 要求前端反向代理将客户端真实IP放在 X-Real-IP 中,如果请求头名字不是这个可以用 CUSTOM_REAL_IP_HEADER 来配置,但是必须有否则会报错。如果后端服务使用 X-Real-IP 识别客户端真实IP( GitLab 中配置 nginx[‘real_ip_header’] = ‘X-Real-IP’ ),结果后端记录的都是 Anubis 的 IP,估计是反代给后端的时候就没有 X-Real-IP ,但在 Anubis 的日志中是正常的,具体我没有探究,在群友的提醒下马上改用 sidecar 模式了。
当然也不是没有解决办法,可以过两次 nginx 。第一次 nginx 把 X-Real-IP 和 X-Forwarded-For 用其它名字的请求头再保存一次,然后将流量反代给 Anubis 。Anubis处理完后将流量反代给 nginx,第二次 nginx 用第一次保存的其它名字做恢复,然后将流量反代给后端服务。配置看起来像下面这样,还是赶快用边车模式吧。
#第一层nginx
listen 443 ssl;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-IP-Frontend $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For-Frontend $proxy_add_x_forwarded_for;
proxy_pass http://anubis:8923;
#anubis
BIND=":8923"
TARGET=: "http://nginx:3923"
#第二层nginx
listen 3923;
proxy_set_header X-Real-IP $http_x_real_ip_frontend;
proxy_set_header X-Forwarded-For $http_x_forwarded_for_frontend;
proxy_pass http://gitlab:80;
不太理解 Anubis 为什么要去这样修改 X-Real-IP 和 X-Forwarded-For,正常的反向代理应该从网络层获取客户端IP然后添加到 X-Forwarded-For 后面。
sidecar 模式
对所有的请求,nginx 都会内部发起子请求 auth_request 到 /.within.website/x/cmd/anubis/api/check ,如果认证服务(anubis)返回 200,则请求继续反代给 gitlab 。如果认证服务(anubis)返回 401,则返回客户端 307 重定向到 /.within.website/?redir=……,引导客户端通过 anubis 验证,成功后再跳回原来的链接。
nginx 配置片段
- /.within.websit 是需要反代给 Anubis 的内容,不包括请求体,只包括请求头,通过请求头传递客户端IP
- @redirectToAnubis 是给客户端的重定向,让客户端通过 Anubis 验证
- / 是主入口,auth_request 把所有的请求先给 /.within.website/x/cmd/anubis/api/check ,如果返回200则继续,如果返回401则转给上面的@redirectToAnubis
- /gitlab-lfs/objects/ 是 LFS 路径,在子请求模式下会因为卡上传而出错
location /.within.website/ {
proxy_pass http://anubis:8923;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass_request_body off;
proxy_set_header content-length "";
auth_request off;
}
location @redirectToAnubis {
return 307 /.within.website/?redir=$scheme://$host$request_uri;
auth_request off;
}
location / {
auth_request /.within.website/x/cmd/anubis/api/check;
error_page 401 = @redirectToAnubis;
include conf.d/proxy_git;
}
location ~ /gitlab-lfs/objects/ {
auth_request off;
include conf.d/proxy_git;
}
其中 proxy_git 的内容
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
proxy_buffering off;
proxy_request_buffering off;
client_max_body_size 0;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://gitlab:80;
anubis 策略配置片段
- 为了能够让 nginx 区别被拦截的请求,需要对 anubis 的拒绝策略更改 HTTP 状态码为403
- 对于生产环境建议持久化数据,而不是使用内存
status_codes:
CHALLENGE: 200
DENY: 403
store:
backend: bbolt
parameters:
path: /data/bbolt/anubis.bdb
anubis 容器配置片段
- 挂载配置文件和持久化目录,注意持久化目录对于1000:1000要可写
- BIND 为接受请求的端口,在 sidecar 模式下 TARGET 为空格
- 持久化存储需要固定一个64个字符的16进制私钥(可用 openssl rand -hex 32 生成)
- Anubis 输出的日志是标准的 ndjson
volumes:
- ./anubis/policy.yaml:/data/cfg/botPolicy.yaml:ro
- ./anubis/bbolt:/data/bbolt:rw
environment:
BIND: ":8923"
TARGET: " "
COOKIE_DOMAIN: "git.nju.edu.cn"
CUSTOM_REAL_IP_HEADER: "X-Real-IP"
ED25519_PRIVATE_KEY_HEX: "123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234"
POLICY_FNAME: "/data/cfg/botPolicy.yaml"
labels:
- co.elastic.logs/json.target=""
- co.elastic.logs/json.add_error_key=true
以上的 nginx 和 GitLab 均在容器中运行,anubis 容器镜像为 ghcr.io/techarohq/anubis:v1.24.0