0Pricing
Linux Command Line & Bash Scripting Mastery · レッスン

ユーザーとグループのプロビジョニング自動化

useradd、chage、sudoersフラグメントの管理を使い、アカウントを一括作成、変更、監査します。

「ユーザーとグループのプロビジョニング自動化」はCoddyKit上の無料Linux Command Line & Bash Scripting Masteryレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLinux Command Line & Bash Scripting Mastery学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

ユーザープロビジョニングを自動化する理由

useraddで一度に1人ずつユーザーを管理する方法は、アカウントが少数であれば問題ありません。しかし、エンタープライズ環境では通常、数十人から数百人のユーザーを同時に登録します。手作業でコマンドを実行すると、ミスが起こりやすく、設定に一貫性がなくなり、監査も困難になります。

Bashスクリプトを使うと、次のことができます。

  • 標準化された設定(シェル、ホームディレクトリ、パスワードポリシー)で、毎回同じようにユーザーを作成する
  • 新入社員のCSVまたはテキストファイルを読み込み、1回の実行でプロビジョニングする
  • すべての操作を記録し、コンプライアンスに対応する監査証跡を残す
  • 構成管理パイプライン(Ansible、Chef、Jenkins)と統合する

このレッスンでは、useradd、chage、usermod、グループ管理、sudoersのドロップイン、実行後の監査を取り上げながら、本番環境向けのユーザープロビジョニングスクリプトをゼロから構築します。

一括ユーザーリストの読み込み

一括プロビジョニングで標準的に使われる入力形式は、区切り文字で区切ったテキストファイルです。1行につき1レコードを記述します。一般的な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 — ログインシェルを設定します
  • -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

初期パスワードの安全な設定

スクリプトにパスワードをハードコードしてはいけません。一括プロビジョニングで安全に行う方法は次の2つです。

  • ランダムな初期パスワードを生成するには、openssl randまたは/dev/urandomを使い、パスワードを1回だけ表示して、初回ログイン時に変更するようユーザーに強制します
  • 事前にハッシュ化したパスワードを設定するには、SHA-512ハッシュを指定してusermod -pを使います。これにより、平文パスワードがプロセス一覧に表示されません

スクリプトでパスワードを設定する場合は、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>というイディオムは、/etc/groupをgrepするよりも移植性が高く、読みやすい方法です。

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"
}

一括プロビジョニングスクリプトの完成

ここまでの内容をまとめると、1つのスクリプトで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の出力を一括で解析できます
  • シェルはあるがホームディレクトリがないユーザー — ログインできなくなる設定ミスです

構造化されたレポートを生成してセキュリティチームにメールで送信することは、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. 削除する前に、ホームディレクトリをtarballとしてアーカイブします
  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"

ドライランモードによるスクリプトのテスト

本番用のプロビジョニングスクリプトは、副作用なしでテストできなければなりません。すべての変更を行うコマンドをechoのスタブに置き換えるDRY_RUNフラグを使って、ドライランモードを実装してください。

パターンは単純です。run()ヘルパーを定義し、フラグに応じてコマンドを実行するか、コマンドを表示します。この方法には次の利点があります。

  • テスト中にすべてのコードパスを実行できる
  • 本番で実行した場合に実際に何が起こるかを正確に出力で確認できる
  • CIパイプラインで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(シェル)、-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のID管理に再現性と監査可能性を備えた安全な自動化レイヤーを構築できます。

よくある質問

「ユーザーとグループのプロビジョニング自動化」レッスンは無料ですか?

はい。「ユーザーとグループのプロビジョニング自動化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Linux Command Line & Bash Scripting Masteryコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

「ユーザーとグループのプロビジョニング自動化」で何を学びますか?

useradd、chage、sudoersフラグメントの管理を使い、アカウントを一括作成、変更、監査します。 ブラウザで直接実行するハンズオンコードでLinux Command Line & Bash Scripting Masteryを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Linux Command Line & Bash Scripting Masteryを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLinux Command Line & Bash Scripting Masteryは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「ユーザーとグループのプロビジョニング自動化」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLinux Command Line & Bash Scripting Masteryレッスンでコードを書いて実行できますか?

はい。すべてのLinux Command Line & Bash Scripting Masteryレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ユーザーとグループのプロビジョニング自動化
  2. systemdサービスの制御とユニットファイルの作成
  3. ディスク、ファイルシステム、マウントの自動化
  4. システムヘルスチェックとアラートスクリプトの構築
← Linux Command Line & Bash Scripting Masteryに戻る