反向代理 ASUS 路由器 WebUI 需要设置 Referer 与 Host 匹配

今天想给 subconverter 添加一个 webhook,来触发路由器中 Merlin Clash 应用的自动重新加载,这就涉及要把路由器的 API 暴露到互联网上。

我一开始的想法是绑定域名,然后从外面直接访问,因为我的环境只有 IPv6 可用,而 GitHub Action 它只支持 IPv4,所以这条路不行。

那另一条路就是在外部公共环境中放置一个反向代理,我尝试了 Cloudflare Tunnel 和自己腾讯云服务器上的 nginx 反向代理,他们都遇到了一个问题:可以打开登录页,但是无法进行登录,点击登录按钮会刷新网页。

正常来说,不论登录不论账号密码是正确还是错误,那它都会有一个提示。那现在点击登录就像是刷新了网页一样。

我一开始还怀疑是 Cloudflare Tunnel ,它动态 IP 导致每次都连接到一个新的 session,也检查了浏览器中的 cookie,但似乎并不是相关的。

此时我想起来之前,调用 API 的时候,给路由器 API 的请求中必须有一个 Referer 参数,这次让 GPT 验证了下确有其事,调用点在 asuswrt-merlin.ng/release/src/router/httpd/httpd.c

int check_noauth_referrer(char *referer, int fromapp_flag)
{
    char *auth_referer = NULL;
 
    if (fromapp_flag != 0)
        return 0;
 
    if (!referer || !strlen(host_name))
        return NOREFERER;
 
    auth_referer = get_referrer(referer);
 
    if (!strcmp(host_name, auth_referer))
        return 0;
 
    return REFERERFAIL;
}

逻辑上即是验证 Host 和 Referer 必须匹配,那就简单了,在 Caddy 反向代理处设置固定值即可

rt-ax86u.home.kokomi.site {
	reverse_proxy RT-AX86U-CE58. {
		header_up Host "RT-AX86U-CE58"
		header_up Referer "http://RT-AX86U-CE58"
	}
}

这其实相当于公网暴露了,留下一个坑就是,任何人如果多输入错误几次,这个 WebUI 就会被锁,需要 service restart_httpd 重新启动

反向代理 OpenList 的 S3 对象存储

对象存储 - OpenList 文档 能把 OpenList 转换为一个 S3 服务器,这样就可以在其他依赖 S3 的地方使用了,比如腾讯云的日志推送。

但它配置使用的端口号不是普通的 80 或 443,而且 SSL 也需要单独配置,那当然是使用 Caddy 去做反向代理。

但又遇到了关于 Host 的问题,这次是签名,因为 AWS 会把 HTTP 的 header 里面有 Host header 一起打包进签名里面,反向代理之后,实际的 HTTP 请求的 Host 会不断的发生变化,最终导致签名验证不通过,提示 The request signature we calculated does not match the signature you provided. Check your key and signing method.

好在 X-Forwarded-Host 这个 header 中有上游 host 的信息,而且我配置的一般只有额外一跳,所以可以这样子做

s3.home.kokomi.site {
	@forwardedHost header X-Forwarded-Host *
 
	handle @forwardedHost {
		reverse_proxy openlist.docker:5246 {
			header_up Host {header.X-Forwarded-Host}
		}
	}
 
	handle {
		reverse_proxy openlist.docker:5246
	}
}