在运维工作中为用户重置密码是常见的操作,虽然手工运行 passwd 命令就可以很方便地设置,但在用户忘记密码后还需要管理员操作。在用户数量很大时也是不小的工作量。因此为用户提供工具来自动重置密码就很有必要。
技术方案
技术方案比较简单,用户在网页上选择重置密码,应用服务即向帐号服务器发出为用户重置密码的请求,然后将新密码通过邮件或其它安全方式发送给用户。
但是帐号服务器重置的密码通常为一次性密码,没有其它选择,导致使用很不方便。但别是对芯片等行业,用户需要先登录ETX/VDI才能使用系统。因此应用服务需要自动为用户通过passwd命令重置密码。
passwd 命令为交互式命令,因此需要借助 expect 来自动化重置密码。在 Python 中已经有 pexpect 模块来简化我们的工作了。
示例代码如下:
import os
import secrets
import string
import traceback
import pexpect
def genPasswd(length=10,numDigit=3,numSchar=1,sChar='@#*_='):
alphabet = string.ascii_letters + string.digits + sChar
password = ''
while True:
password = ''.join(secrets.choice(alphabet) for i in range(length))
if (any(c.islower() for c in password) and
any(c.isupper() for c in password) and
sum(not c.isalnum() for c in password) == numSchar and
sum(c.isdigit() for c in password) >= numDigit):
break
return password
class userHandler():
def __init__(self, request):
...
pass
def changePassword(self):
EMSG = ''
try:
opass = self._tmpPass # 一次性密码
npass = genPasswd() # 新密码
self._userPass = npass
cmd = f"su {self._account} -c passwd"
prompt_00 = ".*(press RETURN).*"
prompt_01 = "Current Password:"
prompt_02 = "New password:"
prompt_03 = "Retype new password:"
prompt_04 = "passwd: all authentication tokens updated successfully."
pattern = [prompt_00, prompt_01, prompt_02, prompt_03, prompt_04, pexpect.EOF, pexpect.TIMEOUT]
logfile = os.path.join(self._config['TMPDIR'], f"{self._account}.log")
logfd = open(logfile,'wb')
cpw = pexpect.spawn(cmd)
cpw.logfile = logfd
succeeded = False
count = 0
while count < 6:
count += 1
index = cpw.expect(pattern)
if index == 0:
logging.info(f'check {prompt_00} before: {cpw.before} after: {cpw.after}')
cpw.sendline('')
elif index == 1:
logging.info(f'check {prompt_01} before: {cpw.before} after: {cpw.after}')
cpw.sendline(opass)
elif index == 2:
logging.info(f'check {prompt_02} before: {cpw.before} after: {cpw.after}')
cpw.sendline(npass)
elif index == 3:
logging.info(f'check {prompt_03} before: {cpw.before} after: {cpw.after}')
cpw.sendline(npass)
elif index == 4:
logging.info(f'check {prompt_04} before: {cpw.before} after: {cpw.after}')
cpw.close(force=True)
succeeded = True
break
elif index == 5:
logging.info(f'EOF before: {cpw.before} after: {cpw.after}')
cpw.close(force=True)
break
elif index == 6:
logging.info(f'TIMEOUT before: {cpw.before} after: {cpw.after}')
cpw.close(force=True)
break
if succeeded and os.path.exists(logfile):
os.unlink(logfile)
if succeeded:
logging.info(f"Change pass succeeded for {self._account}")
else:
logging.info(f"Change pass failed for {self._account}")
EMSG = f"Change password failed for {self._account}"
except Exception as e:
logging.error(f"Change password failed for {self._account}. Error: {str(e)}, stack: {traceback.format_exc()}")
EMSG = f"Change password failed for {self._account}"
return EMSG
pexpect 支持将每次匹配的环境保留到日志文件中。在以上示例程序中,如果重置密码成功将删除日志以免占用空间,如果失败则保留日志文件以便分析失败原因。
以上示例脚本假设运行脚本的帐号能切换到用户身份不需要密码,如果以普通帐号运行此脚本,还需要增加一点代码。