【実務・中級編】 ネットワークアクセスコントロールリスト(NACL)のステートレスな評価 – クラウドインフラと仮想化ネットワーク実践ガイド

こんにちは。クラウドインフラの深淵を覗き、夜な夜なパケットキャプチャと格闘しているシニアSREの私です。

Web APIの設計やインフラ運用を担当していると、必ずと言っていいほどぶつかる壁が「ネットワークの疎通性不良」です。「あれ、セキュリティグループ(SG)はちゃんと許可したはずなのに、なぜAPIサーバーに通信が届かないんだ?」と頭を抱えた経験、あなたにもありませんか?

その原因、もしかするとサブネットの門番であるNACL(ネットワークアクセスコントロールリスト)の「ステートレス」な性格を見落としているせいかもしれません。

今回は、AWS VPCにおけるNACLの基本から、ステートレスな評価の仕組み、そして実務でハマりがちな罠とデバッグの極意まで、現場のリアルな視点を交えて徹底解説します。パケットの旅路を一緒に追いかけましょう。

—

1. NACLとは何か?――セキュリティグループとの決定的な違い

AWSのネットワークセキュリティを語る上で、セキュリティグループ(SG)とNACLは両輪ですが、その役割と性質は全く異なります。

  • セキュリティグループ(SG): ステートフル。仮想サーバー(ENI)単位で適用され、「往きの通信を許可すれば、戻りの通信は自動的に許可される」優しい門番です。
  • NACL(ネットワークアクセスコントロールリスト): ステートレス。サブネット単位で適用され、「往きを許可しても、戻りの通信を個別のルールで再度許可しないと通さない」という、極めて厳格かつ融通の利かない門番です。

多くのエンジニアがSGの感覚のままNACLをいじり、「なぜ通信が片方向しか通らないんだ!」と深夜のデバッグ沼にハマります。NACLの本質は「インバウンド(受信)とアウトバウンド(送信)のルールを完全に個別に評価する」という点にあります。

—

2. NACLの評価メカニズムとパケットのライフサイクル

では、クライアントからプライベートサブネット上のWeb APIサーバーへリクエストが飛ぶとき、パケットはNACLの防壁をどのように突破(あるいはブロック)されるのでしょうか。通信のシーケンスを見てみましょう。

通信フロー(インバウンドとアウトバウンドの往復)

1. インターネットからのリクエスト(インバウンド)

  • パケットがインターネットゲートウェイ(IGW)を抜け、サブネットに到着します。
  • NACLのインバウンドルールが、番号の若い順(上から順)に評価されます。
  • マッチした時点で許可(Allow)または拒否(Deny)が決定され、後続のルールは評価されません。

2. APIサーバーによるレスポンス(アウトバウンド)

  • サーバーが処理を終え、クライアントへレスポンスを返そうとします。
  • ここで重要なのは、SGであれば往きの通信の記憶(ステート)があるため自動で通るのですが、NACLは往きのことは一切覚えていません。
  • サブネットから出ていくパケットに対して、今度はNACLのアウトバウンドルールが改めて若い番号順に評価されます。
  • アウトバウンド側で戻りの通信(例えば、クライアントのエフェメラルポートへの送信)を許可するルールがない場合、パケットは容赦なくドロップされます。

—

3. なぜエフェメラルポートでハマるのか?(実務の罠)

NACLで最も頻発するトラブルが、カスタムNACLを作成した際に、インターネットからのインバウンドは通したのに、アウトバウンドのルール設定をミスしてレスポンスが返せなくなる現象です。

特に意識しなければならないのがエフェメラルポート(一時ポート)です。
クライアントがAPIを叩く際、接続元のポートには通常 1024 から 65535 のランダムなポートが割り当てられます。

正しいNACL設定の考え方

もしあなたがWeb APIサーバーを保護するサブネットのNACLを書く場合、以下の両方を網羅しなければなりません。

  • インバウンドルール: クライアントからのリクエスト(例: 443ポートやカスタムAPIポート)を受け入れる。
  • アウトバウンドルール: サーバーからクライアントのエフェメラルポート(1024-65535)へデータを送り返すことを許可する。

—

4. 実践:TerraformによるNACLの構成例

現場のインフラ構築では、手動ポチポチではなくIaC(Infrastructure as Code)で管理するのが鉄則です。ここではTerraformを用いて、安全かつ適切なNACLを定義するコード例を紹介します。

# プライベートサブネット用のNACL定義
resource "aws_network_acl" "api_private_nacl" {
  vpc_id     = aws_vpc.main.id
  subnet_ids = [aws_subnet.private_api.id]

  # ----------------------------------------------------
  # インバウンドルール (Inbound Rules)
  # ----------------------------------------------------

  # 1. 外部からのHTTPS通信(443)を許可
  ingress {
    protocol   = "tcp"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 443
    to_port    = 443
  }

  # 2. 外部からのHTTP通信(80)を許可(必要に応じて)
  ingress {
    protocol   = "tcp"
    rule_no    = 110
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 80
    to_port    = 80
  }

  # 3. 【超重要】外部からのレスポンスやアクティブな接続に対するエフェメラルポートの受け入れ
  # これがないと、外部サービスへリクエストを投げた際の戻りパケットが受け取れません
  ingress {
    protocol   = "tcp"
    rule_no    = 120
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 1024
    to_port    = 65535
  }

  # デフォルトルール(AWS NACL仕様により、明示しなくても暗黙の Deny All が最後に存在します)

  # ----------------------------------------------------
  # アウトバウンドルール (Outbound Rules)
  # ----------------------------------------------------

  # 1. 外部APIへのリクエストやインターネット通信のためのHTTPS送信を許可
  egress {
    protocol   = "tcp"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 443
    to_port    = 443
  }

  # 2. 外部パッケージのアップデートなどのためのHTTP送信を許可
  egress {
    protocol   = "tcp"
    rule_no    = 110
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 80
    to_port    = 80
  }

  # 3. 【超重要】クライアント(ブラウザや別システム)のエフェメラルポートへレスポンスを返すための許可
  egress {
    protocol   = "tcp"
    rule_no    = 120
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 1024
    to_port    = 65535
  }

  tags = {
    Name = "prod-api-private-nacl"
    Environment = "Production"
  }
}

このコードのポイントは、インバウンド・アウトバウンドの両方で 1024 から 65535 のポートレンジをしっかりと allow している点です。これを忘れると、APIサーバーが外と通信できなくなるか、クライアントにレスポンスが返せなくなります。

—

5. デバッグとトラブルシューティングの現場知見

「設定は入れたはずなのに、APIのレスポンスがタイムアウトする……」
そんな修羅場でシニアSREが取るべき具体的なデバッグ手順を伝授します。

1. VPC Flow Logsを有効化する

パケットがNACLでドロップされているかどうかを確かめる最も確実な方法は、VPC Flow Logsの出力を確認することです。
ログのフィールドにある action が REJECT になっており、かつ srcaddr や dstaddr が意図したものであれば、NACL(あるいはSG)で弾かれています。

2. tcpdump や nc (Netcat) でレイヤー4の疎通を確認する

APIサーバーにSSHやAWS Systems Manager (SSM) Session Managerでログインし、実際に外部との通信がどこまで届いているか確認します。

例えば、Pythonやcurlを使ったシンプルなAPI疎通テストの前に、ネットワークレベルでポートが開いているか確認するには以下のコマンドが有効です。

# 特定のエンドポイントに対してTCPのハンドシェイクが成功するか確認
nc -zv api.example.com 443

もしここでタイムアウト(Connection timed out)し、セキュリティグループのルールに問題がないことが確認できたら、それは9割の確率でNACLのアウトバウンド(または相手側のインバウンド)ルール抜けです。

—

まとめ

AWSのNACLは、セキュリティグループに比べて「ステートレス」という特性ゆえに設定ミスの余地が多く、敬遠されがちです。しかし、VPC全体の境界防衛や、特定の悪意あるCIDRブロックからのアクセスをサブネット手前で完全に遮断(Deny)したい場合には、なくてはならない強力なツールです。

「インバウンドとアウトバウンドは別々に評価される」「エフェメラルポートの考慮を忘れない」。この2点を胸に刻むだけで、あなたのネットワークトラブルシューティングのスピードは劇的に向上するはずです。

現場からは以上です。明日からのクラウドアーキテクチャ設計に、ぜひ役立ててください!

コメント

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