自动重置密码

在运维工作中为用户重置密码是常见的操作,虽然手工运行 passwd 命令就可以很方便地设置,但在用户忘记密码后还需要管理员操作。在用户数量很大时也是不小的工作量。因此为用户提供工具来自动重置密码就很有必要。

技术方案

技术方案比较简单,用户在网页上选择重置密码,应用服务即向帐号服务器发出为用户重置密码的请求,然后将新密码通过邮件或其它安全方式发送给用户。

自动重置密码_第1张图片

但是帐号服务器重置的密码通常为一次性密码,没有其它选择,导致使用很不方便。但别是对芯片等行业,用户需要先登录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 支持将每次匹配的环境保留到日志文件中。在以上示例程序中,如果重置密码成功将删除日志以免占用空间,如果失败则保留日志文件以便分析失败原因。

以上示例脚本假设运行脚本的帐号能切换到用户身份不需要密码,如果以普通帐号运行此脚本,还需要增加一点代码。

你可能感兴趣的:(编程,运维,python)