AWSネットワークの要塞:セキュリティグループとNACLの正しい使い分けと実践防衛術
こんにちは。SREとして数々の修羅場、それこそ深夜の障害対応で冷や汗を流してきたシニアインフラエンジニアの私です。
クラウドインフラの設計において、AWSのVPC(Virtual Private Cloud)はすべての基盤となります。しかし、若いエンジニアから「セキュリティグループ(以下、SG)があるのに、なぜわざわざサブネット境界にNACL(ネットワークアクセスコントロールリスト)を置く必要があるのですか?」という質問をよく受けます。
教科書には「SGはステートフル、NACLはステートレス」と書かれていますが、実際のWeb API設計やマルチティア(多層)アーキテクチャの現場では、この違いがシステムの生死を分けます。今回は、パケットの挙動や現場のトラブルシューティングの視点から、SGとNACLの役割の違い、そして実務で本当に使える設計パターンを徹底的に解説しましょう。
—
1. パケットの旅路から見る:SGとNACLの決定的な違い
まずは、インターネットからやってきたリクエストパケットが、VPCの境界を越えてEC2インスタンスに到達するまでの「旅路」をイメージしてください。パケットは、AWSの巨大なネットワークルーターを通過する際、厳重なセキュリティチェックポイントをくぐり抜けます。
ここで重要なのが、チェックポイントの「場所」と「記憶力」です。
[Internet]
│
▼
[Internet Gateway (IGW)]
│
▼
[NACL (サブネット境界)] ← ここが「門限」と「荷物検査(ステートレス)」
│
▼
[Route Table (ルートテーブル)]
│
▼
[Security Group (ENI単位)] ← ここが「部屋の鍵(ステートフル)」
│
▼
[EC2 Instance (OS/アプリケーション)]
NACL(Network ACL)の特徴:サブネット単位の「門限」とステートレス
NACLは、サブネットの出入り口に陣取る「ステートレス」な関所です。RFCの基本原則に忠実で、パケットが往くとき(インバウンド)と帰るとき(アウトバウンド)の両方でルールを評価します。
例えば、クライアントからのリクエストに対してサーバーが応答を返す際、SGなら往きの通信を覚えてくれているので帰りは自動許可されますが、NACLの場合はアウトバウンドルールで「返りの通信(エフェメラルポート等)」を明示的に許可していないと、パケットは容赦なく捨てられます。これがハマりポイントの筆頭です。
セキュリティグループ(SG)の特徴:ENI単位の「スマートな鍵」
一方、SGはEC2インスタンス(厳密にはENI:Elastic Network Interface)の直近にアタッチされる「ステートフル」な番人です。
一度インバウンドの通信を許可(許可のステートを記憶)すると、それに対するアウトバウンドの応答パケットは、ルールに関わらず自動的に通過させます。現代のWebアプリケーション設計において、このステートフルな挙動は非常に直感的で扱いやすいものです。
—
2. 徹底比較:SG vs NACL の判断基準
実務でどちらを使うべきか迷ったときは、以下の比較表を思い出してください。
| 項目 | セキュリティグループ (SG) | ネットワークACL (NACL) |
| :— | :— | :— |
| 適用単位 | ENI(インスタンス単位) | サブネット単位 |
| ステート(状態) | ステートフル(往きを許可すれば帰りは自動許可) | ステートレス(往きも帰りも個別にルールが必要) |
| 評価の順序 | すべてのルールを評価してから判定 | ルールの番号順(低い順)に評価し、最初に一致した時点で確定 |
| ルール形式 | 許可(Allow)ルールのみ | 許可(Allow)および拒否(Deny)ルールが可能 |
| 主な用途 | アプリケーション層のアクセス制御(例: WebからDBへの接続許可) | ネットワーク全体の特定IPブロック、DDoS初期防御、サブネット間隔離 |
—
3. 実務で直面する「NACLの罠」とデバッグ手順
先日も、パブリックサブネットに配置したWeb APIサーバーから、外部の決済API(HTTPS)へリクエストを投げた際、「コネクションタイムアウト」になる障害が発生しました。
SGのインバウンド・アウトバウンド共に 0.0.0.0/0 の TCP/443 を許可しているにも関わらず、です。原因はデフォルトではないカスタムNACLを作成した際に、エフェメラルポート(一時ポート: 1024-65535)のアウトバウンド許可を入れ忘れていたことでした。
トラブルシューティングの鉄則(フローログとデバッグ)
このようなネットワークの迷宮に入り込んだときは、以下の手順でパケットの足跡を追います。
1. VPCフローログの有効化
CloudWatch LogsやS3に流れるVPCフローログを確認し、対象のトラフィックが REJECT(拒否)されているか、どのインターフェイスで止まっているかを特定します。
2. ログのステータスコード確認
フローログ内で ACCEPT になっているのに通信できない場合は、セキュリティグループではなく、OS内のファイアウォール(iptablesやfirewalld)、あるいはアプリケーションのバインド設定が怪しいと切り分けられます。
—
4. 実践:セキュアなWeb APIインフラの設計パターン
では、実際のプロダクション環境ではどのようにNACLとSGを組み合わせるべきでしょうか。ベストプラクティスは「防御の多層化(ディフェンス・イン・ディープ)」です。
1. セキュリティグループは「最小権限の原則」で細分化する
APIサーバー、ロードバランサー(ALB)、データベース(RDS)の間で、それぞれ専用のSGを作成し、相互の参照(Source SG)で通信を絞ります。
2. NACLは「最後の砦(ブロックリスト)」として活用する
通常のアプリケーション制御はすべてSGに任せ、NACLは以下のような広域ブロック用途に絞るのが、運用のメンテナン性を高めるコツです。
- 特定の悪意ある国・IPレンジ(CIDR)からのアクセスを丸ごと
Denyする - 万が一サブネットがコンプロマイズ(侵害)された際、別サブネットへの横展開(ラテラルムーブメント)を防ぐためのサブネット間隔離
設定例:カスタムNACLにおける推奨ルール(インバウンド)
番号の若い順に評価される特性を利用します。
# ルール番号 / タイプ / プロトコル / ポート範囲 / ソース / アクション
100 / HTTPS (443) / TCP / 443 / 0.0.0.0/0 / ALLOW (外部からのAPIリクエスト受付)
200 / HTTP (80) / TCP / 80 / 0.0.0.0/0 / ALLOW (HTTPからHTTPSへのリダイレクト用)
300 / カスタムTCP / TCP / 1024-65535 / 0.0.0.0/0 / ALLOW (エフェメラルポートの受け入れ)
65535 / すべて / すべて / すべて / 0.0.0.0/0 / DENY (明示的なデフォルト拒否)
—
5. コードから見るネットワーク疎通確認の実装例
インフラの構築が完了したら、正しくパケットが流れるかをアプリケーションコードやスクリプトで検証します。以下に、Pythonを用いた基本的な疎通・APIテストのコードスニペットを示します。
import urllib.request
import urllib.error
import socket
def check_api_connectivity(target_url: str, timeout_sec: int = 3) -> None:
"""
指定されたエンドポイントへの疎通確認を行い、
NACLやSGのミスカンフィグによるタイムアウトや拒否を検知する関数。
"""
print(f"[*] 接続テスト開始: {target_url}")
try:
# タイムアウトを明示的に設定し、パケットがブラックホールに入っていないか確認
req = urllib.request.Request(target_url, headers={"User-Agent": "SRE-Connectivity-Checker/1.0"})
with urllib.request.urlopen(req, timeout=timeout_sec) as response:
status_code = response.getcode()
print(f"[SUCCESS] 接続成功: HTTPステータスコード {status_code}")
except urllib.error.HTTPError as e:
# 4xxや5xxはアプリケーション層に到達している証拠(ネットワーク層はクリア)
print(f"[INFO] サーバーには到達しましたが、HTTPエラーが返されました: {e.code}")
except urllib.error.URLError as e:
# ここに落ちる場合、NACLのアウト/インバウンド拒否やSGの不備、ルートテーブルのルーティングミスが疑われる
print(f"[ERROR] ネットワーク層または名前解決でブロックされました: {e.reason}")
if isinstance(e.reason, socket.timeout):
print(" -> ヒント: NACLのアウトバウンド(エフェメラルポート)またはSGのアウトバウンド設定を確認してください。")
except Exception as ex:
print(f"[CRITICAL] 予期せぬエラーが発生しました: {ex}")
if __name__ == "__main__":
# 自身のVPC内APIエンドポイントや外部検証用URLを指定
TARGET_API = "https://api.ipify.org?format=json"
check_api_connectivity(TARGET_API)
このスクリプトをCI/CDパイプラインや、踏み台サーバーからのデプロイ後チェックに組み込んでおくことで、「デプロイしたのにAPIが叩けない」というインフラ起因のインシデントを未然に防ぐことができます。
—
まとめ:適材適所のネットワーク設計を
AWSのセキュリティグループとNACLは、どちらか一方が優れているというものではありません。
- 細かいアクセス制御(誰が・どこへ)は「セキュリティグループ」に任せる。
- マクロなトラフィック制御(悪意あるIPの遮断・ネットワーク境界のファイアウォール)は「NACL」に任せる。
この役割分担を正しく理解し、パケットの流れを頭の中でクリアに描けるようになれば、あなたのVPCは鉄壁の要塞となります。日々のインフラ運用やAPI設計において、ぜひこの視点を取り入れてみてください。
それでは、また次回の現場でお会いしましょう。快適なクラウドライフを!
コメント