Wikipedia:反向代理进阶
- Linux learning
- 2023-12-03
- 46热度
- 0评论
一、概述
对维基百科做反向代理是比较有挑战的。一方面,维基百科的服务并非由单一域名提供,而是一组域名,例如中文维基百科的网址是zh.wikipedia.org,日文的则是jp.wikipedia.org;手机访问维基百科,还会自动切换到m.wikipedia.org。页面的插图等,还会用到upload.wikimedia.org。另一方面,维基百科的页面大量依赖javascript、css,有一些硬编码为wikipedia.org域名的地址,需要对页面的内容进行替换。
为此需要解决两个关键问题:
1、通配符DNS解析,以及为通配符域名申请SSL证书。
2、需要对nginx进行手动编译,以添加第三方模块ngx_http_substitutions_filter_module,用于实现高级的页面内容替换功能。
二、手动编译nginx
在手动编译nginx之前,强烈建议首先卸载已经安装的nginx,否则在加载模块上会出现混乱和版本问题。当然,有条件的话,在全新系统上安装是最干净的。
1、第三方模块的配置参数
截至2023年12月3日,ubuntu的官方仓库中的nginx版本仍然停留在1.18.0。如果我们还是用1.18.0的源代码来编译nginx,会发现与OpenSSL 3.0存在兼容问题,会影响后面的编译(虽然只是警告信息,但觉的不爽)。所以建议还是用nginx官方的最新版本(目前是1.24.0)。此外,ubuntu官方仓库中的nginx默认附加了第三方模块“http-geoip2”,而nginx官方的源代码里没有这个模块。这个模块用于根据IP识别地理信息,如果暂时用不到,可以不附加这个模块即可。
ubuntu官方仓库中的nginx默认安装的模块,在已经安装nginx的机器上执行nginx -V可以看到:
--with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-zctdR4/nginx-1.18.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --add-dynamic-module=/build/nginx-zctdR4/nginx-1.18.0/debian/modules/http-geoip2 --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module
红色部分,就是附加的第三方模块http-geoip2。我们要做的,就是去掉它,并且再添加一个ngx_http_substitutions_filter_module。
言归正传,下面正式开始。
2、编译nginx
首先卸载已经存在的nginx(从ubuntu官方仓库先安装一遍可以查看它的默认安装配置,就是上面那一大串)。
安装编译需要的工具:
sudo apt update
sudo apt install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
到nginx官方页面查看当前最新的nginx版本,目前是1.24.0。下载nginx源代码并解压缩它:
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
然后下载ngx_http_substitutions_filter_module的源代码:
git clone https://github.com/yaoweibin/ngx_http_substitutions_filter_module.git
此时,在当前用户目录下已经出现了两个新的目录:nginx-1.24.0、ngx_http_substitutions_filter_module。进入nginx-1.24.0目录。
cd nginx-1.24.0
用./configure来配置编译选项:
./configure --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-zctdR4/nginx-1.18.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module --add-module=../ngx_http_substitutions_filter_module
注意,已经删除了http-geoip2,并添加了ngx_http_substitutions_filter_module。这里用../返回上一级目录,再进入ngx_http_substitutions_filter_module,明确告诉编译器第三方模块的地址。
configure将逐个检查编译模块的可用性,如果出现错误,建议检查配置中涉及到的路径是否正确。仅当configure命令执行没有错误,方可进行下一步。
在nginx-1.24-0目录中,依次执行:
make
sudo make install
3、配置nginx
用apt install通过ubuntu官方仓库安装nginx时,它贴心地设置了全局PATH(让你在任意目录都可以直接执行nginx),设置了开机自启,设置了site-enabled和site-avaliable目录。但手动编译安装就没有这些福利了,需要我们自己来设置。
设置开机自启
首先明确nginx的安装路径。通常来说,使用apt install安装的nginx,路径位于/usr/sbin/nginx,且会在/usr/lib/systemd/system/路径下添加一个nginx.service,以实现开机自启。而手动编译安装的nginx通常位于/usr/share/nginx/sbin/目录下。同样地,我们可以在/etc/systemd/system/路径(或/usr/lib/systemd/system/)下创建nginx.service文件,内容如下:
[Unit]
Description=Nginx
After=network.target
[Service]
Type=forking
ExecStart=/usr/share/nginx/sbin/nginx
ExecReload=/usr/share/nginx/sbin/nginx -s reload
ExecStop=/usr/share/nginx/sbin/nginx -s quit
PrivateTmp=true
[Install]
WantedBy=multi-user.target
然后,更新systemd配置,设置开机启动,运行nginx服务,并查看其运行状态:
sudo systemctl daemon-reload
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx
注意,此时可能会出现一个错误,指出/var/lib/nginx/body目录不存在。我们可能需要手动创建以下目录:
sudo mkdir -p /var/lib/nginx/body
sudo mkdir -p /var/lib/nginx/proxy
sudo mkdir -p /var/lib/nginx/fastcgi
设置目录权限:
sudo chown -R www-data:www-data /var/lib/nginx
sudo chmod -R 755 /var/lib/nginx
注意,www-data是nginx执行的默认用户,在安装nginx时会自动添加到系统中。
之后再尝试启动nginx。
设置全局PATH
定位到当前用户根目录,打开.bashrc文件,末尾添加一行:
export PATH=$PATH:/usr/share/nginx/sbin
保存退出,然后source一下以生效:
source ~/.bashrc
此时已经可以在任意目录执行nginx命令了。
构建配置文件存储结构
nginx的配置文件通常位于/etc/nginx/目录。手动在该目录中创建两个子目录:sites-enabled、sites-avaliable,然后编辑nginx.conf,在http块中最后添加一行:
include /etc/nginx/sites-enabled/*;
这就包含了sites-enabled中的所有配置文件。未来每个网站在sites-enabled中放一个对应的配置文件即可,临时不用的就剪切到sites-avaliable目录中。
可以用nginx -T命令查看当前nginx所有详细配置情况。用nginx -t检查配置文件是否合法。
一切顺利的情况下,nginx -s reload以重新加载配置。
三、通配符SSL证书
好了,现在已经安装最新的nginx,并且包含了第三方模块ngx_http_substitutions_filter_module。第二项准备工作是准备好域名解析和SSL证书。
假设我们用于反代的域名是site.com,并且用wiki作为二级前缀。一共需要准备3个域名解析:
wiki.site.com,用来匹配入口www.wikipedia.org
*.wiki.site.com,用来匹配不同语言站,例如zh.wikipedia.org
*.m.wiki.site.com,同上,加上m用来匹配手机访问站m.wikipedia.org。
所有解析地址均为反代服务器地址。
安装certbot:
sudo apt install --classic certbot
对于单个域名,通常可以直接使用certbox --nginx -d a.site.com来配置它,这时候certbot只需要检测a.site.com是否运行在当前服务器的80端口即可通过验证。但是,对于通配符域名的SSL证书,certbot仅支持DNS-01方式,也就是需要直接操作DNS解析才能通过ACME挑战。这就需要DNS托管商提供API了。
以DNS托管在cloudflare为例。cloudflare提供了可支持certbot的API。首先安装certbot的cloudflare插件:
apt install python3-certbot-dns-cloudflare
打开cloudflare账户的个人资料页面,点击左侧API Tokens,找到Global API key,查看它,并保存之。在服务器上任意地址(例如/var/apikeys/)新建一个cloudflare.ini文件:
dns_cloudflare_email = cloudflare帐号邮箱地址
dns_cloudflare_api_key = GLOBAL KEY
然后,就可以用certbot命令来申请SSL证书了:
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /var/apikeys/cloudflare.ini \
-d 'wiki.site.com' \
-d '*.wiki.site.com' \
-d '*.m.wiki.site.com'
--dns-cloudflare-propagation-seconds 30
默认情况下,certbot隐含了参数--dns-digitalocean-propagation-seconds 10,也就是传播延迟10秒。有时候10秒太短,DNS来不及传播导致证书申请失败。那么可以在certbot命令中增加设定参数:--dns-cloudflare-propagation-seconds 30即可。
证书申请成功后,会提示证书存储路径,通常位于/etc/letsencrypt/live/wiki.site.com/,关键的两个文件是fullchain.pem和privkey.pem。
例如,*.wiki.site.com不能匹配wiki.site.com,也不能匹配a.m.wiki.site.com,只可以匹配a.wiki.site.com。
解决方案:
1、地址栏输入chrome://net-internals/#hsts
2、找到Delete domain security policies
3、填入要清除HSTS设置的域名,点击Delete
除此之外,更简便的方法是,永远使用Chrome的“无痕模式”来测试新的网站。
四、设置反向代理
有了前面的准备工作,这一步就相对轻松,唯一需要重点处理的就是页面内容替换(subs_filter)了。在nginx配置中,我们一共需要4个server块,分别对应主站、upload站、各语言站、手机站。
维基百科的搜索框有根据输入内容自动推荐关键词的功能,这个功能是AJAX异步加载的,维基百科非常贴心地把域名做成了变量(portalSearchDomain),变量值为'wikipedia.org',替换这个值时务必带上单引号。
为什么要强调包括单引号?为什么不能直接简单粗暴把wikipedia.org全部替换成wiki.site.com?因为维基百科站点存在名叫wikipedia.org的子目录,例如源代码中的“portal/wikipedia.org/assets”,它原本对应的完整的URL是:“https://www.wikipedia.org/portal/wikipedia.org/assets”,第一个域名我们用反向代理替换了;第二个wikipedia.org实际上是一个目录名称,显然不能直接替换。
这就是为什么要使用一系列复杂的subs_filter进行手术刀式的操作的原因。
1、主站
server {
server_name wiki.site.com;
listen 80;
listen 443 ssl http2;
#强制google DNS,防止域名污染
resolver 8.8.8.8;
ssl_certificate /etc/letsencrypt/live/wiki.site.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wiki.site.com/privkey.pem;
#http转https
if ($http_x_forwarded_proto = 'http')
{
return 301 $server_name$request_uri;
}
location / {
proxy_pass https://www.wikipedia.org;
proxy_buffering off;
#让潜在的跳转跳转到正确代理地址
proxy_redirect https://www.wikipedia.org/ https://wiki.site.com/;
#更换cookie域名
proxy_cookie_domain www.wikipedia.org wiki.site.com;
proxy_redirect ~^https://([\w\.]+).wikipedia.org/(.*?)$ https://$1.wiki.site.com/$2;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
#在subs_filter之前,处理压缩后的数据,否则无法进行明文替换
proxy_set_header Accept-Encoding '';
proxy_set_header referer "https://$proxy_host$request_uri";
#指定替换的多种类型,仅第三方模块支持
subs_filter_types text/css text/xml text/javascript application/javascript application/json;
subs_filter .wikipedia.org .wiki.site.com;
#替换portalSearchDomain变量,连单引号一起防止错误替换
subs_filter \'wikipedia.org\' \'wiki.site.com\';
subs_filter //wikipedia.org //wiki.site.com;
subs_filter upload.wikimedia.org up.wiki.site.com;
}
}
2、upload站
server {
server_name up.wiki.site.com;
listen 80;
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/wiki.site.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wiki.site.com/privkey.pem;
if ($http_x_forwarded_proto = 'http')
{
return 301 $server_name$request_uri;
}
location / {
proxy_pass https://upload.wikimedia.org;
proxy_cookie_domain upload.wikimedia.org up.wiki.site.com;
proxy_buffering off;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header referer "https://upload.wikimedia.org$request_uri";
}
}
3、各语言站
server {
#匹配*.wiki.site.com,并将前缀装入subdomain变量
server_name ~^(?<subdomain>[^.]+)\.wiki\.site\.com$;
listen 80;
listen 443 ssl http2;
resolver 8.8.8.8;
ssl_certificate /etc/letsencrypt/live/wiki.site.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wiki.site.com/privkey.pem;
if ($http_x_forwarded_proto = 'http')
{
return 301 $subdomain.wiki.site.com$request_uri;
}
location / {
proxy_pass https://$subdomain.wikipedia.org;
proxy_buffering off;
proxy_redirect https://$subdomain.wikipedia.org/ https://$subdomain.wiki.site.com/;
proxy_redirect https://$subdomain.m.wikipedia.org/ https://$subdomain.m.wiki.site.com/;
proxy_cookie_domain $subdomain.wikipedia.org $subdomain.wiki.site.com;
proxy_redirect ~^https://([\w\.]+).wikipedia.org/(.*?)$ https://$1.wiki.site.com/$2;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Accept-Encoding '';
proxy_set_header referer "https://$proxy_host$request_uri";
subs_filter_types text/css text/xml text/javascript application/javascript application/json;
subs_filter .wikipedia.org .wiki.site.com;
subs_filter //wikipedia.org //wiki.site.com;
subs_filter 'https://([^.]+).wiki' 'https://$1.wiki' igr;
#避免潜在的混合内容错误,将http子站引用替换为https,如果有问题可删
subs_filter 'http://([^.]+\.)?wikimedia\.org' 'https://$1wikimedia.org' igr;
subs_filter upload.wikimedia.org up.wiki.site.com;
}
}
4、手机站
server {
server_name ~^(?<subdomain>[^.]+)\.m\.wiki\.site\.com$;
listen 80;
listen 443 ssl http2;
resolver 8.8.8.8;
ssl_certificate /etc/letsencrypt/live/wiki.site.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wiki.site.com/privkey.pem;
if ($http_x_forwarded_proto = 'http')
{
return 301 $subdomain.m.wiki.site.com$request_uri;
}
location / {
proxy_pass https://$subdomain.m.wikipedia.org;
proxy_buffering off;
proxy_redirect https://$subdomain.m.wikipedia.org/ https://$subdomain.m.wiki.site.com/;
proxy_cookie_domain $subdomain.m.wikipedia.org $subdomain.m.wiki.site.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Accept-Encoding '';
proxy_set_header referer "https://$proxy_host$request_uri";
subs_filter_types text/css text/xml text/javascript application/javascript application/json;
subs_filter .wikipedia.org .wiki.site.com;
#替换portalSearchDomain变量,连单引号一起防止错误替换
subs_filter \'wikipedia.org\' \'wiki.site.com\';
subs_filter //wikipedia.org //wiki.site.com;
subs_filter 'https://([^.]+).m.wiki' 'https://$1.m.wiki' igr;
#同上
subs_filter 'http://([^.]+\.)?wikimedia\.org' 'https://$1wikimedia.org' igr;
subs_filter upload.wikimedia.org up.wiki.site.com;
}
}
解决办法:右键点击简悦插件图标,进入“选项”->“高级设定”,在黑名单中将wiki.site.com加进去即可。
当然,直接关闭简悦插件也行。