【问题标题】:How to automatically switch ssh config based on local subnet?如何根据本地子网自动切换 ssh 配置?
【发布时间】:2022-01-07 22:56:16
【问题描述】:

当我在某个网络上(子网是 10.10.11.x)时,我需要跳过中间主机才能到达我的目的地,因为我无法更改目标端口并且我可以退出的端口有限受限网络。我成功使用了如下的 ssh 配置:

Host web-direct web
    HostName web.example.com
    Port 1111

Host web-via-jump jweb
    HostName web.example.com
    Port 1111
    ForwardAgent yes
    ProxyCommand ssh -p 110 -q relay.example.com nc %h %p

通过 jumpbox 会对性能造成重大影响,因此我需要在大多数不需要的情况下避免它。切换 ssh/scp/rsync 主机昵称对于交互使用来说很好,但是有一些自动化/脚本化的任务非常痛苦。

我的 shell 在网络转换过程中保持打开状态,因此启动 (.zshrc) 机制无济于事。

我曾想过运行一个脚本来轮询受限子网并通过修改 .ssh/config 文件来自动切换,但我什至不确定是否存在缓存问题。在我实施之前,我想我会问是否有更好的方法。

根据源主机子网检测换出 ssh 配置的最佳方法是什么?

在伪配置中,类似于:

if <any-active-local-interface> is on 10.10.11.x:
    Host web
        HostName web.example.com
        Port 1111
        ForwardAgent yes
        ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
else:    
    Host web
        HostName web.example.com
        Port 1111
endif

【问题讨论】:

    标签: ssh


    【解决方案1】:

    你可以使用Matchexec选项来执行shell命令,所以你可以这样写:

    Match host web exec "hostname -I | grep -qF 10.10.11."
        ForwardAgent yes
        ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
    Host web
        HostName web.example.com
        Port 1111
    

    Match 选项布尔逻辑可以短路,因此将host 放在首位以跳过其他主机的exec 术语。试试ssh web -vvv 看看Match 的实际运作逻辑。

    【讨论】:

    • 我会使用 fgrep 或 grep -F,你的测试会匹配我不认为你期望的东西,比如 10.10.110.
    • 当然。它更像是如何工作的概念。弄清楚细节留给读者。虽然更新了答案。
    • 微不足道的优化:使其成为grep -Fq-q 抑制输出,这是我们想要的,因为我们只对退出状态感兴趣。
    • 我没有比较本地IP地址,而是使用Match host ... exec "ip route | grep ^192.168.123.0/24"来检查是否可以访问目标网络
    • 当有一个答案可以完成您想要的事情而无需任何黑客或半生不熟的外壳脚本时,这是最酷的感觉。谢谢!
    【解决方案2】:

    基于 Fedor Dikarev 的 the answerMike created 一个名为 onsubnet 的 bash 脚本:

    #!/usr/bin/env bash
    
    if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]  || [[ "$1" == "" ]] ; then
      printf "Usage:\n\tonsubnet [ --not ] partial-ip-address\n\n"
      printf "Example:\n\tonsubnet 10.10.\n\tonsubnet --not 192.168.0.\n\n"
      printf "Note:\n\tThe partial-ip-address must match starting at the first\n"
      printf "\tcharacter of the ip-address, therefore the first example\n"
      printf "\tabove will match 10.10.10.1 but not 110.10.10.1\n"
      exit 0
    fi
    
    on=0
    off=1
    if [[ "$1" == "--not" ]] ; then
      shift
      on=1
      off=0
    fi
    
    regexp="^$(sed 's/\./\\./g' <<<"$1")"
    
    if [[ "$(uname)" == "Darwin" ]] ; then
      ifconfig | fgrep 'inet ' | fgrep -v 127.0.0. | cut -d ' ' -f 2 | egrep -q "$regexp"
    else
      hostname -I | tr -s " " "\012" | fgrep -v 127.0.0. | egrep -q "$regexp"
    fi
    
    if [[ $? == 0 ]]; then 
      exit $on
    else
      exit $off
    fi
    

    然后在他的.ssh/config 文件中,他使用Match exec 就像Jakuje's answer

    Match exec "onsubnet 10.10.1." host my-server
        HostName web.example.com
        Port 1111
        ForwardAgent yes
        ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
    
    Match exec "onsubnet --not 10.10.1." host my-server
        HostName web.example.com
        Port 1111
    

    【讨论】:

      【解决方案3】:

      匹配 IP 的最简单方法是使用这样的正则表达式:

      Match exec "[[ '%h' =~ ^10\.10\.11\. ]]"
         ... 
      

      您可以扩展它以匹配其他 IP:

      Match exec "[[ '%h' =~ ^10\.10\.1(1|2)\. ]]"
         ... 
      

      【讨论】:

      • 我在 ssh 配置文档中看到%h The remote hostname在 TOKENS 部分,但 IP 匹配是针对本地 IP 地址的。这将如何工作?这是不同的 %h 吗?
      • 你好 Mike,对 %h 是远程主机。双引号之间是shell语法。在这种情况下正则表达式匹配。如果我运行ˋssh 10.10.11.3ˋ 它匹配并且后面的所有指令(如代理命令)都将被分配。还有什么问题吗?最好的奥利
      【解决方案4】:

      我为此使用了以下函数:

      function ssh() {
        network=`networksetup -getairportnetwork en0 | cut -d: -f2 | tr -d [:space:]`
        if [ -n "$network" -a -f $HOME/.ssh/config.$network ]; then
          /usr/bin/ssh -F $HOME/.ssh/config.$network "$@"
        else
          /usr/bin/ssh "$@"
        fi
      }
      export -f ssh
      

      因此,我需要为每个需要自定义解决方案的 WiFi 网络单独配置文件。它现在对我有用,但它很难看。我只能将它作为一个想法来推荐,而不是最好的解决方案。

      我很高兴知道任何更好的解决方案。

      【讨论】:

      • 我猜这不涉及 rsync、scp 和/或 git。你试过了吗?
      • 只是别名,不影响其他程序。对于scprsync,制作类似的别名很容易,而对于git 则不确定这是否容易。 @Jakuje 使用 Match exec 提供解决方案,我将重写我的配置,因为它看起来像更好的解决方案。
      • 如果您的 Match exec 解决方案有效,请评论您的经验。除非您想使用真正的正则表达式,否则我会将 grep 更改为 fgrep。
      【解决方案5】:

      您可以查看 DHCP 为您提供的域后缀,而不是 checking the subnet CIDR

      • 如果您在 Intranet 外部尝试使用跳转框,则此方法比检查 reserved private 分配中的 IP 范围更可靠(例如,您的家庭网络或远程工作位置使用与 @ 相同的块987654324@的内网)。

      • 如果您的 Intranet 在任何地方都使用相同的 DNS 后缀并且您尝试遍历 Intranet 中的子网,则此方法很有用。如果这是您的情况,请使用Jakuje's solution

      Match Host web Exec "hostname -d | ! grep -q -E '^example\.com'"
          ForwardAgent yes
          ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
      Host web
          HostName web.example.com
          Port 1111
      

      如果您没有具有-d 选项的hostname 的最新版本(例如,您使用的是MacOS),您可以直接查询resolve.conf

      Match Host web Exec "! grep -q -E '^\s*search[ \t]+example\.com' /etc/resolv.conf"
          ...
          ...
      

      【讨论】:

        【解决方案6】:

        我对这个问题的解决方法如下:

        Host myserver
            HostName [internal IP]
            ...
            
        Match Host [internal IP] !Exec "nc -w1 -q0 %h %p < /dev/null"
            ProxyCommand ssh jumphost -W %h:%p
        

        首先要有 Host myserver 行很重要,这样 SSH 客户端就会知道 IP 地址。

        Match 表达式中,

        • Host 选项与该 IP 匹配。 (它接受*,因此您也可以匹配/8、/16 或/24 子网。)
        • Exec 选项执行netcat,超时时间为 1 秒,以测试 SSH 端口是否打开。如果不是,则使用ProxyCommand

        这是我发现实际测试是否需要跳转主机的最清晰方法。如果您的网络滞后,您当然可以设置更高的超时时间。详情请见man ssh_config

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-09
          • 2013-11-17
          • 1970-01-01
          • 2015-06-21
          相关资源
          最近更新 更多