使用Google Authenticator实现SSH两步认证

2011-09-27 07:30

使用Google Authenticator实现SSH两步认证

by yuanyi

at 2011-09-26 23:30:49

original http://item.feedsky.com/~feedsky/heikezhi/~8608072/569336371/6713895/1/item.html

最近,为了能够通过PCI(安全支付行业标准)的自我测评,我得到了几台服务器,其中的一个要求时需要通过两步认证来访问这些服务器,我询问了是否可以使用SSH Key加密码(Passphrase),但是没有得到一个明确的答复,于是我决定自己动手来实现SSH的两步认证,正好我也对这个很感兴趣。

经过一番调研之后,我发现我可以使用Google AuthenticatorPAM认证模块,但是它只支持SSH密码认证,而我使用的是SSH key认证。

意想不到

我想找一种最简单的实现方式,所以我决定先从SSH开始,看看有没什么特性可供利用,很快,我就发现SSH的authorized_keys文件有一个选项,可以为通过public key方式登录的用户设置一个钩子命令:

command="/usr/bin/my_script" ssh-dsa AAA...zzz me@example.com

command选项可以让你为每个key设置一条不同的命令,下面就让我们开始集成Google Authenticator吧!

更新:根据Hacker news上的评论,你也可以在sshd_config中配置ForceCommand选项来设置一个全局钩子。

简单实现

我选择了ruby来编写我的第二步认证脚本,但理论上,你可以使用任何你喜欢的语言,下面的代码只是为了验证概念,要运行下面的代码,你需要安装rotp,一个ruby的HOTP/TOTP协议实现:

gem install rotp

现在,将下面的代码保存到 /usr/bin/two_factor_ssh:

#!/usr/bin/env ruby
require 'rubygems'
require 'rotp'

# we'll pass in a secret to this script from the authorized_keys file
abort unless secret=ARGV[0]

# prompt the user for their validation code

STDERR.write "Enter the validation code: "
until validation_code=STDIN.gets.strip
  sleep 1
end

# check the validation code is correct

abort "Invalid" unless validation_code==ROTP::TOTP.new(secret).now.to_s

# user has validated so we'll give them their shell

Kernel.exec ENV['SSH_ORIGINAL_COMMAND'] || ENV['SHELL']

这里的诀窍就是kernel.exec,如果通过了验证,它会继续调用用户原来正在执行的命令,或是给用户一个全新的shell,从而让整个认证过程更加顺畅。

生成密钥

下面,我们需要为Google Authenticator和服务器生成一个共享密钥,我写了一个简单的脚本:

#!/usr/bin/env ruby
require 'rubygems'
require 'rotp'

secret=ROTP::Base32.random_base32
data="otpauth://totp/#{`hostname -s`.strip}?secret=#{secret}"
url="https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{data}"

puts "Your secret key is: #{secret}"
puts url

运行这个脚本得到:

Your secret key is: 4rr7kc47sc5a2fgt

https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/myserver?secret=4rr7kc47sc5a2fgt

现在,通过Google Authenticator扫描生成的二维码,然后更新authorized_keys:

command="/usr/bin/two_factor_ssh 4rr7kc47sc5a2fgt" ssh-dsa AAA...zzz me@example.com

大功告成!

更新: 感谢@js4all提醒,OpenSSH的某个特定版本可能会打印出正在执行的命令(以及参数),这样我们的密钥就暴露了,解决办法就是在命令行只传递一个标识符,然后在脚本内再通过这个标识符去查找真正的密钥。

试试看

[richard@mbp ~]$ ssh moocode@myserver
Enter the validation code: wrong
Invalid
Connection to myserver closed.
[richard@mbp ~]$
[richard@mbp ~]$ ssh moocode@myserver
Enter the validation code: 410353
moocode@myserver:~$

不错,和我预期的完全一样。

再进一步

这里有一个更完善的例子可以支持根据IP地址自动保存登录信息一段时间,这样,如果你使用同一个IP登录,就不用每次都输入验证码了。

这个例子里面包含了一些简单的日志记录,但是我更愿意使用监控(audit)系统来做这件事(这也是PCI的一项要求),这样就可以记录某个key在什么时间登录过服务器,以及它的认证结果。

或许我们还应该增加一种备份机制(一个主key或是5个一次性的code,就像Google做的那样),以保证我们自己不会在某些意外情况下被挡在外面,如果你有任何好点子,欢迎留言,另外,下周我会再写一篇跟进文章,如果你感兴趣,欢迎通过Twitter follow我

-----------
本文来自"Simple Two-Factor SSH Authentication",作者:Richard Taylor ,翻译:@yuanyiz

想和我们一道传播黑客精神?快来加入吧!

无觅猜您也喜欢:

16条技巧让你更高效使用SSH

Gate One: 基于HTML5的Web SSH客户端

Google SPDY初探:HTTP 1.1之外的世界

在 Google Chrome 中运行 Termkit
无觅