0Pricing
DevOps Bootcamp · 课时

自动化用户与用户组配置

使用 useradd、chage 和 sudoers 片段管理批量创建、修改和审计账户。

自动化用户与用户组配置 是 CoddyKit 上的免费 DevOps Bootcamp 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 DevOps Bootcamp 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 DevOps Bootcamp 课程共包含 4 节课。

为什么要自动化用户配置?

使用 useradd 一次管理一个用户,对于少量账户来说完全可行;但在企业环境中,通常需要同时为数十或数百名用户完成入职配置。手动执行命令容易出错、缺乏一致性,也无法进行审计。

Bash 脚本可以帮助您:

  • 每次都使用标准化设置(Shell、主目录、密码策略)创建用户
  • 读取包含新员工信息的 CSV 或文本文件,并在一次运行中完成配置
  • 记录每项操作,从而为合规性提供审计轨迹
  • 与配置管理流水线(Ansible、Chef、Jenkins)集成

本课将从头开始讲解如何构建生产级用户配置脚本,内容涵盖 useradd、chage、usermod、组管理、sudoers 插入文件以及运行后的审计。

读取批量用户列表

批量配置所采用的规范输入格式是带分隔符的文本文件——每行一条记录。典型的 CSV 可能如下所示:

username,full_name,group,shell
alice,Alice Smith,developers,/bin/bash
bob,Bob Jones,ops,/bin/zsh

请在 while 循环中使用 IFS 和 read 安全地解析该文件。使用 tail -n +2 跳过标题行,可以使逻辑更加清晰。

关键的防御性实践:

  • 去除每个字段开头和结尾的空白字符
  • 跳过空行以及以 # 开头的注释行
  • 在调用任何系统命令之前,验证必填字段不为空
#!/usr/bin/env bash
# parse_users.sh — safely read a CSV of users
set -euo pipefail

USER_FILE="${1:-users.csv}"

[[ -f "$USER_FILE" ]] || { echo "ERROR: $USER_FILE not found"; exit 1; }

tail -n +2 "$USER_FILE" | while IFS=',' read -r username full_name group shell; do
  # trim whitespace
  username="${username// /}"
  [[ -z "$username" || "$username" == \#* ]] && continue

  echo "Parsed -> user=$username group=$group shell=$shell"
done

使用 useradd 创建用户

useradd 是底层工具,会写入 /etc/passwd、/etc/shadow 和 /etc/group。用于编写脚本的最重要选项包括:

  • -m — 创建主目录
  • -s — 设置登录 Shell
  • -c — GECOS 注释字段(全名)
  • -G — 附加组(以逗号分隔)
  • -e — 账户过期日期(YYYY-MM-DD)

调用 useradd 之前,请始终先使用 id 检查用户是否已存在;对已有用户运行该命令会返回退出代码 9,并打印错误信息,从而可能使日志变得杂乱。

注意: useradd 需要 root 权限。请在脚本开头加入权限检查。

#!/usr/bin/env bash
# create_user.sh — idempotent single-user creation
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USERNAME="$1"
FULL_NAME="${2:-}"
GROUP="${3:-staff}"
SHELL="${4:-/bin/bash}"

if id "$USERNAME" &>/dev/null; then
  echo "[SKIP] User $USERNAME already exists"
else
  useradd \
    --create-home \
    --shell "$SHELL" \
    --comment "$FULL_NAME" \
    --groups "$GROUP" \
    "$USERNAME"
  echo "[OK] Created $USERNAME"
fi

安全地设置初始密码

绝不要在脚本中硬编码密码。批量配置时,有两种安全方法:

  • 生成随机初始密码:使用 openssl rand 或 /dev/urandom 生成密码,显示一次,并强制用户在首次登录时修改密码
  • 设置预先哈希的密码:使用 usermod -p 配置 SHA-512 哈希值,使明文密码不会出现在进程列表中

chpasswd 是脚本化设置密码的推荐工具——它从标准输入读取 username:password 对,因此密码不会出现在命令行参数中(不会通过 ps 显示)。

设置密码后,请使用 chage -d 0 强制用户在下次登录时立即重置密码。

#!/usr/bin/env bash
# set_temp_password.sh
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USERNAME="$1"

# Generate a 16-char random password (alphanumeric only)
TMP_PASS=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)

# Set password via chpasswd (password never in argv)
echo "${USERNAME}:${TMP_PASS}" | chpasswd

# Force password change on next login
chage -d 0 "$USERNAME"

echo "[OK] Temporary password for $USERNAME: $TMP_PASS"
echo "[OK] User must change password on first login"

使用 chage 管理密码有效期

chage(change age)用于控制存储在 /etc/shadow 中的密码有效期策略。企业安全策略通常要求:

  • 密码最长有效期(例如 90 天)
  • 密码再次修改前必须经过的最短天数
  • 过期前的警告期限
  • 自上次使用密码后,账户因不活动而锁定的时间

chage 的关键选项:

  • -M <days> — 密码最长有效期
  • -m <days> — 密码最短有效期
  • -W <days> — 过期前的警告天数
  • -I <days> — 账户锁定前的不活动天数
  • -E <date> — 账户的绝对过期日期
  • -l — 列出用户当前设置
#!/usr/bin/env bash
# apply_password_policy.sh — enforce org-wide ageing policy
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

# Policy constants
MAX_AGE=90
MIN_AGE=1
WARN_DAYS=14
INACTIVE_DAYS=30

apply_policy() {
  local user="$1"
  chage \
    -M "$MAX_AGE" \
    -m "$MIN_AGE" \
    -W "$WARN_DAYS" \
    -I "$INACTIVE_DAYS" \
    "$user"
  echo "[OK] Policy applied to $user"
}

# Apply to all non-system users (UID >= 1000)
awk -F: '$3 >= 1000 && $3 < 65534 { print $1 }' /etc/passwd | while read -r user; do
  apply_policy "$user"
done

批量管理组

组是控制资源访问权限的主要机制。配置脚本必须确保所需的组在将用户加入其中之前已经存在——useradd -G nonexistent 会失败。

请通过检查退出代码来幂等地使用 groupadd:如果组已经存在,它会返回 9。使用 getent group <name> 是一种可移植且易读的替代方式,无需 grep /etc/group。

gpasswd -a 会将用户添加到组中,而不会替换现有成员关系(不同于会替换附加组列表的 usermod -G)。

#!/usr/bin/env bash
# ensure_groups.sh — create groups if missing, then add users
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

REQUIRED_GROUPS=(developers ops security auditors)

for grp in "${REQUIRED_GROUPS[@]}"; do
  if getent group "$grp" &>/dev/null; then
    echo "[SKIP] Group $grp already exists"
  else
    groupadd "$grp"
    echo "[OK] Created group $grp"
  fi
done

# Safely add a user to a group (append, don't replace)
add_to_group() {
  local user="$1" group="$2"
  gpasswd -a "$user" "$group" 2>/dev/null && echo "[OK] $user -> $group"
}

完整的批量配置脚本

将所有内容整合起来:一个脚本读取 CSV,创建用户和组,设置密码策略,记录每项操作,并以平稳的方式处理错误,而不会停止整个批次。

下面脚本中的重要设计决策:

  • 带时间戳的 LOG_FILE 会记录所有操作,供审计使用
  • 单个用户的错误会被记录,但不会中止循环(|| log_error)
  • 脚本具有幂等性——部分失败后可以安全地重新运行
  • 所有输出都会通过 tee 同时发送到终端和日志文件
#!/usr/bin/env bash
# bulk_provision.sh — production user provisioning
set -uo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USER_FILE="${1:-users.csv}"
LOG_FILE="/var/log/user_provision_$(date +%F).log"

log()  { echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"; }
err()  { log "ERROR: $*"; }

log "=== Provisioning started from $USER_FILE ==="

tail -n +2 "$USER_FILE" | while IFS=',' read -r username fullname group shell; do
  username="${username// /}"
  [[ -z "$username" || "$username" == \#* ]] && continue
  group="${group:-staff}"
  shell="${shell:-/bin/bash}"

  # Ensure group exists
  getent group "$group" &>/dev/null || groupadd "$group"

  # Create user idempotently
  if id "$username" &>/dev/null; then
    log "[SKIP] $username exists"
  else
    useradd -m -s "$shell" -c "$fullname" -G "$group" "$username" || { err "useradd failed for $username"; continue; }
    TMP="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 14)"
    echo "${username}:${TMP}" | chpasswd
    chage -M 90 -m 1 -W 14 -I 30 -d 0 "$username"
    log "[OK] $username created (group=$group) tmp_pass=$TMP"
  fi
done

log "=== Provisioning complete ==="

sudoers 插入片段管理

直接编辑 /etc/sudoers 很危险——语法错误会使所有人无法使用 sudo。安全的方法是使用 插入文件,将它们放在 /etc/sudoers.d/ 中,并在放置到目标位置之前,使用 visudo -c -f 验证每个文件。

sudoers 片段的最佳实践:

  • 按照授予权限的团队或角色命名文件(例如 10-developers、20-ops)
  • 使用基于组的规则(%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl),而不是针对单个用户的行
  • 始终将权限设置为 0440,所有者设置为 root:root
  • 使用 visudo -c 验证——出现任何语法错误时,它都会以非零状态退出
#!/usr/bin/env bash
# write_sudoers_fragment.sh
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

FRAGMENT_NAME="${1:-10-developers}"
SUDOERS_DIR="/etc/sudoers.d"
TMP_FILE="$(mktemp)"

# Write the fragment to a temp file first
cat > "$TMP_FILE" << 'EOF'
# Developers: restart services and view journals without full root
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *, /usr/bin/journalctl
%ops        ALL=(ALL) NOPASSWD: ALL
EOF

# Validate BEFORE installing
if visudo -c -f "$TMP_FILE"; then
  install -m 0440 -o root -g root "$TMP_FILE" "${SUDOERS_DIR}/${FRAGMENT_NAME}"
  echo "[OK] Installed ${SUDOERS_DIR}/${FRAGMENT_NAME}"
else
  echo "[ERROR] sudoers syntax check failed — fragment NOT installed"
  rm -f "$TMP_FILE"
  exit 1
fi
rm -f "$TMP_FILE"

审计现有账户

完成配置后(以及按照定期计划),您应检查用户数据库中是否存在异常:

  • UID 0 账户——除 root 之外,任何 UID 为 0 的账户都是严重的安全问题
  • 无密码账户——/etc/shadow 中密码字段为空或为 ! 的条目
  • 已过期但仍处于活动状态的账户——可以批量解析 chage -l 的输出
  • 有 Shell 但没有主目录的用户——会导致登录失败的配置错误

使用 mail 将结构化报告发送给安全团队,或将报告追加到受监控的日志路径中,都很简单。

#!/usr/bin/env bash
# audit_users.sh — produce a security-relevant user report
set -uo pipefail

REPORT="/var/log/user_audit_$(date +%F).txt"

echo "=== User Audit Report $(date) ===" > "$REPORT"

echo "" >> "$REPORT"
echo "--- Accounts with UID 0 (should be root only) ---" >> "$REPORT"
awk -F: '$3 == 0 { print $1 }' /etc/passwd >> "$REPORT"

echo "" >> "$REPORT"
echo "--- Accounts with empty password field ---" >> "$REPORT"
awk -F: '($2 == "" || $2 == "!") && $3 >= 1000 { print $1 }' /etc/shadow 2>/dev/null >> "$REPORT" || echo "  (requires root)" >> "$REPORT"

echo "" >> "$REPORT"
echo "--- Normal users (UID 1000-60000) ---" >> "$REPORT"
awk -F: '$3 >= 1000 && $3 < 60000 { printf "%-20s uid=%-6s shell=%s\n", $1, $3, $7 }' /etc/passwd >> "$REPORT"

cat "$REPORT"
echo "Report saved to $REPORT"

锁定、解锁和删除账户

离职处理与入职配置同样重要。当用户离开时,正确的顺序是:

  1. 立即锁定账户(usermod -L)——在 shadow 密码哈希前添加 !,阻止登录但不删除数据
  2. 撤销 sudo 权限——如果存在其 sudoers 片段,则将其删除
  3. 转移所有权——将其文件的所有权转给主管或归档账户
  4. 归档——在删除之前,将主目录打包为 tar 文件
  5. 删除——使用 userdel -r,移除主目录和邮件队列

usermod -U 可以解锁账户(移除 ! 前缀),适用于临时停用的场景。

#!/usr/bin/env bash
# offboard_user.sh — lock, archive, then optionally delete
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USERNAME="$1"
ARCHIVE_DIR="/srv/archived-homes"
mkdir -p "$ARCHIVE_DIR"

# 1. Lock account
usermod -L "$USERNAME"
echo "[OK] Account $USERNAME locked"

# 2. Remove sudoers fragment if present
SUDOERS_FILE="/etc/sudoers.d/${USERNAME}"
[[ -f "$SUDOERS_FILE" ]] && rm -f "$SUDOERS_FILE" && echo "[OK] Removed sudoers fragment"

# 3. Archive home directory
HOME_DIR="$(getent passwd "$USERNAME" | cut -d: -f6)"
if [[ -d "$HOME_DIR" ]]; then
  tar -czf "${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz" -C "$(dirname "$HOME_DIR")" "$(basename "$HOME_DIR")"
  echo "[OK] Home archived to ${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz"
fi

echo "[NOTICE] Review archive, then run: userdel -r $USERNAME"

使用试运行模式测试脚本

生产环境中的配置脚本必须能够在不产生副作用的情况下进行测试。请使用 DRY_RUN 标志实现试运行模式,将所有会修改系统的命令替换为 echo 占位命令。

其模式很简单:定义一个 run() 辅助函数,根据标志决定是执行命令还是输出命令。这样做意味着:

  • 测试期间会执行每条代码路径
  • 输出会准确显示实际运行时将会发生什么
  • 持续集成流水线无需 root 权限即可验证逻辑

请将试运行模式与专用的测试用户前缀结合使用(例如 test_),这样便于在集成测试后进行清理。

#!/usr/bin/env bash
# provision_with_dryrun.sh
set -euo pipefail

DRY_RUN="${DRY_RUN:-false}"

# Wrapper: execute or echo
run() {
  if [[ "$DRY_RUN" == "true" ]]; then
    echo "[DRY-RUN] $*"
  else
    "$@"
  fi
}

create_user() {
  local user="$1" group="$2"
  if id "$user" &>/dev/null; then
    echo "[SKIP] $user exists"
    return
  fi
  run useradd -m -s /bin/bash -G "$group" "$user"
  run chage -M 90 -m 1 -W 14 -d 0 "$user"
  echo "[OK] $user provisioned (dry=$DRY_RUN)"
}

# Test run
DRY_RUN=true create_user testuser developers
echo "---"
create_user realuser developers 2>/dev/null || true

在不删除现有组成员关系的情况下将用户添加到附加组,应使用哪个命令?

在批量配置脚本中,您需要将现有用户加入 auditors 组。该用户已经是 developers 组和 staff 组的成员。哪个命令可以在添加新组的同时保留所有现有成员关系?

课程回顾:自动化用户和组配置

在本课中,您构建了一套完整的生产级用户配置工具。以下是要点:

  • 防御性解析输入——使用 IFS/read 循环,跳过空行和注释行,并在进行任何系统调用前验证字段
  • useradd 的要点——始终使用 -m(主目录)、-s(Shell)、-c(注释)和 -G(组);先用 id 检查用户是否存在,以确保幂等性
  • 密码——通过 chpasswd 的标准输入设置密码,使明文密码不会出现在进程参数中;使用 chage -d 0 强制用户在首次登录时重置密码
  • 使用 chage 配置策略——在所有非系统账户上统一设置最长有效期(-M)、警告天数(-W)和不活动锁定(-I)
  • 组成员关系——使用 gpasswd -a 或 usermod -aG(带有 -a 选项)追加成员关系,而不是替换成员关系
  • sudoers 插入文件——写入 /etc/sudoers.d/,安装前使用 visudo -c -f 验证,并设置 0440 root:root 权限
  • 离职处理——锁定(usermod -L)、归档主目录,然后删除;绝不要跳过归档步骤
  • 试运行模式——将会修改系统的命令封装在 run() 辅助函数中,使流水线能够在不产生 root 权限副作用的情况下验证逻辑

结合这些模式,您就能为任意规模的 Linux 身份管理提供可重复、可审计且安全的自动化层。

常见问题解答

「自动化用户与用户组配置」课时是免费的吗?

是的 — 「自动化用户与用户组配置」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 DevOps Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 DevOps Bootcamp 课程共包含 4 节课。

「自动化用户与用户组配置」这节课中我会学到什么?

使用 useradd、chage 和 sudoers 片段管理批量创建、修改和审计账户。 你通过在浏览器中直接运行的动手代码来练习 DevOps Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 DevOps Bootcamp 需要有经验吗?

无需任何先前经验。CoddyKit 上的 DevOps Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「自动化用户与用户组配置」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 DevOps Bootcamp 课中编写并运行代码吗?

能。每节 DevOps Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 自动化用户与用户组配置
  2. 控制 systemd 服务并编写单元文件
  3. 磁盘、文件系统与挂载自动化
  4. 构建系统健康检查与告警脚本
← 返回 DevOps Bootcamp