穷人的 ngrok 带有 tcp 代理和 ssh 反向隧道

2025-06-09

穷人的 ngrok 带有 tcp 代理和 ssh 反向隧道

2024 年更新

我最近开始使用 Cloudflare Tunnel。它对我来说效果很好,但我还在琢磨如何让我们的团队无需 Cloudflare 帐户也能使用它。

CF Tunnel 的另一个优点是您可以将身份验证页面添加到您的隧道站点,使其比一些随机 URL 更加私密。

2022 年更新

我们目前使用 sirtunnel.py 这个 Python 脚本来实现这一点。Sirtunnel 可以帮助调用 Caddy API 来创建新的 vhost,该 vhost 将监听传入的 http 请求,并将其转发到我们在运行之前建立的 ssh 隧道,从而使实现这一点变得更加容易sirtunnel.py

使用示例:-

ssh -t kamal@yourhost.ee -R 9000:localhost:9000 sirtunnel.py kamal-site 9000
Enter fullscreen mode Exit fullscreen mode

这将在 yourhost.ee 上打开远程端口 9000 并将其转发到本地端口 9000。然后我们在子域名 kamal-site.yourhost.ee 的 caddy 中设置新站点,并将请求代理到服务器上的端口 9000。

新网站现已开放访问:https ://kamal-site.yourhost.ee/

笔记

首次设置 sirtunnel 时,需要告诉 caddy 初始服务器配置:-

curl -X POST "http://localhost:2019/load" -H "Content-Type: application/json" -d @caddy_config.json
Enter fullscreen mode Exit fullscreen mode

caddy_config.json 如下所示:-

{
  "apps": {
    "http": {
      "servers": {
        "sirtunnel": {
          "listen": [":443"],
          "routes": [
          ]
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

******** 更新结束 *****

我们有一个私人开发服务器,大部分工作都在那里完成。但和如今所有现代 Web 应用一样,它也需要与其他应用通信(或被通信)。一种方式是通过 Webhook,你的应用可以通过 http 请求接收来自其他应用的通知。

要接收 Webhook,您的应用程序需要能够从远程应用程序访问。如上所述,我们的开发服务器位于私有网络上。我们只能通过 SSH 隧道从本地计算机访问它。例如,我将以以下方式开始我的一天:

ssh dev-server -L 8000:localhost:8000
Enter fullscreen mode Exit fullscreen mode

然后,当我python manage.py runserver在远程开发服务器上运行时,我可以从我的笔记本电脑以http://localhost:8000/ 的形式访问该 django 应用程序。但是我们当然无法通过这种方式接收来自 github 之类的 webhook。

解决此问题的一个常见方法是使用 ngrok 等服务,我可以在远程开发服务器上运行命令:-

ngrok http 8000
Enter fullscreen mode Exit fullscreen mode

ngrok 会给我们一个随机子域名,例如https://xxyymmdd.ngrok.com/,当通过互联网访问时,它会将请求转发到我们之前运行 ngrok 命令的 Django 开发服务器。它非常完美,我大部分时间都在个人项目中使用它。

但对于工作来说,我不太方便使用我们无法控制的第三方服务。一个解决方案是在完全暴露在互联网上的小型 VPS 上设置代理。我们可以使用 Caddy 或 nginx 来实现。但这是一个永久性的设置。我想要一个类似于上面提到的 ngrok 的临时方案,在我关闭与远程开发服务器的连接后,隧道就会自动断开。

第二个选项是启用 GatewayPorts 的 SSH 反向隧道。这样我就可以在开发服务器上运行如下命令:

ssh -R :8000:localhost:8000 proxy-server-ip
Enter fullscreen mode Exit fullscreen mode

这样就可以将代理服务器 8000 端口的连接转发到运行上述命令的主机。但这样做有一个问题,就是无法与代理服务器 8000 端口建立 TLS 连接。

为了更进一步,我们需要第二个工具——TCP 代理。如果你搜索一下,会发现有很多工具,所以我只选择了一个看起来能满足我需求的工具。我选择了tcpproxy,一个用 Golang 编写的小工具。它满足了我的所有需求。所以,当我想打开代理时,我的命令是这样的(在远程开发服务器上运行):

ssh -t -R 9000:localhost:8000 scarif.planet.rocks -l username "tcpproxy -laddr 0.0.0.0:8000 -raddr localhost:9000 -lcert /etc/letsencrypt/live/scarif.planet.rocks/fullchain.pem -lkey /etc/letsencrypt/live/scarif.planet.rocks/privkey.pem -ltls"
Proxying from 0.0.0.0:8000 to 127.0.0.1:9000
Enter fullscreen mode Exit fullscreen mode

这样就可以公开访问https://scarif.planet.rocks:8000/了,然后它会通过代理连接到我们在 8000 端口上运行的 Django 开发服务器。乍一看,这挺让人困惑的。但我只需要理解这个命令,就足以理解它的作用,而不需要阅读冗长的文档。让我们一步一步来:

  1. 打开从代理服务器的端口 9000 到本地机器(这里的本地是指执行命令的地方)端口 8000 的反向隧道。
  2. 在代理服务器上运行 tcpproxy,监听所有网络接口的 8000 端口。这里我们启用 TLS 协议并使用 letsencrypt 提供的证书。
  3. 将到端口 8000 的连接转发到本地接口上的端口 9000。
  4. 通过 ssh 反向隧道将端口 9000 上的连接转发回我们的本地机器。

这张不太好看的图表由 asciiflow 提供:-

                                                         XXXXXXX
                                                     XX        XX
+--------+              +------------+               X
|        |              |            |               XX            X
|        <--------------+            | <-------------+X  Internet    X
|        |              |            |                X              XX
+--------+              +------------+                 XX             X
        8000            9000        8000                XX          XXX
 local                    proxy-server                     X     XXX
                                                            X XX
Enter fullscreen mode Exit fullscreen mode

这种方法仍然缺少一个功能,那就是 ngrok 所具有的良好请求检查器。

Caddy 的替代方案

首先,我们在远程主机上生成所需的 Caddyfile:-

echo "https://scarif.planet.rocks {\n proxy / http://localhost:9000 {\n header_upstream Host dev.app.int\n }\n}" | ssh scarif.planet.rocks -l ubuntu -R 9000:localhost:80 "cat > Caddyfile"
Enter fullscreen mode Exit fullscreen mode

上述命令将立即结束,我们返回到本地主机,但它会将文件写入远程主机。该文件将写入如下:

https://scarif.planet.rocks {
 proxy / http://localhost:9000 {
 header_upstream Host dev.app.int
 }
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以通过 ssh,在远程反向转发端口并运行 caddy:-

ssh -t scarif.planet.rocks -l ubuntu -R 9000:localhost:80 "caddy"

Enter fullscreen mode Exit fullscreen mode

最终结果在以下几个方面变得更好了:

  • 我们可以通过https://scarif.planet.rocks/访问该网站。注意,caddy 绑定到了 80 端口,因为我们setcap允许 caddy 以非 root 身份绑定到较低的端口。
  • 本地 Apache 或 Django 无需进行任何更改,例如添加 ALLOWED_HOSTS 或设置 ServerAlias。这是因为我们在 Caddy 代理请求时设置了 Host 头。

缺点是我们必须分两步完成。我无法通过单个 ssh 命令完成写入配置文件和执行 caddy 的操作。出现以下错误:

Pseudo-terminal will not be allocated because stdin is not a terminal.
Enter fullscreen mode Exit fullscreen mode

问题

  1. 如果 SSH 连接断开,终止远程 tcpproxy 进程。ssh -t如果只是连接,使用 tcpproxy 似乎有效Ctrl+C,但如果连接断开(例如由于网络问题),代理服务器上的 tcpproxy 仍会继续运行,您必须通过 SSH 连接到该服务器并手动终止该进程。Stackexchange上的这个问题提供了一些解决方法,但我还没有尝试过。

常问问题

问:您知道 ssh GatewayPorts 吗?
答:是的,但如上所述,它不会为我们提供来自公共的 TLS 连接。

问:你试过 pagekite 吗?
答:试过,但是如果我想托管自己的服务器,我很难理解它是如何工作的。

鏂囩珷鏉ユ簮锛�https://dev.to/k4ml/poor-man-ngrok-with-tcp-proxy-and-ssh-reverse-tunnel-1fm
PREV
使用 Javascript 通过 DOM 操作 HTML 元素
NEXT
构建 Talk-to-Page:与任何网站 CoAgents Starter LangGraph Studio 进行聊天或对话故障排除