ssh 通过(可变数量的)中间跃点执行一些命令

ssh 通过(可变数量的)中间跃点执行一些命令

我需要想法来创建一种通过多跳远程执行(任何)命令的方法。主要问题是中间跳数是可变的!所以我需要 1) 描述服务器的“拓扑”,然后 2) 有一个函数?/脚本可以在所有服务器上执行一些命令,根据需要使用中间跃点到达最终目的地。

拓扑示例:(但这也有所不同)

server A communicates with server B directly.
servers C and D are "behind" B.
server E is behing C
etc.... (many servers, many indirect ways to reach some of them, some have multiple ways)

Then from A, you can execute "some command" on E with:
    ssh foo@B ssh bar@C ssh baz@E "some command"
 or you can build intermediary tunnels connecting A to **C** and then:
    ssh -P 1234 baz@E "some command"

我有一些“主跳”可以直接到达很多服务器

我希望能够通过一个不太复杂的命令在所有 5 台服务器上执行一个(可能很复杂的)命令,允许我(在脚本中)执行类似的操作

do_on_all_servers "for dir in /something* ; do cd \"\$dir\" && something using $thisLOCALvariable ; done"
# with an easy way to say "from X, ssh to specificuser@Y using this ssh command and this username" to describe each intermediary hops
# dir is a remote variable, thisLOCALvariable is at the initiating script level, and the amount of "\" needs to be adjusted if going through multiple hops...

附加约束:没有 GNU 工具,没有“nc”,只是普通(非常)旧的 ssh 和 awk 以及类似的旧工具,以实现可移植的方式。可移植性必须很高(必须避免使用漂亮的“1990?”选项和工具)。我不想通过在最终目标上复制脚本并通过该脚本的多个跃点进行 ssh 来完成此操作,但如果您认为它更简单/更好,这也可以。

现在我正在使用变量来连接 ssh ....但这对解决“我需要多少”问题没有帮助。

我的想法包括:将包含“shh user@host”的变量串在一起。或者也许使用这个非常简洁https://stackoverflow.com/a/15198031/1841533隧道方式,如果我能正确地做到这一点,则允许使用正确的本地端口“直接”到达最终目的地(但不确定这种方式在所有跃点上都可用)进行 scp 等。

答案1

您可以编写/使用 shell 函数包装ssh来生成自定义配置文件,该文件使用嵌套ProxyCommands来到达最终主机。然后该函数将调用ssh-F指向生成的临时配置文件。

像这样的东西:

sssh() {
  TMPF=$(mktemp);
  echo -n "$1" |
  awk '
    BEGIN {RS=" "}
    { split($0,a,":"); u=a[1]; p=a[3]; h=a[2]
      print "\nHost "h"\n\tUser "u"\n\tPort "p;
      if (hop) print "\tProxyCommand ssh -q -W %h:%p "hop;
      hop=h }
    END {print "\nHost *\n\tControlMaster auto\n\tControlPath /tmp/%r@%h:%p"}
  ' > $TMPF;   TMPA=($1); TMPAA=(${TMPA[-1]//:/ }); TMPH=${TMPAA[1]}; shift;
  ssh -F $TMPF $TMPH $@;
  rm $TMPF; }

当这样运行时:

sssh "user1:so.me.ho.st:1234 user2:router.internal:2345 user3:device.internal:3456" do_complex_command

会生成一个像这样的 tmp 文件:

Host so.me.ho.st
    User user1
    Port 1234

Host router.internal
    User user2
    Port 2345
    ProxyCommand ssh -q -W %h:%p so.me.ho.st

Host device.internal
    User user3
    Port 3456
    ProxyCommand ssh -q -W %h:%p router.internal

Host *
    ControlMaster auto
    ControlPath /tmp/%r@%h:%p

最后运行:

ssh -F /tmp/tmp.IUVSRrer45 device.internal do_complex_command

这将do_complex_command在最后一个“最内部”主机上执行。如果您需要在所有中介上执行命令,则必须稍微调整该功能。

答案2

我找到了一个相当可移植的解决方案(不需要对 ssh 配置文件进行任何编辑,并且很容易包含在调用脚本本身中,并且非常易于使用):只需将 ssh 串在一起,最后一个调用 shell ( ksh 或 bash 对于“有点旧”的 UNIX 运行良好):

echo "for remotedir in /some/path/*/ ; do \
      cd \"\$remotedir\" && \
      something using $thisLOCALvariable \
      ; done" | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash"
# note: the 'obvious' approach: 
#     ssh u1@h1 ssh u2@h2 "cmd1 ; cmd2; ..."
#  does NOT work : it would execute "cmd2; ..." on h1, not on h2 !!!
#  only cmd1 would be executed on h2 ... BE CAREFUL ! (at least on a not-too-old cygwin)

这个例子表明我们可以混合本地变量和远程变量......并且需要“转义”远程变量,以及远程双引号等......需要小心以确保我们做到我们的意思去做。

但它非常方便,因为即使我们将 3 个或更多 ssh 嵌套串在一起,总是有相同的(只有 1!)级别的引用,这非常方便......它$(cmd)与旧的`(反引号)相比带来的类似)的做法:它简化了书写,而不必考虑嵌套的层次。

警告:将其作为脚本的参数(就像我在问题中问的那样on_all_servers "for dir in /something* ; do cd \"\$dir\" && something using $thisLOCALvariable ; done":)有点难以弄清楚,因为调用 shell 首先解释“\”和引号...所以现在我使用命令-行版本(或带有脚本的类似构造,但不是从其参数获取命令行的脚本)...

我仍然保留这个问题,希望有人能得到更好的解决方案(或对此的改进,即:允许将类似的命令作为参数传递给“调用脚本”)

编辑:我还发现了如何使用这种方法进行远程 tar 提取!这并不简单,因为需要将 stdin 提供给远程 shell,一旦到达正确的目录,然后本地 tar cf - 需要转发到远程 tar 的正确位置:这里是:

# the easy one, nothing special needed: from remote to local:
echo "cd /remote/path && tar cf - *" | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash" | ( cd /local/path && tar xvf - )

# and the hard one: the other way around: needs a trick:
( cd /local/path && tar cf - * ) | { { echo "cd /remote/path/ && tar xvf -" ; cat ;} | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash" ;}

#once again: this trick allows you to have 1, 2, or any level of ssh.
# PLEASE NOTE: I just run bash, and feed it the command, to avoid
#     the command to be split between the remote hose and the first hop!
#  ex of the way the 'obvious' way fails:
#       ssh user1@host1 ssh user2@host2 "hostname ; hostname;"
#    will return not twice the name "host2", but "host2" then "host1" !! 
#    which means the first part of the command was executed on the last hop,
#    but then the rest of it was executed on the first hop!!! DANGEROUS!
#    that is why I choose instead to use that way:
#       echo "hostname; hostname" | ssh user1@host1 ssh user2@host2 "bash"
#    this one properly executes the full (complex) command on host2

相关内容