美文网首页
NGINX+SSH 暴露本地端口

NGINX+SSH 暴露本地端口

作者: 寂寞的原子 | 来源:发表于2019-05-19 21:23 被阅读0次

    我们在 web 开发中,经常会有暴露本地端口的需求:

    • 网站开发到一半,希望分享一下当前成果;
    • 某app小程序的开发,只能通过生产环境默认端口进行接口调试;
    • 通过公网进行联调;
    • ……

    ngrok 的出现让我们可以通过服务器的中转,快速暴露本地端口。那么 ngrok 方案有什么不足呢?

    • ngrok 在客户端和服务端都要安装;
    • ngrok 已经闭源,开始走商业化发展路线,不再适合我等屁民使用;
    • 使用自定义域名有诸多不便。

    定制方案

    我尝试寻找一个简单的方案,来实现部分 ngrok 的能力,满足我们开发调试过程中的大部分需求。

    内网穿透到公网

    SSH 提供了加密的端口流量转发能力,可以用来快速实现这个步骤。通过 SSH 可以把一个本地端口和远程端口映射起来,打通流量隧道。先看一下 SSH 的相关文档:

    -N      Do not execute a remote command.  This is useful for just forwarding ports.
    
    -R [bind_address:]port:host:hostport
    -R [bind_address:]port:local_socket
    -R remote_socket:host:hostport
    -R remote_socket:local_socket
    -R [bind_address:]port
            Specifies that connections to the given TCP port or Unix socket on the remote (server) host are to be forwarded to the local side.
    

    通过指定 -N,我们可以让 SSH 不执行远程命令,而只用于转发流量。

    通过指定 -R 远程地址:本地地址,即可实现端口流量转发。

    举个栗子:

    $ ssh -NR 2333:localhost:8080 server
    

    即把本地的 8080 端口映射到服务器的 2333 端口,由于没指定 bind_address,这个端口默认会绑定到本地环回地址(localhost)上,必须通过反向代理才能从外部访问。

    我们也可以绑定 bind_address0.0.0.0,让端口直接暴露到外部:

    $ ssh -NR 0.0.0.0:2333:localhost:8080 server
    

    这要求我们开启 OpenSSH 的 GatewayPorts 配置。这种情况下,我们就可以直接从外部访问 server_address:2333 了。

    但是,为了提供缓存、资源压缩、权限校验、域名控制等能力,一般来说我们更推荐使用 NGINX 反向代理来暴露端口。通过 NGINX 统一管理,可以在一个端口上服务多个站点,通过域名区分站点,这样我们就可以从默认端口提供服务,地址看上去更自然,同时还能满足某些 app 的特殊要求。

    绑定自定义域名

    如果没有固定的域名,我们每次开启服务,都需要提供一个新的 address:port 或者 ip:port 地址,这显然是很不友好的。ngrok 的免费版提供的就是一个随机域名。而通过 NGINX,我们可以很轻松地定制一个域名映射规则,然后只需要按照规则开启服务,就可以通过想要的域名访问到。

    这里有一个大前提,首先我们要有个域名,而且域名要泛解析到我们的服务器,如 *.gerald.win 添加 CNAME 记录到 gerald.win。只有解析到了我们的服务器,NGINX 才可以有发挥的空间。

    举个栗子,约定域名 tunnel2333.gerald.win 反代到服务器的 localhost:2333,以此类推,我们只要在打通隧道时映射到同样的端口号,就可以用同样的域名访问。NGINX 配置如下:

    server {
      listen 80;
      server_name "~^tunnel(?<port>\d+)\.gerald\.win$";
    
      location / {
        proxy_pass http://127.0.0.1:$port;
        proxy_set_header Host      $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_intercept_errors on;
      }
    }
    

    同样的,我们也可以通过 unix socket 文件名的约定来实现更加定制化的子域名,这里就不详细展开了。

    支持HTTPS

    随着大家对网络安全的重视,HTTPS 几乎已经成了网站的标配,很多接口都要求服务端必须为 HTTPS 协议,这也在无形中提高了我们的开发调试门槛。通过服务器的转发,这个问题也迎刃而解。

    作为普通开发者,我们就不考虑购买昂贵的 HTTPS 证书了。好在良心产品 Let's Encrypt 从 ACME v2 开始已经支持泛解析了,于是 Let's Encrypt 一把梭搞定。这里强烈推荐国人开发的脚本 acme.sh,证书生成过程不再赘述。

    有了证书,我们再配置一下提供 HTTPS 服务的 NGINX 配置,还可以给 HTTP 协议访问的页面做个 301 跳转:

    server {
      listen 80;
      server_name "~^tunnel(?<port>\d+)\.gerald\.win$";
    
      location / {
        return 301 https://$host$request_uri;
      }
    }
    
    server {
      listen 443 ssl http2;
      server_name "~^tunnel(?<port>\d+)\.gerald\.win$";
    
      ssl_certificate      /home/gerald/ssl/fullchain.cer;
      ssl_certificate_key  /home/gerald/ssl/gerald.win.key;
    
      ssl_session_cache shared:SSL:1m;
      ssl_session_timeout  5m;
    
      ssl_ciphers  HIGH:!aNULL:!MD5;
      ssl_prefer_server_ciphers   on;
    
      location / {
        proxy_pass http://127.0.0.1:$port;
        proxy_set_header Host      $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_intercept_errors on;
      }
    }
    

    这样我们就实现了 HTTPS 协议的支持,而且无需为每个新域名单独配置。

    不足

    与 ngrok 相比,我们的简化方案其实还是有很多不足的,比如缺失了强大的 inspector,无法动态展示当前连接的用户、访问的页面和流量。这些功能属于锦上添花了,理论上都可以在客户端进行实现,以后可以考虑增强一下。

    总结

    原理总结如下:

    • SSH 打通隧道
    • NGINX 反向代理
    • Let's Encrypt 实现泛解析 HTTPS 证书

    通过 SSH + NGINX,我们基本实现了本地端口的暴露,完美地支持了通过 HTTPS 协议来调试本地的应用。

    相关文章

      网友评论

          本文标题:NGINX+SSH 暴露本地端口

          本文链接:https://www.haomeiwen.com/subject/anqczqtx.html