代理程序转发和 keychain 改进
Daniel Robbins(drobbins@gentoo.org)
总裁兼首席执行官,Gentoo Technologies, Inc.
2002 年 2 月
在这一系列的第三篇文章中,Daniel Robbins 向您显示了如何利用 OpenSSH 代理程序连接转发来增强安全性。他还分享 keychain shell 脚本的近期改进。
我们中的许多人都使用非常优秀的 OpenSSH 作为古老的 telnet 和 rsh 命令的替代品,OpenSSH 不仅安全而且是加密的。OpenSSH 更吸引人的特性之一是它能够使用以一对互补的数字式“密钥”为基础的 RSA 和 DSA 认证协议来认证用户。RSA 和 DSA 认证承诺不必提供密码就能够与远程系统建立连接,这是其主要魅力之一。有关更多背景资料,请参阅关于 OpenSSH 密钥管理的本系列文章中以前的几篇,分别包括 RSA/DSA 认证(第 1 部分)和 ssh-agent 和 keychain(第 2 部分)。
由于第 2 部分发表在 2001 年 9 月的 developerWorks 上,并且稍后在 Slashdot 和 Freshmeat(请参阅本文后面的参考资料,获取到这些站点的链接)上引用了此文,因此,许多人已经开始使用 keychain,而且它已经做了许多更改。我收到了世界各地开发人员编写的约 20 个高质量的补丁程序。我已将其中许多补丁代码合并入 keychain 源码中,它目前的版本是 1.8(请参阅参考资料)。我真诚地感谢所有提交补丁程序、错误报告、功能请求及感谢信的那些人。
加强 ssh 安全性
在上篇文章中,我花了一些时间来讨论运行 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,如下所示:
图 1. 运行在可信和不可信机器上的 ssh-agent
javascript:window.open(this.src);" style="CURSOR: pointer" onload="return imgzoom(this,550)">
这种方法所带来的问题是如果有人获取 notrust1 或 notrust2 的 root 访问权,那么这个人当然可以从现在易受攻击的 ssh-agent 进程中抽取密钥。为了解决这个问题,drobbins 停止运行不可信主机 notrust1 和 notrust2 上的 ssh-agent 和 keychain。事实上,为了更为小心,drobbins 决定只在 lappy 上使用 ssh-agent 和 keychain。这样限制了他解密的私钥的泄露,同时防止他的私钥被偷窃:
图 2. ssh-agent 只运行在 lappy 上;一个更安全的配置
当然,这种方法带来的问题是 drobbins 现在只能从 lappy 建立无密码的连接。让我们看一下如何启用认证转发并解决这个问题。
假设所有机器都运行 OpenSSH 的最近版本,通过使用认证转发,我们能解决这个问题。认证转发允许远程 ssh 进程联系您正在本地可信机器上运行的 ssh-agent — 而不要求在您正运行 ssh 的同一台机器上运行 ssh-agent 的一个版本。这通常允许您在单个机器上运行 ssh-agent(和 keychain),并且这意味着源于这台机器的所有 ssh 连接(直接或间接)都将使用本地 ssh-agent。
为了启用认证转发,我们在 lappy 和 trustbox 的 /etc/ssh/ssh_config 中添加了下面行。请注意:这是 ssh 的配置文件(ssh_config),而不是 ssh 守护进程 sshd 的配置文件(sshd_config):
清单 1. 将这行添加到 /etc/ssh/ssh_config 中
ForwardAgent Yes
现在,为了利用认证转发,drobbins 可以从 lappy 连接到 trustbox,然后在不提供任何连接的密码的情况下,从 trustbox 连接到 notrust1。这两个 ssh 进程都“进入”运行在 lappy 上的 ssh-agent:
清单 2. 进入 lappy
$ 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 时,实现此操作的内部运行图:
图 3. 正在运作的代理程序转发
正如您看到的,当 ssh 连接到 trustbox 时,它维持与运行在 lappy 上的 ssh-agent 的连接。当产生从 trustbox 到 notrust1 的 ssh 连接时,这个新的 ssh 进程维持与以前 ssh 的认证连接,这样有效地延伸了链。这个认证链是否能延伸到 notrust1 以外的其它主机取决于 notrust1 的 /etc/ssh/ssh_config 是如何配置的。 只要启用了代理程序转发,通过使用在可信 lappy 上运行的 ssh-agent,这个链上的所有部分都能认证。
代理程序连接转发的优点
认证转发提供了许多在此没有提到的安全性优点。为了让我相信代理程序连接转发的重要性,Charles Karney 与我分享了以下三个安全性优点:
1. 私钥只存储在可信机器上。这样防止怀有恶意的用户从磁盘获取加密的密钥并防止他们试图解加密。
2. ssh-agent 只运行在可信机器上。这样防止闯入者进行远程 ssh-agent 进程的内存转储并从转储中抽取出您的解密私钥。
3. 由于您只需要在可信机器上输入密码,所以防止了任何击键记录器在您输入密码时悄悄地截取密码。
使用认证代理程序连接转发的一个缺点是:它不能解决允许 cron 作业利用 RSA/DSA 认证这个问题。解决这个问题的一个方案是设置所有需要 RSA/DSA 认证的 cron 作业,这样它们就可以从局域网中的一台可信机器上执行。如果需要的话,这些 cron 作业能使用 ssh 连接到远程系统来实现自动备份、使文件同步等操作。
既然我们已经了解了认证代理程序连接转发,那么让我们转到近期对 keychain 脚本本身所做的改进上。
keychain 功能改进
感谢用户发来补丁程序,许多重要的改进已添加到 keychain 源码中。 用户提交的 keychain 补丁程序中有几个与功能有关。例如,您应记得 keychain 创建了一个 ~/.ssh-agent 文件;这个文件的名字现在已经改为 ~/.ssh-agent-[hostname],这样 keychain 可以使用从几个不同物理主机上能访问已挂装 NFS 的主目录。除了 ~/.ssh-agent-[hostname] 文件外,现在还有一个 ~/.ssh-agent-csh-[hostname] 文件,兼容 csh 的 shell 利用 source 命令读入并执行该文件。最后,添加了一个新的 --nocolor 选项,这样,如果您碰巧在使用不兼容 vt100 的终端时,就能禁用彩色化功能部件。
Shell 兼容性修正
当完成了许多重要的功能改进时,对 shell 兼容性问题也做了大量的修正。您看,keychain 1.0 需要 bash,而以后的版本则改为可以使用任何 sh 兼容的 shell。这一更改使得 keychain 跳出固有的框架,可以在包括 Linux、BSD、Solaris、IRIX 和 AIX 以及其它 UNIX 平台的几乎所有 UNIX 系统上运行。转至 sh 并与常规 UNIX 兼容,这已经是困难重重了,而同时它也经过了大量的学习经验。创建运行在所有这些平台上的单个脚本事实上是非常棘手的,主要因为我根本无权访问这些操作系统中的大多数系统!要感谢的是,全球范围内的 keychain 用户这样做了,并且许多人在识别兼容性问题以及提交补丁程序来解决它们等方面提供了非常大的帮助。
事实上,有两类兼容性问题必须解决。首先,我需要确信 keychain 只使用所有 sh 实现下完全支持的内置件、表达式和操作符,包括所有流行的免费和商业 UNIX sh shell、zsh(以 sh 兼容的模式)和 bash 版本 1 和 2。这里是用户提交的应用到 keychain 源码中的一些 shell 兼容的修正:
由于较早的 sh shell 不支持 ~ 约定来引用用户的主目录,因此将使用 ~ 的行更改为使用 $HOME 来代替:
清单 3. 使之成为 $HOME
hostname=`uname -n`
pidf=$/.ssh-agent-$
cshpidf=$/.ssh-agent-csh-$
接着,所有对 source 的引用都更改成 .,以确保与纯 NetBSD 的 /bin/sh 兼容,因为它根本不支持 source 命令:
清单 4. 迎合 NetBSD
if [ -f $pidf ]
then
. $pidf
else
SSH_AGENT_PID="NULL"
fi
按照这个方法,我还应用了一些与性能相关的好的修正。一位聪明的 shell 脚本编写者告诉我不要通过输入 touch foo 来“更新”文件的修改日期,您可以这样做:
清单 5. 更新文件的修改日期
> foo
通过使用内置的 shell 语法,而不是使用外部二进制文件,这样避免了使用 fork(),而脚本却变得更加有效。> foo 应使用任何兼容 sh 的 shell;但是,好象 ash 并不支持它。对大多数人来说这不应是个问题,因为 ash 更象是急救磁盘类型的 shell,而不是人们每天都要使用的程序。
平台可执行问题
获取在多个 UNIX 操作系统下运行的脚本不单单需要坚持纯粹的 sh 语法。请记住,大多数脚本还要调用诸如 grep、awk、ps 和其它命令的外部命令,而且必须尽可能以与标准相符的方法来调用这些命令。例如,包含在大多数 UNIX 版本中的 echo 能识别 -e 选项,而 Solaris 中的 echo 却不能识别 — 当使用它时,它只把 -e 打印到标准输出(stdout)。因此为了处理 Solaris,keychain 现在自动检测 echo -e 是否起作用:
清单 6. 寻找 Solaris
if [ -z "`echo -e`" ]
then
E="-e"
fi
上面的代码中,如果支持 -e,那么将 E 设置为 -e。然后,可以按如下所示调用 echo:
清单 7. 更好的 echo
echo $E Usage: $$ [ $options$ ] $sshkey$ ...
通过使用 echo $E,而不是 echo -e,可以根据需要动态地启用或禁用 -e 选项。
pidof,ps
可能最重要的兼容性修正涉及到更改 keychain 如何检测当前正在运行的 ssh-agent 的进程的方法。以前,我使用 pidof 命令来这样做,但是由于有几个系统没有 pidof,所以不得不抛弃这个方案。实际上,pidof 无论如何都不是最佳的解决方案,因为它列出系统上运行的所有 ssh-agent 进程,而不管用户是谁,但我们实际上感兴趣的是当前有效的 UID 所拥有的所有 ssh-agent 进程。
所以,为抽取所需的进程标识,我们不使用 pidof,而是转向将 ps 输出通过管道输送到 grep 和 awk 上。 这是一个用户提交的修正:
清单 8. 管道比 pidof 好
mypids=`ps uxw | grep ssh-agent | grep -v grep | awk '{print }'`
上面的管线将 mypids 变量设置为当前用户拥有的所有 ssh-agent 进程的值。grep -v grep 命令是管线的一部分,这样确保 grep ssh-agent 进程不会成为我们的 PID 列表中的一部分。
这种方法从概念上来说非常好,但是因为 ps 选项未在各类 BSD 和 System V 的 UNIX 派生系统上标准化,所以使用 ps 开启了一个全新的尚未解决的难题。这里是一个示例:虽然 ps uxw 在 Linux 下起作用,而在 IRIX 下不起作用。ps -u username -f 在 Linux、IRIX 和 Solaris 下起作用,而在只理解 BSD 样式的 ps 选项的 BSD 下不起作用。为了解决这个问题,在执行 ps 管线之前,keychain 会自动检测当前系统的 ps 是使用 BSD 语法或还是 System V 语法:
清单 9. 检测 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 : unable to use "ps" to scan for ssh-agent processes.
Report KeyChain version and echo system configuration to drobbins@gentoo.org.
exit 1
fi
mypids=`ps $psopts 2>/dev/null | grep "[s]sh-agent" | awk '{print }'` > /dev/null 2>&1
为了确保我们能同时使用 System V 和 BSD 样式的 ps 命令,脚本试着运行 ps uxw,而丢弃任何输出。如果这个命令的错误码为零,那么我们知道 ps uxw 正常工作,并且我们正确地设置了 psopts 值。但是,如果 ps uxw 返回一个非零的错误码(指出我们需要使用 BSD 样式的选项),那么我们试着运行 ps -u `whoami` -f,并再次丢弃了任何输出。此时,我们有希望发现可以使用的 ps 是 BSD 的变体还是 System V 的变体。如果我们不知道答案,那么打印出错误并退出。但是很有可能这两个 ps 命令中的一个工作正常,在这样的情况下,执行上面代码段的最后一行,即 ps 管线。通过紧跟在 ps 后面使用 $psopts 变量扩展,我们能将正确的选项传送给 ps 命令。
ps 管线还包含一个 grep,它的确是一个宝物,是 Hans Peter Verne 好心发给我的。 请注意 grep -v grep 不再是管线的一部分;实际上它已经被除去并且 grep "ssh-agent" 已经改为 grep "[s]sh-agent"。这样一个 grep 命令最后执行与 grep ssh-agent | grep -v grep 相同的操作;您知道为什么吗?
清单 10. 简洁的 grep 诀窍
mypids=`ps $psopts 2>/dev/null | grep "[s]sh-agent" | awk '{print }'` > /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,您很快就会明白了。
结束语
本专栏文章对讨论的 OpenSSH 作出了结论。希望您已经学到了有关 OpenSSH 的很多知识,足以开始使用 OpenSSH 来保护您系统的安全。
参考资料
“通用线程:OpenSSH 密钥管理,第 1 部分”(developerWorks,2001 年 7 月)涵盖了 RSA/DSA 认证。
“通用线程:OpenSSH 密钥管理,第 2 部分”(developerWorks,2001 年 9 月)介绍了 ssh-agent 和 keychain。
在 Gentoo Linux Keychain 页面上可以获得 keychain 的最新版本。
请务必访问 OpenSSH 的开发主页,并查阅 OpenSSH 常见问题解答。
您可以从 Openbsd.org 下载最新的 OpenSSH 源码 tarball 和 RPM。
PuTTY 是用于 Windows 机器上的一个出色的 ssh 客户程序。
“SSH, The Secure Shell: The Definitive Guide”(O'Reilly & Associates,2001)一书可能会对您有帮助。作者的网站上有关于这本书、常见问题解答、新闻和更新等信息。
请访问 Slashdot,获取“面向初学者的新闻和其它相关内容”。
请查阅 Freshmeat,在开放源码包的新发行版出现时它就列出来它们。
请浏览 developerWorks 上更多 Linux 参考资料。
请浏览 developerWorks 上更多开放源码资料。
关于作者
Daniel Robbins 居住在美国新墨西哥州阿尔布开克,他是 Gentoo Technologies, Inc. 的总裁兼首席执行官,他主创了 Gentoo Linux,这是一种用于 PC 机的高级 Linux,以及 Portage 系统,是用于 Linux 的下一代移植系统。他还是几本 Macmillan 出版的书籍 Caldera OpenLinux Unleashed、SuSE Linux Unleashed 和 Samba Unleashed 的投稿人。Daniel 自二年级起就和计算机结下不解之缘,那时他最先接触的是 Logo 编程语言,并沉溺于 Pac Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 首席图形设计师的原因所在。Daniel 喜欢和他的妻子 Mary 以及他们的女儿 Hadassah 一起共度时光。您可以通过 drobbins@gentoo.org 与 Daniel 联系。