2021年にCentOS 8がEOLを迎えたとき、5台のサーバーをRocky Linuxにわずか1週間で移行しなければならなかった。ひどいストレスだったが、セキュリティ設定全体を見直す貴重な機会でもあった — そこで気づいたのは、ほとんどのサーバーのパスワードポリシーがほぼデフォルトのままだということ。複雑さの強制なし、再利用の制限なし、何もない状態。それがlibpwqualityを本格的に調べ始めたきっかけだ。
この記事は、CentOS Stream 9での実際の導入経験をもとにしている — LDAPとローカルアカウントを組み合わせた約30ユーザーのproduction環境だ。教科書的な理論ではない。
Linuxでパスワードポリシーを強制する3つのアプローチ
決断する前に3つのアプローチを検討した — それぞれ検討する理由があった:
1. pam_cracklib(レガシー)
古いPAMモジュールで、多くのディストリビューションにまだ存在する。かつてはRHEL 6/7の標準だった。今でも動作するが、RHEL 8以降はdeprecatedになっている。CentOS Stream 9では、このパッケージはデフォルトのリポジトリに存在しない。
2. libpwquality + pam_pwquality(現在の推奨)
RHEL 7以降のpam_cracklibの公式後継。同じ開発者(Tomáš Mráz)によるもので、後方互換性のあるAPIを持ちながら、より多くのオプション、pwscore CLIとの優れた統合、そして積極的なメンテナンスが特徴だ。CentOS Stream 9ではこれがデフォルトになっている。
3. カスタムPAMスクリプト + pam_exec
独自のシェルスクリプトを書き、pam_exec経由で呼び出す方法。最も柔軟だが、メンテナンスの悪夢だ。あるクライアントでこういったセットアップを見たことがある:コメントなしの200行近いbashスクリプトで、誰も触ろうとしない。これは絶対に避けたい。
実際の比較:pam_cracklib vs pam_pwquality
| 比較項目 | pam_cracklib | pam_pwquality |
|---|---|---|
| RHEL 9でのステータス | リポジトリに存在しない | プリインストール済み、デフォルト |
| 専用設定ファイル | なし(PAM引数のみ) | あり(/etc/security/pwquality.conf) |
| pwscore CLIツール | なし | あり |
| 辞書チェック | cracklib dict | cracklib dict(改善版) |
| リトライ試行 | あり | あり(設定可能) |
結論は明快:pam_pwquality一択だ。CentOS Stream 9ではpam_cracklibはデフォルトリポジトリにそもそも存在しない — 比較の余地すらない。
現在の環境を確認する
インストール済みと思い込まないこと。何かする前に必ず確認しよう:
# パッケージを確認
rpm -q libpwquality
# Output: libpwquality-1.4.4-8.el9.x86_64
# 現在のPAM設定を確認
grep -n pwquality /etc/pam.d/system-auth
grep -n pwquality /etc/pam.d/password-auth
CentOS Stream 9を新規インストールした場合、次のような出力が表示される:
password requisite pam_pwquality.so try_first_pass local_users_only
まだインストールされていない場合やインストールし直す必要がある場合:
dnf install libpwquality -y
/etc/security/pwquality.conf の設定
これが中心的な設定ファイルだ。編集前は必ずバックアップを取る — 昨年、PAM設定のミスでproductionサーバーがロックアウトされた経験から身に付いた習慣だ:
cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak
vim /etc/security/pwquality.conf
ユーザーが不満を持つほど厳しくない、適度なenterprise環境向けの設定:
# /etc/security/pwquality.conf
# 最小パスワード長
minlen = 12
# 文字種ごとの最小文字数(負の値 = 必須)
minclass = 3 # upper、lower、digit、otherの4種類のうち少なくとも3種類が必要
dcredit = -1 # 数字を最低1文字必須
ucredit = -1 # 大文字を最低1文字必須
lcredit = -1 # 小文字を最低1文字必須
ocredit = 0 # 特殊文字:任意(ユーザーから苦情が多い)
# 辞書とパターンのチェック
dictcheck = 1 # cracklibの辞書でチェック
usercheck = 1 # パスワードにユーザー名を使用禁止
enforcing = 1 # 品質基準を満たさない場合は失敗(警告だけでなく)
# 連続する同一文字の制限
maxrepeat = 3 # 3回以上の連続は禁止:「aaaa」は拒否される
maxsequence = 4 # 連続した文字列は禁止:「1234」、「abcd」
# 旧パスワードと異なる文字数
difok = 5
# 入力ミス時のリトライ回数
retry = 3
# 追加の禁止ワード(状況に応じて追加)
# badwords = company password admin root
creditロジックの説明
dcredit、ucreditなどは最も混乱しやすい部分だ。ルールはシンプル:
- 負の値(例:
-1):その種類の文字を最低1文字必須にする - 正の値(例:
1):その種類の文字1文字ごとにminlenにボーナスポイントを加算する(cracklibからの旧来の動作) - 0:このルールを適用しない
負の値を使うことをお勧めする — より明確で監査もずっとしやすい。
ポリシーを適用するためのPAM設定
重要な注意:authselectを使用するCentOS Stream 9では、/etc/pam.d/system-authや/etc/pam.d/password-authを直接編集してはならない。そのファイルは上書きされる。
# 現在のプロファイルを確認
authselect current
# 通常の出力:
# Profile ID: sssd
# Enabled features: with-faillock
pam_pwqualityにオプションを追加する必要がある場合は、authselectカスタムプロファイルを使用する:
# sssdをベースにカスタムプロファイルを作成
authselect create-profile custom-security --base-on sssd
# 新しいプロファイルのpassword-authファイルを編集
vim /etc/authselect/custom/custom-security/password-auth
pam_pwquality.soの行を見つけて調整する。rootにもポリシーを適用したい場合はenforce_for_rootを追加する:
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 enforce_for_root authtok_type=
その後、プロファイルを適用する:
authselect select custom/custom-security with-faillock --force
chageを使ったパスワードエイジングの設定
しかし、libpwqualityだけでは不十分だ。パスワード変更時の品質チェックしかできず、定期的な変更を強制しない。chageと/etc/login.defsも必要になる:
# /etc/login.defsで新規ユーザーのデフォルト設定を確認
grep -E 'PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_WARN_AGE' /etc/login.defs
# /etc/login.defsを編集
PASS_MAX_DAYS 90 # 90日後にパスワード変更を強制
PASS_MIN_DAYS 1 # 再変更まで最低1日待つ
PASS_WARN_AGE 14 # 有効期限14日前に警告
# 既存ユーザーに適用(例:ユーザーhieu)
chage -M 90 -m 1 -W 14 hieu
# ユーザーのエイジング設定をすべて確認
chage -l hieu
pwscoreを使った実際のテスト
libpwqualityがpam_cracklibより優れている最大の点:pwscoreツールがあることだ。このCLIはユーザーを作成したり実際のパスワードを変更したりすることなく、即座にパスワードをテストできる — ポリシーのデバッグや自動化スクリプトの検証に非常に便利だ:
# パスワードの強度をテスト(stdinで入力)
echo "password123" | pwscore
# Output: 0 (非常に弱い)
echo "P@ssw0rd" | pwscore
# Output: 50 (普通)
echo "Xk9#mNqL2vBp" | pwscore
# Output: 100 (強い)
スコアは0〜100。ポリシーを満たさないパスワードは具体的な理由とともにエラーを表示する:
echo "abc" | pwscore
# The password is shorter than 12 characters.
# Password quality check failed:
# The password is shorter than 12 characters.
pwscoreをオンボーディングスクリプトに組み込んで、アカウント作成前にパスワードを検証している。直接試して拒否されるよりもずっと効率的だ。
設定全体の検証
最もシンプルな実際のテスト:パスワードを変更してシステムの反応を確認する。テストユーザーを使用し、rootは使わないこと:
# 一般ユーザーのパスワード変更でテスト(テストにrootは使用しないこと)
passwd testuser
# 弱いパスワードを試す:「simple」
# Expected: BAD PASSWORD: The password is shorter than 12 characters.
# クラス不足のパスワードを試す:「alllowercasehere」
# Expected: BAD PASSWORD: The password contains less than 1 uppercase letters
# 有効なパスワードを試す:「Secure#2024Linux」
# Expected: passwd: all authentication tokens updated successfully.
# パスワード変更時のログを確認
tail -f /var/log/secure | grep passwd
Production環境での注意点
rootとenforce_for_root:デフォルトでは、rootは他のユーザーに弱いパスワードを設定できるが、警告が表示されるだけでブロックはされない。rootにも強制したい場合は、PAM設定のpam_pwquality.so行にenforce_for_rootを追加する(上記参照)。注意:rootは古いパスワードを求められないため、新旧パスワードの比較チェックは実行されない — これは正常な動作であり、バグではない。
LDAP/ADユーザー:libpwqualityはPAM経由でのローカルパスワード変更時のみ適用される。ユーザーがLDAPサーバーで直接パスワードを変更した場合、このポリシーは効果がない。LDAPサーバー側で別途設定が必要だ。
自動化スクリプト:平文パスワードでchpasswdを使用する場合、PAMは通常通り呼び出される — pam_pwqualityがチェックする。例外:chpasswd -eは事前にハッシュ化されたパスワードを受け取るため、品質チェックが完全にバイパスされる。後でセキュリティ監査をしたとき驚かないように知っておくこと。強制を確実にしたい場合はpasswdを使うこと。
# ランダムな強力なパスワードで複数のユーザーを作成
for user in user1 user2 user3; do
useradd "$user"
# 16文字のランダムなパスワードを生成
pass=$(openssl rand -base64 16 | tr -d '=+/' | head -c 16)
echo "${user}:${pass}" | chpasswd
# 初回ログイン時にパスワード変更を強制
chage -d 0 "$user"
echo "User: $user | Pass: $pass"
done
このconfigをproductionで6ヶ月間運用した結果、最初の1ヶ月は「パスワードを忘れた」というチケットが約15%増加したが(ユーザーからパスワードが覚えにくいという苦情)、その後は安定した。ブルートフォースやcredential stuffingのリスクを軽減できることを考えれば、受け入れられるトレードオフだ。

