firewalld cơ bản cho VPS Rocky và AlmaLinux
Mở SSH, cho phép hoặc chặn port web và giữ cấu hình sau reboot với firewalld trên Rocky, AlmaLinux. Hiểu zone và tránh bẫy --permanent.
firewalld là gì và vì sao Rocky và AlmaLinux cài sẵn nó
firewalld là trình quản lý firewall được cài mặc định trên Rocky Linux, AlmaLinux và các bản dựng lại khác của Red Hat Enterprise Linux (RHEL). Nó không tự kiểm tra packet. Nó lưu cấu hình và chuyển cấu hình đó thành các rule nftables. Bạn có thể dùng một lệnh, firewall-cmd, để chỉnh cấu hình trong khi server vẫn đang online.
Nếu bạn đã biết ufw hoạt động thế nào trên một Ubuntu VPS, bạn đã biết mục đích chính của công cụ này. firewalld bổ sung hai khái niệm mà ufw không có. Thứ nhất là zone: một policy có tên để phân loại packet. Thứ hai là sự tách biệt giữa rule đang áp dụng và rule đã lưu, được điều khiển bằng flag --permanent. Đây là nguồn gây nhầm lẫn lớn nhất khi dùng công cụ này.
Mọi nội dung bên dưới đều là các command bạn chạy trên server của mình. Hãy kiểm tra từng thay đổi từ một máy thứ hai, vì một rule trông có vẻ đúng trên server vẫn có thể sai khi nhìn từ Internet.
Mở SSH trước khi làm bất cứ việc gì khác
Hầu hết các bản cài Rocky và AlmaLinux đều đã có firewalld và đang chạy, đồng thời cấu hình được cung cấp sẵn cho phép SSH. Một số cloud image tối giản đã loại bỏ firewalld. Hãy kiểm tra thay vì mặc định cho rằng nó có sẵn.
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --statefirewall-cmd --state in running. Nếu service đã dừng, mọi lệnh gọi firewall-cmd khác đều trả về FirewallD is not running và kết thúc với mã khác 0. Đây là điều đầu tiên cần kiểm tra khi một lệnh có vẻ hoàn toàn không làm gì.
Bây giờ hãy xem những gì đang được cho phép.
sudo firewall-cmd --list-allOutput thực tế có thêm vài dòng. Những dòng quan trọng là:
public (active)
target: default
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
rich rules:ssh trong dòng services: là lý do session của bạn vẫn hoạt động. Nếu thiếu dòng này, hãy thêm nó trước khi làm bất cứ việc gì khác, vì khởi động firewall khi không có rule SSH sẽ kết thúc session và không cho phép bạn đăng nhập lại.
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reloadtarget: default có nghĩa là packet không khớp với rule nào sẽ bị từ chối bằng phản hồi ICMP (internet control message protocol) host-prohibited, vì vậy client truy cập vào một port đóng sẽ thấy No route to host ngay lập tức. Đặt target thành DROP sẽ khiến server im lặng thay vào đó, và scanner sẽ phải chờ timeout.
sudo firewall-cmd --permanent --zone=public --set-target=DROP
sudo firewall-cmd --reloadHãy hiểu rõ tác động trước khi chạy lệnh đó: DROP cũng ngăn server phản hồi ping, vì vậy hệ thống monitoring của chính bạn cũng sẽ không còn nhận được phản hồi.
Vì sao rule của tôi biến mất? Flag --permanent
firewalld giữ đồng thời hai cấu hình. Cấu hình runtime là cấu hình kernel đang áp dụng ngay lúc này. Cấu hình permanent nằm trong /etc/firewalld/zones/public.xml và được khôi phục sau khi reload hoặc reboot.
Lệnh không có --permanent chỉ thay đổi runtime. Lệnh có hiệu lực ngay và thay đổi đó mất sau lần reload hoặc boot tiếp theo. Lệnh có --permanent ghi vào file nhưng không thay đổi cấu hình đang chạy, nên cổng vẫn đóng cho đến khi bạn reload. Cả hai hành vi này đều không phải bug. Chúng thường gây nhầm lẫn vì lệnh đều in ra success.
Hãy luôn ghi cả cặp lệnh.
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reloadBạn có thể đọc cả hai cấu hình. Đây là cách nhanh nhất để xác định bạn đã mắc lỗi nào trong hai lỗi trên.
sudo firewall-cmd --list-services
sudo firewall-cmd --permanent --list-servicesLệnh đầu tiên in ra tập rule đang hoạt động. Lệnh thứ hai in ra tập rule đã lưu. Nếu tập đang hoạt động có một service mà tập đã lưu không có, rule đó sẽ mất ở lần reload tiếp theo. Nếu tập đã lưu có một service mà tập đang hoạt động không có, bạn đã quên reload. sudo firewall-cmd --runtime-to-permanent sao chép toàn bộ cấu hình đang hoạt động vào file đã lưu. Lệnh này hữu ích sau một buổi thử nghiệm.
--reload giữ lại trạng thái connection tracking, nên phiên SSH của bạn vẫn hoạt động. --complete-reload cũng reload các kernel module và làm mất trạng thái đó, nên thường chấm dứt mọi connection đang mở, bao gồm cả connection của bạn. Hãy dùng lệnh reload thông thường.
firewalld có sẵn một cơ chế an toàn. Rule runtime có thể tự hết hạn.
sudo firewall-cmd --add-service=http --timeout=5mRule đó sẽ tự xóa sau năm phút. Nó không thể kết hợp với --permanent. Đây chính là mục đích của nó: dùng để kiểm thử một thay đổi mà bạn chưa chắc chắn. Cơ chế an toàn cũ vẫn tốt hơn. Hãy mở sẵn một phiên SSH thứ hai khi chỉnh sửa rule và không đóng phiên đó cho đến khi đăng nhập mới xác nhận các rule mới hoạt động.
Zone và lý do chỉ zone mặc định quan trọng trên VPS
Zone là một tập quyền được đặt tên và gắn với một mức độ tin cậy. firewalld đưa mỗi packet đến đúng một zone. Trước tiên, nó đối chiếu địa chỉ nguồn của packet với danh sách sources: của từng zone. Nếu không có mục nào khớp, nó dùng zone mà interface nhận packet đang được gán vào. Nếu interface không được gán vào zone nào, packet đi vào zone mặc định.
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zonesTrên VPS có một network interface, câu trả lời đầu tiên gần như luôn là public, và đó là zone duy nhất bạn sẽ dùng. firewall-cmd không có đối số --zone= sẽ tác động lên zone mặc định. Vì vậy, mọi command ngắn trong hướng dẫn này đều hoạt động mà không cần chỉ định zone.
Đây là lỗi có thể làm bạn mất cả buổi chiều. Nếu interface được gán vào một zone khác, các rule của bạn được thêm vào public nhưng traffic lại được xử lý ở nơi khác. Vì vậy, mọi thứ bạn thêm đều không có tác dụng và cũng không có cảnh báo nào. --get-active-zones hiển thị việc gán zone:
public
interfaces: eth0Nếu interface xuất hiện dưới tên zone khác, hãy viết rule trong zone đó bằng --zone=, hoặc chuyển interface sang zone cần dùng.
sudo firewall-cmd --permanent --zone=public --change-interface=eth0
sudo firewall-cmd --reloadNetworkManager quản lý các interface trên Rocky và AlmaLinux, đồng thời gán lại zone khi connection được đưa lên. Hãy đặt zone ở đó nữa để reboot không hoàn tác cấu hình của bạn. Lấy tên connection từ command đầu tiên, vì tên này hiếm khi giống tên device.
sudo nmcli connection show
sudo nmcli connection modify "System eth0" connection.zone publicĐối chiếu source có độ ưu tiên cao hơn đối chiếu interface. Đây là cách một địa chỉ có thể nhận policy khác. Zone tích hợp trusted cho phép mọi thứ.
sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10/32
sudo firewall-cmd --reloadHãy cẩn thận với zone này. Nó mở mọi port trên server cho địa chỉ đó, bao gồm cả database mà bạn nghĩ là private. Khi chỉ muốn mở một port thay vì cho phép toàn bộ một host, hãy dùng rich rule.
Dịch vụ firewalld là gì?
Một service là một nhóm các port được đặt tên, được cung cấp dưới dạng file XML. --add-service=https mở 443/tcp vì /usr/lib/firewalld/services/https.xml định nghĩa https là gì.
sudo firewall-cmd --get-services
sudo firewall-cmd --info-service=https--info-service hiển thị các port đứng sau tên đó:
https
ports: 443/tcpHãy dùng tên khi đã có định nghĩa. Sáu tháng sau, tên đó vẫn dễ đọc trong --list-all, và các package như Cockpit sẽ cài file service riêng. Dùng --add-port cho mọi port chưa có định nghĩa.
Điểm cần lưu ý: service ssh chỉ có nghĩa là 22/tcp, không có gì khác. Nếu bạn đã chuyển SSH sang port khác khi tăng cường bảo mật truy cập SSH trên server, thì --add-service=ssh sẽ không mở port mà bạn thực sự đang dùng.
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reloadTrên hệ thống RHEL cài lại, còn có một lớp kiểm soát thứ hai. SELinux (security-enhanced Linux) gắn nhãn cho các số port, và sshd không được phép bind vào port nằm ngoài các nhãn đó. Khi đó, nó sẽ từ chối khởi động và log sẽ ghi error: Bind to port 2222 on 0.0.0.0 failed: Permission denied. Hãy gắn nhãn cho port trước.
sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t ssh_port_t -p tcp 2222Xem các cổng đang mở hiện tại như thế nào?
sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules
sudo nft list table inet firewalld | head -n 40Hai lệnh đầu tiên báo cáo trạng thái mà firewalld nhận biết. Lệnh thứ ba đọc các rule mà kernel thực sự đang giữ trong table do firewalld quản lý. Kết quả của chúng phải khớp nhau.
Nhưng các lệnh đó chưa đủ để xác nhận. Hãy kiểm tra từ một máy khác:
nc -zv 203.0.113.20 443Không chạy bài kiểm tra trên chính server. firewalld chấp nhận mọi kết nối đến qua loopback interface, nên curl http://localhost:8080 vẫn thành công bất kể các rule của bạn là gì. Bài kiểm tra đó chỉ cho biết service đang hoạt động. Nó không cho biết gì về firewall.
Cho phép cổng web
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-servicesLệnh cuối cùng bây giờ phải liệt kê http https cùng với các mục đã có trước đó. Nếu site vẫn không phản hồi, firewall có thể không phải là nguyên nhân. Một rule chỉ cho phép packet đi qua. Vẫn phải có một tiến trình đang listening để nhận packet đó.
sudo ss -tlnpSocket hiển thị là 0.0.0.0:443 hoặc *:443 chấp nhận kết nối từ mọi địa chỉ. Socket hiển thị là 127.0.0.1:443 chỉ phản hồi trên loopback, và không có firewall rule nào có thể làm nó truy cập được từ bên ngoài. Cổng và socket listening trên Linux giải thích chi tiết hơn về điểm khác biệt này.
Làm thế nào để đóng một port?
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reloadQuy tắc --permanent cũng áp dụng ở đây, và hậu quả nghiêm trọng hơn. Nếu chỉ xóa một service khỏi runtime, port sẽ có vẻ đã đóng, nhưng lần reload hoặc reboot tiếp theo sẽ mở lại port đó từ file đã lưu. Đây là một lỗ hổng mà bạn sẽ không nhận ra, vì lần kiểm tra đã chạy vẫn thành công.
Xóa một mục chưa từng tồn tại sẽ in ra Warning: NOT_ENABLED: http nhưng vẫn thoát với mã 0. Thêm cùng một mục hai lần sẽ in ra Warning: ALREADY_ENABLED: http. Cả hai trường hợp đều an toàn. Tên bị gõ sai lại khác: Error: INVALID_SERVICE nghĩa là firewalld không có định nghĩa nào với tên đó và hoàn toàn không thay đổi gì.
Nếu --list-all của bạn hiển thị cockpit và bạn không dùng Cockpit web console trên port 9090, hãy xóa mục đó. Mỗi port đang mở là một service mà bạn phải duy trì bản vá.
Giới hạn một cổng cho một địa chỉ nguồn
Rich rule là dạng khai báo đầy đủ, dùng khi chỉ tên service không thể diễn đạt chính xác yêu cầu. Giới hạn SSH cho một địa chỉ văn phòng cần 2 lệnh, và lệnh thứ hai thường bị quên.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" service name="ssh" accept'
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reloadZone là một tập quyền, không phải danh sách đánh số và dừng ở mục khớp đầu tiên. Rich rule thêm quyền accept cho một địa chỉ. Nó không deny địa chỉ nào. Khi ssh vẫn còn trong dòng services:, toàn bộ Internet vẫn truy cập được cổng 22 và rich rule không tạo ra thay đổi thực tế nào. Hãy xóa entry mở rộng, nếu không rule giới hạn chỉ có trên giấy.
Với cổng không có tên service, hãy chỉ định cổng thay thế.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" port port="5432" protocol="tcp" accept'Để drop một mạng gây nhiều log và vẫn lưu lại bản ghi, đặt phần tử log trước action. Đây là thứ tự mà ngôn ngữ rich rule yêu cầu.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" log prefix="fw-drop " level="info" limit value="3/m" drop'
sudo firewall-cmd --reload
sudo journalctl -k -g fw-dropGiá trị limit ngăn flood packet làm đầy journal. Trước khi khóa SSH cho một địa chỉ duy nhất, hãy bảo đảm địa chỉ đó ổn định. Kết nối tại nhà có IP thay đổi có thể khiến bạn mất quyền truy cập ngay khi IP đổi. Vì vậy, trước tiên hãy kiểm tra và bảo đảm console access của nhà cung cấp hoạt động.
Các lệnh ufw và phiên bản tương đương bằng firewall-cmd
Cùng một tác vụ, khác công cụ. Mỗi dòng --permanent cần có một sudo firewall-cmd --reload đi kèm; đây là điểm duy nhất mà danh sách như thế này không thể thể hiện.
sudo ufw enabletrở thànhsudo systemctl enable --now firewalldsudo ufw disabletrở thànhsudo systemctl disable --now firewalldsudo ufw status verbosetrở thànhsudo firewall-cmd --list-allsudo ufw allow OpenSSHtrở thànhsudo firewall-cmd --permanent --add-service=sshsudo ufw allow 443/tcptrở thànhsudo firewall-cmd --permanent --add-port=443/tcpsudo ufw delete allow 443/tcptrở thànhsudo firewall-cmd --permanent --remove-port=443/tcpsudo ufw allow from 203.0.113.10 to any port 22trở thành rich rule được hiển thị ở trênsudo ufw reloadtrở thànhsudo firewall-cmd --reloadsudo ufw default deny incomingvốn đã là cách zonepublichoạt động, còn--set-target=DROPlà phiên bản im lặng của nósudo ufw logging ontrở thànhsudo firewall-cmd --set-log-denied=all
Có một khác biệt cần nói rõ. ufw duy trì một danh sách được đánh số, và bạn có thể chèn một rule ở vị trí 1. firewalld không có số thứ tự cho rule, nên yêu cầu “đặt rule này lên đầu” không có ý nghĩa ở đây. Khi hai entry của firewalld có vẻ mâu thuẫn, rule accept rộng hơn sẽ được áp dụng, vì không có rule nào trong tập này thực hiện deny. Bạn phải tự xóa entry rộng đó.
Vì sao Docker container của tôi vẫn có thể truy cập được khi firewall có vẻ đã đóng?
Vì port được publish của container không đi qua phần firewall mà zone của bạn kiểm soát. docker run -d -p 8080:80 nginx yêu cầu Docker tự ghi các rule NAT (network address translation) và forwarding. Packet đến cổng 8080 được rewrite và route tiếp đến container, nên được forward thay vì giao đến host. Các dòng services: và ports: trong zone của bạn chỉ kiểm soát packet giao đến host. Rule của Docker kiểm soát đường forward và cho phép các packet này.
Kết quả là server có sudo firewall-cmd --list-all không hiển thị cổng 8080, nhưng nc -zv 203.0.113.20 8080 từ một máy khác vẫn kết nối được. Hãy xem Docker đã cài những gì:
sudo iptables -t nat -L DOCKER -nCách sửa nằm ở publish flag. Bind port vào loopback và đặt một reverse proxy ở phía trước.
docker run -d -p 127.0.0.1:8080:80 nginxContainer hiện chỉ phản hồi curl http://127.0.0.1:8080 trên server và không nhận kết nối nào từ bên ngoài. Người dùng Ubuntu cũng gặp vấn đề tương tự, được mô tả trong vì sao Docker container publish port vượt qua ufw. Rootful Podman, được Rocky và AlmaLinux cung cấp trong base repository, publish port bằng cùng cách NAT. Vì vậy, hãy kiểm tra từ một máy khác thay vì chỉ tin vào danh sách của zone.
Cấu hình để firewall hoạt động sau khi reboot và các lỗi bạn sẽ gặp
sudo systemctl is-enabled firewalld
sudo systemctl status firewalldenabled và active (running) là trạng thái bạn cần. Firewall đang chạy nhưng chưa được enable sẽ chỉ bảo vệ máy chủ cho đến lần reboot đầu tiên. Hãy đưa việc kiểm tra này vào danh sách công việc trong 10 phút đầu tiên trên một VPS mới, cùng với SSH key và việc cập nhật.
Không trộn các lệnh nftables trực tiếp với firewalld. firewalld sở hữu một table có tên inet firewalld. sudo nft flush ruleset xóa table này, khiến server mở cho mọi lưu lượng, nhưng firewall-cmd --list-all vẫn hiển thị cấu hình bạn dự kiến vì firewalld báo cáo trạng thái mà nó tin là đúng, không phải trạng thái kernel đang giữ. sudo firewall-cmd --reload cài lại các rule. Hãy ghi rule bằng firewall-cmd để chúng được khôi phục sau khi reload.
Chạy hai firewall manager trên cùng một server. Cài ufw hoặc iptables-services cùng với firewalld khiến hai chương trình cùng ghi rule nhưng không biết về nhau. Kết quả phụ thuộc vào service khởi động sau cùng. Hãy chọn một. Trên Rocky và AlmaLinux, firewalld là firewall được bản phân phối hỗ trợ.
Firewall của nhà cung cấp ở trước server. Nhiều panel VPS có một network firewall riêng. Nếu --list-all cho thấy một port đang mở nhưng kết nối từ bên ngoài vẫn thất bại, hãy kiểm tra panel trước khi thay đổi bất kỳ thứ gì trên server. Trường hợp ngược lại cũng xảy ra: rule mở trong panel không có tác dụng nếu firewalld từ chối packet.
Chạy firewall-cmd không có sudo. Mọi thay đổi đều cần root. Nếu không có quyền này, yêu cầu sẽ bị từ chối trong bước kiểm tra authorization và không có gì được sửa đổi. Nhìn qua, điều này giống như command đã bị bỏ qua.
Sáu command đáp ứng hầu hết công việc hằng ngày: --list-all để xem trạng thái, --permanent --add-service hoặc --add-port để mở một thành phần, --permanent --remove-service để đóng, --reload để áp dụng file đã lưu và --runtime-to-permanent sau một loạt thử nghiệm. Zone là public, flag là --permanent, và cách kiểm tra duy nhất đáng tin cậy là từ một máy khác.
FAQ
Vì sao rule firewalld của tôi biến mất sau khi reboot?
Rule chỉ được thêm vào cấu hình runtime. sudo firewall-cmd --add-service=http áp dụng ngay và bị xóa ở lần reload hoặc boot tiếp theo, vì cấu hình đã lưu trong /etc/firewalld/zones/public.xml chưa từng được cập nhật. Hãy thêm --permanent rồi chạy sudo firewall-cmd --reload. Để giữ các rule đã thêm thủ công, chạy sudo firewall-cmd --runtime-to-permanent. Lệnh này sao chép tập rule đang hoạt động vào file đã lưu.
Vì sao không có gì thay đổi sau khi tôi thêm rule bằng --permanent?
Vì --permanent ghi vào file nhưng không thay đổi firewall đang chạy. Cổng vẫn đóng cho đến khi sudo firewall-cmd --reload nạp cấu hình đã lưu vào kernel. So sánh sudo firewall-cmd --list-services với sudo firewall-cmd --permanent --list-services: nếu danh sách đã lưu có một entry mà danh sách đang hoạt động không có, bạn đang thiếu bước reload.
Tôi nên dùng --add-service hay --add-port?
Dùng --add-service khi đã có name tương ứng với dịch vụ bạn chạy. Cách này thể hiện rõ intent, và sudo firewall-cmd --info-service=https cho biết chính xác name đó bao phủ những cổng nào. Dùng --add-port khi không có name định nghĩa cho service của bạn hoặc khi service lắng nghe trên cổng không chuẩn. Service ssh chỉ có 22/tcp, vì vậy nếu SSH chuyển sang 2222, bạn cần --add-port=2222/tcp và SELinux label cho cổng đó.
Vì sao Docker container của tôi vẫn truy cập được khi firewall-cmd hiển thị cổng đã đóng?
Cổng được publish bị các NAT rule riêng của Docker rewrite và forward đến container. Vì vậy packet không bao giờ được chuyển đến host, còn danh sách service và port của zone chỉ áp dụng cho packet được chuyển đến host. Container vẫn trả lời từ Internet trong khi --list-all không hiển thị gì. Hãy publish vào loopback bằng docker run -d -p 127.0.0.1:8080:80 nginx rồi đặt một reverse proxy phía trước container.
Tôi có thể cài ufw trên Rocky Linux thay cho firewalld không?
Hai firewall manager trên cùng một server sẽ ghi rule mà không biết về nhau. Bộ rule nào được giữ lại phụ thuộc vào service nào khởi động sau cùng. firewalld là tool được hỗ trợ trên Rocky Linux và AlmaLinux, đã được cài sẵn, đồng thời sử dụng cùng backend nftables mà ufw sẽ sử dụng. Chỉ cần nắm default zone và flag --permanent là bạn đã có đủ công cụ cần thiết.