环境:
CentOS 6.3 minimal i386
基础组件安装:
1 |
yum -y install wget gcc make pam-devel libpng-devel |
一、安装qrencode
在Linux上,有一个名为 QrenCode 的命令行工具可以很容易帮我们生成二维码,google authenticator命令行生成二维码就是调用它。
1 |
wget http://fukuchi.org/works/qrencode/qrencode-3.3.1. tar .gz |
2 |
tar zxf qrencode-3.3.1. tar .gz |
3 |
cd qrencode-3.3.1 |
4 |
./configure --prefix=/usr && make && make install |
二、安装google authenticator PAM插件
1 |
wget http://google-authenticator.googlecode.com/files/libpam-google-authenticator-1.0- source . tar .bz2 |
2 |
tar jxf libpam-google-authenticator-1.0- source . tar .bz2 |
3 |
cd libpam-google-authenticator-1.0 |
4 |
make && make install |
三、配置google authenticator
Google Authenticator 的服务器端已经安装好了,那么客户端呢?Android 用户请点这里安装,iOS 用户请点这里安装,其他智能手机用户也有相应的开源解决方案,请自行搜索下载。非智能手机用户暂时无解。
Google Authenticator 其实是一套开源的解决方案,所以不仅在 Google 的网站上能用,在其他地方也能用的。然而,在 Google 的网站上,会直接给你一个 QR 码让你扫的,而自己配置的 Google Authenticator 则要自己生成了。
首先需要切换到对应的用户,如果 VPS 上只有一个用户的话,自然是可以省略这一步的,但是多用户的 VPS 需要先切换到对应的用户,再运行 google-authenticator 命令,程序会问你Do you want authentication tokens to be time-based (y/n),大意是基于时间生成验证码(及TOTP),这里选择y。结果类似这样:
这个 QR 码自然是给 Google Authenticator 应用程序来扫描的,也可以访问上面的那个链接,用 Google Chart API 生成的 QR 码来扫描。还可以照着 QR 码下面的文字密钥手工输入。当 Google Authenticator 识别了这个账号之后,验证器就配置好了。在文字密钥下面还提供了几个应急码,为手机丢了等情况下所用的,可以妥善保管。
这时 Google Authenticator 虽然运行了,但是相关设置还没有保存,程序会问你 Do you want me to update your “/root/.google_authenticator” file (y/n) (是否将配置写入家目录的配置文件),当然是回答 y 了。又会问
Do you want to disallow multiple uses of the same authentication token? This restricts you to one login about every 30s, but it increases your chances to notice or even prevent man-in-the-middle attacks (y/n)
大意是说是否禁止一个口令多用,自然也是答 y。下一个问题是
By default, tokens are good for 30 seconds and in order to compensate for possible time-skew between the client and the server, we allow an extra token before and after the current time. If you experience problems with poor time synchronization, you can increase the window from its default size of 1:30min to about 4min. Do you want to do so (y/n)
大意是问是否打开时间容错以防止客户端与服务器时间相差太大导致认证失败。这个可以根据实际情况来。我的ipad时间很准(与网络同步的),所以答 n,如果一些平板电脑不怎么连网的,可以答 y 以防止时间错误导致认证失败。再一个问题是
If the computer that you are logging into isn't hardened against brute-force login attempts, you can enable rate-limiting for the authentication module. By default, this limits attackers to no more than 3 login attempts every 30s. Do you want to enable rate-limiting (y/n)
选择是否打开尝试次数限制(防止暴力攻击),自然答 y。
问题答完了,家目录中多出一个 .google_authenticator 文件(默认权限为 400),这时客户端与服务端已经配套起来了,以后不用再运行 google-authenticator 命令了,否则会重新生成一组密码。
四、配置SSH验证
此时虽然 Google Authenticator 已经配置好了,但是并没有任何程序会去调用它。所以需要设置 SSH 登录的时候去通过它验证。
打开 /etc/pam.d/sshd 文件,添加
auth required pam_google_authenticator.so
这一行,保存。再打开 /etc/ssh/sshd_config 文件,找到
ChallengeResponseAuthentication no
把它改成
ChallengeResponseAuthentication yes
并保存。最后,输入
service ssh restart
来重启 SSH 服务以应用新的配置。
这时候再用 SSH 登录的话就会这样了:
这样就成功了。
五、错误解决
5.1 selinux需设定为disabled,不然SSH就无法登陆了。(不关闭的方法还没找到,继续探索中…)
5.2 google authenticator命令行下不生成二维码问题解决方法
strace -o log google-authenticator
log信息如下:
execve("/usr/local/bin/google-authenticator", ["google-authenticator"], [/* 20 vars */]) = 0 brk(0) = 0x9993000 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ce000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=24158, ...}) = 0 mmap2(NULL, 24158, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77c8000 close(3) = 0 open("/lib/libdl.so.2", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\n\0\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=17892, ...}) = 0 mmap2(NULL, 16500, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x415000 mmap2(0x418000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x418000 close(3) = 0 open("/lib/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@n\1\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1886288, ...}) = 0 mmap2(NULL, 1644936, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x71d000 mmap2(0x8a9000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18c) = 0x8a9000 mmap2(0x8ac000, 10632, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x8ac000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77c7000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb77c76c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0x8a9000, 8192, PROT_READ) = 0 mprotect(0x418000, 4096, PROT_READ) = 0 mprotect(0x384000, 4096, PROT_READ) = 0 munmap(0xb77c8000, 24158) = 0 getuid32() = 0 brk(0) = 0x9993000 brk(0x99b4000) = 0x99b4000 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) close(3) = 0 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) close(3) = 0 open("/etc/nsswitch.conf", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=1688, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77cd000 read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1688 read(3, "", 4096) = 0 close(3) = 0 munmap(0xb77cd000, 4096) = 0 open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=24158, ...}) = 0 mmap2(NULL, 24158, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77c8000 close(3) = 0 open("/lib/libnss_files.so.2", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\32\0\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=58704, ...}) = 0 mmap2(NULL, 53964, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x564000 mmap2(0x570000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb) = 0x570000 close(3) = 0 mprotect(0x570000, 4096, PROT_READ) = 0 munmap(0xb77c8000, 24158) = 0 open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 3 fcntl64(3, F_GETFD) = 0x1 (flags FD_CLOEXEC) fstat64(3, {st_mode=S_IFREG|0644, st_size=1471, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77cd000 read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1471 close(3) = 0 munmap(0xb77cd000, 4096) = 0 uname({sys="Linux", node="srv", ...}) = 0 open("/dev/urandom", O_RDONLY) = 3 read(3, "\310\322\201\33\371\21\222\357YLD\24n\214&\312\320\210\306\216\305\367\274X\210\21*\2\214\377", 30) = 30 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77cd000 write(1, "\n", 1) = 1 write(1, "Do you want authentication token"..., 57) = 57 fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77cc000 read(0, "y\n", 1024) = 2 write(1, "https://www.google.com/chart?chs"..., 113) = 113 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 open("/etc/ld.so.cache", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0644, st_size=24158, ...}) = 0 mmap2(NULL, 24158, PROT_READ, MAP_PRIVATE, 4, 0) = 0xb77c1000 close(4) = 0 open("/lib/tls/i686/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/tls/i686/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/lib/tls/i686/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/tls/i686", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/lib/tls/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/tls/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/lib/tls/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/tls", {st_mode=S_IFDIR|0555, st_size=4096, ...}) = 0 open("/lib/i686/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/i686/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/lib/i686/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/i686", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 open("/lib/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/lib/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/lib", {st_mode=S_IFDIR|0555, st_size=12288, ...}) = 0 open("/usr/lib/tls/i686/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/tls/i686/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/usr/lib/tls/i686/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/tls/i686", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/usr/lib/tls/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/tls/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/usr/lib/tls/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/tls", {st_mode=S_IFDIR|0555, st_size=4096, ...}) = 0 open("/usr/lib/i686/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/i686/sse2", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/usr/lib/i686/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/i686", 0xbfda1c78) = -1 ENOENT (No such file or directory) open("/usr/lib/sse2/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib/sse2", {st_mode=S_IFDIR|0555, st_size=4096, ...}) = 0 open("/usr/lib/libqrencode.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/lib", {st_mode=S_IFDIR|0555, st_size=20480, ...}) = 0 munmap(0xb77c1000, 24158) = 0 open("/etc/ld.so.cache", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0644, st_size=24158, ...}) = 0 mmap2(NULL, 24158, PROT_READ, MAP_PRIVATE, 4, 0) = 0xb77c1000 close(4) = 0 open("/lib/tls/libqrencode.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) open("/lib/i686/libqrencode.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) open("/lib/libqrencode.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/tls/libqrencode.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/sse2/libqrencode.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/libqrencode.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) munmap(0xb77c1000, 24158) = 0 write(1, "Your new secret key is: ZDJICG7Z"..., 41) = 41 write(1, "Your verification code is 770393"..., 33) = 33 write(1, "Your emergency scratch codes are"..., 34) = 34 write(1, " 42189708\n", 11) = 11 write(1, " 50825864\n", 11) = 11 write(1, " 83761911\n", 11) = 11 write(1, " 12434961\n", 11) = 11 read(3, "\345\216Qk", 4) = 4 read(3, "\260\3374\0", 4) = 4 write(1, " 19934208\n", 11) = 11 close(3) = 0 write(1, "\n", 1) = 1 write(1, "Do you want me to update your \"/"..., 71) = 71 read(0, "n\n", 1024) = 2 exit_group(0) = ?
问题出在google-authenticator调用libqrencode.so.3时,在相应路径查询不到,解决方法很简单。
①编译qrencode时指定路径即可及。./configure �Cprefix=/usr。(默认编译安装至/usr/local)
这样libqrencode.so.3就会安装到/usr/lib目录下。
②还有种方法就是默认编译安装,然后做个软连接。