【问题标题】:Temporarily remove the ssh private key password in a shell scriptI临时删除 shell 脚本中的 ssh 私钥密码
【发布时间】:2011-09-20 18:52:15
【问题描述】:

我需要将一些文件从服务器 A 部署到服务器 B。我通过 SSH 连接到服务器 A,然后从那里通过 ssh 连接到服务器 B,使用存储在服务器 A 上的私钥,其中的公钥位于在服务器 B 的授权密钥文件中。从 A 到 B 的连接发生在服务器 A 上的 Bash shell 脚本中。

这一切都很好,很好,很简单,直到有安全意识的管理员指出我存储在服务器 A 上的 SSH 私钥没有密码保护,所以任何可能侵入我在服务器 A 上的帐户的人也会有访问服务器 B,以及 C、D、E、F 和 G。我猜他说得有道理。

他提出了一个复杂的场景,在这个场景下我会添加一个密码,然后修改我的 shell 脚本,在开头添加一行,我会在其中调用

ssh-keygen -p -f {private key file}  

用密码回答我的旧密码的提示和(两个)提示我的新密码的提示,只需返回即可摆脱密码,然后在最后,在我的 scp 命令之后 调用

ssh-keygen -p -f {private key file} 

再次,把密码短语放回去

我说“Yecch!”。

好吧,我可以通过先阅读脚本中的密码短语来稍微改进一下

read -s PASS_PHRASE

然后根据需要使用 ssh-keygen 的 -N 和 -P 参数提供它。

它几乎可以使用,但我讨厌 shell 脚本中的交互式提示。我想把它归结为一个交互式提示,但让我感到难过的部分是我必须按两次回车键才能摆脱密码短语的部分

这可以从命令行工作:

ssh-keygen -p -f {private key file} -P {pass phrase} -N ''

但不是来自 shell 脚本。在那里,似乎我必须删除 -N 参数并接受输入两个返回的需要。

这是我能做到的最好的。任何人都可以改善这一点吗?或者有没有更好的方法来处理这个?我不敢相信没有。

最好采用某种方式安全地处理此问题,而无需输入密码,但这可能要求太多。每次脚本调用我都会满足于一次。

这是整个脚本的骨架形式的简化版本

#! /bin/sh
KEYFILE=$HOME/.ssh/id_dsa
PASSPHRASE=''

unset_passphrase() {
        # params
        # oldpassword keyfile
        echo "unset_key_password()"
        cmd="ssh-keygen -p -P $1 -N '' -f $2"
        echo "$cmd"
        $cmd
        echo 
}

reset_passphrase() {
        # params
        # oldpassword keyfile
        echo "reset_key_password()"
        cmd="ssh-keygen -p -N '$1' -f $2" 
        echo "$cmd"
        $cmd
        echo
}

echo "Enter passphrase:"
read -s PASSPHRASE
unset_passphrase $PASSPHRASE $KEYFILE
# do something with ssh
reset_passphrase $PASSPHRASE $KEYFILE

【问题讨论】:

    标签: bash ssh passphrase


    【解决方案1】:

    查看 ssh-agent。它会缓存密码,因此无论您有多少会话,您都可以在特定时间段内使用密钥文件。

    Here are more details about ssh-agent.

    【讨论】:

      【解决方案2】:

      OpenSSH 支持所谓的“控制主机”模式,您可以在其中连接一次,让它在后台运行,然后让其他 ssh 实例(包括 scp、rsync、git 等)重用该现有连接。这样可以只输入一次密码(在设置控制主机时),但可以对同一个目的地执行多个 ssh 命令。

      man ssh_config 中搜索ControlMaster 了解详情。

      优于ssh-agent:

      • 你不必记得运行ssh-agent
      • 您不必生成 ssh 公钥/私钥对,如果脚本将由许多用户运行,这一点很重要(大多数人不了解 ssh 密钥,所以让一大群人生成它们是一种累人的运动)
      • 根据其配置方式,ssh-agent 可能会在脚本中途使您的密钥超时;这不会
      • 只启动一个 TCP 会话,因此如果您反复连接(例如,一次复制许多小文件)会更快

      示例用法(请原谅 Stack Overflow 的语法高亮):

      REMOTE_HOST=server
      
      log() { printf '%s\n' "$*"; }
      error() { log "ERROR: $*" >&2; }
      fatal() { error "$*"; exit 1; }
      try() { "$@" || fatal "'$@' failed"; }
      
      controlmaster_start() {
          CONTROLPATH=/tmp/$(basename "$0").$$.%l_%h_%p_%r
          # same as CONTROLPATH but with special characters (quotes,
          # spaces) escaped in a way that rsync understands
          CONTROLPATH_E=$(
              printf '%s\n' "${CONTROLPATH}" |
              sed -e 's/'\''/"'\''"/g' -e 's/"/'\''"'\''/g' -e 's/ /" "/g'
          )
          log "Starting ssh control master..."
          ssh -f -M -N -S "${CONTROLPATH}" "${REMOTE_HOST}" \
              || fatal "couldn't start ssh control master"
          # automatically close the control master at exit, even if
          # killed or interrupted with ctrl-c
          trap 'controlmaster_stop' 0
          trap 'exit 1' HUP INT QUIT TERM
      }
      
      controlmaster_stop() {
          log "Closing ssh control master..."
          ssh -O exit -S "${CONTROLPATH}" "${REMOTE_HOST}" >/dev/null \
              || fatal "couldn't close ssh control master"
      }
      
      controlmaster_start
      try ssh -S "${CONTROLPATH}" "${REMOTE_HOST}" some_command
      try scp -o ControlPath="${CONTROLPATH}" \
          some_file "${REMOTE_HOST}":some_path
      try rsync -e "ssh -S ${CONTROLPATH_E}" -avz \
          some_dir "${REMOTE_HOST}":some_path
      
      # the control master will automatically close once the script exits
      

      【讨论】:

      • 谢谢,这样做和通过 ssh-agent 有什么区别,它为我提供了类似的一次性密码输入?
      • @Steve:更新了答案以包含优于ssh-agent的优势
      【解决方案3】:

      我可以为此指出一个替代解决方案。我不会将密钥存储在服务器A 上,而是将密钥保存在本地。现在我将在端口4000 上创建一个本地端口转发到服务器B

      ssh -L 4000:B:22 usernam@A
      

      然后在一个新的终端中通过隧道连接到服务器B

      ssh -p 4000 -i key_copied_from_a user_on_b@localhost
      

      我不知道这对你来说有多可行。

      【讨论】:

        【解决方案4】:

        正如您所发现的,将命令构建为字符串很棘手。使用数组更加健壮:

        cmd=( ssh-keygen -p -P "$1" -N "" -f "$2" )
        echo "${cmd[@]}"
        "${cmd[@]}"
        

        甚至使用位置参数

        passphrase="$1"
        keyfile="$2"
        set -- ssh-keygen -p -P "$passphrase" -N "" -f "$keyfile"
        echo "$@"
        "$@"
        

        空的参数不会被引号包围,但它就在那里

        【讨论】:

        • 数组是特定于 bash 的——它们不能在 POSIX shell (/bin/sh) 中工作。以空格安全和符合 POSIX 的方式构建命令的唯一方法是通过位置参数(如您所建议的那样)或转义特殊字符并使用 eval
        • @Richard,是的,但问题确实有 bash 标签。
        • Glenn:是的,尽管 ssh-agent 看起来像是解决我的问题的一个完整解决方案,但仍然存在一个令人烦恼的问题,即为什么在命令行上输入 ssh-keygen -p -P $1 -N '' -f $2 会成功,但在bash 脚本没有。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-05
        • 2013-08-20
        • 2014-10-17
        • 1970-01-01
        • 2021-12-14
        • 2010-12-28
        • 2013-04-14
        相关资源
        最近更新 更多