Vấn Đề: Log Server Toàn Rác
Một thời gian trước, tôi đang quản lý server production cho một khách hàng có toàn bộ người dùng tập trung ở Đông Nam Á và Nhật Bản. Một buổi sáng mở log ra, tôi thấy hàng nghìn lần thử SSH thất bại, quét cổng, và các probe khai thác lỗ hổng ngẫu nhiên — tất cả đến từ một số quốc gia mà khách hàng không có bất kỳ quan hệ kinh doanh nào. fail2ban đã chạy sẵn. Rate limiting đã được thiết lập. Tiếng ồn vẫn cứ đến, ăn mòn dung lượng log và kích hoạt cảnh báo liên tục.
Đó là lúc tôi triển khai chặn GeoIP ở tầng tường lửa — drop traffic từ toàn bộ dải IP của một quốc gia trước khi nó chạm đến application stack. Nếu bạn đang vận hành bất kỳ server Linux nào public-facing, đây là thứ bạn cần có trong bộ công cụ của mình. Đây là thiết lập tôi đang dùng: nftables cho các quy tắc tường lửa và MaxMind GeoLite2 cho việc ánh xạ quốc gia sang địa chỉ IP.
Khái Niệm Cốt Lõi: Bạn Đang Làm Gì
nftables và Tính Năng Set
nftables là thế hệ kế tiếp của iptables. Nó đã có mặt trong kernel Linux từ phiên bản 3.13 và hiện là mặc định trên Ubuntu 22.04+, Debian 11+, và hầu hết các distro hiện đại. Lý do nó hoàn hảo cho GeoIP filtering chính là set với interval support tích hợp sẵn — bạn nạp hàng chục nghìn dải CIDR vào một named set và khớp với tất cả chúng bằng một quy tắc duy nhất. Không cần ipset, không cần daemon thêm. Cú pháp nftables thuần túy.
MaxMind GeoLite2
MaxMind duy trì các database ánh xạ dải IP sang quốc gia. GeoLite2 là gói miễn phí — tạo tài khoản, tạo license key, và bạn có thể tải về. Database Country được cập nhật hai lần mỗi tháng. Nó không bắt được mọi VPN exit node, nhưng để loại bỏ traffic quét hàng loạt từ các vùng cụ thể, nó tạo ra sự khác biệt rõ rệt.
Cách Các Phần Kết Hợp Lại
- Tải database GeoLite2 Country MMDB từ MaxMind
- Trích xuất dải IP cho các quốc gia mục tiêu bằng script Python
- Nạp các dải đó vào nftables set
- Viết quy tắc drop traffic inbound khớp với set đó
- Tự động làm mới database hai tuần một lần qua cron
Thực Hành Thiết Lập
Bước 1: Tải Database GeoLite2
Đăng ký tại maxmind.com và lấy license key miễn phí. Sau đó kéo file MMDB về:
sudo apt install mmdb-bin wget -y
MAXMIND_KEY="YOUR_LICENSE_KEY_HERE"
wget -q "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_KEY}&suffix=tar.gz" \
-O /tmp/GeoLite2-Country.tar.gz
tar -xzf /tmp/GeoLite2-Country.tar.gz -C /tmp/
sudo mkdir -p /etc/maxmind
sudo cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /etc/maxmind/
rm -rf /tmp/GeoLite2-Country*
Bước 2: Trích Xuất Dải IP theo Quốc Gia
File MMDB là định dạng nhị phân. Bạn cần một script Python nhỏ để lấy các dải CIDR cho các quốc gia cụ thể. Cài thư viện trước:
pip3 install maxminddb
#!/usr/bin/env python3
# /usr/local/bin/extract_country_ips.py
import maxminddb
import sys
MMDB_PATH = "/etc/maxmind/GeoLite2-Country.mmdb"
TARGET_COUNTRIES = set(sys.argv[1:]) # ví dụ: CN RU KP
ranges = []
with maxminddb.open_database(MMDB_PATH) as db:
for record, network in db:
if record and "country" in record:
iso = record["country"].get("iso_code", "")
if iso in TARGET_COUNTRIES:
ranges.append(str(network))
for r in sorted(set(ranges)):
print(r)
sudo chmod +x /usr/local/bin/extract_country_ips.py
# Thử nghiệm — CN + RU cộng lại thường cho 10.000–13.000 dòng
python3 /usr/local/bin/extract_country_ips.py CN RU | wc -l
Bước 3: Tạo Cấu Hình nftables
Thiết lập bảng và các set trong một file riêng — giúp mọi thứ gọn gàng và dễ reload:
sudo nano /etc/nftables-geoip.conf
#!/usr/sbin/nft -f
# Bảng chặn GeoIP
table inet geoip_filter {
set blocked_countries {
type ipv4_addr
flags interval
auto-merge
}
set blocked_countries_v6 {
type ipv6_addr
flags interval
auto-merge
}
chain input {
type filter hook input priority 0; policy accept;
# Tùy chọn: ghi log trước khi drop trong quá trình test
# ip saddr @blocked_countries log prefix "geoip-block: " drop
ip saddr @blocked_countries counter drop
ip6 saddr @blocked_countries_v6 counter drop
}
}
Hai chỉ thị cần hiểu: flags interval báo cho nftables biết set chứa dải CIDR thay vì IP đơn lẻ. auto-merge tự động gộp các dải chồng lấp. Cả hai đều cần thiết khi làm việc với dataset cấp quốc gia có thể chứa hàng nghìn prefix chồng nhau.
Bước 4: Viết Script Cập Nhật
Script này tải database mới khi cần, trích xuất các dải IP, và reload nftables set mà không cần khởi động lại toàn bộ tường lửa:
sudo nano /usr/local/bin/update-geoip-block.sh
#!/bin/bash
set -e
MMDB_PATH="/etc/maxmind/GeoLite2-Country.mmdb"
MAXMIND_KEY="${MAXMIND_LICENSE_KEY}"
BLOCKED_COUNTRIES="CN RU KP IR" # Điều chỉnh theo nhu cầu của bạn
# Làm mới database nếu cũ hơn 15 ngày
if [ ! -f "$MMDB_PATH" ] || find "$MMDB_PATH" -mtime +15 | grep -q .; then
echo "[+] Đang làm mới database GeoLite2..."
wget -q "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_KEY}&suffix=tar.gz" \
-O /tmp/GeoLite2.tar.gz
tar -xzf /tmp/GeoLite2.tar.gz -C /tmp/
cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb "$MMDB_PATH"
rm -rf /tmp/GeoLite2*
fi
echo "[+] Đang trích xuất dải IP cho: $BLOCKED_COUNTRIES"
python3 /usr/local/bin/extract_country_ips.py $BLOCKED_COUNTRIES > /tmp/blocked_ranges.txt
# Tách IPv4 và IPv6
IPV4=$(grep -v ':' /tmp/blocked_ranges.txt | paste -sd ',' -)
IPV6=$(grep ':' /tmp/blocked_ranges.txt | paste -sd ',' -)
# Nạp cấu hình cơ sở nếu bảng chưa tồn tại
nft list table inet geoip_filter &>/dev/null || nft -f /etc/nftables-geoip.conf
# Xóa và nạp lại các set
nft flush set inet geoip_filter blocked_countries 2>/dev/null || true
nft flush set inet geoip_filter blocked_countries_v6 2>/dev/null || true
[ -n "$IPV4" ] && nft add element inet geoip_filter blocked_countries "{ $IPV4 }"
[ -n "$IPV6" ] && nft add element inet geoip_filter blocked_countries_v6 "{ $IPV6 }"
TOTAL=$(wc -l < /tmp/blocked_ranges.txt)
echo "[+] Hoàn tất. Đã nạp ${TOTAL} dải IP."
sudo chmod +x /usr/local/bin/update-geoip-block.sh
# Lưu license key an toàn
echo "MAXMIND_LICENSE_KEY=your_key_here" | sudo tee /etc/geoip.env
sudo chmod 600 /etc/geoip.env
Bước 5: Chạy Và Kiểm Tra
# Lần chạy đầu tiên
sudo bash -c 'source /etc/geoip.env && /usr/local/bin/update-geoip-block.sh'
# Kiểm tra dải IP đã được nạp chưa
sudo nft list set inet geoip_filter blocked_countries | head -5
# Kiểm tra bộ đếm packet
sudo nft list ruleset | grep counter
# Kết quả mong đợi:
# ip saddr @blocked_countries counter packets 1842 bytes 147360 drop
Bước 6: Tồn Tại Qua Reboot Và Tự Động Hóa Cập Nhật
Lưu ruleset hiện tại, bật nftables khi khởi động, rồi tạo systemd service để reload các GeoIP set sau mỗi lần khởi động lại:
# Lưu các quy tắc cơ sở
sudo nft list ruleset > /etc/nftables.conf
sudo systemctl enable --now nftables
sudo nano /etc/systemd/system/geoip-reload.service
[Unit]
Description=Reload GeoIP nftables sets
After=nftables.service
Wants=nftables.service
[Service]
Type=oneshot
EnvironmentFile=/etc/geoip.env
ExecStart=/usr/local/bin/update-geoip-block.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now geoip-reload
Để làm mới database hai tuần một lần, thêm một cron job:
sudo nano /etc/cron.d/geoip-update
MAXMIND_LICENSE_KEY=your_key_here
0 3 1,15 * * root /usr/local/bin/update-geoip-block.sh >> /var/log/geoip-update.log 2>&1
Bài Học Từ Thực Tế Vận Hành
Sau năm lần triển khai production khác nhau, những vấn đề tương tự cứ lặp đi lặp lại. Tiết kiệm cho mình khỏi đau đầu:
- Whitelist IP của bạn trước. Trước khi nạp bất kỳ quy tắc chặn nào, thêm quy tắc accept tường minh cho các IP quản lý của bạn. Bị khóa khỏi VPS cloud vì bạn tự chặn quốc gia của mình là cách tệ nhất để tiêu một buổi chiều.
- Bắt đầu với log, không phải drop. Trên server mới, đổi
dropthànhlog prefix "geoip-block: " droptrong 48 giờ đầu. Quét/var/log/kern.logđể tìm bất kỳ điều bất thường nào trước khi cam kết chặn cứng. - Chú ý bộ nhớ trên VPS nhỏ. Chặn Trung Quốc và Nga cùng lúc sẽ nạp khoảng 10.000–12.000 dải IP. nftables xử lý điều này hiệu quả, nhưng trên VPS 512MB bạn sẽ thấy bộ nhớ tăng nhẹ ở lần nạp đầu tiên. Kiểm tra một lần với
free -mtrước và sau khi nạp. - VPN là điểm mù. Cách này loại bỏ hiệu quả các cuộc quét hàng loạt thiếu tinh vi — kiểu liên tục tấn công port 22 từ cùng một /24. Kẻ tấn công có chủ đích định tuyến qua quốc gia không bị chặn sẽ vượt qua hoàn toàn. Xem GeoIP là một lớp bảo vệ trong kiến trúc bảo mật theo chiều sâu, không phải giải pháp hoàn chỉnh. Kết hợp với fail2ban, xác thực SSH key, và rate limiting ở tầng ứng dụng.
- Đừng chặn quá mức. Các CDN edge node, dịch vụ giám sát uptime, và API bên thứ ba có thể đi qua các vùng bạn đang chặn. Bắt đầu với các quốc gia có nhiều noise nhất và ít liên quan nhất rồi mở rộng dần — không phải ngược lại.
Kết Quả Đáng Kể
Trên một server của khách hàng, các cuộc tấn công brute-force SSH giảm hơn 70% trong tuần đầu tiên triển khai. Access log từ chỗ đọc không được vì toàn rác trở thành thứ con người có thể đọc qua trong năm phút. Script cập nhật tự động đã chạy nhiều tháng liên tục mà không có một lần thất bại.
Thiết lập ban đầu chưa đến một tiếng. Sau đó, cron job và systemd service xử lý tất cả. Nếu server của bạn phục vụ đối tượng theo khu vực và đang bị tấn công liên tục từ các quốc gia không có người dùng hợp lệ, hiếm có thay đổi tường lửa nào mang lại sự yên tĩnh với chi phí vận hành ít như thế này.

