あなたのサーバー間のトラフィックを保護するために、Iptablesファイアウォールのセットアップ方法
はじめに
アプリケーションのセットアップにおいて、異なるノードに分散コンポーネントを展開することは、負荷を減らし、水平方向に拡張する一般的な方法です。典型的な例としては、データベースをアプリケーションとは別のサーバーに配置することがあります。このようなセットアップには多くの利点がありますが、ネットワーク経由での接続には新たなセキュリティ上の懸念が伴います。
このガイドでは、分散型のセットアップにおいて、各サーバーにファイアウォールを設定する方法を説明します。私たちは、コンポーネント間の意図したトラフィックを許可し、他のトラフィックを拒否するポリシーを設定します。
あなたはデジタルオーシャンのクラウドファイアウォールも設定することができます。これは、デジタルオーシャンのインフラストラクチャ上のサーバーの追加的な外部層として実行されます。これにより、サーバー自体にファイアウォールを設定する必要はありません。
このガイドのデモンストレーションでは、2つのUbuntu 22.04サーバーを使用します。1つはNginxで提供されるWebアプリケーションを持ち、もう1つはアプリケーションのMySQLデータベースをホストします。このセットアップは例として使用しますが、あなた自身のサーバーの要件に合わせて技術を推測することができるはずです。
前提条件
始めるためには、新しいUbuntu 22.04サーバーを2つ準備する必要があります。それぞれにsudo権限を持つ通常のユーザーアカウントを追加します。これを行うためには、当社のUbuntu 22.04初期サーバーセットアップガイドに従ってください。
このガイドに基づいて、私たちが保護するアプリケーションのセットアップを行います。もし同じ例に沿って進めたい場合は、そのチュートリアルに示されている通りにアプリケーションとデータベースサーバーをセットアップしてください。それ以外の場合は、この記事を一般的な参考としてご利用いただけます。
ステップ1〜ファイアウォールの設定
最初に、サーバーごとにベースラインファイアウォールの設定を実装します。私たちが実施するポリシーは、セキュリティを最優先に考えています。SSHトラフィック以外のほとんどすべてを制限し、その後ファイアウォールに特定のアプリケーション用の穴を開けます。
このガイドはiptablesの構文に従います。Ubuntu 22.04では、nftablesバックエンドを使用してiptablesが自動的にインストールされるため、追加のパッケージをインストールする必要はありません。
ナノまたはお気に入りのテキストエディタを使用して、/etc/iptables/rules.v4 ファイルを開いてください。
- sudo nano /etc/iptables/rules.v4
ファイアウォールのテンプレートガイドから設定を貼り付けてください。
/etc/iptables/rules.v4の文を日本語で言い換えると次のようになります。
– /etc/iptables/rules.v4
*filter
# Allow all outgoing, but drop incoming and forwarding packets by default
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Custom per-protocol chains
:UDP - [0:0]
:TCP - [0:0]
:ICMP - [0:0]
# Acceptable UDP traffic
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
# Acceptable ICMP traffic
# Boilerplate acceptance policy
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
# Drop invalid packets
-A INPUT -m conntrack --ctstate INVALID -j DROP
# Pass traffic to protocol-specific chains
## Only allow new connections (established and related should already be handled)
## For TCP, additionally only allow new SYN packets since that is the only valid
## method for establishing a new TCP connection
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
-A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
# Reject anything that's fallen through to this point
## Try to be protocol-specific w/ rejection message
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
# Commit the changes
COMMIT
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
*security
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
ファイルを保存して閉じてください。もしnanoを使用している場合は、終了するためにCtrl+Xを押し、次にプロンプトが表示されたらYとEnterを押してください。
もしもこれを実際の環境で実行する場合は、まだファイアウォールのルールを再読み込みしないでください。ここで示されたルールセットを読み込むと、アプリケーションとデータベースサーバー間の接続が直ちに切断されます。再読み込みする前に、運用上のニーズに合わせてルールを調整する必要があります。
ステップ2 – サービスで使用されているポートを特定する
自分のコンポーネント間で通信を許可するためには、使用されているネットワークポートを把握する必要があります。構成ファイルを調べることで正しいネットワークポートを見つけることもできますが、アプリケーションに依存しない正しいポートを見つける方法は、各マシン上で接続待ちのサービスを確認するだけです。
これを確認するために、netstatツールを使用できます。あなたのアプリケーションはIPv4のみを使用して通信しているため、-4引数を追加しますが、IPv6も使用している場合はそれを削除できます。実行中のサービスを見つけるために必要な他の引数は、-p、-l、-u、-n、および -tです。これらは、-pluntとして提供することができます。
これらの議論は以下のように分解できます。
- p: Show the PID and name of the program to which each socket belongs.
- l: Show only listening sockets.
- u: Show UDP traffic.
- n: Show numeric output instead of service names.
- t: Show TCP traffic.
- sudo netstat -4plunt
あなたのウェブサーバーでは、出力は次のようになるかもしれません:
Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1058/sshd tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 4187/nginx
最初にハイライトされた列は、行の最後にハイライトされたサービスがリッスンしているIPアドレスとポートを示しています。特別な0.0.0.0アドレスは、該当するサービスが利用可能なすべてのアドレスでリッスンしていることを意味します。
データベースサーバー上の出力は、以下のようになる可能性があります。
- sudo netstat -4plunt
Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1097/sshd tcp 0 0 192.0.2.30:3306 0.0.0.0:* LISTEN 3112/mysqld
これらの列はまったく同じように読めます。この例では、192.0.2.30のアドレスはデータベースサーバーのプライベートIPアドレスを表しています。前提のチュートリアルでは、セキュリティ上の理由からMySQLをプライベートインターフェースに制限しました。
このステップで見つけた値をメモしてください。これは、ファイアウォールの設定を調整するために必要なネットワーキングの詳細です。
ウェブサーバー上で、以下のポートがアクセス可能であることを確認する必要があります。
- Port 80 on all addresses
- Port 22 on all addresses (already accounted for in firewall rules)
あなたのデータベースサーバーは、次のポートにアクセスできるようにする必要があります。
- Port 3306 on the address 192.0.2.30 (or the interface associated with it)
- Port 22 on all addresses (already accounted for in firewall rules)
ステップ3 — ウェブサーバのファイアウォールルールを調整します。
必要なポート情報が入手できたので、ウェブサーバーのファイアウォールのルールセットを調整します。sudo特権でエディタでルールファイルを開いてください。
- sudo nano /etc/iptables/rules.v4
ウェブサーバー上で、許可されるトラフィックのリストにポート80を追加する必要があります。サーバーは利用可能なアドレス全てでリッスンしているため、通常はどこからでもアクセス可能なことを想定しています。インターフェースや宛先アドレスによってルールを制限しないでください。
あなたのウェブ訪問者は、TCPプロトコルを使用して接続します。あなたのフレームワークにはすでに、TCPアプリケーション例外用のカスタムチェインがあります。あなたはそのチェインに、SSHポートの例外のすぐ下にポート80を追加することができます。
「/etc/iptables/rules.v4」
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 80 -j ACCEPT
. . .
あなたのウェブサーバーがデータベースサーバーとの接続を開始します。あなたのファイアウォールでは送信トラフィックに制限はなく、確立された接続に関連する受信トラフィックも許可されているため、この接続を許可するためにこのサーバーで追加のポートを開ける必要はありません。
作業が終了したら、ファイルを保存して閉じてください。ウェブサーバーには、すべての正規のトラフィックを許可し、それ以外のものはすべてブロックするファイアウォールポリシーが設定されました。
ルールファイルの構文エラーをテストしてください。
- sudo iptables-restore -t < /etc/iptables/rules.v4
構文エラーが表示されない場合は、新しいルールセットを実装するためにファイアウォールを再読み込みしてください。
- sudo service iptables-persistent reload
ステップ4 — データベースサーバーのファイアウォールルールを調整してください。
データベースサーバーでは、サーバーのプライベートIPアドレスであるポート3306へのアクセスを許可する必要があります。この場合、そのアドレスは192.0.2.30でした。このアドレスに対して特にアクセスを制限することもできますし、そのアドレスに割り当てられているインターフェースに対して一致させることでもアクセスを制限できます。
そのアドレスに関連付けられたネットワークインターフェースを見つけるには、ip -4 addr show scope globalコマンドを実行してください。
- ip -4 addr show scope global
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 203.0.113.5/24 brd 104.236.113.255 scope global eth0 valid_lft forever preferred_lft forever 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 192.0.2.30/24 brd 192.0.2.255 scope global eth1 valid_lft forever preferred_lft forever
ハイライトされた領域は、eth1インターフェースがそのアドレスに関連付けられていることを示しています。
次に、データベースサーバーのファイアウォールのルールを調整します。データベースサーバーでsudo権限を持っている状態でルールファイルを開いてください。
- sudo nano /etc/iptables/rules.v4
あなたは再び、ウェブサーバーとデータベースサーバーの接続に例外を形成するために、私たちのTCPチェーンにルールを追加することになります。
該当する実際のアドレスに基づいてアクセスを制限する場合、次のようにルールを追加します。
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -d 192.0.2.30 -j ACCEPT
. . .
もしもそのアドレスを含むインターフェースに基づいて例外を許可したいのであれば、代わりに以下のようなルールを追加することができます。
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -i eth1 -j ACCEPT
. . .
作業が終わったらファイルを保存し、閉じてください。
このコマンドで構文エラーをチェックしてください。
- sudo iptables-restore -t < /etc/iptables/rules.v4
準備ができたら、ファイアウォールのルールをリロードしてください。
- sudo service iptables-persistent reload
両方のサーバーは、必要なデータの流れを制限せずに保護されるべきです。
結論
アプリケーションの設定時には、適切なファイアウォールの導入は常に展開計画の一部であるべきです。上記で示した設定は、NginxとMySQLを実行する二つのサーバーを使用していますが、具体的な技術の選択に関係なく、上記で示した技術は適用可能です。
特にファイアウォールとiptablesについて詳しく学びたい場合は、以下のガイドを参考にしてみてください。
- How To Choose an Effective Firewall Policy to Secure your Servers
- A Deep Dive into Iptables and Netfilter Architecture
- How To Test your Firewall Configuration with Nmap and Tcpdump