端口映射

端口映射 背景

  • 公网服务器上有些服务实际上没有必要直接暴露在公网端口,比如各种dashboard,因为仅仅是自己查看即可,为了尽量避免端口暴露,从而多大被攻击面,尝试使用ssh端口映射实现跨防火墙的服务访问
  • SSH端口转发也称作SSH隧道,通过SSH登陆之后,在SSH客户端与SSH服务端之间建立了一个隧道,从而进行通信。SSH隧道是非常安全的,因为SSH是通过加密传输数据的(SSH全称为Secure Shell)
分类
  • ssh端口映射可分为三类:
    • 本地端口映射
      • 将发送到本地端口的请求,转发到目标端口,一般用于在本地安全的访问放在公网服务器的一些私密服务,比如数据库服务,而不用在公网服务器的防火墙中打开该服务的端口
    • 远程端口映射
      • 将发送到远程端口的请求,转发到目标端口,一般用于做内网穿透,即将内网中的服务映射到公网服务器的某端口,从而实现从公网访问内网的服务
    • 动态端口映射
      • 可用于科学,使用远程主机作为proxy,进行请求转发
autossh
  • 创建SSH隧道实际上使用ssh命令即可,但是该命令创建的隧道服务在网络波动或者网络断开的情况下就会断开连接,很不稳定,很难用在生产环境,并且其对于SSH隧道的支持也并不完善,配置起来相对繁琐。而autossh命令则是专门为构建SSH隧道构建的,支持对SSH连接进行监控,支持自动重连,大大提升SSH隧道的稳定性
实例
  • 这里以本地端口映射为例进行说明,如果要实现其他类型的端口映射可以参考下边的链接进行尝试
  • 假设这样一个背景:
    我有一个dashboard服务部署在腾讯云的公网服务器A,同时在我的内网开发环境中还有一台服务器B。因为该dashboard服务仅仅是自己做监控用的,为了安全期间,没必要在腾讯云的防火墙设置中专门为该dashboard开一个端口出来,所以考虑使用SSH端口映射。假设公网服务器A的IP是IP_A,服务器B的IP是IP_B,服务器A的用户名是root,服务器A的SSH服务的端口就是默认的22,目的是要把服务器A上监听在60031这个端口的服务映射到服务器B的50031端口
准备工作
  • 对公网服务器A与内网服务器B统一做如下配置:
    # 编辑文件 nano /etc/ssh/sshd_config # 添加下述配置 GatewayPorts clientspecified # 重启ssh服务 service sshd restart

  • 在内网服务器B中创建SSH秘钥对,如果有的话(~/.ssh/文件夹下),就不用创建了,可以直接用,若没有使用ssh-keygen命令创建,随后将服务器B的公钥~/.ssh/id_rsa.pub的内容复制到公网服务器A的authorized_keys文件中(若没有,则创建一个,位置是~/.ssh/authorized_keys
  • 在服务器B上执行ssh root@IP_A -p 22,可能会有对话询问是否将服务器A添加到本地记录当中,键入Y确定即可,如果能成功的通过SSH连接到服务器A,则准备工作完毕
部署端口映射
  • 博主本人比较喜欢干净的宿主机环境,因此直接使用Docker部署autossh服务,将该容器部署在服务器B上,实际上该容器将服务器A的端口映射到容器的某个端口,然后又可以通过Docker自身的端口映射,将其映射到宿主机的50031端口,使用的镜像是autossh Docker Image
  • 在服务器B上使用Docker Compose构建服务,配置文件autossh.yaml如下:
    version: '3.7'services:ssh-local-forward: image: jnovack/autossh container_name: autossh-ssh-local-forward environment: - SSH_REMOTE_USER=root - SSH_REMOTE_HOST=IP_A - SSH_REMOTE_PORT=22 - SSH_BIND_IP=0.0.0.0 - SSH_TUNNEL_PORT=50031 - SSH_TARGET_HOST=127.0.0.1 - SSH_TARGET_PORT=60031 - SSH_MODE=-L restart: always ports: - 50031:50031 volumes: - /root/.ssh/id_rsa:/id_rsa

    • 替换IP_A的值
    • SSH_BIND_IP指定的实际上是要将服务器A的端口服务映射到服务器B的哪个网络接口上,一般就是0.0.0.0即全部接口
    • SSH_TARGET_HOST指的是将服务器A的哪个网络接口上的服务进行映射,一般是127.0.0.1,可根据场景适时更改
    • 注意ports指定了将容器的端口服务最终映射到了宿主机上,这样访问起来更加方便些
    • volumes指定映射了服务器B上的私钥的位置,如果不是root用户,则灵活更改即可
  • 执行docker-compose -f autossh.yaml up -d即可构建SSH隧道,实际上执行的命令是autossh -M 0 -N -o StrictHostKeyChecking=no -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -t -t -L 0.0.0.0:50031:127.0.0.1:60031 -p 22 root@IP_A
  • 此时则可以通过访问服务器B的50031端口来实现对服务器A上的60031端口的服务进行访问,而不用再服务器A的防火墙开放60031端口
其他
  • 下面以纯命令行方式介绍其他类型的端口映射,如果要使用Docker,直接参考其DockerHub页面,仿照着来即可
本地端口映射
  • 接续上边的背景,可以使主机B作为中转,最终实现通过与主机B同一内网的主机C(IP为IP_C)的某端口访问主机A,只需要做下边的配置(主机B上执行)
    autossh -M 0 -N -o StrictHostKeyChecking=no -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -t -t -L IP_C:50031:127.0.0.1:60031 -p 22 root@IP_A

    • 当然,要实现设置主机C顺利SSH连接到主机A,参考前边的准备工作部分
  • 同理的,可以通过以与主机A同一内网的服务器D(公网IP为IP_D)为中转,实现主机A到主机B的端口映射,假设主机A的内网IP是IP_A_LAN
    autossh -M 0 -N -o StrictHostKeyChecking=no -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -t -t -L IP_C:50031:IP_A_LAN:60031 -p 22 root@IP_D

    • 当然,要实现设置主机B顺利SSH连接到主机D,参考前边的准备工作部分
远程端口映射
  • 背景:
    没有公网IP的内网主机B部署了监听在3000端口的web服务,需要通过公网主机A去代理此服务,以将服务暴露到公网主机A的4000端口,供任意其他主机访问
  • 准备工作同理
  • 在内网主机B上执行
    autossh -N -f -M ${监听端口} -i /root/.ssh/id_rsa -R 0.0.0.0:4000:localhost:3000 root@IP_A -p 22

    • 监听端口用来执行对SSH隧道的监听,以实现隧道保活
      • autossh会默认占用此端口以及此端口+1的端口,注意不要有端口冲突
    • -f 表示后台运行
    • -N 不执行远程命令,只进行端口转发,意思就是指进行端口转发而不接入远程shell
    • -R 表示是远程端口映射(反向代理)区别于-L本地端口映射(正向代理)
    • 一般来说验证autossh有没有生效的方法就是查看进程中有没有对应的ssh命令,ps -aux | grep ssh看看除了autossh之外,有没有自动执行其他的ssh命令,如果有的话,就是执行成功了,如果失败可以尝试更换 -M 后的端口
    • 仿照上一节的本地端口映射的补充内容,这里也可以灵活设置
动态端口映射
  • 准备工作同理
  • 对于本地端口转发和远程端口转发,都存在隧道两端的服务器的两个一一对应的端口,而动态端口转发则只是绑定了一个本地端口,而目标地址:目标端口则是不固定的,由发起的请求决定
  • 背景:
    可以将在本地主机B发起的请求,转发到远程主机A,而由A去真正地发起请求(对主机A的本机服务发起请求,或者是其他A能访问到的服务)
  • 在内网主机B上执行:
    autossh -N -f -M ${监听端口} -i /root/.ssh/id_rsa -D IP_B:2000 root@IP_A -p 22

    • 2000端口可以认为是B上的代理服务的端口
    • IP_B是B的内网IP,可以认为是B上的代理服务的地址
    • IP_A是A的公网IP
  • 内网B上应用发起的请求,需要由Socket代理(Socket Proxy)转发到内网代理端口(即上边的2000)。以Firefox浏览器为例,配置Socket代理需要找到首选项 → 高级 → 网络 → 连接 → 设置
    【端口映射】端口映射
    文章图片

    • Firefox浏览器发起的请求都会转发到2000端口,然后通过SSH转发到真正地请求地址。假如服务器A上有一个Node.js服务,则在Firefox中访问localhost:${Node.js服务端口}即可以访问该服务
参考
  • autossh
  • autossh Docker Image

    推荐阅读