ansible批量修改服务器密码

Ansible批量修改服务器密码:从Shell到Python的进化之路

📌 需求背景

服务器密码定期轮换是运维的基本要求。之前我写过一套基于Python socket的客户端主动上报密码脚本,这次尝试用Ansible主控端直接修改的方式。网上大多数方案需要编写复杂的playbook或手动传递变量,本文提供两种更轻量的实现:Shell版本和Python版本,让你可以交互式选择目标主机并自动生成安全密码。

🏗️ 一、方案对比

方案优点缺点适用场景
Python socket客户端上报 无需主控端,被动接收 每台机器都要部署客户端 大规模、跨网络环境
Ansible Shell版本 无需额外模块,依赖少 交互式输入,需手动选择 少量主机,临时修改
Ansible Python版本 代码更优雅,扩展性强 需要安装passlib库 需要更多功能定制

🐚 二、Shell版本:快速轻量

2.1 完整代码

#!/bin/bash
# ============================================
# Ansible批量修改root密码脚本 (Shell版)
# 功能:读取hosts文件、选择主机、生成密码、加密、修改
# 依赖:ansible, python3, passlib
# ============================================

# 获取所有定义的主机(过滤空行、注释行、分组行)
allhost=`egrep -v '^$|^#|^\[' /etc/ansible/hosts | awk -F ' ' '{print $1}'`
now=`date +'%Y-%m-%d %H:%M:%S'`

# 显示所有主机
echo -e "\033[32m可用的主机列表:\033[0m"
for ip in $allhost
do
    echo $ip
done

# 选择目标主机
echo -e "\033[33;5m-----------------------\033[0m"
read -p "请输入以上其中一台主机: " host

# 验证输入是否在列表中
if ! echo "$allhost" | grep -q "^$host$"; then
    echo -e "\033[31m错误:输入的主机不在列表中!\033[0m"
    exit 1
fi

# 生成15位随机密码(字母+数字)
passwd=`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15`

# 保存密码到文件(审计用)
echo "$now $host $passwd" >> ~/script/passwd.txt
echo -e "\033[32m主机: $host  密码: $passwd\033[0m"

# 使用Python生成SHA512加密密码(Ansible user模块需要加密后的密码)
newpass=`/usr/bin/python3 -c "from passlib.hash import sha512_crypt; print(sha512_crypt.using(rounds=5000).hash('$passwd'))"`

# 执行Ansible修改密码
ansible $host -m user -a "name=root password='$newpass' update_password=always"

if [ $? -eq 0 ]; then
    echo -e "\033[32m密码修改成功!\033[0m"
else
    echo -e "\033[31m密码修改失败,请检查Ansible连接\033[0m"
fi

2.2 运行效果

Shell版本运行截图

🐍 三、Python版本:优雅扩展

3.1 完整代码

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# ============================================
# Ansible批量修改root密码脚本 (Python版)
# 功能:列出主机索引、选择、生成密码、加密、修改
# 依赖:ansible, passlib
# ============================================

import os
import random
import string
from passlib.hash import sha512_crypt

def get_hosts():
    """从ansible hosts文件读取所有主机"""
    # 过滤空行、注释行和分组行
    cmd = "grep -vE '^$|^#|^\[' /etc/ansible/hosts | awk '{print $1}'"
    with os.popen(cmd) as f:
        hosts = [line.strip() for line in f if line.strip()]
    return hosts

def randpass(length=15):
    """生成指定长度的随机密码(字母+数字)"""
    chars = string.ascii_letters + string.digits
    return ''.join(random.choice(chars) for _ in range(length))

def display_hosts(hosts):
    """带索引显示主机列表"""
    print("\n\033[32m可用的主机列表:\033[0m")
    for idx, host in enumerate(hosts):
        print(f"{idx}: {host}")
    print("\033[33m-----------------------\033[0m")

def main():
    # 获取主机列表
    hosts = get_hosts()
    if not hosts:
        print("\033[31m错误:没有找到有效主机配置!\033[0m")
        return
    
    # 显示主机
    display_hosts(hosts)
    
    # 选择主机
    try:
        choice = int(input('请选择主机编号: '))
        if choice < 0 or choice >= len(hosts):
            print("\033[31m错误:编号超出范围!\033[0m")
            return
    except ValueError:
        print("\033[31m错误:请输入有效的数字!\033[0m")
        return
    
    # 获取选中的主机
    target_host = hosts[choice].strip()
    
    # 生成密码
    plain_password = randpass()
    encrypted_password = sha512_crypt.using(rounds=5000).hash(plain_password)
    
    # 显示结果
    print(f"\n\033[32m您选择的主机: {target_host}")
    print(f"新密码: {plain_password}\033[0m\n")
    
    # 保存密码到文件(审计用)
    with open(os.path.expanduser('~/script/passwd.txt'), 'a') as f:
        f.write(f"{target_host} {plain_password}\n")
    
    # 执行Ansible修改密码
    ansible_cmd = f"ansible {target_host} -m user -a 'name=root password={encrypted_password} update_password=always'"
    print(f"执行命令: {ansible_cmd}")
    
    result = os.system(ansible_cmd)
    if result == 0:
        print("\033[32m✅ 密码修改成功!\033[0m")
    else:
        print("\033[31m❌ 密码修改失败,请检查Ansible连接\033[0m")

if __name__ == '__main__':
    main()

3.2 运行效果

Python版本运行截图

🔍 四、关键代码解析

4.1 从hosts文件提取主机

# Shell版本:使用egrep过滤空行、注释行和分组行
egrep -v '^$|^#|^\[' /etc/ansible/hosts | awk '{print $1}'

# Python版本:使用grep和列表推导
cmd = "grep -vE '^$|^#|^\[' /etc/ansible/hosts | awk '{print $1}'"
hosts = [line.strip() for line in os.popen(cmd) if line.strip()]

4.2 随机密码生成

# Shell版本:从/dev/urandom读取随机字符
passwd=`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15`

# Python版本:使用random和string模块
def randpass(length=15):
    chars = string.ascii_letters + string.digits
    return ''.join(random.choice(chars) for _ in range(length))

4.3 密码加密(SHA512)

# 为什么需要加密?
Ansible的user模块修改密码时,需要传入加密后的密码字符串,而不是明文。
Linux系统使用/etc/shadow存储加密密码,通常使用SHA512算法($6$开头)。

# 使用passlib库(推荐)
from passlib.hash import sha512_crypt
encrypted = sha512_crypt.using(rounds=5000).hash('明文密码')

# 或者使用crypt库(Python内置)
import crypt
encrypted = crypt.crypt('明文密码', crypt.mksalt(crypt.METHOD_SHA512))

⚠️ 五、安全注意事项

🔴 1. 密码存储安全

脚本将明文密码保存到~/script/passwd.txt,请确保该文件权限严格:

chmod 600 ~/script/passwd.txt
# 或考虑使用加密存储/密码管理工具

🔴 2. Ansible连接安全

确保Ansible使用ssh密钥认证,而不是密码认证(否则陷入死循环)。

🔴 3. 审计日志

所有密码修改操作都应记录日志,包括时间、操作人、目标主机。可以在脚本中添加:

echo "$(date '+%Y-%m-%d %H:%M:%S') $USER 修改 $host 密码" >> /var/log/password_change.log

🔴 4. 密码复杂度

如果系统有密码复杂度要求(如必须包含特殊字符),修改randpass函数:

chars = string.ascii_letters + string.digits + "!@#$%^&*()"

📝 六、扩展与改进

6.1 批量修改多台主机

# 修改Python版本,支持多选(输入1,3,5或1-5)
choices = input('请选择主机编号(多个用逗号分隔): ')
# 解析选择并循环执行

6.2 使用ansible-playbook的替代方案

# 编写playbook change_pass.yml
- hosts: "{{ target }}"
  tasks:
    - name: Change root password
      user:
        name: root
        password: "{{ new_password | password_hash('sha512') }}"
        update_password: always

# 执行
ansible-playbook -e "target=web01 new_password={{ 生成的密码 }}" change_pass.yml

6.3 集成密码管理工具

可以将生成的密码自动上传到Vault或KeePass等密码管理工具,避免明文文件存储。

📊 七、总结

本文提供了两种基于Ansible的批量密码修改方案:

  • Shell版本:简单轻量,依赖少,适合快速使用
  • Python版本:代码优雅,交互友好,易于扩展

相比之前用Python socket客户端上报的方式,Ansible主控端修改省去了客户端部署的麻烦,特别适合已经用Ansible管理的环境。关键是安全处理密码存储和加密,避免明文传输和存储风险。


—— 从被动上报到主动管理,运维工具在进化,我们的技能也要跟上。

posted @ 2019-12-25 20:13  一起走过的路  阅读(5296)  评论(0)    收藏  举报