ssh 在所有设备上工作,但某些设备上的 scp 给出“连接已关闭”错误

ssh 在所有设备上工作,但某些设备上的 scp 给出“连接已关闭”错误

快速版本:

ssh&scp在我的台式机和笔记本电脑上工作。从我的 Android 手机上的 termuxssh可以运行,但scp不能运行。在这种情况下,我收到有关“连接已关闭”的错误。


扩展版

最初,当我问这个问题时,我在连接到我的 Fedora Linux 盒子时在 Android 手机上从 termux 运行 scp 时遇到了这个问题。我很困惑,因为ssh一直在工作,所以我提供了很多调试信息。由于我后来了解到这个问题与 termux 完全无关,而与 OpenSSH 版本无关,因此我尝试清理大量无关的信息转储并使其更易于访问,同时仍然保留相关错误消息以实现更好的 seo 匹配。

设备:

  • 设备 A(计算机):运行 openssh.x86_64 8.7p1-3.fc35 的 Fedora 35
  • 设备 B(计算机):运行 openssh.x86_64 8.7p1-3.fc35 的 Fedora 35
  • 设备C(手机):android上的termux(我最初未能捕获openssh版本,大概是v8.8或更高版本)

计算机 A 和 B 具有基本相同的sshd_config文件,所有 3 台计算机的文件内容基本相同~/.ssh/config

问题:

A->B、B->A、C->A 和 C->B 之间的 ssh 连接都工作正常。我对从任何一台计算机连接到我的手机不感兴趣,并且从未在其上配置 sshd,因此没有测试该场景。

但是,对于通过 ssh 运行的 scp,我遇到了以下情况:A->B 和 B->A 工作,而 C->A 和 C->B 失败并出现以下错误:

$ scp -rp "[email protected]:/home/desktop-user/Pictures/test.jpg" .
scp: Connection closed
 

调试:

  • 大量验证 ssh 文件权限和所有权、~/.ssh/config设置等。没有明显的问题。
  • 我(暂时)在 A 和 B 上禁用了 SELinux 和firewalld,但仍然遇到同样的问题。
  • 基于这个帖子,我将自定义的 id_rsa(和 id_rsa.pub)文件名重命名为使用默认名称,并编辑了相应的~/.ssh/config定义。不用找了。
  • 而不是(从 C)尝试接收/拉取文件遥控器,我尝试推送/发送文件遥控器。不用找了。
  • 从 C 开始,我确保所有包都是最新的。不用找了。
  • 我从 C 语言运行scp带有-v标志的命令来提供详细的调试信息。在编辑哈希值等之后,我添加了帕斯特宾。我看到的主要事情是密钥似乎被接受,但最后一行表明“debug1:退出状态 127”。在线结果似乎表明scp主机上不存在......但就我而言,它是。所以还是没有改变。

主机的 sshd 配置

同样,A 和 B 的配置基本相同

# grep -Pv '^\s*(#|$)' /etc/ssh/sshd_config
Include /etc/ssh/sshd_config.d/*.conf
Port 22
Ciphers [email protected],aes256-ctr
LoginGraceTime 1m
PermitRootLogin no
MaxAuthTries 4
MaxSessions 6
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
UseDNS yes
AcceptEnv LANG LC_*
Subsystem   sftp    /usr/lib/openssh/sftp-server
Protocol 2
DenyUsers root docker-user
MACs [email protected],hmac-sha2-512,[email protected],hmac-sha2-256
KexAlgorithms diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256

答案1

快速版本(TL;DR)

更新:对于那些只想快速了解并阅读最​​少内容的人。

知道:

  1. 这实际上与 Termux 无关 - 它会影响任何使用 OpenSSH 8.8+ 的 Linux。我也在 Fedora 35 上使用 OpenSSH 8.7p1 遇到了这个问题
  2. 虽然sftp在紧要关头可以工作,但它处理符号链接复制的方式与此不同,scp因此不要只是将其作为替代品放入脚本中。更好用rsync

递归地将目录从远程复制到本地:

# deprecated / insecure (see CVE link below) but works
scp -O -rp -P <port> "<user>@<host_ip>:<SRC_PATH>" "<DST_PATH>"
 
# secure / recommended by openssh - also works
rsync -saLPz --port <port> -e ssh "<user>@<host_ip>:<SRC_PATH>" "<DST_PATH>"

递归地将目录从本地复制到远程:

# deprecated / insecure (see CVE link below) but works
scp -O -rp -P <port> "<SRC_PATH>" "<user>@<host_ip>:<DST_PATH>"
 
# secure / recommended by openssh - also works
rsync -saLPz --port <port> -e ssh "<SRC_PATH>" "<user>@<host_ip>:<DST_PATH>"

这也适用于多路径,例如

# copy multiple paths
rsync -saLPz --port <port> -e ssh "<SRC_PATH_1>" "<SRC_PATH_2>" "<SRC_PATH_3>" "<user>@<host_ip>:<DST_PATH>"
 
# copy multiple paths from array
arrPaths=("<SRC_PATH_1>" "<SRC_PATH_2>" "<SRC_PATH_3>");
rsync -saLPz -e ssh "${arrPaths[@]}" "<user>@<host_ip>:<DST_PATH>"

根本原因

之后很多网上狩猎,我最后找到了答案这里- 感谢/感谢 manjaro 论坛用户 zbe 和 canyue980 找到了最初的修复并让我了解实际情况。

引用canyue980的话:

感谢所有提供帮助的人!我刚刚在上面的链接中找到了解决方案:

注意:自 OpenSSH 8.8 起,scp 实用程序默认使用 SFTP 协议。必须使用 -O 选项才能使用旧版 SCP 协议。

通过使用 -O 选项就可以了!

scp -O 文件名 目标目录

当然你也可以在~/.zshrc中为它添加一个别名,如下所示:

别名 scp="scp -O"

事实上,我的命令从:

scp -rp -P 1234 "[email protected]:/home/desktop-user/Pictures/test.jpg" .

到:

scp -rpO -P 1234 "[email protected]:/home/desktop-user/Pictures/test.jpg" .

它的效果就像冠军一样。将保留这一点,希望对其他人有帮助。


-O 的安全影响

使用的安全含义-O(即“Oh”,如旧的 - 不是零):根据发行说明对于 openssh 8.8,听起来主要风险是:

旧版 scp/rcp 通过远程 shell 执行远程文件名的通配符扩展(例如“scp host:* .”)。这样做的副作用是需要在 scp(1) 命令行中包含的文件名中对 shell 元字符进行双引号,否则它们可能会被解释为远程端的 shell 命令。

我个人并不认为这对于习惯旧命令并手动运行或通过家庭计算机上的受信任脚本运行的人来说是一个很大的风险。但您的风险评估/用例可能会有所不同 - 特别是对于使用未经验证/不受信任的脚本的业务系统或设置。对于某些人来说似乎就是这种情况RHEL 9 正在弃用 scp 协议,引用:

我们做出这一改变是因为 SCP 协议已有数十年历史,并且存在多种安全风险和问题,且没有直接的解决方案。新问题经常被报告(CVE-2020-15778是撰写本文时最新的,但我们不能确定它会是最后一个)并且正确修复它们相当困难,因为该协议本质上是经过身份验证的会话值得信赖的。

从上面的发行说明和链接,以及注释拱门维基,看来意图是完全弃用SCP(例如协议)支持诸如sftp和 之类的替代方案rsync(见下文)。我不清楚该公司的未来打算如何scp 命令是的,尤其是在短期内,但它似乎有可能甚至可能在未来被弃用。


推荐替代品

在脚本中使用sftprsync作为插入替换,可能rsync是更容易的插入替换,因为它不需要在主机上进行任何更改,而至少从 Fedora 35 开始sftp需要。

sftp只要您可以sftp <user>@<host_ip>像使用一样进行连接,下面的命令就应该有效ssh(以这种方式连接将使您进入sftp“shell”。要退出,请使用exit)。否则,您可能必须在主机上启用它。对于 Fedora 35,该过程如下:

  1. sudo cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.$(date +'%F_%T').bak
  2. sudo sed -Ei 's~^(Subsystem[ \t]+sftp[ \t]+)(/usr/lib/openssh/sftp-server)~#\1\2\n\1internal-sftp~g' /etc/ssh/sshd_config
  3. sudo systemctl restart sshd

完成后,您应该看到类似这样的内容(例如,sftp 服务器行应被注释掉,并添加一个带有 internal-sftp 的新行来取代它的位置):

sudo grep Subsystem /etc/ssh/sshd_config
#Subsystem  sftp    /usr/lib/openssh/sftp-server
Subsystem   sftp    internal-sftp

如果需要提高安全性,您还可以使用组和 chroot 来限制此操作,如图所示这里这里

注意:scp默认情况下复制符号链接的内容而不是它们作为一个符号链接。 Rsync 允许您通过使用-l复制为符号链接或-L复制符号链接引用的内容来控制此行为。 (archive)选项-a将默认为-l,除非-L也指定了。sftp将复制符号链接作为链接(例如指向的内容是不是已复制),并且截至撰写本文时尚未提供控制此行为的选项。您可以通过分别查看和-r下选项的文档来验证这一点。man scpman sftp

以下是一些大致等效命令的示例,这些命令都应该通过 SSH 并获取您的~/.ssh/config设置和身份:

递归地将目录从远程复制到本地:

scp -O -rp -P <port> "<user>@<host_ip>:<SRC_PATH>" "<DST_PATH>"
 
rsync -saLPz --port <port> -e ssh "<user>@<host_ip>:<SRC_PATH>" "<DST_PATH>"

递归地将目录从本地复制到远程:

scp -O -rp -P <port> "<SRC_PATH>" "<user>@<host_ip>:<DST_PATH>"
 
rsync -saLPz --port <port> -e ssh "<SRC_PATH>" "<user>@<host_ip>:<DST_PATH>"

sftp 语法更正

编辑:2022 年 9 月 13 日:今天在重新测试时注意到我所执行的命令sftp给出了错误,因此我将它们从上面移出并添加了一些免责声明。我个人并不推荐sftp作为替代品,scp因为我上面提到的在涉及符号链接时它们如何处理复制方面存在差异。

我对使用它作为脚本中的替代品特别感兴趣。如果您只需要交互模式,不介意为 制作“批处理”指令文件sftp,或者已经rsync安装在您将使用的所有计算机上,那么您可能不需要走这么远。我的以下笔记部分基于这个答案,我做了一些额外的测试。

我在这里最大的抱怨(除了sftp使用不同的上传和下载语法之外)是文档非常差。查看man sftp,语法没有记录根本不(这意味着虽然该工具以非交互方式使用,仅记录了它在批处理和交互模式下的使用,并且走出这些用例(虽然受支持,但没有记录)。手册页没有提供常见用法的示例,甚至没有像许多 GNU 工具那样提供更详细文档的网站。并且sftp -h提供了少于手册页。从字面上看,如果没有搜索引擎和这样的网站,我就会完全放弃sftp作为一个选择。

1.本地拉取(例如从本地终端,将文件从远程下载到本地)

这大多是直接的,scp除了上面提到的差异(例如符号链接)和稍微不同的语法之外,其工作原理有些相似。其中一种变体将导致交互式提示。

# Download a single file with same name to current dir
sftp -pC -P <port> "<user>@<host_ip>:<SRC_PATH>"
# or
sftp -pC -P <port> "<user>@<host_ip>:<SRC_PATH>" .
 
# Download a single file to name and path in "<DST_PATH>"
sftp -pC -P <port> "<user>@<host_ip>:<SRC_PATH>" "<DST_PATH>"
 
# Download a folder and all of its contents,
# keep the same name as the remote and save to current dir
# note that any symlinks will copy only the symlink itself
# and NOT the data the link references
sftp -rpC -P <port> "<user>@<host_ip>:<SRC_PATH>" .
 
# Now if you omit "<DST_PATH>" entirely like you can with
# the single-file version (first command in this code-block)
# you instead drop to an interactive prompt and have to type
# type 'exit' to get back to your bash prompt
# e.g. AVOID THIS IN NON-INTERACTIVE SCRIPTS!!!
sftp -rpC -P <port> "<user>@<host_ip>:<SRC_PATH>"
Connected to <host_ip>.
Changing to: <SRC_PATH>
sftp> 
sftp> exit
  
# Download a folder and all of its contents,
# to name and path in "<DST_PATH>"
# note that any symlinks will copy only the symlink itself
# and NOT the data the link references
sftp -rpC -P <port> "<user>@<host_ip>:<SRC_PATH>" "<DST_PATH>"

2.本地推送(例如从本地终端,将文件从本地上传到远程)

首先,如果您只是尝试反转参数,就像我原来的答案一样,那么您将收到连接错误。为什么sftp上传语法与下载语法不一致?你的猜测和我的一样好……我本人就是这种方法的忠实粉丝。但如果您尝试一下,会发生以下情况(注意:此输出来自两台运行 Fedora 35 且正确配置了 ssh 的计算机,scp并且rsync ... -e ssh工作正常,并且不会提示进行身份验证)。

# single file
sftp -pC -P <port> test.txt "<user>@<host_ip>:<DST_PATH>"
ssh: Could not resolve hostname test.txt: Name or service not known
Connection closed.  
Connection closed

# dir
sftp -rpC -P <port> "<SRC_PATH>" "<user>@<host_ip>:<DST_PATH>"
ssh: Could not resolve <SRC_PATH>: Name or service not known
Connection closed.  
Connection closed

答案我链接到上面,正确的方法如下

# push a single file from local to remote
sftp -pC -P <port> "<user>@<host_ip>:<DST_PATH>" <<< 'put test.txt'
# or
echo 'put test.txt' | sftp -pC -P <port> "<user>@<host_ip>:<DST_PATH>"

# push a entire dir (and its contents) from local to remote
# notice the inner single quotes to escape <SRC_PATH>
# for enclosing paths containing whitespace
sftp -rpC -P <port> "<user>@<host_ip>:<DST_PATH>" <<< "put '<SRC_PATH>'"
# or
echo "put '<SRC_PATH>'" | sftp -rpC -P <port> "<user>@<host_ip>:<DST_PATH>"
# or
printf 'put "%s" "%s"\n' "<SRC_PATH>" "<DST_PATH>" | \
sftp -rpC -P <port> "<user>@<host_ip>"

我在我的脚本中发现使用类似的语法进行下载,例如

# pull a entire dir (and its contents) from remote to local
# notice the inner single quotes to escape <SRC_PATH>
# for enclosing paths containing whitespace
printf 'get "%s" "%s"\n' "<SRC_PATH>" "<DST_PATH>" | \
sftp -rpC -P <port> "<user>@<host_ip>"

不太令人困惑,但以上所有内容都应该有效。

要一次复制多个路径,可以使用数组(或者您可以创建一个批处理文件,put在单独的行上列出每个命令并将其传递给sftp使用-b <batchfile>选项)。

# copy multiple paths from array
arrPaths=("<SRC_PATH_1>" "<SRC_PATH_2>" "<SRC_PATH_3>");
printf 'put %s\n' "${arrPaths[@]}" | sftp -rpC -P <port> "<user>@<host_ip>:<DST_PATH>"

2022 年 10 月 10 日更新:在遇到rsync包含空格的副本的一些问题后,我更新了命令以使用-s选项(在这个答案

然而,我注意到 rsync 与scpand 的不同之处sftp(在处理空格方面)是,scp对于包含内部引号的路径总是很好,例如

scp -rp "remoteuser@remotehost:'/tmp/rsync-test/stuff from server/the data.txt'" "."

然而,如果你在 中这样做rsync,你会得到一个错误:

$ rsync -saXULz --progress -e ssh \
  "remoteuser@remotehost:'/tmp/rsync-test/stuff from server/the data.txt'" "."
 
receiving incremental file list
rsync: [sender] change_dir "/home/remoteuser/'/tmp/rsync-test/stuff from server" failed: No such file or directory (2)
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1839) [Receiver=3.2.5]
rsync: [Receiver] write error: Broken pipe (32)

解决这个问题非常简单,但如果您像scp我一样习惯使用内部引号路径语法,则可能不直观:只需使用-s并删除内部引号。rsync足够聪明来处理它并且它确实有效。

因此,上面出错的命令只需改为:

$ rsync -saXULz --progress -e ssh \
  "remoteuser@remotehost:/tmp/rsync-test/stuff from server/the data.txt" "."

一切都应该很好(假设scp在同一路径上工作正常)。

相关内容