SSH over HTTPS:通过反向代理实现SSH隧道
通过安装在Proxmox机器上的反向代理,也可以暴露对单个机器的SSH访问。
所需组件清单
一台安装了Proxmox的真实PC,作为"服务器"
一台安装了操作系统(OSX、Linux或Windows都可以)的真实PC,作为"客户端"
一个LAN网络,服务器和客户端都连接在其中
在服务器上运行的nginx和nginx-stream-module
在Proxmox中定义的VNET,CIDR为10.4.1.1/24,作为"vmnet"
在Proxmox中定义的一些VM,每个VM具有:
已安装的操作系统(Linux)
运行并暴露端口22的ssh-server
连接到内部vmnet
为VM选择域名名称,比如".fundom":每个VM都可以从客户端通过"ssh vm1.fundom"、"ssh vm2.fundom"等方式访问。
准备服务器
安装Proxmox,社区版就足够了
在Proxmox上设置VNET:在Datacenter -> SDN -> VNets中点击"Create",并将其命名为"vmnet"
为创建的vnet创建子网类:在Datacenter -> SDN -> VNets中选择创建的vnet,在Subnets中点击"Create"。子网CIDR是10.4.1.1/24,网关为10.4.1.1
创建一些VM:在Datacenter -> pve右上角点击"Create VM"或"Create CT"然后克隆VM
对每个VM,Hardware -> Add -> Network Device,并为其选择vmnet
对每个VM,在"Cloud-Init"中为vmnet设备分配IP
对每个VM,在Cloud-Init中设置用户并加载其SSH公钥
在服务器上打开shell并运行apt install nginx libnginx-mod-stream
在/etc/nginx/nginx.conf中设置stream { ... }部分,定义如何通过SNI反向代理到目标VM(vm1.fundom、vm2.fundom等)
为nginx的SSL生成自签名证书,并在nginx配置中引用它
通过systemctl reload nginx重新加载nginx配置
要生成自签名证书,使用命令:
- mkdir /etc/nginx/ssl
- cd /etc/nginx/ssl
- openssl req -x509 -newkey rsa:4096 -keyout self.key -out minihost.crt -sha256 -days 365
在/etc/nginx/nginx.conf中添加这些行:
- stream {
- ssl_preread on;
- map $ssl_server_name $ssh_backend {
- ## 这些是在这台机器上由Proxmox设置的VNET上运行的VM
- ~^vm1\.fundom$ 10.4.1.11:22;
- ~^vm2\.fundom$ 10.4.1.12:22;
- default unix:/run/nginx-blackhole.sock; # 只是丢弃其他任何东西的一种方式
- }
- server {
- listen 443 ssl;
- proxy_pass $ssh_backend;
- ssl_preread on;
- ssl_certificate /etc/nginx/ssl/self.crt;
- ssl_certificate_key /etc/nginx/ssl/self.key;
- ssl_protocols TLSv1.2 TLSv1.3;
- }
- }
准备客户端
安装ssh客户端
安装openssl工具
从服务器复制自签名证书
使用ProxyCommand设置.ssh/config
通过以下方式从服务器复制minihost.crt到客户端:
- scp root@192.168.1.10:/etc/nginx/ssl/minihost.crt ~/minihost.crt
其中192.168.1.10是服务器的IP,即运行Proxmox和nginx的地方
.ssh/config文件是:
- Host *.fundom
- ProxyCommand openssl s_client -quiet -verify_quiet -CAfile ~/minihost.crt -servername %h -connect 192.168.1.10:443
其中:
s_client子命令创建带有客户端协商的SSL隧道
告诉它安静地处理错误
将minihost.crt指示为CAfile,从服务器上nginx使用的自签名证书复制
使用SNI(-servername)作为主机名%d,由上面的Host行中的通配符.fundom匹配
连接到服务器IP 192.168.1.10的443端口,这是HTTPS的默认端口
幕后原理
通过这种设置,现在可以从客户端执行:
- ssh user@vm1.fundom
其中'user'是在内部VM中定义的用户名,该VM由Proxmox运行
这是发生的事情:
从客户端机器,ssh使用openssl s_client设置一个ssl通道,协商作为vm1.fundom的连接
nginx服务器将SNI vm1.fundom匹配为到10.4.1.11端口22的流
服务器可以访问192.168.1.1/xx接口和10.4.1.1/24 vnet
在验证CA签名后,数据流通过路由发送到目标VM
特性
这种技术不会将内部VM暴露给LAN,而只是暴露一些选定的服务,如果需要的话,还可以暴露一些选定的VM。
暴露到广域网
可以通过从出站路由器路由到Proxmox服务器的443端口,将此服务暴露到互联网。
通过这种方式,人们可以只暴露一个常规的https端口,但仍然能够使用ssh shell在给定的VM上工作。
一些ISP和一些政府正在阻止端口22,或者只是阻止SSH流量,因为它是安全的,某种程度上是可疑的。但通过暴露443上的SSL,您可以克服这种限制。
SSL连接通过添加证书验证增加了另一层安全性,所以您的客户端可以确保与正确的服务器通信,即使当您更新ssh证书或重新安装VM时也是如此。
只有一个端口
我在社交媒体上分享了视频,我收到了抱怨,因为"这不是ssh隧道",建议使用SSH隧道设置指南,它描述了ssh -L ...类命令。这个命令可以在proxmox服务器上打开一个端口,直接指向每个VM。ssh -L的一些缺点:
这需要打开与要映射/暴露的VM一样多的端口
proxmox服务器的管理员必须通过ssh访问每个VM,通过公钥或密码
它选择一个单一用户,或者如果更多用户想要访问VM,每个用户必须有自己的端口
其他选项可以是让每个用户"跳转"到Proxmox ssh服务器,然后进入VM的ssh。缺点:
想要访问内部VM的用户还必须访问Proxmox的VM
进一步改进
当双方(在这种情况下是VM和客户端)都准备好时,可以通过切换到UDP来实现额外的安全级别
使用UDP是大多数VPN实现的常见做法,实际上端口会自动从接收端(VM)和通信端(客户端)进行NAT
MOSH在低质量连接或连接切换的情况下也具有稳定性
我制作了一个视频来展示这个内容。
一些图表的源代码在GitHub仓库
