Docker重度依赖患者的Nginx通配符配置

环境声明

以下内容都是建立在2个前提

  • 免费版CF大善人域名托管
  • 运行私有化服务的公网IP主机(eg: 甲骨文 ARM 424主机)

这是弄什么

结合域名DNS解析的通配符机制,使用一个Nginx配置的HTTPS反向代理通配多个服务,实现接近于一次配置,始终有效,不用每次部署新服务都手动设置反代。

例如,当我访问 https://10000.kr.xxx.com 时,反向代理这台主机的10000端口的http服务,即约等于 https://kr.xxx.com:10000,不同在于原本的10000端口可能是HTTP,而我希望它是HTTPS。

PS:尝试使用kr-10000.xxx.com或10000-kr.xxx.com这种通配方式,在CF上都解析不了,不得已采用2级子域名方案。

感兴趣的话,先看完,再动手,确保已经知悉并评估有哪些风险。

为什么要弄

  • Docker部署项目后,每次都要去修改Nginx配置HTTPS反向代理,想偷懒。
  • 由于懒,有一些服务在HTTP上运行而不是HTTPS。
  • 由于HSTS和Strict-Transport-Security的存在,导致偷懒的HTTP的服务有时可能无法访问,不得不
    • 手动删除浏览器中的HSTS缓存
    • 或让强制HTTPS的服务在另一个浏览器中使用
    • 或找到这个默认开启HSTS的服务并修改它的Nginx设置,并承担可能导致服务工作异常的风险。
  • 在同一个域名下使用端口号访问服务,如 kr.xxx.com:10086 ,多个服务在同一域下是有安全性风险的。同时也可能出现一些麻烦,例如ope-api和new-api会互相踢下线,因为使用的cookies key冲突了。
  • 使用HTTPS是个好习惯,明知不该偷懒确还是忍不住偷懒,那就换一种偷懒的方式,于是形成这篇折腾笔记。

怎么弄

可选步骤

如果你只有一台主机(VPS/NAS),那么可以:

  • 跳过域名通配符的设置(如果你已经设置),因为你不需要用到2级子域名。
  • 跳过申请SSL证书的步骤(如果你已经拥有),因为你可以直接使用*.xxx.com的证书。

设置域名通配符

在DNS解析服务中,将*.kr.xxx.com解析到位于韩国的主机上,A或CNAME均可,如果你的域名托管在CF,不要开启Proxied;

如果你购买了CF的高级证书,那么你可以在后面的步骤中开启Proxied。如果你是白嫖大善人,那么保持一直关闭Proxied。如果你既要又要,那我建议你用*.woqinshihuang.xxx.com之类的只有你知道的子域稍微来点安慰剂。

申请SSL证书

由于*.xxx.com的证书是不能用于*.kr.xxx.com的,所以需要签个新证书。

此处使用acme.sh生成证书(推荐)
# 如果你的服务器支持IPV6
sudo CF_Token="此处填写CF_API_TOKEN" "$HOME/.acme.sh/acme.sh" --issue -d "*.kr.xxx.com" --dns dns_cf -k ec-256 --server "letsencrypt" --listen-v6
# 如果你的服务器不支持IPV6
sudo CF_Token="此处填写CF_API_TOKEN" "$HOME/.acme.sh/acme.sh" --issue -d "*.kr.xxx.com" --dns dns_cf -k ec-256 --server "letsencrypt"
设置证书自动更新(推荐)
crontab -e

12 * * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
设置CF中的边缘证书(可选)

如果你没有开启2级子域名的Proxied(小黄云),你可以跳过此步骤,并且以后也不要开启2级子域名的Proxied。

CF免费版不提供2级子域的通配证书,这一节的内容不适合CF免费托管用户,只需要保持*.kr.xx.com没有开启Proxied即可,否则你将遇到ERR_SSL_VERSION_OR_CIPHER_MISMATCH错误。

具体操作路径为

你的域名 -> SSL/TLS -> 边缘证书 -> 上传自定义SSL证书
你的域名 -> SSL/TLS -> Edge Certificates -> Upload Custom SSL Certificate

但是话说回来,付了钱也没必要上传,窗口往下滑动,直接开启Total TLS。(理论,我也没给大善人上贡)

添加Nginx配置

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name *.kr.xxx.com;

    ssl_certificate /root/.acme.sh/*.kr.xxx.com_ecc/fullchain.cer;
    ssl_certificate_key /root/.acme.sh/*.kr.xxx.com_ecc/*.kr.xxx.com.key;

    location / {
    # 如果你出于安全考虑,想屏蔽例如10086和10010端口被反代,可以尝试使用下面一行写法代替,理论写法,未测试
    # if ($host ~* "^(\d+)\.kr\.xxx\.com$" && $1 !~* ^(10086|10010)$) {
    if ($host ~* "^(\d+)\.kr\.xxx\.com$") {
        proxy_pass http://127.0.0.1:$1;
    }
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        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;
    }
}

验证设置

使用Ping woqinshihuang.kr.xxx.com -4验证解析的IP地址是否正确。

访问https://80.kr.xxx.com验证SSL证书和Nginx是否配置正确,访问这个URL应该返回的是这个主机的80端口的信息,如果你没有使用80端口,就调整为你正在使用的其他端口。

结束语

也曾想过将docker容器服务名作为域名前缀,而不是纯端口号,但有很多容器需要暴露出多个端口,还是需要手动维护,这不符合偷懒的初衷,遂放弃。

你需要知道的是,这种方式会绕过iptables的保护,让内网的HTTP(S)服务都通过443端口暴露出来,如果有人知道这个规则,就可以尝试每一个端口来发现你的内网HTTP(S)服务。

我认为我也没这么大影响力,也没有被人针对性渗透的价值,此处使用的域名我从未公开过(小尾巴的域名是单独注册的),并且我有良好的密码习惯,我不在乎。但我仍建议你开启fail2ban等工具保护服务安全,ban掉HTTP服务中大量错误的IP。

手动添加到类似于/etc/fail2ban/jail.local的配置中

[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 169.254.0.0/16 ::1 你信任的公网IP.....

# 其他你的配置

[nginx-404]
enabled = true
port = http,https,10000:65535
logpath  = /var/log/nginx/access.log
filter = nginx-404

[nginx-5xx]
enabled = true
port = http,https,10000:65535
logpath  = /var/log/nginx/access.log
filter = nginx-5xx

添加规则文件

# ban掉扫ip的
sudo bash -c 'cat <<EOF > /etc/fail2ban/filter.d/nginx-404.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) .*" 404 .*$
ignoreregex = 域名甲\.com|域名乙\.com|域名丙\.com
EOF'
# ban掉所有5xx状态码的(nginx反代到一个不存在的端口是502错误)
sudo bash -c 'cat <<EOF > /etc/fail2ban/filter.d/nginx-5xx.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) .*" 5[0-9]{2} .*
EOF'

# 重启服务生效
systemctl restart fail2ban

你还需要知道的是,这不是万金油,充其量是9k金油,可以在很多情况下偷懒,但不能百分百在所有情况下有效,某些情况下你还是需要手动配置反代,具体来说:

这只是反代http服务,因此如果你的服务本身已经开启了https,访问会出错,例如Portainer就会返回Client sent an HTTP request to an HTTPS server。例如JumpServer会在域名发生变化后要求你更改配置文件,这都是项目的特性,并不是我造成的。

使用landscape批量推送配置到所有服务器,5分到手美滋滋

60 个赞

感谢分享,5分超值。

8 个赞

感谢分享,好东西

4 个赞

真的详细,感谢教程

3 个赞

多的点点滴滴

3 个赞

学习了,课后练习一下

3 个赞

中途还是走了很多弯路

比如在servername上用正则会被*.xxx.com截胡,调试了半天

比如开了proxied导致证书一直报错,反复查nginx配置和证书问题,又调试了半天

比如重新编译nginx装lua模块,装各种编译依赖,后来觉得还是太麻烦了,不优雅,又折腾了半天发现了现在的配置方法

6 个赞

好贴:+1:

1 个赞

学到了,感谢

1 个赞

好帖子

1 个赞

太强了!!!

1 个赞

好像很有用!

干货贴,收藏了!!!

学到了学到了。

好东西,学习

感谢分享, 已收藏

学习了

帅!好思路!

顶顶顶

好方法