2

我需要将一些文件从服务器 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!”。

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

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
4

4 回答 4

4

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

以下是有关 ssh-agent 的更多详细信息。

于 2011-06-22T22:17:27.717 回答
2

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

详情请搜索ControlMasterman ssh_config

优势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
于 2011-06-23T00:40:15.097 回答
1

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

ssh -L 4000:B:22 usernam@A

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

ssh -p 4000 -i key_copied_from_a user_on_b@localhost

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

于 2011-06-22T22:17:08.060 回答
0

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

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 "$@"
"$@"

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

于 2011-06-23T02:21:05.400 回答