SCP 的工作原理

SCP 的工作原理

WinSCP 工具能够sudo在远程计算机上以scpsudo 用户身份执行。这让我想到,命令行版本scp也应该可以做到这一点。

那么,WinSCP 是如何执行这一操作的呢?

我不想通过具有用户暂存目录的解决方案。文件可能太大,我需要直接使用用户发布sudo。结果文件被复制过来,但复制到我的本地目录,而不是 sudo 目录。

一个解决方案可能是通过 ssh 手动进行管道传输

tar zcvf -  MyBackups | ssh user@server "cat > /path/to/backup/foo.tgz"

我猜想可以自定义执行 sudo 如下:

cat local.txt | ssh user@server "sudo -u sudouser cat > ~/remote_file"

唯一的问题是,通过这种方式,我需要解决很多已经使用基础实现的负担scp。出于这个和其他原因,我也不想使用这个解决方案。

我怎样才能让它scp直接复制到远程,使用sudo更改用户来获取其权限。

答案1

Scp 的工作原理是与远程服务器建立 ssh 连接,然后使用该连接scp在远程系统上执行另一个命令。本地 scp 实例和远程实例通过 ssh 链接进行通信以发送或接收文件。

OpenSSH scp 特别构建了一个要在远程系统上运行的 scp 命令字符串。然后它启动 ssh 来运行该命令。最终调用的 ssh 命令相当于以下内容:

/path/to/ssh [ssh options...] "scp [scp options...]"
  • /path/to/ssh 通常是 ssh 程序的内置路径。
  • [ssh options...] 是 ssh 程序的一个或多个选项。
  • “scp [scp options...]” 是一个包含 scp 命令及其在远程系统上运行的参数的单个参数。

OpenSSH scp 没有提供太多选项来改变远程 scp 命令的形式。没有办法让它调用除“scp”之外的其他命令,或者在“scp”部分前面插入“sudo”之类的命令。

正如您所注意到的,scp 确实有一个选项可以调用其他程序而不是 ssh。知道如何编写 ssh 包装程序并让 scp 调用包装程序而不是直接调用 ssh。包装程序必须检查其命令行参数以找到包含 scp 命令的参数,根据需要更改命令,然后使用更改后的命令行参数调用 ssh。

答案2

SCP 的工作原理

的内部工作scp依赖于 的两种未记录形式scp,即scp -fscp -t。它们基本上是监听 stdin 并通过 stdout 生成数据的远程服务器。这些进程使用与 sftp 类似的协议。更多信息可以在Oracle 博客

启动时scp,它将首先启动 ssh 会话以启动scp -tscp -f。然后,您的本地scp进程将通过管道传输到 ssh 的 stdout 和 stdin 与远程scp进程通信(完全加密),并将作为远程scp会话的 stdin/stdout。

如何定制

scp命令提供了一个选项 -s,允许您自定义用于设置远程scp会话的程序。它应该处理 的所有典型选项ssh。为了帮助调试它的外观,我们设置了一个仅转储参数的虚拟程序:

/tmp/scp-dump.sh:

echo $0 $*

我们将按如下方式执行:

scp -S /tmp/scp-dump.sh /tmp/dump.txt [email protected]:/tmp

我们得到了以下输出:

/home/me/dump.sh \
    -x -oForwardAgent=no \
    -oPermitLocalCommand=no \
    -oClearAllForwardings=yes \
    -l me -- server.com scp -t /tmp

为了便于阅读,添加了带有反斜杠的换行符。

这使我们有机会在将其发送给命令之前修改参数和命令ssh

设置 sudo

现在我们可以编写一个程序来根据需要修改参数,启动 ssh,并使用修改后的形式启动scp -t。我们基本上会捕获参数,捕获我们想要在内部使用的参数,只添加额外的参数,然后进行更改。

/tmp/scp-sudo.sh

add_ssh_option() {
  local option
  if [ $# = 2 ]; then
    option="$1 \"$2\""
  else
    option="$1"
  fi
  if [ -z "${ssh_options}" ]; then
    ssh_options=${option}
  else
    ssh_options="${ssh_options} ${option}"
  fi
}

parse_options() {
  ssh_options=
  local option
  while [ $# > 0 ] ; do
    case $1 in
      -oSudoUser=* )
        SSH_SUDO_USER=${option##*=}
        shift
        ;;
      -l ) ssh_user=$2 ; shift 2 ;;
      -i ) add_ssh_option "$1" "$2" ; shift 2 ;;
      -- ) shift ; break ;;
      -* ) add_ssh_option "${1}" ; shift ;;
      *  ) break ;;
    esac
  done
  ssh_host=$1
  shift
  ssh_command="$*"
}

parse_options "$@"
# To avoid intererence with Standard-In, we change this to
# Strict:
add_ssh_option "-oStrictHostKeyChecking=yes"

if [ -z "${SSH_SUDO_USER}" ]; then
  # As before without sudo
  cat | ssh $ssh_options -l $ssh_user $ssh_host $ssh_command
  exit $?
else
  # Send standard-in to the customized "scp -t" sink.
  cat | ssh $ssh_options -l $ssh_user $ssh_host sudo -i -u ${SSH_SUDO_USER} $ssh_command
  exit $?
fi

现在我们可以使用修改后的 scp 形式:

scp -oSudoUser=other \
    -i /tmp/me-id_rsa \
    /tmp/scp-sudo.sh \
    /tmp/dump.txt \
    [email protected]:/tmp

这对于将文件发送到远程来说很有效。但是当我检索远程文件时,它不知何故挂起了。当我中断会话(control-c)时,我确实看到传输成功了,但我不知何故有一个名为“0”的额外文件。不知道有没有人能帮我解决这个问题?

更好的选择:sftp

然而 sftp 要好得多。

  1. 它有一个-S选项 - 允许设置整个 ssh 命令行。我很难弄清楚并让它工作。我怀疑我不了解如何在 shell-ssh 包装器中正确绑定 stdin/stdout。
  2. 它有一个-s(小写)选项 - 允许仅设置负责设置远程服务器的子命令。我发现这更容易让它工作。
  3. 您可以根据 sftp 的手册页设置远程路径、远程文件权限、执行 put/get 等。

在这种情况下我们只需

sftp -s "sudo -u sudo-user -- /usr/libexec/openssh/sftp-server" [email protected]

这给了我快乐的“ sftp>”提示

请注意,子程序有时位于不同的路径中,例如/usr/libexec/openssh/sftp-server。我建议从文件Subsystem sftp内部的“ ”字符串进行解析/etc/ssh/sshd_conf

相关内容