唠唠闲话

在管理内网和公网服务时,可能有这样的问题:如何既保证服务的安全性,又能简化维护和管理流程?

最近实践了一种方案:使用 Nginx 结合双域名配置来管理内网服务。在深入讲解具体的配置步骤之前,让我们先来看看希望实现的效果是什么样的。

假设我们在内网部署了一个服务,正常情况下,只能在内网环境访问。现在,我们想在公网访问,且不需要将其迁移过去。经过本篇的配置,每次更新或新增内网服务,公网无需做任何操作,就会自动转发内网对应的服务。

举个例子,如果内网的维护地址为 next-web.local.wzhecnu.cn,公网用户则通过 next-web.wzhecnu.cn 访问同一个服务,这样的好处在于:

  1. 安全性提高:服务不直接暴露在公网上,减少了潜在的安全风险。
  2. 维护简化:所有的更新和维护工作仅需在内网进行,无需额外关注公网服务的配置。
  3. 访问便捷:无论用户身处内网还是外网,都可以通过相应的域名访问到服务。

接下来,我们将详细介绍每一步的操作过程,并提供详细的代码和操作指南。

实战演示

内容概要

  1. 设置泛解析 DNS:通过泛解析来简化 DNS 管理,将所有子域名解析到指定的 IP 地址。
  2. 配置 SSL 证书:介绍使用 Certbot 和 Let’s Encrypt 来自动化 SSL 证书的申请和续期过程。
  3. 内网服务配置:如何设置内网服务以通过 HTTPS 提供服务。
  4. 内网穿透设置:使用内网穿透工具将内网服务转发到公网。
  5. 公网服务器配置:最后,配置公网服务器,以便通过公网域名访问内网服务。

在实操设置中,我们将使用两个不同的域名泛解析:*.local.wzhecnu.cn*.wzhecnu.cn。前者将指向我们的内网服务器,而后者则用于公网访问。

注:借助内网穿透,理论上公网服务器只需能对外提供网络服务就足够了,不需要有很高的配置。

设置泛解析 DNS

泛解析 DNS 允许将任何子域名解析到指定的 IP 地址,注意只匹配一个级别的子域名。比如 *.wzhecnu.cn 匹配 a.wzhecnu.cn,但不匹配 a.b.wzhecnu.cn

以下假设:

1
2
*.local.wzhecnu.cn    => 内网服务器 IP
*.wzhecnu.cn => 公网服务器 IP

可以根据需要,改成其他形式。

配置 SSL 证书

为了使用 HTTPS 访问服务,需要为域名配置 SSL 证书。利用 Certbot 和 Let’s Encrypt,可以自动化这个过程,简化证书的申请和续期。

这部分内容可以参考之前写的一篇:『Let's Encrypt 域名证书增强网站安全

内网服务配置

以下是配置内网服务的步骤。

作为示例,我们在 /var/www/hello 下放置了一个简单的静态网页,命名为 index.html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>Hello, World!</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>

如果你已有内网服务,可以替换这个示例。

然后配置 Nginx 以将流量代理到内网服务的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# HTTP 到 HTTPS 的重定向
server {
listen 80;
server_name test.local.wzhecnu.cn;
# 可考虑设置重定向
# return 301 https://$host$request_uri;
root /var/www/hello;
index index.html;
}

# HTTPS 配置
server {
listen 443 ssl;
server_name test.local.wzhecnu.cn;

ssl_certificate /etc/letsencrypt/live/local.wzhecnu.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/local.wzhecnu.cn/privkey.pem;

root /var/www/hello;
index index.html;
}

现在,测试一下是否能够通过 test.local.wzhecnu.cn 访问到内网服务。如果一切正常,我们就可以继续配置内网穿透服务。

1
2
curl http://test.local.wzhecnu.cn
curl https://test.local.wzhecnu.cn

穿透服务配置

内网穿透是一个关键步骤,它允许我们的内网服务能够通过特定的穿透服务,在公网安全地访问。

我们将使用内网穿透工具(如frp)来实现这一点。具体来说,我们需要在内网服务器上配置相应的穿透规则,以便将来自公网的请求转发到内网服务。以下是具体配置的步骤:

  1. 在内网服务器上安装内网穿透工具。
  2. 配置穿透规则,指定本地和远程端口,以及流量的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# frp 内网穿透配置
[[proxies]]
name = "web-80"
type = "tcp"
localIP = "127.0.0.1"
localPort = 80
remotePort = 10080

[[proxies]]
name = "web-443"
type = "tcp"
localIP = "127.0.0.1"
localPort = 443
remotePort = 10443

这个配置创建了两个代理:web-80web-443web-80 代理将内网的 80 端口(通常用于 HTTP 流量)映射到远程端口 10080;而 web-443 代理则将内网的 443 端口(通常用于 HTTPS 流量)映射到远程端口 10443。通过这种方式,外部流量可以通过特定的远程端口安全地访问内网服务。

注意,远程端口 1008010443 可以不对外开放,内网穿透只需服务器在内部可以访问。

更详细的介绍参考之前写的文章:『内网穿透 | FRP 使用教程

接下来,我们在公网服务器的 Nginx 配置中引用这些端口,以确保来自公网的请求能够正确地被转发到内网服务。

公网服务器配置

公网服务器的配置旨在使得外部用户能够通过公网域名访问内网服务。关键在于设置 Nginx 以正确地转发来自公网的请求到内网穿透服务。以下是配置公网服务器的步骤:

  1. 使用 map 指令动态地映射请求头中的 host。
  2. 配置 Nginx 服务器块以监听公网域名,并将请求转发到内网穿透的端口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 公网服务器配置
map $http_host $host_with_cn {
default $http_host; # 避免重定向时丢失原始域名
~^(.+)\.wzhecnu\.cn$ $1.local.wzhecnu.cn;
}

server {
listen 80;
server_name ~^(.*)\.wzhecnu\.cn$; # 使用正则表达式匹配所有子域名
location / {
proxy_pass http://127.0.0.1:10080; # 转发到内网穿透的端口
proxy_set_header Host $host_with_cn; # 映射后的内网域名
}
}

这段配置首先使用 map 指令将公网域名(如 something.wzhecnu.cn)映射到对应的内网域名(如 something.local.wzhecnu.cn)。然后在 server 块中,将所有匹配的公网域名请求转发到内网穿透指定的端口(此例中为 10080):

  • proxy_set_header Host $host_with_cn; 使用映射后的内网域名,确保内网服务能正确地处理请求
  • default $http_host; 避免重定向时丢失原始域名,也即防止公网服务重定向时,变成内网访问的域名

此外,这个配置下,即使内网 *.local.wzhecnu.cn 的解析不存在,外网链接也能访问。也就是说,如果我们希望服务仅通过公网访问,可以将 *.local.wzhecnu.cn 替换为任意域名,只要与公网端的配置相匹配即可。

简化版本

如果内网不需要用域名访问,这里的配置会更简单:map 字段以及 proxy_set_header Host 可以去掉,且不需要正则表达式。

1
2
3
4
5
6
7
server {
listen 80;
server_name *.wzhecnu.cn;
location / {
proxy_pass http://127.0.0.1:10080; # 转发到内网穿透的端口
}
}

将域名 *.wzhecnu.cn 解析到公网服务器。内网 Nginx 使用 xxx.wzhencu.cn 监听相关的服务,当访问 xxx.wzhecnu.cn 时,实际上是在公网访问内网的服务。

前边配置双域名的目的是为了内网环境下,也有单独能访问的版本。

Nginx 配置

一些复杂服务需要的 Nginx 规则较多,比如 GitLab,Overleaf。需要为 Nginx 设置其他请求头,以确保服务能被正常转发。

贴一些常用的配置,供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 设置代理服务器以正确地处理 HTTP/HTTPS 请求和 WebSocket 连接
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

location / {
... # 其他配置
proxy_ssl_server_name on;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}

或者 streamlit 的配置:

1
2
3
4
5
6
7
8
9
10
11
location / {
... # 其他配置
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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;
proxy_set_header X-Forwarded-Host $server_name;
}

通常,公网配置好泛解析后,只需维护内网的 Nginx 配置即可。但如果某些服务的 Nginx 配置冲突,自动化规则不能同时满足,可以把特殊的部分单独进行配置,例如:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name gitlab.wzhecnu.cn;
location / {
proxy_pass http://loalhost:10080;
proxy_set_header Host gitlab.local.wzhecnu.cn;
}
}

总结

通过以上步骤,我们搭建了一个使用双域名和内网穿透技术的服务器管理系统。这种策略不仅提高了安全性,还极大简化了服务的维护和管理工作。这对于那些需要频繁更新和维护服务的人来说,无疑是一个非常实用的解决方案。

如果有任何疑问或想法,随时留言讨论!