linux上各应用的权限认证使用pam机制(http://www.linux-//linux-pam/linux-pam)。
这里需要一个支持otp验证的pam模块,可以直接用pam_script模块,通过编写脚本实现。
1. 安装pam_script
yum install pam_script或从/jeroennijhof/pam_script编译安装。
这里直接yum安装,会安装下面几个文件:
[root@localhost root]# ll /lib64/security/pam_script.so
-rwxr-xr-x. 1 root root 15416 8月 23 /lib64/security/pam_script.so
[root@localhost root]# ll /etc/pam_script*
-rwxr-xr-x. 1 root root 3836 2月 13 12:21 /etc/pam_script
lrwxrwxrwx. 1 root root 10 2月 10 14:59 /etc/pam_script_acct -> pam_script
lrwxrwxrwx. 1 root root 10 2月 10 14:59 /etc/pam_script_auth -> pam_script
lrwxrwxrwx. 1 root root 10 2月 10 14:59 /etc/pam_script_passwd -> pam_script
lrwxrwxrwx. 1 root root 10 2月 10 14:59 /etc/pam_script_ses_close -> pam_script
lrwxrwxrwx. 1 root root 10 2月 10 14:59 /etc/pam_script_ses_open -> pam_script
另外还会生成/etc/pam-script.d/目录。
2. 修改/etc/pam.d/sshd文件,最前面增加下面一行配置,表示使用pam_script认证,只要通过pam_script认证成功就通过,否则,则使用原来的方式验证。
auth sufficientpam_script.so
3. 参考pam_script的资料和源码可知,使用pam_script进行认证,会调用/etc/pam_script_auth,实际调用的是/etc/pam_script,进一步调用/etc/pam-script.d/*_auth。
/etc/pam-script.d/*_auth里能得到请求认证的用户名密码等,exit的值为1即为认证失败。
所以只需要在/etc/pam-script.d/下增加一个*_auth的文件,能以otp方式判断用户名密码是否正确,相应返回exit值即可。
文件:/etc/pam-script.d/totp_auth
#! /bin/shstamp=`/bin/date +'%Y-%m-%d %H:%M:%S'`script=`basename $0`LOGFILE=/tmp/pam-script.logecho ============= >> $LOGFILEecho $stamp $script $PAM_SERVICE $PAM_TYPE\user=$PAM_USER ruser=$PAM_RUSER rhost=$PAM_RHOST\tty=$PAM_TTY \args=["$@"] \>> $LOGFILEecho check $PAM_USER >> $LOGFILE#if [[ "x$PAM_AUTHTOK" == "123456" ]]; then# exit 0#fi#exit 1python3 /root/check_totp.py $PAM_USER $PAM_AUTHTOKexit $?
这里进一步使用python脚本检查otp动态口令是否正确。
totp密钥配置在认证用户的个人目录下的.pam_totp.conf中,以一行一组k=v格式配置key、key_format、window、interval参数,文件所有者需要为认证用户,读写权限为0600。
文件:/root/check_totp.py
# -*- coding: utf-8 -*-import timeimport os,sysimport pwd,statfrom otp import *# 日志def logprint(*msg):pass#print(*msg)# 读取配置,格式每行一组k=vdef get_config(path):logprint('load config: ', path)config = {}with open(path, 'r') as f:while True:line = f.readline()if not line:breakif line.startswith('#'):continueidx = line.find("=")if idx >= 0:a = line[0:idx]b = line[idx+1:]config[a.strip()] = b.strip()logprint('config: ', config)return config# 检查totp口令def check(username, token):if not username or not token:return 1#### 获取用户uu = pwd.getpwnam(username)if not uu:logprint('用户没找到')return 1#### 获取路径if not uu.pw_dir:logprint('用户目录没找到')return 1path = uu.pw_dir + '/.pam_totp.conf'### 获取totp配置文件信息,要求文件权限为0600,文件所属为当前认证用户fst = os.stat(path)mode = fst.st_modeif not stat.S_ISREG(mode):logprint('totp配置文件不是普通文件')return 1if fst.st_size <= 0 or fst.st_size > 1024 * 10:logprint('totp配置文件为空或文件大小超过10KB')return 1if stat.S_IMODE(mode) != (0o600):logprint('totp配置文件权限不是0600')return 1uid = fst.st_uidif fst.st_uid != uu.pw_uid:logprint('totp配置文件所有者不是当前认证用户')return 1#### 获取配置config = get_config(path)key = config.get('key')key_format = config.get('key_format')window = config.get('window')interval = config.get('interval')if not key:logprint('otp参数项key为空')return 1if not key_format:key_format = "string"if not window:window = 3if not interval:interval = 30#### 验证totpt = TOTP(None, int(window), int(interval))t.setSecret(key, key_format)if t.verify(token) != True:logprint('totp检测失败')return 1else:logprint('totp检查成功')return 0def main(argv):#time.sleep(3)if len(argv) < 3:logprint('参数个数错误')exit(1)username = argv[1]token = argv[2]ret = check(username, token)logprint('检测结果:', ret)exit(ret)if __name__ == "__main__":main(sys.argv)
文件:/root/otp.py
# 参看/superzlc/otp
文件:.pam_totp.conf
key=02191234567890qwertyuiopkey_format=stringwindow=3interval=30
4. 其他
totp动态口令,可以用微信小程序“动态口令”生成。