openssh 密钥上传
我们许多人都使用出色的OpenSSH作为古老的telnet
和rsh
命令的安全加密替代品。 OpenSSH的更吸引人的功能之一是能够使用RSA和DSA身份验证协议对用户进行身份验证,该协议基于一对互补的数字“密钥”。 RSA和DSA身份验证的主要吸引力之一是可以在不提供密码的情况下建立与远程系统的连接。 有关更多背景信息,请参阅本系列有关OpenSSH密钥管理的前几期,分别介绍RSA / DSA身份验证 (第1部分)以及ssh-agent和密钥链 (第2部分)。
由于第2部分2001年9月发表在developerWorks上,后来在Slashdot和Freshmeat的引用(见相关主题这篇文章的链接,这些网站以后),很多人已经开始使用keychain
,这是经历了很多变化。 我已经从世界各地的开发人员那里收到了大约20个左右的高质量补丁。 我已经将许多补丁添加到了keychain
源中,该源现在是1.8版(请参阅参考资料 )。 我衷心感谢所有提交补丁,错误报告,功能请求和感谢记录的人。
在我的上一篇文章中 ,我花了一些时间讨论运行ssh-agent
的安全性和权衡。 在第二篇文章出现在developerWorks上的几天后,我收到了Sarnoff Corporation的Charles Karney的电子邮件,他有礼貌地向我通报了OpenSSH的新身份验证代理转发功能,我们将在后面进行介绍。 此外,Charles强调,在不受信任的机器上运行ssh-agent
非常危险:如果有人设法获得系统上的root用户访问权限,则可以从ssh-agent
提取解密的密钥。 即使提取密钥会有些困难,但这仍在专业人士的能力范围内。 而这一事实私钥盗窃是可能的手段,我们应该采取措施,以防止它摆在首位发生。
为了制定保护私钥的策略,我们必须首先将要访问的计算机归为以下两类之一。 如果特定主机的安全性或隔离性很好-很难对其进行成功的root攻击-那么该机器应被视为可信主机 。 但是,如果许多人使用了一台计算机,或者您对系统的安全性有疑问,则应将该计算机视为不受信任的主机 。 为了保护您的私钥免于提取,绝对不要在不受信任的主机上运行ssh-agent
(以及keychain
)。 这样一来,即使系统的安全受到威胁,入侵者也将没有ssh-agent
可以从一开始就从中提取密钥。
但是,这产生了问题。 如果无法在不受信任的主机上运行ssh-agent
,那么如何从这些系统建立安全的,无密码的ssh
连接? 答案是仅在受信任的主机上使用ssh-agent
和keychain
,并使用OpenSSH的新身份验证转发功能将无密码身份验证扩展到任何不受信任的主机。 简而言之,身份验证转发通过允许远程ssh
会话联系在受信任系统上运行的ssh-agent
工作。
为了了解身份验证转发的工作原理,我们首先来看一个假设情况,即用户drobbins
拥有一个称为lappy
的受信任的笔记本电脑,一个名为trustbox
的受信任服务器以及他必须访问的其他两个不受信任的系统,即notrust1
和notrust2
,分别。 目前,他在所有四台机器上都使用ssh-agent
和keychain
,如下所示:
这种方法的问题在于,如果某人获得了notrust1
或notrust2
根访问权限,那么此人当然有可能从现在容易受到攻击的ssh-agent
进程中提取密钥。 为了解决这个问题, drobbins
停止在不受信任的主机notrust1
和notrust2
上运行ssh-agent
和keychain
。 实际上,要更加小心, drobbins
决定只在lappy
上使用ssh-agent
和keychain
。 这限制了他解密后的私钥的暴露,从而保护他免遭私钥盗窃:
当然,这种方法的问题在于, drobbins
现在只能从lappy
建立无密码连接。 让我们看看如何启用身份验证转发并解决此问题。
假设所有机器都运行OpenSSH的最新版本,我们可以通过使用身份验证转发来解决此问题。 身份验证转发使远程ssh
进程可以与在本地受信任计算机上运行的ssh-agent
联系-而不是要求ssh-agent
版本在要从ssh
退出的同一台计算机上运行。 通常,这使您可以在一台计算机上运行ssh-agent
(和keychain
),并且意味着所有(直接或间接)源自此计算机的ssh
连接都将使用本地ssh-agent
。
为了启用身份验证转发,我们trustbox
添加到lappy
和trustbox
的/ etc / ssh / ssh_config 。 请注意,这是ssh
( ssh_config )的配置文件,而不是ssh守护程序sshd
( sshd_config )的配置文件:
ForwardAgent Yes
现在,为了利用认证转发, drobbins
可以从连接lappy
到trustbox
,然后从trustbox
到notrust1
而无需任何连接提供密码短语。 这两个ssh
进程都“接入”在lappy
运行的ssh-agent
:
$ ssh drobbins@trustbox
Last login: Wed Sep 26 13:42:08 2001 from lappy
Welcome to trustbox!
$ ssh drobbins@notrust1
Last login: Tue Sep 25 12:03:40 2001 from trustbox
Welcome to notrust1!
$
如果尝试类似的配置,但发现代理转发不起作用,请尝试使用ssh -A
而不是普通的旧ssh
来显式启用身份验证转发。 这是上面使用身份验证转发登录到trustbox
和notrust1
时幕后情况的trustbox
:
如您所见,当ssh
连接到trustbox
,它保持与在lappy
运行的ssh-agent
的连接。 当从trustbox
到notrust1
建立ssh
连接时,这个新的ssh
进程将保持与先前ssh
的身份验证连接,从而有效地扩展了链。 此身份验证链是否可以扩展到notrust1
以外的其他主机,取决于notrust1
的/ etc / ssh / ssh_config的配置方式。 只要启用了代理转发,链中的所有部分都将能够使用在受信任的lappy
上运行的ssh-agent
进行身份验证。
身份验证转发具有许多此处未涉及的安全优势。 为了使我相信代理连接转发的重要性,Charles Karney与我分享了这三个安全优势:
ssh-agent
仅在受信任的计算机上运行。 这样可以防止入侵者执行远程ssh-agent
进程的内存转储,然后从转储中提取解密的私钥。 依赖身份验证代理连接转发的一个缺点是它不能解决允许cron作业利用RSA / DSA身份验证的问题。 解决此问题的一种方法是设置所有需要RSA / DSA身份验证的cron作业,以便它们从LAN上的受信任计算机执行。 如有必要,这些cron作业可以使用ssh
连接到远程系统以自动执行备份,同步文件等等。
现在,我们已经研究了身份验证代理连接转发,接下来我们来看看对keychain
脚本本身所做的最新改进。
由于提交了用户补丁程序,因此对keychain
源进行了许多重大改进。 用户提交的几个keychain
补丁与功能相关。 例如,您会记得keychain
创建了一个〜/ .ssh-agent文件。 该文件的名称现已更改为〜/ .ssh-agent- [hostname],以便keychain
可以与可从多个不同物理主机访问的NFS安装的主目录配合使用。 除了〜/ .ssh-agent- [hostname]文件之外,现在还有一个〜/ .ssh-agent-csh- [hostname]文件,可以通过csh
兼容的shell来获取。 最后,添加了新的--nocolor
选项,以便在您碰巧使用不兼容vt100的终端时可以禁用着色功能。
虽然功能上的改进非常重要,但绝大多数修复程序都解决了Shell兼容性问题。 您会看到,虽然钥匙串1.0需要bash
,但后来的版本已更改为可与任何与sh
兼容的shell一起使用。 这项更改使keychain
几乎可以在任何UNIX系统上“开箱即用”,包括Linux,BSD,Solaris,IRIX和AIX以及其他UNIX平台。 向sh
和一般UNIX兼容性的过渡是一个坎bump的旅程,但它也已经是一个很棒的学习经验。 创建一个可以在所有这些平台上运行的脚本确实非常棘手,主要是因为我根本无法访问大多数这些操作系统! 值得庆幸的是,来自全球的keychain
用户确实如此,许多人在识别兼容性问题和提交修补程序方面都提供了很大的帮助。
实际上,必须解决两种兼容性问题。 首先,我需要确保keychain
仅使用所有sh
实现完全支持的内置函数,表达式和运算符,包括所有流行的免费和商业UNIX sh
shell, zsh
(在sh
兼容模式下)和bash
版本1和2。这是一些用户提交的,适用于keychain
源的外壳兼容性修补程序:
由于较早的sh
shell不支持~
约定来引用用户的主目录,因此将使用~
的行改为使用$HOME
:
hostname=`uname -n`
pidf=${HOME}/.ssh-agent-${hostname}
cshpidf=${HOME}/.ssh-agent-csh-${hostname}
接下来,所有对source
引用都更改为.
确保与纯粹的NetBSD的/bin/sh
兼容,后者根本不支持source
命令:
if [ -f $pidf ]
then
. $pidf
else
SSH_AGENT_PID="NULL"
fi
在此过程中,我还应用了一些与性能相关的不错的修复程序。 一位精明的shell脚本编写者告诉我,您可以通过输入touch foo
来“触摸”文件,而不是:
> foo
通过依靠内置的shell语法而不是使用外部二进制文件,可以避免fork()
,并且脚本的效率会稍微提高。 > foo
应该与任何sh
兼容的shell一起工作; 但是, ash
似乎不支持它。 对于大多数人来说,这应该不是问题,因为ash
更多是应急磁盘类型的外壳,而不是人们日常使用的外壳。
要使脚本在多个UNIX操作系统下都可以工作,不仅需要坚持纯sh
语法。 请记住,大多数脚本还会调用外部命令,例如grep
, awk
, ps
等,并且这些命令必须尽可能以符合标准的方式进行调用。 例如,虽然大多数UNIX版本中包含的echo
可以识别-e
选项,但是Solaris不能-在使用时,它只是将-e
打印到stdout。 因此,为了应对Solaris, keychain
现在可以自动检测echo -e
是否有效:
if [ -z "`echo -e`" ]
then
E="-e"
fi
上面,如果支持-e
转义,则E
设置为-e
。 然后,可以按如下方式调用echo:
echo $E Usage: ${CYAN}${0}${OFF} [ ${GREEN}options${OFF} ] ${CYAN}sshkey${OFF} ...
通过使用echo $E
代替echo -e
,可以根据需要动态启用或禁用-e
选项。
最重要的兼容性修补程序可能涉及更改keychain
检测当前正在运行的ssh-agent
进程的方式。 以前,我是使用pidof
命令执行此操作的,但是由于一些系统没有pidof
,因此必须将其删除。 确实, pidof
并不是最佳解决方案,因为当我们对当前有效UID拥有的所有ssh-agent
进程真正感兴趣时, pidof
会列出系统上运行的所有 ssh-agent
进程(无论用户如何)。
因此,我们不再依赖pidof
,而是切换到将ps
输出管道传输到grep
和awk
,以提取所需的进程ID。 这是用户提交的修复程序:
mypids=`ps uxw | grep ssh-agent | grep -v grep | awk '{print $2}'`
上面的管道会将mypids
变量设置为当前用户拥有的所有ssh-agent
进程的值。 grep -v grep
命令是管道的一部分,以确保grep ssh-agent
进程不会成为我们PID列表的一部分。
尽管这种方法在概念上是好的,但是使用ps
开辟了一个全新的蠕虫罐,因为ps
选项未在各种BSD和System V UNIX派生产品中标准化。 这是一个示例:虽然ps uxw
在Linux下工作,但在IRIX下却不工作。 而且,虽然ps -u username -f
在Linux,IRIX和Solaris下均可工作,但在BSD下却无法工作,后者仅了解BSD风格的ps
选项。 为了解决这个问题, keychain
会在执行ps
管道之前自动检测当前系统的ps
是否使用BSD或System V语法:
psopts="FAIL"
ps uxw >/dev/null 2>&1
if [ $? -eq 0 ]
then
psopts="uxw"
else
ps -u `whoami` -f >/dev/null 2>&1
if [ $? -eq 0 ]
then
psopts="-u `whoami` -f"
fi
fi
if [ "$psopts" = "FAIL" ]
then
echo $0: unable to use \"ps\" to scan for ssh-agent processes.
Report KeyChain version and echo system configuration to [email protected].
exit 1
fi
mypids=`ps $psopts 2>/dev/null | grep "[s]sh-agent" | awk '{print $2}'` > /dev/null 2>&1
为了确保我们同时使用System V和BSD风格的ps
命令,该脚本会对ps uxw
进行“ ps uxw
运行”, ps uxw
丢弃所有输出。 如果此命令的错误代码为零,则说明ps uxw
有效,并且我们适当地设置了psopts
值。 但是,如果ps uxw
返回了非零的错误代码(表明我们需要使用BSD样式的选项),我们ps -u `whoami` -f
空运行,再次丢弃所有输出。 至此,希望我们找到了可以使用的ps
的BSD或System V变体。 如果没有,则打印出错误并退出。 但是很可能两个ps
命令之一都起作用了,在这种情况下,我们执行了上面的代码片段ps
管道中的最后一行。 通过在ps
之后立即使用$psopts
变量扩展,我们可以将正确的选项传递给ps
命令。
ps
管道还包含一个真正的grep
gem,由Hans Peter Verne寄给我。 注意grep -v grep
不再是管道的一部分; 相反,它已被删除, grep "ssh-agent"
已更改为grep "[s]sh-agent"
。 这个单一的grep
命令最终完成的功能与grep ssh-agent | grep -v grep
相同grep ssh-agent | grep -v grep
grep ssh-agent | grep -v grep
; 你能弄清楚为什么吗?
mypids=`ps $psopts 2>/dev/null | grep "[s]sh-agent" | awk '{print $2}'` > /dev/null 2>&1
难过吗 如果您确定grep "ssh-agent"
和grep "[s]sh-agent"
应该完全相同的文本行,那么您是正确的。 那么,为什么将ps
的输出通过管道传递给它们时它们会产生不同的结果呢? 它是这样工作的:当您使用grep "[s]sh-agent"
,您将更改grep
命令在ps
进程列表中的显示方式。 这样做可以防止grep
自身匹配,因为[s]sh-agent
字符串与[s]sh-agent
正则表达式不匹配。 那不是很聪明吗? 如果仍然不了解它, grep
尝试一下grep
,您将很快得到它。
本专栏总结了我对OpenSSH的介绍。 希望您已经学到了足够的知识,可以开始使用OpenSSH保护系统。 下个月的“ 公共线程”专栏将继续“高级文件系统实施者指南”系列。
翻译自: https://www.ibm.com/developerworks/opensource/library/l-keyc3/index.html
openssh 密钥上传