根据等保要求,要符合双因子认证要求,密码+证书,密码过于简单一成不变也不行,结合googleauth模块可解决上述问题,OTP多种随机数生成规则,我选用基于时间的OTP,基于企业微信完成对用户访问的限制,测试与生产使用2套密钥。
- openvpn配置
script-security 3 #认证过程将用户提交的用户名及密码保存在系统变量中
reneg-sec 0 #不进行密码过期检查
auth-user-pass-verify /etc/openvpn/checkpwd.py via-env #认证脚本,这里需要注意via-env与via-file ,都说via-env不安全,还是要根据接受程度来吧。
username-as-common-name #把用户名当作通用凭证
2 . 企业微信开启API调用并读取用户进入应用消息通知,这里我用的django
1).django建模
class gjskvpnuser(models.Model):
'''VPN用户列表'''
username = models.CharField('用户名', max_length=200)
userslat = models.CharField('加盐值', max_length=200)
otpcode = models.IntegerField('动态码')
otptype = models.CharField('环境类型', max_length=200)
realname = models.CharField('真实姓名', max_length=200)
status = models.CharField('状态', max_length=200)
2).django views.py
from django.http import HttpResponse
from wechatpy.enterprise.crypto import WeChatCrypto
from wechatpy.exceptions import InvalidSignatureException
from wechatpy.enterprise.exceptions import InvalidCorpIdException
from wechatpy.enterprise import parse_message, create_reply
import time
import pyotp
import pymysql
from .models import vpnuser
import re
def index(request):
if(request.method == 'POST'):
signature=request.GET.get('msg_signature')
timestamp=request.GET.get('timestamp')
echo_str=request.GET.get('echostr')
nonce=request.GET.get('nonce')
crypto = WeChatCrypto("xxxx", "yyyy", "zzzz")
raw_message = request.body
#######解密用户请求
decrypted_xml = crypto.decrypt_message(
raw_message,
signature,
timestamp,
nonce
)
msg = parse_message(decrypted_xml)
######获取用户进入应用事件是哪个标签,0生产、1测试
parte = re.match(r'^.*#sendmsg#_(\d{1})#', str(msg))
sourceuser = msg.source
reqtype = parte.group(1)
obj = vpnuser.objects.filter(username=sourceuser, otptype=reqtype).values('userslat')
print(obj)
usersalt = obj[0].get('userslat')
charp = pyotp.TOTP(usersalt, interval=300)
otpcode = charp.now()
if int(reqtype) == 0:
rtype="生产"
else:
rtype="测试"
rmsg="""您的{0}环境在动态密码为:{1}\n有效期5分钟。""".format(rtype, otpcode)
########回复用户
xml = create_reply(rmsg).render()
encrypted_xml = crypto.encrypt_message(xml, nonce, timestamp)
return HttpResponse(encrypted_xml)
- openvpn服务器端checkpwd.py结合mysql对用户身份进行OTP认证,脚本获取传入的系统变量
#!/usr/bin/python
import pymysql
import sys
import os
import pyotp
db_cmd="`which mysql`"
db_host="127.0.0.1"
db_user="dbuser"
db_pass="dbpass"
db_name="dbname"
conn = pymysql.connect(
host=db_host,
user=db_user,
password=db_pass,
database=db_name,
charset="utf8",
cursorclass=pymysql.cursors.DictCursor)
cursor = conn.cursor()
sql_cmd = """select userslat from otp_vpnuser where username='{0}' and otptype='0'""".format(os.environ['username'])
if cursor.execute(sql_cmd):
re_list = cursor.fetchall()[0]
if re_list.get('userslat'):
authotp = pyotp.TOTP(re_list.get('userslat'), interval=300)
if authotp.verify(os.environ['password']):
sys.exit(0)
else:
sys.exit(1)
else:
sys.exit(1)
else:
sys.exit(1)
conn.close()
- openvpn客户端设置,因为动态密码缘故,过期难免要重启服务自动填充密码
1).密码文件,设置权限600
auth-user-pass /etc/openvpn/.passwd.txt
2).auto_pass.py密码自动更新文件,并加入定时任务。
#!/usr/bin/env python
import socket
import pyotp
import os
slatcode = "xxxxxx" #用户标识码为固定的,不用去数据库读取
hostname = socket.gethostname()
with open(r"/etc/openvpn/.passwd.txt", 'w') as f:
o_otp = pyotp.TOTP(slatcode, interval=300)
f.write(hostname + '\n')
f.write(o_otp.now())
scode = os.system("ping 172.16.0.1 -c 1 -W 1 -q")
if scode != 0:
os.system("systemctl restart openvpn@client")