基于nginx + ssh搭建内网服务的公网代理

许多中小企业,或者家庭都有在局域网外远程访问内网服务的需求。

花生壳之类的应用可能是一个不错的解决方案,但毕竟要收费。

其实,只需要你有Linux系统的云主机和内网服务器,用ssh+nginx就可以很简单地搭建出这样的系统。

环境准备

准备以下设备:

  • 一台代理服务器:可以是一台服务器/PC,或一台网关/路由器(要取得root权限),用于向云端反向代理本地服务。
  • 一台业务服务器:用于搭建本地服务。
    • 此服务器是非必需的:
      • 如果内网已经有开放的业务(如OA,内网可访问的任何网页),可以不用此服务器
      • 也可以把反向代理在与内网业务搭建在同一台服务器上。
  • 一台公网服务器:可以是云端的虚拟服务器,要有公网IP,用于向用户转发代理流量。
  • 另一台PC:用于模拟用户,访问本地服务。

基础步骤

Step 1: 启动内网服务

如果内网已经有开放的业务(如OA,内网可访问的任何网页),可跳过此步骤。

在我的环境中,是用nginx搭了一个简单的http服务,开放在8011端口上。长这样:

Step 2:启动反向代理

在你要建立反向代理的内网服务器上,在浏览器中输入http://[local_ip]:[local_port],验证一下可以正常访问内网服务。

如果没问题,可通过ssh建立反向代理。命令如下:

1
ssh -Nf -R [cloud_port]:[local_ip]:[local_port] [cloud_user]@[cloud_ip]

各字段含义:

  • cloud_port:云端服务器上,向公网提供服务的端口号
  • local_ip/local_port:在企业内网提供服务的IP和端口,如果代理与内网业务部署在同一台服务器上,local_ip写为”localhost”
  • cloud_user:云端服务器的用户名
  • cloud_ip:云端服务器IP

配置示例:

我的环境如下:在代理服务器上,通过192.168.150.4:8011可访问内网服务,要将此服务代理到云端服务器的20081端口上。

在我的环境中,在代理服务器上输入的命令是:

1
ssh -Nf -R 20081:192.168.150.4:8011 user_d@[cloud_ip]

输入完成后,会需要你输入云服务器的密码,进行认证。

Step 3:在云服务器上开启代理转发

确保云端服务器的 SSH 配置文件 /etc/ssh/sshd_config 中允许反向隧道:

1
GatewayPorts yes

如果进行了修改,重启 SSH 服务:

1
sudo systemctl restart sshd

至此,代理就搭建完成了,通过公网IP就已经可以访问内网业务了。

TIPS:

  • 如果还访问不了,可以检查一下服务器和云服务商的防火墙配置,看是否有阻断端口的策略

进阶步骤

内网业务就这么大咧咧地开放到公网上了,难免会有一些安全性上的顾虑。我们能不能在访问之前,对用户先进行一个认证,认证通过后,才开放访问具体的业务呢?(有点零信任ZTNA的意思了)

大致的方案是:在云服务器上,通过nginx做一个简单的认证系统,认证通过后,跳转到代理的业务端口(上例中的20081)上。

Step 4:基于nginx搭建简单的认证系统

安装 Apache 工具包(用于 htpasswd)

1
sudo apt install apache2-utils

创建密码文件,使用 htpasswd 创建一个密码文件。这里会要求输入user_a的密码

1
sudo htpasswd -c /etc/nginx/.htpasswd user_a

配置nginx

编辑 Nginx 配置文件,假设配置文件路径为 /etc/nginx/sites-enabled/auth.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 5000;
server_name your_domain_or_IP;
location / {
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;

proxy_pass http://localhost:20081/;
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;
}
}

这份配置文件中,在5000端口上开启了一个认证服务,认证通过后,请求会被转发到代理的端口上,即:http://localhost:20081/;

重启 Nginx

1
sudo systemctl restart nginx

通过以上配置后,浏览器通过http://[cloud_ip]:5000访问,会跳出认证对话框,输入正确的用户名密码后,才可正常访问业务。

Step 5: 限制业务只能通过本地回环口访问

经过step 4的配置,通过http://[cloud_ip]:5000访问,可以进行用户的认证。但是通过http://[cloud_ip]:20081仍然可以直接访问业务,这就存在一个很大的漏洞:访问20081端口可以绕过认证,直接访问业务。

办法当然也是有的,只要限制业务只能通过本地回环口访问,即只能通过nginx的跳转来访问。这样,用户直接访问http://[cloud_ip]:20081就会被拦截下来。

使用 iptables:允许来自回环地址的流量

1
sudo iptables -A INPUT -p tcp -s 127.0.0.1 --dport 20081 -j ACCEPT

拒绝其他所有访问

1
sudo iptables -A INPUT -p tcp --dport 20081 -j DROP

至此,系统就搭建完成了,访问http://[cloud_ip]:5000,认证通过后,跳转到代理至公网上的内网业务。

小结

基于ssh和nginx搭建了如下的系统:

  • 使用ssh反向代理将内网业务代理至公网上
  • 使用nginx的认证,实现对公网上开放的端口访问的安全检查。