保护我们的源站服务器


推弹的打击让大量极右、纳粹之类的互联网垃圾涌向了Fediverse,为了避免被这些肮脏的家伙沾上,我想我们很有必要来好好保护一下我们喵窝的Misskey服务。

对于版本 ≥1.19.4 的 nginx ,后文会提到一种更为简便的保护方案。

首先是配置nginx的默认证书。由于nginx的策略是没有被后续 server_name 捕获的请求都会被送往 default 文件中默认的 server_name _; ,没有的话就用配置文件中读取的第一个站点,因而80端口的流量确实已经是默认去了 Welcome to nginx! ,但对于HTTPS的443端口,第一个监听的站点就会被用来作为默认的承载站,进而导致源站的证书授权名泄露、IP面临被关联泄露的风险。为了同样保护起HTTPS协议的默认源站,我们可以使用openssl为自己签发一张不包含敏感信息的证书:

1
2
3
4
5
6
# 生成密钥
openssl genrsa -out server.key 4096
# 生成证书请求,内容随意填写
openssl req -new -key server.key -out server.csr
# 生成公钥,有效期10年
openssl x509 -req -in server.csr -out server.cer -signkey server.key -days 3650

生成的 server.cer 即为SSL证书文件, server.key 则为SSL的私钥供nginx使用。修改/etc/nginx/sites-enabled/default中的对应行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# ...
server {
listen 80 default_server;
listen [::]:80 default_server;

# SSL configuration
#
+ listen 443 ssl default_server;
+ listen [::]:443 ssl default_server;

+ ssl_certificate /path/to/your/cert;
+ ssl_certificate_key /path/to/your/key;

root /var/www/html;

# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;

server_name _;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}

# ...

但光是这样还不够,为了避免遭受中间人攻击,我们需要保证我们到CloudFlare的服务器的连接不被任何家伙干扰。

可以开启经过身份验证的源服务器拉取选项。

参照CloudFlare官方的配置教程,下载 CloudFlare的CA公钥证书 ,并配置到nginx中您站点配置文件中的对应段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.ltd;

## SSL
ssl_session_cache shared:ssl_session_cache:10m;
ssl_certificate /path/to/site/cert;
ssl_certificate_key /path/to/site/key;

## CloudFlare Pull Origin Authentication
+ ssl_client_certificate /path/to/your/cloudflare/origin-pull-ca.pem;
+ ssl_verify_client on;

## reverse proxy
location / {
proxy_pass http://upstream;
client_max_body_size 0;
include conf.d/shared/revproxy.conf;
}
}

ADD-SP大佬的提醒,还有一种更简便的方案可以实现SSL证书的保护:

在 nginx-1.19.4 或更高版本中,新增了配置 ssl_reject_handshake,只要在未指定server_name的 server 块(即默认块)里设置为 on,就可以直接在握手阶段阻断,证书也不会泄露,且不会影响指定了server_name并正确配置了证书的块。只需要把下列 server 块写到默认配置(80端口的默认请求可以直接拒绝了):

1
2
3
4
5
6
7
8
9
server {
listen 80;
deny all;
}

server {
listen 443 ssl;
+ ssl_reject_handshake on;
}

可以参照 官方文档 获取更多信息与详细样例。

这样就能从应用层保护我们的源站安全。

然后是网关层,通过iptables和ipset,将80/443端口的访问权限制为仅有CloudFlare的IP可以访问,以便于进一步地阻碍攻击者的行为:

1
2
3
4
5
6
7
8
9
10
11
# For IPv4
ipset create cloudflare_ips_v4 hash:net
for x in $(curl https://www.cloudflare.com/ips-v4); do ipset add cloudflare_ips_v4 $x; done
iptables -A INPUT -m set --match-set cloudflare_ips_v4 src -p tcp -m multiport --dports http,https -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dport http,https -j DROP

#For IPv6
ipset create cloudflare_ips_v6 hash:net family inet6
for x in $(curl https://www.cloudflare.com/ips-v6); do ipset add cloudflare_ips_v6 $x; done
ip6tables -A INPUT -m set --match-set cloudflare_ips_v6 src -p tcp -m multiport --dports http,https -j ACCEPT
ip6tables -A INPUT -p tcp -m multiport --dport http,https -j DROP

为了避免前后端的请求数据在公网明文传输,我们搭建了一个 WireGuard VPN 将两个节点加入对等的网络环境。由于是在 Debian 9 上搭建,您可以参照本站的 在 Debian 9 上安装 WireGuard VPN 这篇文章,我们成功完成了内网的组建。值得一提的是,文章中的wg0并不是限定的,只要保证该conf文件的文件名和文件中涉及到的防火墙规则网卡名一致即可,WireGuard会自动读取配置目录下的配置文件,并且在启动时根据对应的文件自动初始化网卡。

我们新增了一些防火墙规则,将不是从我们WireGuard可信客户端中上传的数据包全部丢弃:

1
2
3
4
ipset create wgclients hash:ip
ipset add wgclients 1.2.3.4 # WireGuard的客户端公网IP
iptables -A INPUT -m set --match-set wgclients src -p udp --dport 51820 -j ACCEPT
iptables -A INPUT -p udp -m multiport --dport 51820 -j DROP

测试一下ping,应该已经成功组建成对应的私有网络了。

为了方便Misskey的流量也走保护机的公网IP流出,我们为Misskey配置了HTTP代理。
在default.yml文件中取消proxy: http://127.0.0.1:3128行前的注释号#,将后面的地址改为我们的http代理地址。
例如根据本站使用的WireGuard配置,设置为http://192.168.8.1:3128

之后编辑保护机的squid配置,取消对应分配的 局域网IP地址行 和 访问请求控制行 前的注释号#,便于让使用WireGuard的后端站可以访问到squid:

1
2
3
4
# 对应于您在WireGuard中设置的子网段
+acl localnet src 192.168.0.0/16
# 允许本地网络的代理请求
+http_access allow localnet

由于保护机使用的是多IP多网卡设置,因而需要手动指定squid的主机名和出口地址,以避免squid卡死在启动阶段一直报警告:

1
2
3
4
# 您的域名
+visible_hostname hostname.your.ltd
# 您的出口地址
+tcp_outgoing_address 1.2.3.4

完成之后启动squid、重启Misskey即可。