LinuxでiptablesによるFirewall設定:本番Webサーバー向け実践スクリプト

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

誰も気づかないまま開放されたサーバー

先週、クライアントのUbuntuサーバーをレビューした。PHPのWebアプリとMySQLが動いているサーバーだ。外部インターネットからnmap -sV <ip>を実行してみると、3306(MySQL)、6379(Redis)、8080(開発サーバー)など、あらゆるポートが外部に開放されていた。ファイアウォールは一切なし。

よくある話だ。サーバーは問題なく動いていて、警告も出ないから、ファイアウォールの設定はずっとToDoリストの下に追いやられる。MySQLがブルートフォース攻撃を受けるか、Redisがcryptominerに使われて初めて、慌てることになる。

意図しないのに「開放」されてしまう理由

MySQLやRedisなど、どのサービスをインストールしても、デフォルトでは0.0.0.0にバインドされる。つまり、インターネット接続インターフェースを含む、すべてのネットワークインターフェースで待ち受けるということだ。ファイアウォールで遮断しなければ、誰でも接続を試みることができる。

Linuxカーネルにはnetfilterが組み込まれている。カーネルレベルでパケットをフィルタリングするフレームワークだ。iptablesはnetfilterを設定するためのコマンドラインツールである。UbuntuやCentOSを新規インストールした直後は、iptablesはデフォルトですべてACCEPT状態になっており、何もブロックしない。

もう一つの問題:多くの管理者がufw(Ubuntu)やfirewalldCentOS/RHEL)をブラックボックスとして使っている。コマンドは知っていても、内部の仕組みは理解していない。NAT、ポートフォワーディング、高度なレート制限が必要になると、これらのラッパーツールは限界を見せ始める。iptablesを直接理解しておくと、そういった状況でも適切な場所でデバッグできる。

作業を始める前にiptablesの構造を理解する

iptablesはルールをテーブルチェーンで管理する。最もよく使われるテーブルはfilterで、3つのチェーンを持つ:

  • INPUT:このサーバーへの受信パケット
  • OUTPUT:このサーバーからの送信パケット
  • FORWARD:サーバーを通過するパケット(サーバーをルーター/ゲートウェイとして使う場合)

各ルールにはパケットの行き先を決めるターゲットがある:

  • ACCEPT:通過を許可する
  • DROP:通知なしに静かにブロックする
  • REJECT:ブロックしてエラー通知を返す
  • LOG:ログに記録して次のルールの処理を続ける

ルールは上から順に評価される。最初にマッチしたルールが適用され、それ以降のルールは処理されない。

ufwとiptables直接設定、どちらを使う?

方法1:ufwを使う — シンプルだが制限あり

固定ポートをいくつか開けるだけの小規模サーバーには十分:

sudo ufw enable
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose

メリット:速くて覚えやすい。デメリット:NAT、レート制限、高度なルールの処理が難しい。特殊なケースに直面すると、結局iptablesに戻ることになる。

方法2:iptablesを直接設定する — 完全なコントロール

これが私が本番サーバーで使う方法だ。少し複雑だが、細部まで完全にコントロールできる。

本番Webサーバー向けiptables完全スクリプト

バラバラにコマンドを叩いて何をしたか忘れるより、スクリプトを書いておけば管理しやすく、必要なときに再デプロイもできる。私が管理しているUbuntu 22.04サーバーでは、1時間に数百件のボット接続がブロックされている。ファイアウォールがなければ、それらすべてがアプリケーションに届いてしまう。

#!/bin/bash
# firewall.sh — Webサーバー向けiptables設定

# 既存のルールをすべて削除
iptables -F
iptables -X
iptables -Z

# デフォルトポリシー:INPUTとFORWARDをすべてDROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# ループバックを許可(localhost — 必須)
iptables -A INPUT -i lo -j ACCEPT

# ステートフルファイアウォール:established接続を許可
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SSHを許可(DROPポリシー設定前に必ず追加すること)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# HTTPとHTTPSを許可
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# pingを許可
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# DROPされたパケットをデバッグ用にログ記録
iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4
iptables -A INPUT -j DROP

echo "Firewall configured!"
iptables -L -v --line-numbers

重要な警告:SSHでサーバーに接続中の場合、--dport 22のルールはデフォルトポリシーをDROPに設定するに追加しなければならない。順序を間違えると、即座に自分自身をロックアウトしてしまう。

初回テスト時の安全対策:万が一SSHをロックアウトしてしまった場合に備え、5分後に自動リセットするコマンドを設定しておく:

echo "iptables -F && iptables -P INPUT ACCEPT" | at now + 5 minutes

atがインストールされていない場合は先にインストール:sudo apt install at

レート制限によるSSHブルートフォース対策

すべてのSSH接続をACCEPTするのではなく、パスワードスキャンボットをブロックするレート制限を追加する:

# 上記のシンプルなSSHルールをこちらに置き換える:
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

このルールは、60秒以内に4回以上の新規SSH接続を試みるIPアドレスをブロックする。

MySQLとRedisをロック — localhostまたは内部IPのみ許可

# MySQL:localhostからのみ許可
iptables -A INPUT -p tcp --dport 3306 -s 127.0.0.1 -j ACCEPT

# または内部IPレンジから(VPC、プライベートネットワーク)
iptables -A INPUT -p tcp --dport 3306 -s 10.0.0.0/8 -j ACCEPT

# Redis:同様に
iptables -A INPUT -p tcp --dport 6379 -s 127.0.0.1 -j ACCEPT

# この2つのポートへの残りの通信をすべてブロック
iptables -A INPUT -p tcp --dport 3306 -j DROP
iptables -A INPUT -p tcp --dport 6379 -j DROP

再起動時に自動ロードされるようにルールを保存する

iptablesのルールは保存しないと再起動後に消えてしまう。これは多くの人が忘れがちな点だ:

# Ubuntu/Debian
sudo apt install iptables-persistent
sudo netfilter-persistent save

# 保存されたファイルを確認
cat /etc/iptables/rules.v4

# CentOS/RHEL 7
sudo service iptables save
# ルールは /etc/sysconfig/iptables に保存される

よく使う確認・デバッグコマンド

# すべてのルールを番号とパケット/バイト数付きで表示
iptables -L -v --line-numbers

# 特定のテーブルのルールを表示
iptables -t nat -L -v

# 番号でルールを削除(例:INPUTの3番目のルールを削除)
iptables -D INPUT 3

# DROPされたログを確認
dmesg | grep "IPT-DROP"
# または
tail -f /var/log/kern.log | grep "IPT-DROP"

# すべてをデフォルトにリセット(やり直したいとき)
iptables -F && iptables -P INPUT ACCEPT && iptables -P FORWARD ACCEPT

iptables設定でよくある落とし穴

  • SSHのロックアウト:必ずtmux/screen内でテストし、上記の自動リセットコマンドと組み合わせること。
  • 再起動後にルールが消えるiptables-persistentをインストールしてnetfilter-persistent saveを実行することを忘れずに。
  • ufw/firewalldとの競合:ufwを使用中の場合、iptablesを直接使う前に無効化すること:sudo ufw disable
  • DockerによるFirewallの迂回:Dockerは自動的にiptablesにルールを追加し、FORWARDポリシーを無効化する可能性がある。Dockerを使う場合はDOCKER-USERチェーンの追加設定が必要。
  • ESTABLISHEDルールの欠如--ctstate ESTABLISHED,RELATEDの行を忘れると、既存の接続がDROPされ、Webサイトはヘッダーをロードしたあとにハングアップする。

まとめ

iptablesは設定したら終わりではない。新しいサービスを追加するたびに自問する必要がある:このポートを外部に公開する必要があるか?デフォルトはNoだ。上記のテンプレートスクリプトはほとんどのWebサーバーに対応できる。アプリケーションに応じてルールを追加・削除してほしい。

新しいサーバーを引き継いだときに私がいつも実践する原則:まず外部からnmap -sV <ip>を実行する。存在する理由のないポートが見つかったら、即座に閉じる。MySQL、Redis、Elasticsearch、Memcachedはすべてlocalhostまたは内部ネットワークからのみ待ち受けるべきだ。

Share: