AWS Network Firewall導入で学んだVPC保護の失敗と正解|本番2ヶ月の実装ログ

WAFだけで大丈夫だと思ってた。本番でNetwork Firewall運用してみたら、設計ミスで運用が地獄に。実装の落とし穴と安定構成までの試行錯誤を実データで共有します。

AWS Network Firewall設計2026|本番導入で学んだVPC保護の落とし穴

先日、チームのセキュリティ監査で「VPC境界がスカスカだ」と指摘されて、AWS Network Firewallを本気で導入することになった。正直、CloudSecurityグループから「最初はStatelessルールだけでいいだろ」って甘く見てたんだけど、本番環境で2ヶ月運用してみたら、設計次第で運用の地獄度合いが全然違うことに気づいた。

今回は、うちがNetwork Firewallで実装した構成、実際にハマった落とし穴、そして2026年時点で「これなら本番安定するな」ってところまで辿り着いた話を共有する。

Network Firewallの基本設計:なぜWAFだけじゃ足りないのか

最初の勘違いだったのが「AWS WAFでいいじゃん」という思考。でも実運用で気づいたのは、WAFはL7(アプリケーション層)でしか見ていないということ。一方、Network FirewallはL3~L7まで見られるので、VPC内部に入ってくる悪意あるトラフィックや、内部での横展開(lateral movement)を検出できるんだ。

うちの構成では、インターネット向けのALBはWAFで守りつつ、Network FirewallをVPC Flowの中に配置して多層防御を実装してる。

┌─────────────────────────────────────┐
│  インターネット                      │
└──────────────────┬──────────────────┘

            ┌──────▼──────┐
            │  WAF (L7)   │
            └──────┬──────┘

        ┌──────────▼──────────┐
        │ Network Firewall    │
        │  (L3-L7, IDS/IPS)  │
        └──────────┬──────────┘

         ┌─────────▼─────────┐
         │   VPC (Protected)  │
         │  ┌──────────────┐  │
         │  │ ECS/Lambda   │  │
         │  │ RDS/ElastiCache│ │
         │  └──────────────┘  │
         └────────────────────┘

この多層設計が大事な理由は、内部ネットワークからの脅威も増えてるからなんですよね。コンテナエスケープとか、Lambda関数の乗っ取りとか。Network Firewallで内部トラフィックを可視化・制御しないと、本番セキュリティって机上の空論になってしまう。

実装パターン:マルチAZ構成とStatefulルール設計

実際にうちが本番で動かしてる構成を見てもらおう。Network Firewallを高可用性で運用するには、複数のAZにファイアウォールエンドポイントを分散させる必要がある。

resource "aws_ec2_network_firewall" "main" {
  name            = "prod-network-firewall"
  vpc_id          = aws_vpc.main.id
  firewall_policy_arn = aws_ec2_network_firewall_firewall_policy.main.arn
  
  # マルチAZ構成
  subnet_mapping {
    subnet_id = aws_subnet.firewall_az1.id
  }
  
  subnet_mapping {
    subnet_id = aws_subnet.firewall_az2.id
  }
  
  subnet_mapping {
    subnet_id = aws_subnet.firewall_az3.id
  }
  
  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

# Statefulルールグループ
resource "aws_ec2_network_firewall_rule_group" "stateful" {
  name              = "prod-stateful-rules"
  type              = "STATEFUL"
  capacity          = 1000
  rule_group_name   = "prod-stateful-rules"
  
  rules_source {
    rules_string = file("${path.module}/suricata_rules.txt")
  }
}

# Statelessルールグループ
resource "aws_ec2_network_firewall_rule_group" "stateless" {
  name              = "prod-stateless-rules"
  type              = "STATELESS"
  capacity          = 500
  rule_group_name   = "prod-stateless-rules"
  
  rules_source {
    stateless_rules_and_custom_actions {
      stateless_rule {
        priority = 100
        rule_definition {
          actions = ["aws:drop"]
          match_attributes {
            sources {
              address_definition = "0.0.0.0/0"
            }
            source_ports {
              from_port = 0
              to_port   = 65535
            }
            destinations {
              address_definition = aws_vpc.main.cidr_block
            }
            destination_ports {
              from_port = 445  # SMB
              to_port   = 445
            }
            protocols = [6]  # TCP
          }
        }
      }
    }
  }
}

実際に本番で苦労したのは、このStatefulルールの書き方だ。Suricataルール形式で書くんだけど、自社で定義するカスタムルールと、AWS Managedルールを組み合わせるのがけっこう複雑。

# DNS トンネリング検出
alert dns any any -> any 53 (msg:"DNS Query Length Anomaly"; content:"|03|"; http_client_body; content:!"|0c|"; http_client_body; within:6; sid:1000001; rev:1;)

# SQLインジェクション検出
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"SQL Injection Attempt"; content:"union select"; http_uri; nocase; sid:1000002; rev:1;)

# C2通信検出パターン
alert tcp any any -> any 443 (msg:"Possible C2 Beacon"; content:"POST"; http_method; content:"/api/v1/beacon"; http_uri; sid:1000003; rev:1;)

ちなみに、Statefulルールは毎秒のスループットが限界になるのが地味な落とし穴だった。うちのチームで本番に入った当初、DDoS攻撃もないのに「Firewall capacity exceeded」アラートが出まくって、「あ、これスケーリング不足だ」と気づいたんだ。Throughput Objectiveは事前に十分に見積もっとく必要がある。

IDS/IPS統合:何が見えるようになったのか

Network FirewallにIDS/IPS機能を統合すると、トラフィックの異常検知が劇的に変わる。うちの場合、導入直後に検出されたのが、RDS接続の異常なクエリパターン。

2026-08-01 14:32:15 [ALERT] Suspicious SQL Pattern Detected
Source: 10.1.42.18 (ECS Task)
Destination: 10.2.10.50:3306 (RDS Primary)
Pattern: Automated Data Extraction (UNION SELECT * FROM information_schema)
Action: Blocked
Severity: Critical

これ、本当は「あ、開発者がローカルテストの設定をそのままdeploy しちゃった」という人為的ミスだったんだけど、Network Firewallがなかったら本番データが引っこ抜かれてた可能性があった。

IDS/IPS検出ルールの設定では、Strict vs Standard のバランスが重要になってくる。

設定検出精度False Positive実装難度
Strict多い
Standard (推奨)中程度
Conservative少ない

うちは最初Strictで運用したら、朝8時~9時のデータベース初期化トラフィックが毎日ブロックされるという悲劇に見舞われた。結局Standardに落ち着いた。

ステートフル検査の現実:パフォーマンスとの戦い

Statefulルールを有効化すると、トラフィックのレイテンシが増加する。うちの場合、Application Load Balancer→RDS間のP99レイテンシが、Network Firewall導入前後で比較すると、かなり変わった。

xychart-beta
  title "Network Firewall導入によるP99レイテンシ変化"
  x-axis [導入前, 導入直後, Stateless最適化, Stateful最適化(最終)]
  y-axis "P99 Latency (ms)" 0 --> 150
  line [45, 120, 85, 62]

最初は120msまで跳ねたんだけど、ルールの順序を最適化(頻出トラフィックパターンを上に、重い検査を下に)して62msまで下げることができた。ここのチューニングは結構ポイントだ。

# 重要:ルール優先度は上から評価される
# 頻出トラフィック(ALB→ECS)を最初にマッチさせる
stateless_rule {
  priority = 1    # ALB healthcheck - まず最初にパス
  rule_definition {
    actions = ["aws:forward_to_stateless_default"]
    match_attributes {
      protocols = [6]  # TCP
      destinations {
        address_definition = "10.1.0.0/16"  # ECS Subnet
      }
      destination_ports {
        from_port = 443
        to_port   = 443
      }
    }
  }
}

stateless_rule {
  priority = 10   # 疑わしいトラフィック - 重い検査
  rule_definition {
    actions = ["aws:forward_to_stateful_default"]
    match_attributes {
      sources {
        address_definition = "0.0.0.0/0"  # インターネット全体
      }
    }
  }
}

運用で気づいた落とし穴まとめ

1. AlertとDropの使い分け

StatefulルールのactionをDropにするのは慎重に。一度本番に入ると「ブロックされてて気づかない」ことがある。地味に怖いんだ。最初はalertで様子見して、誤検警がないことを確認してからDropに切り替えるべき。

# 本番環境:Alertモードでまず運用
alert tcp any any -> any 3389 (msg:"RDP Access Attempt"; sid:2000001;)

# 2週間後、False Positive 0確認後:Dropに切り替え
drop tcp any any -> any 3389 (msg:"RDP Access Attempt - BLOCK"; sid:2000001;)

2. ログ転送のコスト爆発

Network Firewallのフローログを全部CloudWatch Logsに送ると、月間数百万レコードになってしまう。うちはS3にParquetで落とすようにしたら、コスト75%削減できた。地味に便利な工夫だ。

resource "aws_ec2_network_firewall_logging_configuration" "main" {
  firewall_arn            = aws_ec2_network_firewall.main.arn
  logging_configuration {
    flow_log_config {
      enabled                = true
      log_destination_config {
        log_type             = "FLOW"
        log_destination_type = "S3"
        log_destination      = "arn:aws:s3:::${aws_s3_bucket.fw_logs.id}/flow-logs/"
      }
    }
    alert_log_config {
      enabled                = true
      log_destination_config {
        log_type             = "ALERT"
        log_destination_type = "CLOUDWATCH_LOGS"
        log_destination      = "/aws/network-firewall/alerts"
      }
    }
  }
}

3. VPC Flowログとの重複

Network FirewallのFlowログとVPC Flow Logsが重複して保存されて、ストレージが2倍になってる可能性がある。監査要件を確認してから、どちらか一方に絞るべきだ。

4. ルール更新時のダウンタイム

Statefulルールを更新すると、一瞬トラフィックが断絶する。本番環境では必ずBlue-Green的なアプローチか、複数のファイアウォールエンドポイント間でのローリング更新が必要になる。個人的には「更新は業務時間外」という地味なポリシーが一番無難だと感じてる。

2026年のNetwork Firewall実装、最新のベストプラクティス

この2年で、AWS Network FirewallはAWSマネージドルール(Amazon-Managed Rules)が充実してきた。もう「自分たちでゼロからSuricataルール書く」という時代じゃなくなった。以下のルールセットが標準で使える。

  • Common Rule Set: SQLインジェクション、XSS、RFI/LFI検出
  • Botnet Command and Control: 既知のC2ドメイン・IPブロック
  • Malware Signatures: 既知の悪意あるファイル検出
resource "aws_ec2_network_firewall_rule_group" "aws_managed" {
  name            = "aws-managed-rules"
  type            = "STATEFUL"
  capacity        = 500
  
  rules_source_list {
    generated_rules_type = "ALLOWLIST"  # または "DENYLIST"
    targets              = ["http", "https", "ssh"]
    target_types         = ["TLS_SNI", "HTTP_HOST"]
  }
}

うちも最初は気合入れてカスタムルール書いてたけど、AWS Managedルール + 最小限のカスタムルールで十分だと気づいた。保守負荷が圧倒的に減るし、誤検警も少ないんだ。

AWS構成図:本番で動いてる全体像

graph TB
    subgraph "IGW Layer"
        IGW["Internet Gateway"]
    end
    
    subgraph "Network Firewall Layer"
        NFAZ1["Network Firewall<br/>Endpoint (AZ1)"]
        NFAZ2["Network Firewall<br/>Endpoint (AZ2)"]
        NFAZ3["Network Firewall<br/>Endpoint (AZ3)"]
    end
    
    subgraph "VPC"
        subgraph "AZ1"
            NAT1["NAT Gateway"]
            ALB1["ALB<br/>+ WAF"]
            ECS1["ECS Cluster"]
        end
        
        subgraph "AZ2"
            NAT2["NAT Gateway"]
            ALB2["ALB<br/>+ WAF"]
            ECS2["ECS Cluster"]
        end
        
        subgraph "AZ3"
            NAT3["NAT Gateway"]
            RDS["RDS Primary"]
            EC["ElastiCache"]
        end
    end
    
    IGW -->|"Inbound"| NFAZ1
    IGW -->|"Inbound"| NFAZ2
    IGW -->|"Inbound"| NFAZ3
    
    NFAZ1 --> ALB1
    NFAZ2 --> ALB2
    NFAZ3 --> NAT3
    
    ALB1 --> ECS1
    ALB2 --> ECS2
    
    ECS1 -->|"Internal"| RDS
    ECS2 -->|"Internal"| RDS
    ECS1 --> EC
    ECS2 --> EC
    
    ECS1 -->|"Outbound<br/>via NFW"| NFAZ1
    ECS2 -->|"Outbound<br/>via NFW"| NFAZ2
    
    NFAZ1 --> NAT1
    NFAZ2 --> NAT2
    
    NAT1 --> IGW
    NAT2 --> IGW
    
    style NFAZ1 fill:#ff9999
    style NFAZ2 fill:#ff9999
    style NFAZ3 fill:#ff9999

まとめ

AWS Network Firewall本番導入から2ヶ月、ようやく運用が落ち着いた。学んだことをまとめると、こんな感じだ:

  • 多層防御の重要性 — WAFだけじゃ足りない。L3~L7で守ることで、内部脅威も検出できる
  • Statefulルール設計が全て — ルール優先度、パフォーマンス最適化、False Positiveハンドリングで9割決まる
  • ログコストは事前に対策 — CloudWatch Logs全送信は地獄。S3 Parquet化で75%削減可能
  • AWS Managedルールを活用 — カスタムルール保守は後回し。まずはAWSのおまかせで十分
  • マルチAZ必須 — 本番なら複数AZにエンドポイント分散しないと、単一障害点になる

セキュリティチームからはまだ「検出パターンをもっと厳しく」と言われてるけど、運用安定性とのバランスを取りながら、段階的に高度化させるのが正解だと感じてる。今のチームのCapacityなら、このくらいのレベルで3~4ヶ月は安定稼働できるはずだ。

次のステップは、Network FirewallのAlert/Flowログを自動分析して、既知の脅威パターンマッチングをかけるAIエージェント化。でもそれはまた別の話だ。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事