Security

Hardening SSH cho VPS mới: chỉ dùng key, đổi port, fail2ban và CrowdSec

VPS mới dựng xong, IP public vừa gán, là bot đã gõ cửa port 22. Log /var/log/auth.log của một máy chưa làm gì đặc biệt vẫn đầy hàng nghìn dòng thử root với đủ mật khẩu phổ biến, mỗi ngày. Đây không phải chuyện dọa nhau: internet có các đợt quét liên tục, và một VPS để mặc định PasswordAuthentication yes cùng tài khoản root mật khẩu yếu là mục tiêu bị chiếm trong vài giờ. Bài này đi qua bộ tối thiểu nên làm cho mọi VPS mới: chuyển sang xác thực bằng key, tắt password và root login, siết sshd_config, rồi thêm một lớp chặn brute-force bằng fail2ban hoặc CrowdSec. Trọng tâm là làm đúng thứ tự để không tự khóa mình ra ngoài.

Điểm cốt lõi: key auth, không phải đổi port

Trước khi làm gì, cần phân biệt rạch ròi hai loại biện pháp, vì nhầm lẫn ở đây khiến nhiều người tưởng mình an toàn trong khi không. Xác thực bằng SSH key mới là điểm chính. Một key Ed25519 hoặc RSA 3072-bit trở lên về thực tế không thể brute-force, trong khi mật khẩu con người đặt thì có. Khi đã tắt hẳn PasswordAuthentication, toàn bộ các đợt dò mật khẩu tự động trở nên vô nghĩa vì server không còn chấp nhận cơ chế đó.

Đổi port SSH (ví dụ từ 22 sang 2222) thì khác. Nó chỉ là security by obscurity: giảm nhiễu log và né được đám bot ngu chỉ quét port 22, nhưng bất kỳ ai nmap một cái là thấy port mới ngay. Đổi port không làm server an toàn hơn về bản chất; nó chỉ làm log sạch hơn. Cứ làm nếu muốn đỡ nhiễu, nhưng đừng coi đó là lớp bảo mật, và tuyệt đối đừng bỏ key auth chỉ vì đã đổi port.

Tạo SSH key và cài lên server

Làm trên máy client (máy của bạn), không phải trên VPS. Ed25519 là khuyến nghị mặc định cho key mới hiện nay: ngắn, nhanh, an toàn.

# Trên máy local
ssh-keygen -t ed25519 -a 100 -C "you@laptop"
# Enter để nhận đường dẫn mặc định ~/.ssh/id_ed25519
# ĐẶT passphrase cho key (bảo vệ khi laptop bị mất)

Đẩy public key lên VPS. Cách gọn nhất là ssh-copy-id (khi còn đang bật password để đăng nhập lần cuối):

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-vps-ip
# Hoặc thủ công: dán nội dung .pub vào ~/.ssh/authorized_keys trên server
# và đặt quyền cho đúng:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Lưu ý một quy tắc vàng: dùng một tài khoản thường (không phải root) có quyền sudo. Nếu chưa có, tạo trước khi siết cấu hình:

# Trên VPS, với quyền root
adduser deploy
usermod -aG sudo deploy      # Debian/Ubuntu
# usermod -aG wheel deploy    # RHEL/Rocky/Alma

Kiểm tra key hoạt động trước khi tắt password

Đây là bước hay bị bỏ qua và là nguyên nhân số một khiến người ta tự khóa mình ra khỏi VPS. Mở một terminal MỚI và đăng nhập bằng key, trong khi vẫn giữ nguyên session cũ đang mở. Chỉ khi login mới thành công không hỏi mật khẩu thì mới đi tiếp.

# Terminal mới, giữ terminal cũ mở nguyên
ssh -i ~/.ssh/id_ed25519 deploy@your-vps-ip
# Nếu vào được mà không hỏi password -> key OK, đi tiếp
# Nếu bị hỏi password -> DỪNG, sửa authorized_keys/quyền trước

Siết sshd_config

Sửa /etc/ssh/sshd_config (hoặc thả file riêng vào /etc/ssh/sshd_config.d/). Đây là bộ thiết lập tối thiểu loại bỏ brute-force credential:

# /etc/ssh/sshd_config.d/99-hardening.conf

# Xác thực: chỉ key
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey

# Không cho root đăng nhập trực tiếp
PermitRootLogin no

# Chỉ cho phép tài khoản cụ thể
AllowUsers deploy

# Giới hạn thử và session rỗi
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2

# Tắt các cơ chế ít dùng, dễ bị lạm dụng
X11Forwarding no
AllowAgentForwarding no

Vài điểm cần hiểu đúng thay vì chép máy móc:

  • PermitRootLogin no, đừng dùng without-password. Giá trị without-password vẫn cho root vào bằng key. Đặt hẳn no và bắt buộc leo thang quyền qua sudo từ một user có tên, để mọi hành động đặc quyền đều truy được về một danh tính cụ thể.
  • AllowUsers là whitelist: chỉ những tài khoản liệt kê ở đây mới được SSH vào. Nhớ thêm đúng tên user của bạn, nếu không dòng này sẽ khóa tất cả.
  • MaxAuthTries 3 giới hạn số lần thử trong một kết nối; ClientAliveInterval 300 + ClientAliveCountMax 2 ngắt session rỗi sau khoảng 10 phút.

Nếu muốn đổi port (chỉ để giảm nhiễu, đã nói ở trên là obscurity), thêm Port 2222 và nhớ mở port đó trên firewall trước khi restart. Test cú pháp rồi mới nạp lại, luôn giữ session cũ mở phòng khi hỏng:

sudo sshd -t                 # kiểm cú pháp, không output = OK
sudo systemctl reload ssh    # hoặc reload sshd tùy distro
# Debian/Ubuntu mới dùng socket: có thể cần
# sudo systemctl restart ssh.socket

fail2ban: lớp chặn brute-force cổ điển

Khi đã tắt password, brute-force login gần như vô hại, nhưng fail2ban vẫn hữu ích để cắt nhiễu và chặn IP quét dai dẳng ở tầng firewall. Nó đọc log, đếm số lần fail trong một cửa sổ thời gian, và ban IP vi phạm. Cài và cấu hình jail cho sshd:

sudo apt install fail2ban        # Debian/Ubuntu
# Tạo /etc/fail2ban/jail.local (KHÔNG sửa jail.conf gốc)
# /etc/fail2ban/jail.local
[sshd]
enabled  = true
backend  = systemd
maxretry = 3
findtime = 10m
bantime  = 1h
# bantime = -1 để ban vĩnh viễn; hoặc bật bantime.increment

Ba tham số cần nắm: findtime là cửa sổ đếm fail (10 phút), maxretry là số lần fail tối đa trong cửa sổ đó trước khi ban, bantime là thời gian bị chặn. Trên distro hiện đại nên đặt backend = systemd để đọc journald thay vì file log. Bật và kiểm tra:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd   # xem IP đang bị ban

CrowdSec: lựa chọn hiện đại hơn

CrowdSec giải quyết cùng vấn đề nhưng theo kiến trúc khác và mạnh hơn ở quy mô lớn. Khác biệt lớn nhất là threat intelligence cộng đồng: khi agent của bạn phát hiện một IP tấn công, nó báo lên dịch vụ trung tâm, và đổi lại bạn kéo về một blocklist do cộng đồng đóng góp. Nghĩa là một IP tấn công người khác có thể bị chặn trên máy bạn trước khi nó chạm tới, thay vì chờ nó tự tấn công bạn rồi mới phản ứng như fail2ban.

Vài khác biệt kỹ thuật đáng cân nhắc:

  • Tách phát hiện và thực thi. CrowdSec chia thành agent (phân tích log, quyết định) và bouncer (thực thi chặn). Bạn có thể phát hiện ở một máy và chặn ở nhiều máy khác, hợp với hạ tầng nhiều server.
  • Scenario theo hành vi. Thay vì chỉ khớp regex một dòng log, CrowdSec dùng scenario YAML diễn đạt hành vi theo thời gian ("5 lần fail trong 2 phút"), giảm false positive.
  • Chịu tải tốt hơn. fail2ban đuối khi có hàng nghìn rule firewall; CrowdSec dùng IP set (nftables) xử lý hàng triệu IP hiệu quả, hợp với server public bị quét dày.
  • Chặn ở tầng ứng dụng. Bouncer nginx của CrowdSec có thể hiện CAPTCHA cho khách "khả nghi nhưng có thể là người" thay vì drop thẳng.

Cài nhanh CrowdSec cùng bouncer firewall cho SSH:

curl -s https://install.crowdsec.net | sudo sh
sudo apt install crowdsec
sudo apt install crowdsec-firewall-bouncer-nftables
sudo cscli metrics          # xem log đang được phân tích
sudo cscli decisions list   # xem IP đang bị chặn

Nên chọn cái nào? Dùng fail2ban khi ưu tiên đơn giản, ít phụ thuộc, một server đơn lẻ với jail đã quen. Chọn CrowdSec khi quản lý nhiều máy hoặc muốn hưởng lợi từ blocklist cộng đồng. Cả hai đều là lớp phụ; lớp chính vẫn là key auth. Không có công cụ nào cứu được một server còn bật password login với mật khẩu yếu.

Tóm lược

Thứ tự an toàn cho một VPS mới, tránh tự khóa mình ra ngoài:

  1. Tạo Ed25519 key trên máy local, cài public key lên VPS, tạo user thường có sudo.
  2. Mở terminal mới, xác nhận login bằng key thành công trước khi đụng cấu hình.
  3. Trong sshd_config: PasswordAuthentication no, PermitRootLogin no, AllowUsers, MaxAuthTries 3; sshd -t rồi reload, giữ session cũ mở.
  4. Thêm fail2ban (đơn giản, một máy) hoặc CrowdSec (nhiều máy, có threat intel cộng đồng).
  5. Đổi port nếu muốn giảm nhiễu, nhưng hiểu đó là obscurity chứ không phải security.

Quy trình này áp dụng được cho mọi VPS mới, bất kể nhà cung cấp. Tham khảo tài liệu chính thức: sshd_config manual (OpenSSH), fail2ban, và CrowdSec docs.