CentOS/RHELでfirewalldを使ったサービス管理ガイド

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

5分でわかるfirewalldの基本

firewalldを初めて触ったのは、Ubuntuからサーバー管理のためCentOS 7に移行したときのことだ。ufwに慣れていたので、最初はfirewalldが少し戸惑った。「zone」という概念も難しく感じた。しかし数ヶ月実際に使い込んでみると、特に2〜3枚のNICでそれぞれ異なるポリシーが必要なサーバーでは、素のiptablesよりずっと使いやすいことがわかった。

まず実行すべき4つのコマンドはこれだ:

# firewalldの状態を確認する
sudo systemctl status firewalld

# firewalldを有効化してシステム起動時に自動起動する
sudo systemctl enable --now firewalld

# 現在のzoneとアタッチされているインターフェースを表示する
sudo firewall-cmd --get-active-zones

# 適用中のルールをすべて表示する
sudo firewall-cmd --list-all

この4つのコマンドを実行するだけで、サーバーのファイアウォール状態を把握できる。デフォルトのzoneは通常publicで、--list-allの出力から現在開いているサービスを確認できる。

ポートやサービスをすぐに開放する

# HTTPとHTTPSを許可する
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# カスタムポートを開放する(例:Node.jsアプリがポート3000で動作する場合)
sudo firewall-cmd --permanent --add-port=3000/tcp

# 変更を適用する — permanentルール追加後に必須
sudo firewall-cmd --reload

--permanentフラグに注意:このフラグを省略すると、ルールは次回の再起動までしか有効にならない。一方--reloadはpermanentルールを即時有効化する — サービス全体を再起動する必要はない。

zoneを理解する — firewalldの核心

zoneこそが、firewalldを素のiptablesと大きく異なるものにしている。チェーン(INPUT/OUTPUT/FORWARD)でルールを書く代わりに、インターフェースをzoneに割り当てる — 各zoneは独自のポリシーを持ち、独立して管理できる。

デフォルトのzone一覧:

  • drop — すべて拒否、応答なし
  • block — すべて拒否、ICMPリジェクションを送信
  • public — デフォルトzone、特定のサービスのみ許可
  • external — 外向きインターフェース、NATマスカレードあり
  • internal — 内部ネットワーク、publicより信頼度が高い
  • trusted — すべてのトラフィックを許可
  • dmz — DMZエリア、インバウンドを制限
# すべてのzoneを一覧表示する
sudo firewall-cmd --get-zones

# デフォルトzoneを変更する
sudo firewall-cmd --set-default-zone=public

# インターフェースを特定のzoneに割り当てる(例:内部NIC)
sudo firewall-cmd --permanent --zone=internal --add-interface=eth1
sudo firewall-cmd --reload

定義済みサービスの管理

firewalldには60以上の定義済みサービスが付属しており、/usr/lib/firewalld/services/に保存されている。各XMLファイルに対応するポートとプロトコルが宣言されているので、ポート番号を手動で覚えるよりずっと便利だ。

# 利用可能なサービスの一覧を表示する
sudo firewall-cmd --get-services

# サービスを追加・削除する
sudo firewall-cmd --permanent --add-service=mysql
sudo firewall-cmd --permanent --remove-service=telnet

# publicゾーンで許可されているサービスを確認する
sudo firewall-cmd --zone=public --list-services

必要なサービスの定義がまだない場合は、独自のXMLファイルを作成する:

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>Custom application on port 8080</description>
  <port protocol="tcp" port="8080"/>
</service>
# /etc/firewalld/services/myapp.xml に保存してからreloadする
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --add-service=myapp
sudo firewall-cmd --reload

応用編:Rich RulesとPort Forwarding

Rich Rules — より細かい制御

CentOS 8のEOL後、1週間で5台のサーバーをRocky Linuxに急いで移行しなければならなかった。そのとき初めてrich rulesをしっかり読み込んだ — SSHに特定のIPをホワイトリスト登録する必要があるサーバーがいくつかあり、単純な--add-sourceでは柔軟性が足りなかったのだ。

# 特定のIPからのSSHのみ許可する
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.10" service name="ssh" accept'

# 特定のIPを完全にブロックする
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.5" reject'

# ブルートフォース対策のSSHレート制限(最大5接続/分)
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" limit value="5/m" accept'

# drop前にトラフィックをログに記録する(デバッグや監査用)
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="http" log prefix="HTTP-BLOCK" level="warning" drop'

sudo firewall-cmd --reload

Port Forwarding

アプリはポート8080で動いているが、転送だけのためにNginxを立てずにユーザーにポート80でアクセスさせたい?ポートフォワーディングがスッキリ解決してくれる:

# ポート80を同じマシンの8080に転送する
sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080

# 内部ネットワーク上の別マシンに転送する
sudo firewall-cmd --permanent --add-forward-port=port=3306:proto=tcp:toaddr=192.168.1.20:toport=3306

sudo firewall-cmd --reload

本番環境からの実践的なTips

1. permanentにする前に一時テストを行う

これは痛い目に遭って学んだ教訓だ:新しいファイアウォールルールをテストして--permanentを付け忘れ、保存したつもりが再起動後に消えていた。幸いそのサーバーはそれほどクリティカルではなかったが。

# ステップ1:permanentなしでテスト — 再起動後にルールは消える
sudo firewall-cmd --add-service=https
# 接続を確認して動作するかテストする

# ステップ2:問題なければpermanentとして保存する
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

2. 大きな変更前にconfigをバックアップする

# firewalldのconfigは /etc/firewalld/zones/ に保存されている
ls /etc/firewalld/zones/

# タイムスタンプ付きで全設定をバックアップする
sudo cp -r /etc/firewalld /etc/firewalld.bak.$(date +%Y%m%d)

# 必要に応じてリストアする
sudo cp -r /etc/firewalld.bak.20240115/* /etc/firewalld/
sudo firewall-cmd --reload

3. ルールが期待通りに動かない場合のデバッグ

# runtimeとpermanentを比較する(この2つはよくズレる)
sudo firewall-cmd --list-all            # runtime
sudo firewall-cmd --list-all --permanent  # permanent

# firewalldのログを確認する
sudo journalctl -u firewalld -f

# パニックモード — 全トラフィックを即時遮断(攻撃を受けているときに使用)
sudo firewall-cmd --panic-on
sudo firewall-cmd --panic-off

4. SELinuxとの組み合わせ — このレイヤーを忘れずに

firewalldとSELinuxは完全に独立した2つのセキュリティレイヤーだ。firewalldでポートを開けても、SELinuxがブロックしていればサービスはそのポートにバインドできない。以前、ファイアウォールは開いているのになぜかアクセスできないと30分悩んだことがある — 最終的にSELinuxが原因だったと気づいた。それ以来、ネットワーク問題が発生したら最初から両方を確認するようにしている:

# 例:nginxをポート8080で動かすには両方の手順が必要

# 1. firewalldで開放する
sudo firewall-cmd --permanent --add-port=8080/tcp

# 2. SELinuxで許可する(enforcingモードの場合)
sudo semanage port -a -t http_port_t -p tcp 8080

sudo firewall-cmd --reload

5. 複数サーバーへのファイアウォール設定同期スクリプト

5台のサーバーがそれぞれ異なるファイアウォール設定で動いているのは、監査時に悪夢だ。Rocky Linuxへの移行が終わった後、すべてのサーバーで統一されたルールが適用されているか担保するための小さなスクリプトをすぐに書いた:

#!/bin/bash
# sync-firewall.sh — 複数のサーバーに統一したファイアウォールルールを適用する

SERVERS=("server1.example.com" "server2.example.com" "server3.example.com")
RULES=(
  "--add-service=http"
  "--add-service=https"
  "--add-service=ssh"
  "--add-port=8080/tcp"
)

for server in "${SERVERS[@]}"; do
  echo "Configuring $server..."
  for rule in "${RULES[@]}"; do
    ssh root@$server "firewall-cmd --permanent $rule"
  done
  ssh root@$server "firewall-cmd --reload"
  echo "Done: $server"
done

firewalldを使う上で覚えておくべき3つのこと:zoneがポリシーを決定する、--permanentを付けて初めて本当に保存される、そしてfirewalldはあくまで一つのレイヤーに過ぎず — SELinuxがその上で拒否権を持っている。CentOS 8から5台のサーバーをRocky Linuxに移行した後も、firewalldの設定は一行も修正せずにそのまま移行できた。

Share: