从 Docker 容器内部访问远程 SSH 隧道

从 Docker 容器内部访问远程 SSH 隧道

我的家庭网络中,在 NAT 后面的 MacBook 上有一个 Web 服务器,服务于端口 80。我还有一个运行 Ubuntu 的可公开访问的服务器,我想从该服务器访问我的本地 Web 服务器,因此我打开了一个远程 SSH 隧道:

ssh -fnNT -R 8080:localhost:80 remote-host

成功了。在远程机器上,我可以curl localhost:8080得到预期的答案。

但是当我从docker容器内部尝试这个时,例如:

docker run -it --add-host host.docker.internal:host-gateway ubuntu:latest /bin/bash

# curl -vvv host.docker.internal:8080
* connect to 172.17.0.1 port 8080 failed: Connection timed out

我发现其他地方的“解决方案”,例如使用--network hostdocker 选项或让隧道在所有接口上监听(ssh -R 0.0.0.0:8080:localhost:80)由于安全问题不是一个选择。

从容器内部访问主机系统上的任何其他端口都没有问题,就像curl host.docker.internal:80主机的 Caddy 服务器的响应一样。

我尝试设置防火墙规则iptables -I INPUT -i docker0 -j ACCEPT(但并不真正了解其作用)但这并没有改变什么。

我尝试确保启用了 IPv6 数据包转发(net.ipv6.conf.all.forwarding=1在 中/etc/sysctl.conf)并且让隧道仅绑定到 IPv4 地址(使用选项-4),但没有成功。

我尝试使用接口的 IPdocker0作为绑定地址对于隧道(ssh -R 172.17.0.1:8080:localhost:80,当然已经GatewayPorts clientspecified在 sshd_config 中设置了)。然后我可以curl 172.17.0.1:8080成功地从主机系统访问,但仍然不能从容器内部访问。

一个可能的复杂因素是,我在服务器上使用ufw,仅允许端口 80、443 和我的 SSH 端口上的流量进入。当我 时sudo ufw disable,上述 curl 请求以Connection refused而不是终止timed out,我发现这很有趣。

我觉得我已经接近了。也许我可以设置一个 iptables 过滤器来使其工作?我没有使用过 iptables。如何对从容器内部到主机系统上的隧道端口的请求进行分类,是进入还是出去?还有其他想法可以调试此问题吗?

答案1

经过两周对网络、docker 网络以及 docker 如何与 iptables 交互的深入研究,我现在可以自信地回答我自己的问题了:

首先,这与 ssh 隧道无关。绑定到环回地址 127.0.0.1 的任何内容都只能从本地计算机本身访问,而不能从计算机网络接口外部访问。Docker 在这里算作“外部”,因为它有自己的网络(默认情况下在 172.16.0.0/12 范围内)。这是 docker 整个隔离概念的关键部分,它将容器与主机和彼此分开。

那么如何实现这一点呢?有以下几种选择:

  • 将您需要的任何服务绑定到docker网关ip:

    ssh -fnNT -R 172.17.0.1:8080:localhost:80 remote-host
    

    为了使其工作,你需要一个额外的 iptables 规则,例如:

    iptables -I INPUT -i docker0 -d 172.17.0.1 -p tcp --dport 8080 -j ACCEPT
    

    这允许来自默认 docker0 网络的流量到端口 8080 上的 docker 网关 ip。如果没有此规则,这些数据包将被丢弃。

  • 更简单一点,只需将其绑定到所有接口即可:

    ssh -fnNT -R 0.0.0.0:8080:localhost:80 remote-host
    

    这里的安全问题可能取决于您的个人用例和网络架构。但是,如果您有一个简单的 VPS 和一个公共 IP,并且您的 iptables INPUT 策略是 DROP,那么这不是问题。我在这里感到困惑,因为当您使用 docker 发布端口时-p 8080:80,docker 将创建一个 iptables 规则来接受来自主机外部的请求,因为它是默认不安全如果你将任何其他(非docker)服务绑定到0.0.0.0上的端口,它仍然会被阻止,直到你手动打开它。

  • 使用主机作为跳转代理,通过隧道进入容器。这需要您的容器运行 openssh 服务器,这本身就有点棘手,并且端口也需要在主机上发布,例如使用-p 127.0.0.1:2222:22。如果您正在使用 docker compose 并且不介意启动其他服务,您可以使用docker-openssh-服务器为了这。

    那么问题就只是:

    ssh -fnNT -R 0.0.0.0:8080:localhost:80 remote-container
    

    .ssh/config例如,与中的设置结合使用时

    Host remote-host
        HostName remote-host.example.com
        User peter
        Port 22
    
    Host remote-container
        ProxyJump remote-host
        HostName localhost
        User containerpeter
        Port 2222
    

    这将首先通过 ssh 进入远程主机,然后从那里打开远程隧道直接进入容器。当然,您可以向公众开放容器 ssh 端口并直接通过隧道进入那里,但跳转会更有趣。

  • 您可以按照建议干预 nat 表中的 iptables PREROUTING 链这里,将请求路由172.17.0.1127.0.0.1。但我还没有尝试过,所以不能保证结果。

  • 您可以使用一个名为的工具,socat如解释的那样这里,它可以进行 TCP 转发等许多操作。还没有尝试过这种方法。

如果您现在很想自己解决类似的问题,这里有一些方便的工具。

查看哪些进程监听哪个接口上的哪个端口。以下之一:

lsof -i -P -n | grep LISTEN
netstat -tulpn | grep LISTEN
ss -tulpn | grep LISTEN

通过 iptables 链跟踪请求:

# First insert a rule with target TRACE into the INPUT chain 
iptables -I INPUT -p tcp --dport 8080 -j TRACE

# Then look at the packets while they are going through the chains
xtables-monitor --trace

祝您社交愉快。

相关内容