Thiết lập chính sách mật khẩu với libpwquality trên CentOS Stream 9: Bảo mật Enterprise thực chiến

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Sau cái vụ CentOS 8 EOL năm 2021, mình phải migrate gấp 5 server sang Rocky Linux trong đúng 1 tuần. Stress kinh khủng, nhưng cũng là dịp hiếm để audit lại toàn bộ cấu hình bảo mật — và mình phát hiện ra rằng chính sách mật khẩu trên phần lớn server đang ở mức… gần như mặc định. Không enforce độ phức tạp, không giới hạn reuse, không có gì cả. Đó là lúc mình bắt đầu tìm hiểu nghiêm túc về libpwquality.

Bài này dựa trên kinh nghiệm triển khai thực tế trên CentOS Stream 9 — môi trường production với ~30 user accounts, kết hợp LDAP và local accounts. Không phải lý thuyết sách giáo khoa.

3 Approach để Enforce Chính Sách Mật Khẩu trên Linux

Mình đã ngồi cân nhắc 3 hướng trước khi quyết định — mỗi cái đều có lý do riêng để xem xét:

1. pam_cracklib (legacy)

Module PAM cũ, vẫn còn trên nhiều distro. Từng là tiêu chuẩn trên RHEL 6/7. Vẫn chạy được — nhưng RHEL 8+ đã deprecate nó. Trên CentOS Stream 9, package này không còn tồn tại trong default repo.

2. libpwquality + pam_pwquality (khuyến nghị hiện tại)

Thay thế chính thức cho pam_cracklib từ RHEL 7 trở đi. Cùng developer (Tomáš Mráz), API tương thích ngược, nhưng nhiều option hơn, tích hợp tốt với pwscore CLI, và được maintain tích cực. Trên CentOS Stream 9, đây là mặc định.

3. Custom PAM script + pam_exec

Viết shell script riêng, gọi qua pam_exec. Flexible nhất — nhưng là maintenance nightmare. Mình từng thấy một setup như vậy tại khách hàng: script gần 200 dòng bash, không có comment, không ai dám đụng vào. Hard pass.

So Sánh Thực Tế: pam_cracklib vs pam_pwquality

Tiêu chí pam_cracklib pam_pwquality
Trạng thái trên RHEL 9 Không có trong repo Cài sẵn, mặc định
File config riêng Không (chỉ PAM args) Có (/etc/security/pwquality.conf)
pwscore CLI tool Không
Kiểm tra dictionary cracklib dict cracklib dict (tốt hơn)
Retry attempts Có (cấu hình được)

Câu trả lời ngắn: pam_pwquality, không bàn cãi. Trên CentOS Stream 9, pam_cracklib còn không có trong repo mặc định — chẳng có gì để so sánh nữa.

Kiểm Tra Môi Trường Hiện Tại

Đừng giả sử đã cài sẵn. Kiểm tra trước khi làm bất cứ thứ gì:

# Kiểm tra package
rpm -q libpwquality
# Output: libpwquality-1.4.4-8.el9.x86_64

# Kiểm tra PAM config hiện tại
grep -n pwquality /etc/pam.d/system-auth
grep -n pwquality /etc/pam.d/password-auth

Trên CentOS Stream 9 cài mới, bạn sẽ thấy output kiểu:

password    requisite     pam_pwquality.so try_first_pass local_users_only

Nếu chưa có hoặc cần cài lại:

dnf install libpwquality -y

Cấu Hình /etc/security/pwquality.conf

Đây là file cấu hình trung tâm. Mình luôn backup trước rồi mới sửa — thói quen từ cái lần server production bị lock out vì PAM config lỗi hồi năm ngoái:

cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak
vim /etc/security/pwquality.conf

Config mình đang dùng cho môi trường enterprise vừa phải (không quá strict đến mức user nổi điên):

# /etc/security/pwquality.conf
# Độ dài tối thiểu
minlen = 12

# Số ký tự tối thiểu mỗi loại (giá trị âm = bắt buộc)
minclass = 3        # Phải có ít nhất 3 trong 4 loại: upper, lower, digit, other
dcredit = -1        # Bắt buộc ít nhất 1 chữ số
ucredit = -1        # Bắt buộc ít nhất 1 chữ hoa
lcredit = -1        # Bắt buộc ít nhất 1 chữ thường
ocredit = 0         # Ký tự đặc biệt: không bắt buộc (user hay kêu)

# Kiểm tra dictionary và pattern
dictcheck = 1       # Kiểm tra từ điển cracklib
usercheck = 1       # Không cho dùng username trong password
enforcing = 1       # Fail hard nếu không đạt quality (không chỉ warn)

# Giới hạn ký tự lặp liên tiếp
maxrepeat = 3       # Không được lặp quá 3 lần: "aaaa" bị từ chối
maxsequence = 4     # Không cho sequence: "1234", "abcd"

# Số ký tự phải khác so với password cũ
difok = 5

# Số lần retry khi nhập sai
retry = 3

# Badwords bổ sung (thêm theo context)
# badwords = company password admin root

Giải thích logic credit

Phần dcredit, ucredit… hay gây nhầm lẫn nhất. Quy tắc đơn giản:

  • Giá trị âm (ví dụ: -1): bắt buộc phải có ít nhất 1 ký tự loại đó
  • Giá trị dương (ví dụ: 1): mỗi ký tự loại đó được cộng điểm vào minlen (legacy behavior từ cracklib)
  • Giá trị 0: không áp dụng rule này

Dùng giá trị âm đi — rõ ràng hơn và dễ audit hơn nhiều.

Cấu Hình PAM để Enforce Policy

Lưu ý quan trọng: Trên CentOS Stream 9 với authselect, KHÔNG sửa trực tiếp /etc/pam.d/system-auth hay /etc/pam.d/password-auth. File đó sẽ bị overwrite.

# Kiểm tra profile hiện tại
authselect current

# Output thường là:
# Profile ID: sssd
# Enabled features: with-faillock

Nếu cần thêm option vào pam_pwquality, dùng authselect custom profile:

# Tạo custom profile dựa trên sssd
authselect create-profile custom-security --base-on sssd

# Sửa file password-auth trong profile mới
vim /etc/authselect/custom/custom-security/password-auth

Tìm dòng pam_pwquality.so và điều chỉnh. Thêm enforce_for_root nếu muốn áp dụng policy cả cho root:

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 enforce_for_root authtok_type=

Sau đó apply profile:

authselect select custom/custom-security with-faillock --force

Cấu Hình Password Aging với chage

Nhưng libpwquality một mình không đủ. Nó chỉ kiểm tra chất lượng lúc user đang đổi mật khẩu — không tự bắt buộc đổi định kỳ. Cần thêm chage/etc/login.defs vào cuộc:

# Cấu hình mặc định cho user mới trong /etc/login.defs
grep -E 'PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_WARN_AGE' /etc/login.defs
# Sửa /etc/login.defs
PASS_MAX_DAYS   90    # Bắt buộc đổi mật khẩu sau 90 ngày
PASS_MIN_DAYS   1     # Phải chờ ít nhất 1 ngày trước khi đổi lại
PASS_WARN_AGE   14    # Cảnh báo 14 ngày trước khi hết hạn
# Áp dụng cho user hiện có (ví dụ: user hieu)
chage -M 90 -m 1 -W 14 hieu

# Xem toàn bộ aging settings của user
chage -l hieu

Kiểm Tra Thực Tế với pwscore

Lợi thế lớn nhất của libpwquality so với pam_cracklib: có công cụ pwscore. CLI này cho phép test mật khẩu ngay lập tức mà không cần tạo user hay đổi password thật — cực kỳ tiện khi debug policy hoặc validate script automation:

# Test độ mạnh của mật khẩu (nhập vào stdin)
echo "password123" | pwscore
# Output: 0 (rất yếu)

echo "P@ssw0rd" | pwscore
# Output: 50 (trung bình)

echo "Xk9#mNqL2vBp" | pwscore
# Output: 100 (mạnh)

Score từ 0-100. Mật khẩu không đạt policy thì báo lỗi kèm lý do cụ thể:

echo "abc" | pwscore
# The password is shorter than 12 characters.
# Password quality check failed:
#  The password is shorter than 12 characters.

Mình tích hợp pwscore vào script onboarding để validate mật khẩu trước khi tạo account. Tiết kiệm hơn nhiều so với thử trực tiếp rồi mới biết bị reject.

Verify Toàn Bộ Config

Test thực tế đơn giản nhất: thử đổi password và xem system phản ứng. Dùng user test, không dùng root:

# Test bằng cách đổi password user thường (KHÔNG dùng root để test)
passwd testuser

# Thử mật khẩu yếu: "simple"
# Expected: BAD PASSWORD: The password is shorter than 12 characters.

# Thử mật khẩu không đủ class: "alllowercasehere"
# Expected: BAD PASSWORD: The password contains less than 1 uppercase letters

# Thử password hợp lệ: "Secure#2024Linux"
# Expected: passwd: all authentication tokens updated successfully.
# Kiểm tra log khi đổi mật khẩu
tail -f /var/log/secure | grep passwd

Một Số Lưu Ý Khi Triển Khai Production

Root và enforce_for_root: Mặc định, root vẫn có thể đặt mật khẩu yếu cho user khác mà chỉ bị warn, không bị block. Muốn enforce cứng cho cả root, thêm enforce_for_root vào dòng pam_pwquality.so trong PAM config (xem phần trên). Lưu ý: root không bị hỏi old password nên các check so sánh old/new không chạy — đây là behavior bình thường, không phải bug.

LDAP/AD users: libpwquality chỉ enforce khi đổi mật khẩu local qua PAM. Nếu user đổi mật khẩu trực tiếp trên LDAP server, policy này không có tác dụng. Cần configure riêng ở phía LDAP.

Script automation: Khi dùng chpasswd với plaintext password, PAM vẫn được invoke bình thường — pam_pwquality sẽ check. Ngoại lệ: chpasswd -e nhận pre-hashed password nên quality check bị bypass hoàn toàn. Biết điều này để không ngạc nhiên khi audit sau này. Muốn đảm bảo enforce, dùng passwd thay thế.

# Tạo nhiều user với password ngẫu nhiên mạnh
for user in user1 user2 user3; do
    useradd "$user"
    # Tạo password ngẫu nhiên 16 ký tự
    pass=$(openssl rand -base64 16 | tr -d '=+/' | head -c 16)
    echo "${user}:${pass}" | chpasswd
    # Bắt buộc đổi mật khẩu lần đầu đăng nhập
    chage -d 0 "$user"
    echo "User: $user | Pass: $pass"
done

Sau 6 tháng chạy config này trên production, mình thấy tỷ lệ ticket “quên mật khẩu” tăng khoảng 15% trong tháng đầu (user kêu password khó nhớ), nhưng sau đó ổn định. Trade-off chấp nhận được so với việc giảm thiểu rủi ro bị brute-force hay credential stuffing.

Share: