Hướng dẫn cấu hình xác thực 2 yếu tố (2FA) cho SSH trên CentOS Stream 9 với Google Authenticator và PAM

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

Tại sao chỉ dùng SSH Key thôi là chưa đủ?

Hầu hết sysadmin đều tắt đăng nhập bằng mật khẩu (PasswordAuthentication no) và chỉ dùng SSH Key. Cách này an toàn hơn nhiều so với đặt password thông thường. Tuy nhiên, rủi ro vẫn tồn tại nếu laptop của developer bị dính trojan, mã độc đánh cắp file id_ed25519 không có passphrase, hoặc vô tình push nhầm key lên GitHub repo công khai.

Kẻ tấn công có được private key là vào thẳng server. Để chặn đứng nguy cơ này, bạn nên dựng thêm lớp xác thực 2 yếu tố (2FA). Khi đó, người đăng nhập phải thỏa mãn cả 2 điều kiện: có SSH Key trên máy và có mã OTP 6 số sinh ra từ điện thoại theo thời gian thực.

Dưới đây là các bước mình đã triển khai thực tế trên dàn server CentOS Stream 9 nội bộ, sử dụng module PAM của Google Authenticator. Cơ chế này dùng chuẩn TOTP (RFC 6238), tương thích tốt với mọi app như Google Authenticator, Microsoft Authenticator, 1Password hay Authy.

Cài đặt Google Authenticator PAM

Gói google-authenticator không có sẵn trong repo mặc định của CentOS Stream 9 mà nằm trong EPEL (Extra Packages for Enterprise Linux).

Bước 1: Cài đặt package từ EPEL

Chạy các lệnh sau với quyền sudo:

# Cài kho EPEL
sudo dnf install -y epel-release

# Cài module PAM Google Authenticator và thư viện tạo mã QR
sudo dnf install -y google-authenticator qrencode-libs

Bước 2: Tạo secret key và mã OTP cho từng user

Lưu ý: Hãy switch sang đúng user cần cấp quyền SSH rồi mới chạy lệnh khởi tạo. Không nên dùng tài khoản root để tạo key cho user thường.

google-authenticator

Script tương tác của Google Authenticator sẽ hỏi bạn 5 câu hỏi cấu hình:

  • Do you want authentication tokens to be time-based (y/n)? → Gõ y để dùng cơ chế TOTP (mã đổi mới mỗi 30 giây).
  • Màn hình sẽ hiển thị mã QR kèm Secret key và 5 emergency scratch codes (mã cứu hộ). Hãy quét QR vào app trên điện thoại ngay, sau đó copy 5 mã cứu hộ cất vào Bitwarden/1Password phòng khi mất máy.
  • Do you want me to update your "~/.google_authenticator" file (y/n)? → Gõ y để ghi file cấu hình vào thư mục home.
  • Do you want to disallow multiple uses of the same authentication token (y/n)? → Gõ y nhằm chặn replay attack (mỗi mã OTP chỉ được dùng 1 lần duy nhất).
  • By default, a new token is generated every 30 seconds… (y/n)? → Gõ n để giữ độ trễ chuẩn (cửa sổ 3 mã, lệch tối đa ±30 giây). Chỉ chọn y nếu server và điện thoại thường xuyên bị lệch giờ quá nặng.
  • Do you want to enable rate-limiting (y/n)? → Gõ y để giới hạn tối đa 3 lần thử sai trong 30 giây, giúp chống brute-force OTP.

Cấu hình PAM và SSH Daemon

Sau khi có file ~/.google_authenticator, ta cần cấu hình SSHD và PAM để bắt buộc nhập OTP khi đăng nhập.

Bước 1: Khai báo module trong PAM

Mở file /etc/pam.d/sshd:

sudo vi /etc/pam.d/sshd

Thêm dòng sau vào vị trí đầu file (trước các quy tắc auth khác):

auth required pam_google_authenticator.so nullok

Mẹo thực tế: Tùy chọn nullok giúp user chưa kịp chạy lệnh tạo OTP vẫn đăng nhập được bằng SSH key như cũ. Khi toàn bộ thành viên trong team đã quét mã 2FA xong, bạn hãy bỏ chữ nullok đi để ép buộc 100% tài khoản phải có OTP mới vào được server.

Bước 2: Cấu hình OpenSSH trên CentOS 9

Trên CentOS Stream 9 (OpenSSH 8.7+), bạn nên viết cấu hình vào thư mục /etc/ssh/sshd_config.d/ thay vì sửa trực tiếp file chính:

sudo vi /etc/ssh/sshd_config.d/2fa.conf

Nội dung file 2fa.conf:

# Bật xác thực tương tác bàn phím (để SSH client hiển thị prompt nhập OTP)
KbdInteractiveAuthentication yes

# Bật PAM
UsePAM yes

# Buộc người dùng qua 2 lớp: đúng Public Key TRƯỚC, rồi mới nhập OTP
AuthenticationMethods publickey,keyboard-interactive

Cấu hình AuthenticationMethods publickey,keyboard-interactive đảm bảo nếu client không có SSH Key hợp lệ, server sẽ từ chối kết nối ngay từ đầu mà không thèm hỏi đến OTP.

Bước 3: Kiểm tra cú pháp và khởi động lại SSHD

Luôn test file cấu hình trước khi restart service để tránh bị lỗi cú pháp làm crash SSHD:

sudo sshd -t

Nếu không có cảnh báo nào, restart lại dịch vụ:

sudo systemctl restart sshd

Cực kỳ quan trọng: Giữ nguyên terminal SSH hiện tại! Mở một tab terminal mới trên máy cá nhân để test kết nối trước. Nếu cấu hình sai, bạn vẫn còn session cũ để sửa lỗi ngay mà không bị lock out.

Kiểm tra kết nối và theo dõi log

1. Test đăng nhập thực tế

Mở terminal trên máy cá nhân và SSH vào server:

ssh username@your-server-ip

Trình tự xác thực sẽ như sau:

  1. Client tự động bắt cặp SSH Key thành công.
  2. Server phản hồi prompt: Verification code:.
  3. Nhập 6 chữ số từ app Authenticator trên điện thoại và nhấn Enter để vào shell.

2. Theo dõi log xác thực

Để kiểm tra xem module PAM có hoạt động đúng không hoặc điều tra khi ai đó nhập sai mã, bạn xem log real-time qua journalctl:

sudo journalctl -u sshd -f

Khi đăng nhập thành công, bạn sẽ thấy log dạng:

Accepted keyboard-interactive/pam for user from 192.168.1.50 port 52341 ssh2

Nếu nhập sai OTP, log sẽ báo Invalid verification code hoặc Failed keyboard-interactive/pam. Dựa vào chuỗi log này, bạn có thể cấu hình Fail2ban quét và chặn IP tự động sau 3 lần gõ sai.

3. Xử lý khi mất điện thoại hoặc đổi máy mới

Nếu không thể mở app lấy mã OTP:

  • Nhập 1 trong 5 mã Emergency scratch codes (mã 8 chữ số) đã lưu khi cài đặt. Mỗi mã dùng được đúng 1 lần.
  • Sau khi vào được server, xóa cấu hình cũ và tạo lại mã QR mới:
rm -f ~/.google_authenticator
google-authenticator

Thiết lập này tốn khoảng 5-10 phút nhưng nâng mức độ bảo mật cho cụm server Linux lên rất nhiều, loại bỏ hoàn toàn nguy cơ bị xâm nhập khi lộ file private key.

Share: