【実務・中級編】 セキュリティグループ(SG)とNACLの組み合わせにおけるステートフル・ステートレスの二重防御 – クラウド&コンテナネットワーク実践ガイド

ネットワークの迷宮を解く:SGとNACLで構築する「二重の鉄壁」の作法

SREとして現場を歩いていると、「なぜか通信が通らない」というSOSが舞い込んでくることがよくあります。その多くは、クラウドのネットワーク設計における「ステートフル」と「ステートレス」という、全く異なる性質を持つ二つの防御層を混同したことに起因します。

今日は、AWSにおける「セキュリティグループ(SG)」と「ネットワークACL(NACL)」という、一見似て非なる二つの門番が、パケットをどのように裁いているのか、現場のリアルな視点から紐解いていきましょう。

—

1. 守護神たちの性格:ステートフル vs ステートレス

まず、この二つの違いを正確に把握しておく必要があります。

  • セキュリティグループ (SG): ステートフルな守護神
  • 性格: 「一度通した通信は、戻りも許可する」という柔軟性を持っています。
  • 場所: インスタンス(NIC)レベル。
  • ネットワークACL (NACL): ステートレスな冷徹な門番
  • 性格: 「往路」と「復路」を完全に別個の通信として見なします。往路を通したからといって、戻りの通信を自動的に通すことはありません。
  • 場所: サブネットレベル。

現場でよくある失敗は、NACLでアウトバウンド(戻り)の通信を考慮せず、インバウンドの許可だけで満足してしまうケースです。

—

2. パケットが辿る「検問所」の序列

パケットがプライベートサブネット内のWebサーバーに届くとき、あるいはサーバーから外へ出るとき、その序列は決まっています。

【インバウンド(外部からサーバーへ)】
1. NACL(サブネットの入口): 最初にパケットをチェック。ここで落とせばインスタンスには届かない。
2. SG(インスタンスの入口): ここで最終判定。

【アウトバウンド(サーバーから外部へ)】
1. SG(インスタンスの出口): 送信元が許可されているか確認。
2. NACL(サブネットの出口): サブネットを出る直前の最終チェック。

この順序を理解していないと、トラブル時に「どこでパケットが消えたのか」の特定に時間がかかります。

—

3. 実践:NACLの「復路」設定を忘れないためのTips

NACLを設定する際、エフェメラルポート(一時ポート)の考慮が不可欠です。Web API通信や、curl による外部API叩きを行う際、戻りの通信は 1024-65535 のポート番号が使われます。

TerraformでのNACL設定例(抜粋)

# Webサーバー用サブネットのNACLルール(アウトバウンド)
resource "aws_network_acl_rule" "outbound_allow" {
  network_acl_id = aws_network_acl.main.id
  rule_number    = 100
  egress         = true # アウトバウンドルール
  protocol       = "tcp"
  rule_action    = "allow"
  cidr_block     = "0.0.0.0/0"
  from_port      = 1024 # エフェメラルポートの範囲を指定
  to_port        = 65535
}

この「1024-65535」という範囲を忘れると、Webサーバーは外からのリクエストを受け取れても、自分のレスポンスがNACLで弾かれ、クライアントには「Connection Timeout」が返るという、非常にタチの悪い現象が発生します。

—

4. トラブルシューティング:現場で使うデバッグの定石

通信が通らないとき、私はまず以下の順で切り分けを行います。

Step 1: curl での疎通確認

まずは接続先が生きているか、curl の詳細フラグを使って確認します。

# -v を付けてハンドシェイクのどこで止まるかを確認
curl -v https://api.external-service.com

Step 2: フローログの解析

クラウドベンダーのフローログ(AWS VPC Flow Logs)を確認しましょう。REJECT されているパケットがあれば、それが「SGによるものか」「NACLによるものか」をログのフィールドから見極めます。

Step 3: Pythonでの簡易チェック

もしアプリケーション層の問題かネットワーク層の問題か迷ったら、Pythonで単純なソケット通信を試すのが一番早いです。

import socket

# 外部APIへの疎通確認
target_host = "api.external-service.com"
target_port = 443

try:
    with socket.create_connection((target_host, target_port), timeout=5) as sock:
        print(f"成功: {target_host}:{target_port} に接続できました")
except Exception as e:
    print(f"失敗: {e}")

—

5. まとめ:SREとしての心構え

セキュリティの鉄則は「多層防御」ですが、二重に防御を敷くということは、それだけ「設定漏れ」の温床が増えるということでもあります。

  • SGは「サーバーのアイデンティティ」として管理する: Webサーバーなら 80/443 を開けるといった、本来の目的を記述する。
  • NACLは「サブネットの交通整理」として扱う: 非常に限定的なアクセス制御や、特定のIPブロック(ブラックリスト)など、広範囲で防ぐために使う。

「なんとなく動くから」と、SGやNACLを 0.0.0.0/0 で全開放してしまうのは、エンジニアとして最も避けたい怠慢です。パケットがどこで通って、どこで弾かれるのか。その地図を頭の中に描けるようになると、ネットワークトラブルは「障害」から「パズル」に変わります。

皆さんのインフラが、今日も安全で軽快に通信を捌けることを願っています。

コメント

タイトルとURLをコピーしました