AWSのVPC設計に取り組み始めたエンジニアが最初にぶつかる壁のひとつです。原因を調べていくと、セキュリティグループとは別に「Network ACL」というファイアウォールが存在することに気づきます。
この記事では、VPCのセキュリティを支えるNetwork ACL(NACL)とセキュリティグループ(SG)の違いと、2層防御として組み合わせる設計パターンを解説します。Amazon Linux 2023 / RHEL 9.4での実機確認コマンドも交え、「SGで許可したのに繋がらない」エラーの切り分け手順まで説明します。さらに、NACLとSGだけではカバーできないL7レベルの検査(ドメインフィルタ・シグネチャ検知)が必要になるケースと、AWS Network Firewallのドメインリスト型・Suricata互換型ステートフルルールグループ・ポリシー作成からデプロイ・ルートテーブル変更・CloudWatch Logsへのログ設定まで、CLIコマンドを交えた実践的な導入手順もまとめます。複数VPCやマルチアカウント環境で有効な検査VPC(Inspection VPC)パターンとTransit Gateway連携の考え方も解説します。
本記事のAWS CLIコマンドはAWS CLI v2(2.x系)、リージョンap-northeast-1(東京)を想定しています。
この記事のポイント
・NACLはサブネット単位のステートレスFW、SGはインスタンス単位のステートフルFW
・ステートレスのNACLはエフェメラルポートのアウトバウンド許可が必須
・「NACL=不審IP遮断」「SG=インスタンス間の通信制御」で役割を分担する
・SGはSG IDを参照したチェーン設計にすることで、IP変動やオートスケーリングに強くなる
・マルチAZ構成では同じ役割のサブネットに同じNACLを関連付けてルール不一致を防ぐ
・L7のドメイン・ペイロード検査が必要なときはNetwork Firewallを追加する
・Network Firewallのステートフルルールは5タプル・ドメインリスト・Suricata互換の3形式から選べる
・StatefulEngineOptionsはSTRICT_ORDERを指定してルール評価順を明示的に制御する
・複数VPC環境は検査VPC(Inspection VPC)パターンでFirewallをTransit Gateway経由で集中配置できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜVPCには2種類のファイアウォールが必要なのか
VPCのセキュリティを理解するには、「どのレイヤーでトラフィックを制御するか」という視点が重要です。AWSのVPCには2つの独立したファイアウォールが存在します。
・Network ACL(NACL):サブネット単位で働く。サブネットに出入りするトラフィックをルール番号順に評価する
・セキュリティグループ(SG):EC2インスタンス(正確にはENI)単位で働く。「許可したトラフィックの戻りは自動で許可」するステートフルな動作
この2層構造は意図的な設計です。大企業の物理ネットワークでは「フロアごとのFW」と「端末ごとのFW」を組み合わせるのと同じ発想です。NACLが「建物の受付」なら、SGは「各部屋の鍵」です。
NACLとSGはどちらもIPアドレスとポート番号(L4)を基準にトラフィックを制御します。パケットの中身(ペイロード)を検査したり、HTTPのURLパターンでフィルタリングしたりすることはできません。「マルウェアが443番ポートのHTTPS通信に乗ってきた」ような場合は、NACLとSGでは防げません。ペイロード検査まで必要な場合はAWS Network FirewallやWAFを組み合わせることになりますが、多くの設計ではNACL+SGの2層で十分です。
VPCが複数のAWSアカウントにまたがって増えてきた段階では、各VPCにNACLとSGを個別設定するだけでは管理しきれなくなるケースがあります。そのような環境でネットワーク層のセキュリティを集中管理するのがAWS Network Firewallと、後述する検査VPC(Inspection VPC)パターンです。
4つのセキュリティレイヤーを比較すると、以下のようになります。
| レイヤー | 適用単位 | 状態管理 | フィルタ対象 | ドメインフィルタ |
|---|---|---|---|---|
| セキュリティグループ | インスタンス(ENI) | ステートフル | IP/ポート(L4) | 不可 |
| ネットワークACL | サブネット境界 | ステートレス | IP/ポート(L4) | 不可 |
| AWS WAF | CloudFront/ALB配下のHTTP(S) | ステートレス | L7(Webアプリ層) | 可能(URLパターン・ヘッダー) |
| AWS Network Firewall | VPC全体のトラフィック | ステートレス+ステートフルの2段 | L3~L7(SNI・シグネチャ) | 可能 |
セキュリティグループ(SG)の設計──ステートフルの意味を理解する
セキュリティグループの最大の特徴は「ステートフル」であることです。インバウンドルールで許可したトラフィックに対する戻りのパケットは、アウトバウンドルールを設定していなくても自動的に許可されます。1. インバウンドルールの設計
SGのインバウンドルールは「このポートへの通信を誰から許可するか」を定義します。まずセキュリティグループを作成してから、許可ルールを追加します。# セキュリティグループを作成する $ aws ec2 create-security-group --group-name web-sg --description "Web server security group" --vpc-id vpc-0123456789abcdef0 # 実行結果(GroupIdを控えておく) { "GroupId": "sg-0123456789abcdef0" } # HTTPS(443)をインターネット全体から許可 $ aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef0 --protocol tcp --port 443 --cidr 0.0.0.0/0 # SSH(22)を社内ネットワーク(プライベートアドレス)からのみ許可 $ aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef0 --protocol tcp --port 22 --cidr 10.0.0.0/8 # インバウンドルールを確認する $ aws ec2 describe-security-groups --group-ids sg-0123456789abcdef0 --query 'SecurityGroups[*].IpPermissions' [ { "FromPort": 443, "IpProtocol": "tcp", "IpRanges": [ { "CidrIp": "0.0.0.0/0", "Description": "HTTPS from anywhere" } ], "ToPort": 443 }, { "FromPort": 22, "IpProtocol": "tcp", "IpRanges": [ { "CidrIp": "10.0.0.0/8", "Description": "SSH from private network only" } ], "ToPort": 22 } ]
2. アウトバウンドルールとSGのチェーン設計
SGのアウトバウンドルールのデフォルトは「全て許可(0.0.0.0/0)」です。セキュリティを強化する場合は、アウトバウンドを「必要な宛先のみ許可」に絞ります。SGの強力な機能のひとつが「SGを参照したルール(SGチェーン)」です。CIDRではなく別のSG IDを参照して許可を設定できます。
# DBサーバーのSGに「AppサーバーのSGからの接続のみ許可」を設定する例 $ aws ec2 authorize-security-group-ingress --group-id sg-db-xxxxxxxx --protocol tcp --port 3306 --source-group sg-app-xxxxxxxx # SGチェーンの確認 $ aws ec2 describe-security-groups --group-ids sg-db-xxxxxxxx --query 'SecurityGroups[*].IpPermissions[*].UserIdGroupPairs' [ [ { "GroupId": "sg-app-xxxxxxxx", "UserId": "123456789012" } ] ]
SGをCIDRではなくSG IDで参照する設計にすることで、インスタンスのIP変更やオートスケーリングによる台数変動が起きても、ルール変更なしで通信制御が維持されます。DBサブネットへの通信をCIDRで許可してしまうと、Appサブネット内の全インスタンスが接続可能になりますが、SGチェーンならAppサーバーSGに属するインスタンスだけに絞れます。
3. セキュリティグループのルールを確認する
本番環境では定期的なSGルール棚卸しが重要です。特に0.0.0.0/0(全公開)になっているインバウンドルールが意図したものかを確認します。# SGのルール一覧を確認する(名前とインバウンドルールを表示) $ aws ec2 describe-security-groups --group-ids sg-0123456789abcdef0 --query 'SecurityGroups[*].{Name:GroupName,Inbound:IpPermissions}' --output table # EC2インスタンスに紐づくSGを確認する $ aws ec2 describe-instances --instance-ids i-0123456789abcdef0 --query 'Reservations[*].Instances[*].SecurityGroups' [ [ { "GroupName": "web-sg", "GroupId": "sg-0123456789abcdef0" } ] ]
0.0.0.0/0 で開いているルールが意図したものか、四半期ごとに確認する習慣をつけましょう。Network ACL(NACL)の設計──ステートレスの落とし穴を理解する
NACLはSGと大きく異なる「ステートレス」な動作をします。ステートレスとは「各パケットを独立して評価する」ということです。インバウンドでHTTPSを許可しても、それに対する応答(アウトバウンド)は別途許可が必要です。1. ルール番号と評価順序
NACLのルールは「番号の小さい順に評価」され、最初にマッチしたルールで通過・拒否が確定します。まずカスタムNACLを作成し、ルールを追加します。# カスタムNACLを作成する $ aws ec2 create-network-acl --vpc-id vpc-0123456789abcdef0 # 実行結果(NetworkAclIdを控えておく) { "NetworkAcl": { "NetworkAclId": "acl-0123456789abcdef0" } } # NACLのルール一覧を確認する $ aws ec2 describe-network-acls --network-acl-ids acl-0123456789abcdef0 --query 'NetworkAcls[*].Entries[*].[RuleNumber,RuleAction,Protocol,PortRange,CidrBlock,Egress]' --output table ------------------------------------------------------------------- | Rule# | Action | Protocol | PortRange | CidrBlock | Egress | |-------|--------|----------|-----------|-----------|------------| | 100 | allow | 6(TCP) | 443-443 | 0.0.0.0/0 | False(IN) | | 32767 | deny | -1(ALL) | - | 0.0.0.0/0 | False(IN) | | 32767 | deny | -1(ALL) | - | 0.0.0.0/0 | True(OUT) | ------------------------------------------------------------------- # アウトバウンドルールが暗黙DENYのみ → レスポンスが返せない
NACLはSGと異なり「DENYルール」を明示的に書けます。これを活かして特定のIPアドレスやCIDRを先行してブロックする使い方が有効です。ボット攻撃が多い送信元CIDRを低い番号のDENYルールで弾くことで、SGに到達する前に不正トラフィックを遮断できます。たとえばルール番号10でDENYを入れておけば、以降のALLOWルール(100番以降)に到達させずに即座に破棄できます。
2. エフェメラルポートを必ず許可する
NACLを使う際に最も多いトラブルが「エフェメラルポート(一時ポート)の未許可」です。クライアントがWebサーバーに接続するとき、クライアントは送信元ポートとして1024~65535番のランダムなポート(エフェメラルポート)を使います。NACLのアウトバウンドルールでこの範囲を許可していないと、WebサーバーからクライアントへのHTTPSレスポンスが届きません。
# エフェメラルポートの範囲をOS別に確認する # Amazon Linux 2023 / RHEL 9.4 $ cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999 # NACLのアウトバウンドルールに追加する(安全側で1024-65535を許可) $ aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 --no-ingress --rule-number 100 --protocol tcp --rule-action allow --cidr-block 0.0.0.0/0 --port-range From=1024,To=65535
3. サブネット単位で適用する
NACLはサブネットに紐づきます。1つのNACLを複数のサブネットに関連付けることも、サブネットごとに独立したNACLを作ることも可能です。# サブネットとNACLの関連付けを確認する $ aws ec2 describe-subnets --subnet-ids subnet-0123456789abcdef0 --query 'Subnets[*].[SubnetId,Tags[?Key==`Name`].Value|[0]]' [ [ "subnet-0123456789abcdef0", "public-subnet-1a" ] ] # そのサブネットに紐づくNACLを確認する $ aws ec2 describe-network-acls --filters "Name=association.subnet-id,Values=subnet-0123456789abcdef0" --query 'NetworkAcls[*].NetworkAclId' [ "acl-0123456789abcdef0" ] # 既存サブネットのNACLを新しいカスタムNACLに切り替える # (association-idはdescribe-network-aclsのAssociationsから取得する) $ aws ec2 replace-network-acl-association --association-id aclassoc-0123456789abcdef0 --network-acl-id acl-0new456789abcdef0
replace-network-acl-association で既存の関連付けを切り替えます。NACLとSGを組み合わせた2層防御パターン
NACLとSGをどう使い分けるか──実際の3層構成(パブリック・アプリ・DBサブネット)で設計します。1. 3層サブネット構成でのNACL設計方針
| サブネット | NACLの役割 | SGの役割 |
|---|---|---|
| パブリック(ALB配置) | インターネットからHTTPS(443)のみ許可、それ以外はDENY | ALBからEC2のHTTP 8080のみ許可(SGチェーン) |
| プライベート(Appサーバー) | パブリックサブネットCIDRからの通信のみ許可 | AppサーバーSGからDBへのMySQL 3306のみ許可 |
| プライベート(DB) | Appサブネット以外からの全通信をDENY | AppサーバーSGからの3306のみ許可(SGチェーン) |
たとえばDB層のSGが誤って
0.0.0.0/0 から3306番を許可してしまっても、NACLが「Appサブネットからのみ許可」と設定されていれば、外部からの直接アクセスはサブネット境界でブロックされます。一方が誤設定されても、もう一方で防御できる多層防御(Defense in Depth)が完成します。2. マルチAZ構成でのNACL設計ポイント
マルチAZ構成では、AZごとにサブネットが存在します(例: ap-northeast-1aのパブリックサブネットとap-northeast-1cのパブリックサブネット)。NACLはサブネット単位のため、AZの数だけサブネットがあれば、それぞれに同じNACLを関連付けるか、同一のNACLを複数サブネットで共有するかを選べます。実践的には「同じ役割のサブネット(例: 全AZのパブリックサブネット)に同じカスタムNACLを関連付ける」設計が管理しやすく、AZ間のルール不一致による設計ミスを防ぎます。
# 同じカスタムNACLを複数サブネットに関連付ける例 # ap-northeast-1a のパブリックサブネット $ aws ec2 replace-network-acl-association --association-id aclassoc-1a-xxxxxxxxx --network-acl-id acl-public-xxxxxxxxx # ap-northeast-1c のパブリックサブネット(同じNACL IDを指定) $ aws ec2 replace-network-acl-association --association-id aclassoc-1c-xxxxxxxxx --network-acl-id acl-public-xxxxxxxxx # 1つのNACLに関連付けられているサブネット一覧を確認する $ aws ec2 describe-network-acls --network-acl-ids acl-public-xxxxxxxxx --query 'NetworkAcls[*].Associations[*].[SubnetId,NetworkAclAssociationId]' --output table
3. NACL設計で実際に使うルールセット例
パブリックサブネット(ALB用)のNACL設定例です。# パブリックサブネットNACL(インバウンド) Rule 100: ALLOW TCP 443 from 0.0.0.0/0 # HTTPS(インターネット→ALB) Rule 200: ALLOW TCP 80 from 0.0.0.0/0 # HTTP(ALBでHTTPSへリダイレクト) Rule 32767: DENY ALL # 暗黙のDENY # パブリックサブネットNACL(アウトバウンド) Rule 100: ALLOW TCP 1024-65535 to 0.0.0.0/0 # エフェメラルポート(レスポンス) Rule 200: ALLOW ALL to 10.0.0.0/8 # VPC内部通信 Rule 32767: DENY ALL # 暗黙のDENY # DBサブネットNACL(インバウンド) Rule 100: ALLOW TCP 3306 from 10.0.2.0/24 # Appサブネット(10.0.2.0/24)のみ Rule 32767: DENY ALL # 他は全てDENY # DBサブネットNACL(アウトバウンド) Rule 100: ALLOW TCP 1024-65535 to 10.0.2.0/24 # エフェメラルポートでの応答 Rule 32767: DENY ALL
4. NACLとSGだけで対応できないケースとNetwork Firewallの導入
NACLとSGは送信元IPとポート番号を基準にしたL4フィルタリングが得意ですが、「通信の中身(ペイロード)」は検査できません。以下のような要件が加わる場合は、追加のセキュリティサービスを検討します。・HTTPのURLパターンやヘッダーで制御したい:ALBやCloudFrontと組み合わせてAWS WAFを使う
・マルウェアのシグネチャや不審なドメインへの通信を検知・遮断したい:VPC内にAWS Network Firewallを配置してDeep Packet Inspection(DPI)を有効化する
・PCI DSS・SOC 2などコンプライアンス要件でIPSが必要:AWS Network FirewallのSuricata互換ルールで対応する
Network Firewallの構築手順(CLIステップ)
Network Firewallは「ルールグループ」「ファイアウォールポリシー」「ファイアウォール本体」の3層構造で設定します。作成順序を誤るとポリシーへのアタッチが失敗するため、以下の順番どおりに進めてください。
① ステートレスルールグループの作成
ステートレスルールはパケット単位で高速に処理されます。アクションは3種類あります。
・aws:pass:該当パケットをそのまま通過させる(ステートフルルールをバイパスする)
・aws:drop:パケットを破棄する
・aws:forward_to_sfe:ステートフルルールエンジンへ転送する(最も一般的な設定)
大量の既知安全トラフィックを
aws:pass で先にバイパスさせ、残りをステートフルルールに渡すことでパフォーマンスを最適化できます。以下は、ICMPを aws:pass でバイパスし、それ以外を全てステートフルエンジンへ転送するステートレスルールグループの例です。# ステートレスルールグループ: ICMPをpassし残りをステートフルへ転送 aws network-firewall create-rule-group --rule-group-name stateless-icmp-pass --type STATELESS --capacity 10 --rule-group '{ "RulesSource": { "StatelessRulesAndCustomActions": { "StatelessRules": [ { "RuleDefinition": { "MatchAttributes": { "Protocols": [1] }, "Actions": ["aws:pass"] }, "Priority": 1 } ] } } }' --region ap-northeast-1 # 実行結果(RuleGroupArnを控えておく) { "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:stateless-rulegroup/stateless-icmp-pass", "RuleGroupName": "stateless-icmp-pass", "RuleGroupStatus": "ACTIVE", "Type": "STATELESS" } }
ステートフルルールグループはコネクション単位で検査します。記述形式は3種類から選べます。
・5タプルルール:送信元IP・宛先IP・プロトコル・送信元ポート・宛先ポートの組み合わせ制御。コンソールから直感的に設定できる
・ドメインリスト:許可・拒否するドメイン名を列挙する(TLS SNIとHTTP Hostヘッダを検査)。Suricata構文不要で手軽
・Suricata互換ルール:Suricataのシグネチャ形式で詳細な検査ルールを記述する。HTTPメソッド・URIパス・ペイロードのパターンマッチなど高度な条件を設定できる
以下はドメインリスト型のALLOWLISTとDENYLISTの例です。TLS SNI(Server Name Indication)とHTTP Hostヘッダを参照してドメインレベルでフィルタリングします。
# ドメインリスト型ALLOWLIST(.amazonaws.com / amazon.com のみHTTPS/HTTP通信を許可) cat > /tmp/stateful-rules.json << 'EOF' { "StatefulRuleOptions": { "RuleOrder": "DEFAULT_ACTION_ORDER" }, "RulesSource": { "RulesSourceList": { "Targets": [ ".amazonaws.com", "amazon.com" ], "TargetTypes": ["TLS_SNI", "HTTP_HOST"], "GeneratedRulesType": "ALLOWLIST" } } } EOF aws network-firewall create-rule-group --rule-group-name domain-allowlist --type STATEFUL --capacity 100 --rule-group file:///tmp/stateful-rules.json --region ap-northeast-1 # ドメインリスト型DENYLIST(悪意あるドメインをブロック) aws network-firewall create-rule-group --rule-group-name block-malicious-domains --type STATEFUL --capacity 100 --rule-group '{ "RulesSource": { "RulesSourceList": { "Targets": [ "malware.example.com", ".evil-domain.net" ], "TargetTypes": ["TLS_SNI", "HTTP_HOST"], "GeneratedRulesType": "DENYLIST" } } }' --region ap-northeast-1
TLS_SNI はHTTPS通信のSNIフィールドからドメイン名を取り出して評価します。HTTP_HOST はHTTP通信のHostヘッダを評価します。本番ではHTTPS通信が主流ですが、両方指定しておくのが確実です。ALLOWLISTは許可するドメイン以外を全てブロック、DENYLISTは指定したドメインのみブロック(他は通過)という動作をします。Suricata互換ルールでより細かい制御をする場合は、以下のような形式でドメインをブロックできます。
# HTTPSのSNIでドメインをブロック(TLSのClientHelloを検査) drop tls any any -> any any ( tls.sni; content:"malware-c2.example"; nocase; msg:"Block C2 domain over HTTPS"; sid:100001; rev:1; ) # HTTPのHostヘッダでドメインをブロック drop http any any -> any any ( http.host; content:"malware-c2.example"; nocase; msg:"Block C2 domain over HTTP"; sid:100002; rev:1; ) # SSHブルートフォース検知(5秒以内に10パケット) drop tcp any any -> any 22 ( msg:"ET SCAN SSH Brute Force Attempt"; flow:to_server; threshold: type both, track by_src, count 10, seconds 5; classtype:attempted-admin; sid:2001219; rev:4; )
ポリシーではステートレスのデフォルトアクションを「ステートフルエンジンへ転送」、ステートフルのデフォルトアクションを「DROPして明示許可以外を全遮断」に設定します。本番環境では最初は
aws:alert_established(通過しつつログに記録)で動作確認してからDROPへ移行することを推奨します。# ファイアウォールポリシー定義JSONを作成 cat > /tmp/firewall-policy.json << 'EOF' { "StatelessDefaultActions": ["aws:forward_to_sfe"], "StatelessFragmentDefaultActions": ["aws:forward_to_sfe"], "StatelessRuleGroupReferences": [ { "ResourceArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:stateless-rulegroup/stateless-icmp-pass", "Priority": 1 } ], "StatefulRuleGroupReferences": [ { "ResourceArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:stateful-rulegroup/domain-allowlist", "Priority": 1 } ], "StatefulDefaultActions": ["aws:drop_established"], "StatefulEngineOptions": { "RuleOrder": "STRICT_ORDER" } } EOF # ファイアウォールポリシーを作成 aws network-firewall create-firewall-policy --firewall-policy-name vpc-egress-policy --firewall-policy file:///tmp/firewall-policy.json --region ap-northeast-1
StatefulEngineOptions.RuleOrder には STRICT_ORDER を指定することを推奨します。デフォルトの DEFAULT_ACTION_ORDER だとルールグループの評価順が保証されず、複数のルールグループを組み合わせた際に意図しない通過や遮断が発生することがあります。STRICT_ORDER にするとルールグループのPriority順(数値の小さいほうが先)で評価が確定します。作成後に新たなルールグループを追加するには
update-firewall-policy コマンドを使います。--update-token には describe-firewall-policy で取得したUpdateTokenを指定してください。# 現在のUpdateTokenを取得する aws network-firewall describe-firewall-policy --firewall-policy-name vpc-egress-policy --query "UpdateToken" --output text --region ap-northeast-1 # ルールグループを追加してポリシーを更新する aws network-firewall update-firewall-policy --update-token "取得したUpdateToken" --firewall-policy-name vpc-egress-policy --firewall-policy '{ "StatelessDefaultActions": ["aws:forward_to_sfe"], "StatelessFragmentDefaultActions": ["aws:forward_to_sfe"], "StatefulDefaultActions": ["aws:drop_established"], "StatefulEngineOptions": {"RuleOrder": "STRICT_ORDER"}, "StatefulRuleGroupReferences": [ {"ResourceArn": "arn:aws:...:stateful-rulegroup/domain-allowlist", "Priority": 1}, {"ResourceArn": "arn:aws:...:stateful-rulegroup/block-malicious-domains", "Priority": 2} ] }' --region ap-northeast-1
Network Firewallのエンドポイントを配置するためのサブネット(ファイアウォールサブネット)を既存サブネットとは別に用意します。Network Firewallはサブネット内のIPアドレスを複数予約するため、CIDRは /28 以上(16アドレス)を確保してください。/28 未満のCIDRを指定するとサブネット作成後にFirewall作成が失敗します。マルチAZ構成ではAZごとに1サブネット必要です。
# ファイアウォールサブネットを作成(例: ap-northeast-1a に /28) aws ec2 create-subnet --vpc-id vpc-0xxxxxxxxxxxxxxxxx --cidr-block 10.0.10.0/28 --availability-zone ap-northeast-1a --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=firewall-subnet-1a}]' --region ap-northeast-1 { "Subnet": { "SubnetId": "subnet-0firewall1axxxxxxx", "VpcId": "vpc-0xxxxxxxxxxxxxxxxx", "CidrBlock": "10.0.10.0/28", "AvailabilityZone": "ap-northeast-1a" } } # ファイアウォール本体を作成(マルチAZ: ap-northeast-1a / ap-northeast-1c) aws network-firewall create-firewall --firewall-name vpc-egress-firewall --firewall-policy-arn "arn:aws:network-firewall:ap-northeast-1:123456789012:firewall-policy/vpc-egress-policy" --vpc-id vpc-0xxxxxxxxxxxxxxxxx --subnet-mappings SubnetId=subnet-0firewall1axxxxxxx SubnetId=subnet-0firewall1cxxxxxxx --region ap-northeast-1 # ステータス確認(PROVISIONINGからREADYになるまで5~10分かかる) aws network-firewall describe-firewall --firewall-name vpc-egress-firewall --query "FirewallStatus.Status" --output text --region ap-northeast-1 # READY になると各AZのEndpointId(vpce-xxx)が取得できる aws network-firewall describe-firewall-status --firewall-name vpc-egress-firewall --query "FirewallStatus.SyncStates" --output yaml --region ap-northeast-1 # 出力例 # ap-northeast-1a: # Attachment: # EndpointId: vpce-0abc1234def56789a # Status: READY # ap-northeast-1c: # Attachment: # EndpointId: vpce-0f1e2d3c4b5a67890 # Status: READY # "Status": "READY" と EndpointId を確認したらルートテーブル変更に進む
Firewall本体を作成しただけではトラフィックはFirewallを通過しません。ルートテーブルを変更して、EC2のEgress通信がFirewallエンドポイントを経由するように誘導します。設定が必要なルートテーブルは2種類です。
・プライベートサブネットのルートテーブル:0.0.0.0/0 の次のホップを Firewallエンドポイント(vpce-xxx)に変更する
・Firewallサブネットのルートテーブル:0.0.0.0/0 の次のホップを NAT Gateway に設定する
# プライベートサブネット(AZ: ap-northeast-1a)のルートテーブルを更新 # Egress通信をFirewallエンドポイント(vpce-0abc1234def56789a)経由に変更 aws ec2 create-route --route-table-id rtb-0private1a --destination-cidr-block 0.0.0.0/0 --vpc-endpoint-id vpce-0abc1234def56789a --region ap-northeast-1 # Firewallサブネット(AZ: ap-northeast-1a)のルートテーブルを設定 # 検査後のEgressをNAT Gatewayへ転送 aws ec2 create-route --route-table-id rtb-0firewall1a --destination-cidr-block 0.0.0.0/0 --nat-gateway-id nat-0abc1234def56789a --region ap-northeast-1 # ルートテーブルを確認する(Firewallエンドポイントが向き先になっているか) aws ec2 describe-route-tables --route-table-ids rtb-0private1a --query "RouteTables[0].Routes" --output table --region ap-northeast-1 # 出力例(抜粋) # DestinationCidrBlock GatewayId State # 10.0.0.0/16 local active # 0.0.0.0/0 vpce-0abc1234def56789a active
aws:drop_established によってドロップされます。Network Firewallのサブネット設計とAZ構成
Network Firewallを有効にすると、各AZに「ファイアウォールエンドポイント」が生成されます。このエンドポイントはGateway Load Balancerエンドポイントとして動作し、VPCのルートテーブルでここへトラフィックを向けることで強制的に検査を通過させます。
マルチAZ構成(ap-northeast-1a / ap-northeast-1c)で最も重要な設計上の注意点が対称ルーティングです。Network Firewallはフローレベルでパケットを追跡するため、EC2からの送信パケットがAZ-1aのFirewallエンドポイントを通過し、戻りパケットがAZ-1cのFirewallエンドポイントを通過すると、フローが途切れたと判断してドロップしてしまいます。AZをまたいでトラフィックをFirewallエンドポイントに通過させることはできないため、AZごとにエンドポイントを配置し、ルートテーブルもAZ単位で分ける設計が必須です。
AZ構成例(ap-northeast-1での設計):
| サブネット種別 | AZ | CIDR |
|---|---|---|
| ファイアウォールサブネット-1a | ap-northeast-1a | 10.0.0.0/28 |
| ファイアウォールサブネット-1c | ap-northeast-1c | 10.0.0.16/28 |
| パブリックサブネット-1a | ap-northeast-1a | 10.0.1.0/24 |
| パブリックサブネット-1c | ap-northeast-1c | 10.0.2.0/24 |
# ① IGWルートテーブル(Ingress Routing用) # インバウンドをAZ対応のファイアウォールエンドポイントへ向ける 10.0.1.0/24 → vpce-xxxxxxxx-1a # 1a パブリックサブネット宛はエンドポイント-1aへ 10.0.2.0/24 → vpce-xxxxxxxx-1c # 1c パブリックサブネット宛はエンドポイント-1cへ # ② ファイアウォールサブネットのルートテーブル # 検査後トラフィックをIGWへ送る 0.0.0.0/0 → igw-xxxxxxxx # ③ パブリックサブネットのルートテーブル(変更前→変更後) # 変更前: 0.0.0.0/0 → igw-xxxxxxxx ← これをエンドポイントに付け替える # 変更後: アウトバウンドを同AZのエンドポイントへ向ける 0.0.0.0/0 → vpce-xxxxxxxx-1a # 同AZのファイアウォールエンドポイントIDを指定 # 注意:AZ対応を必ず守ること # 1aサブネットのトラフィックを1cエンドポイントへ向けると # 非対称ルーティングが発生して通信が切断される
VPCが複数のAWSアカウントにまたがって増えてきた段階では、各VPCにNetwork Firewallを個別に配置するのは管理コストが高くなります。そのような環境で有効なのが検査VPC(Inspection VPC)パターンです。専用の「検査専用VPC」を1つ作り、すべてのVPC間通信・インターネット向け通信をTransit Gatewayを介してその1箇所に集中させる設計です。
基本構成は以下の4種類のVPCで設計します。
| VPC名 | 役割 | CIDRの例 |
|---|---|---|
| Inspection VPC | Network Firewallを配置する検査専用VPC | 100.64.0.0/16 |
| Egress VPC | NAT Gateway経由でインターネットに接続するVPC | 100.65.0.0/16 |
| Spoke VPC A | アプリケーションサーバー等のワークロードVPC | 10.1.0.0/16 |
| Spoke VPC B | 別のワークロードVPC(East-West通信の相手方) | 10.2.0.0/16 |
TGWのルートテーブルは3種類に分離して設計します。
・Spoke Attachment RT(Spoke VPC側が参照):0.0.0.0/0をInspection VPC Attachmentへ向ける。他SpokeのCIDRも同様にInspection Attachmentへ
・Inspection Attachment RT(Inspection VPC側が参照):Spoke VPC A/BのCIDRをそれぞれのSpoke Attachmentへ向ける。Egress VPCへの0.0.0.0/0ルートも持つ
・Egress Attachment RT(Egress VPC側が参照):SpokeのCIDR(10.0.0.0/8など)をInspection VPC Attachmentへ向ける
この設計により「Spoke→Internet」と「Spoke間(East-West)」のどちらの通信もInspection VPCのNetwork Firewallを必ず経由します。設計後は
aws ec2 describe-transit-gateway-route-tables で各ルートテーブルの内容を実際に確認してください。ルートが抜けていると検査をバイパスした通信が発生します。CloudWatch Logsへのフローログ・アラートログ・TLSログ設定
Network Firewallのログは3種類あります。
・Alertログ:ルールにマッチした(dropまたはalert)パケットの記録。侵害調査の主要ソース
・Flowログ:Firewallを通過したすべてのフローを記録(VPC Flow Logsの代替として利用可能)
・TLSログ:TLS接続のSNI・証明書情報を記録
初期運用ではAlertログとFlowログをCloudWatch Logsへ設定して動作確認します。
# CloudWatch LogsのロググループをCLIで作成 aws logs create-log-group --log-group-name /aws/network-firewall/flow --region ap-northeast-1 aws logs create-log-group --log-group-name /aws/network-firewall/alert --region ap-northeast-1 # フローログとアラートログを両方CloudWatch Logsへ設定 aws network-firewall update-logging-configuration --firewall-name vpc-egress-firewall --logging-configuration '{ "LogDestinationConfigs": [ { "LogType": "FLOW", "LogDestinationType": "CloudWatchLogs", "LogDestination": { "logGroup": "/aws/network-firewall/flow" } }, { "LogType": "ALERT", "LogDestinationType": "CloudWatchLogs", "LogDestination": { "logGroup": "/aws/network-firewall/alert" } } ] }' --region ap-northeast-1 # アラートログからブロックされたトラフィックを絞り込んで確認する aws logs filter-log-events --log-group-name /aws/network-firewall/alert --filter-pattern '"blocked"' --start-time $(date -d "10 minutes ago" +%s000) --limit 10 --region ap-northeast-1 --query "events[].message" --output text | python3 -m json.tool # 設定後、許可外ドメインへの通信テスト(タイムアウトになればブロック成功) curl -m 5 https://example.com
LogDestinationType を S3 に変更し、LogDestination に {"bucketName":"バケット名"} を指定するだけで切り替えられます。実際のアラートログ出力例です。
"action": "blocked" の行がルールでブロックされたトラフィックです。SNIフィールドにブロックされたドメイン名が記録されます。{ "firewall_name": "vpc-egress-firewall", "availability_zone": "ap-northeast-1a", "event": { "src_ip": "10.0.1.100", "src_port": 54321, "dest_ip": "93.184.216.34", "dest_port": 443, "proto": "TCP", "tls": { "sni": "example.com" }, "alert": { "action": "blocked", "signature": "not matching any TLS allowlisted FQDNs" } } }
ステートフルポリシーのデフォルトアクションは最初「ALERT_ALL(通過しつつログに記録)」で本番運用の動作確認を行い、問題がないことを確認してから「DROP_ESTABLISHED(明示的に許可していないコネクションをブロック)」へ移行することを推奨します。本番環境で最初からDROP_ESTABLISHEDを設定すると、許可漏れのルールで正常なサービス通信が遮断されるリスクがあります。
ドメインリスト型ルールはTLS SNIが可視な段階でのみ機能します。クライアントがTLS 1.3のEncrypted Client Hello(ECH)を有効にしている場合はSNIが暗号化されてドメイン名を読み取れないため、IP/ポートベースのルールを補完として組み合わせてください。より確実にブロックしたい場合はRoute 53 Resolver DNS Firewallとの併用が有効です。DNS FirewallはDNS名前解決の段階でドメインをブロックするため、SNI暗号化の影響を受けません。
Network Firewallの費用はFirewallエンドポイント課金(1エンドポイントあたり0.395 USD/時間)とデータ処理課金(0.065 USD/GB程度)が発生します。マルチAZ構成ではAZ数分のエンドポイント費用がかかるため、本番導入前にトラフィック量から試算しておくことを推奨します。
多くの設計ではNACL+SGで十分です。要件定義の段階で「何を検査したいか」を明確にすることが、過剰なサービス導入を防ぐコツです。
「SGで許可したのに繋がらない」エラーの切り分け手順
「SGの設定は正しいはずなのに繋がらない」という状況は、多くの場合NACLの設定が原因です。以下の4ステップで順番に切り分けてください。・ステップ1 SGのインバウンドルール:対象ポートと送信元CIDRが正しく設定されているか
・ステップ2 NACLの設定:SGが正しくても、サブネットのNACLでブロックされていないか(エフェメラルポートのアウトバウンド許可を忘れていないか)
・ステップ3 ルートテーブル:インターネットゲートウェイ(IGW)やNAT Gatewayへのルートが存在するか
・ステップ4 OS内のファイアウォール:EC2内部のiptablesやfirewalldが該当ポートをブロックしていないか
1. NACLのルールを確認する
# 問題のサブネットに紐づくNACLを確認する $ aws ec2 describe-network-acls --filters "Name=association.subnet-id,Values=subnet-0123456789abcdef0" --query 'NetworkAcls[*].Entries[?Egress==`false`].[RuleNumber,RuleAction,Protocol,PortRange.From,PortRange.To,CidrBlock]' --output table # インバウンドの許可ルールにアクセスしたいポートが含まれているか確認 # アウトバウンドのエフェメラルポート(1024-65535)許可があるか確認 $ aws ec2 describe-network-acls --filters "Name=association.subnet-id,Values=subnet-0123456789abcdef0" --query 'NetworkAcls[*].Entries[?Egress==`true`].[RuleNumber,RuleAction,PortRange.From,PortRange.To]' --output table | Rule# | Action | PortFrom | PortTo | |-------|--------|----------|--------| | 32767 | deny | None | None | # アウトバウンドが暗黙DENYのみ → エフェメラルポート許可を追加する
2. エフェメラルポート(1024~65535)の許可を追加する
# アウトバウンドにエフェメラルポート許可ルールを追加する $ aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 --no-ingress --rule-number 100 --protocol tcp --rule-action allow --cidr-block 0.0.0.0/0 --port-range From=1024,To=65535 # 追加後の確認 $ aws ec2 describe-network-acls --network-acl-ids acl-0123456789abcdef0 --query 'NetworkAcls[*].Entries[?Egress==`true`].[RuleNumber,RuleAction,PortRange.From,PortRange.To]' --output table | Rule# | Action | PortFrom | PortTo | |-------|--------|----------|--------| | 100 | allow | 1024 | 65535 | | 32767 | deny | None | None | # ルール100でエフェメラルポートが許可された
3. VPC Flow LogsでパケットのACCEPT/REJECTを確認する
NACLとSGの両方を確認しても原因が不明な場合は、VPC Flow Logsでパケットレベルの記録を確認します。Flow LogsはENIを通過するトラフィックのACCEPTとREJECTを記録するため、どのレイヤーで弾かれているかを特定する手がかりになります。# VPC Flow Logsを有効化する(CloudWatch Logsへ出力) $ aws ec2 create-flow-logs --resource-type VPC --resource-ids vpc-0123456789abcdef0 --traffic-type ALL --log-destination-type cloud-watch-logs --log-group-name /aws/vpc/flow-logs --deliver-logs-permission-arn arn:aws:iam::123456789012:role/FlowLogsRole # Flow Logsのフォーマット(主なフィールド) # version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status # action が REJECT の行がNACLまたはSGで弾かれたトラフィック # CloudWatch Logs Insightsでリアルタイムに絞り込む例 # フィールド: @timestamp, srcAddr, dstAddr, dstPort, action # | filter action = "REJECT" # | sort @timestamp desc # | limit 20
4. LinuxのポートをSSHログイン後に確認する
NACLとSGの設定を修正しても繋がらない場合、Linux側でプロセスが正常にポートをリッスンしているかを確認します。EC2インスタンスにSSHでログイン後、以下を実行してください。# Linuxで待ち受けているポートを確認する(Amazon Linux 2023 / RHEL 9.4) $ ss -tlnp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1234,fd=6)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=888,fd=3)) # 443番ポートでNginxがリッスンしていることを確認 # ポートがリッスンしていない場合はアプリ側の問題(AWSのFW設定ではない) $ systemctl status nginx Active: active (running) since Thu 2026-07-25 10:00:00 UTC; 1h ago
5. Network Firewall導入後にトラフィックが届かない場合
Network Firewallを導入した直後に「EC2への通信が届かない」という問題が発生することがあります。原因は主に3パターンに分かれます。症状1:許可したドメインへの通信もタイムアウトになる
ルートテーブルの設定ミスが最多原因です。以下を確認してください。
・プライベートサブネットのルートテーブルで 0.0.0.0/0 がファイアウォールエンドポイント(vpce-xxx)を向いているか
・ファイアウォールサブネット自体のルートテーブルで 0.0.0.0/0 がIGWを向いているか(ファイアウォールサブネット自身もインターネットへ出る経路が必要)
・ファイアウォールのStatusが READY になっているか
# ファイアウォールのステータスを確認 aws network-firewall describe-firewall --firewall-name vpc-egress-firewall --query "FirewallStatus" --region ap-northeast-1
ステートフルルールの
StatefulDefaultActions が aws:drop_established になっているかを確認します。デフォルトが aws:alert_established のままの場合、ログは記録されますが通信は素通りします。# ファイアウォールポリシーのStatefulDefaultActionsを確認 aws network-firewall describe-firewall-policy --firewall-policy-name vpc-egress-policy --query "FirewallPolicy.StatefulDefaultActions" --region ap-northeast-1
AZ-aのサブネットのトラフィックがAZ-cのエンドポイントへ向いていると非対称ルーティングが発生して通信が切断されます。AZとエンドポイントIDの対応を必ず確認してください。
# パブリックサブネット-1aのルートテーブルで0.0.0.0/0の向き先を確認する $ aws ec2 describe-route-tables --filters Name=tag:Name,Values=public-rt-1a --query "RouteTables[].Routes[?DestinationCidrBlock=='0.0.0.0/0']" # IGWルートテーブルで1aパブリックサブネット宛のエンドポイントIDを確認する $ aws ec2 describe-route-tables --filters Name=tag:Name,Values=igw-rt --query "RouteTables[].Routes[?DestinationCidrBlock=='10.0.1.0/24']" # エンドポイントIDが同AZのものになっているか照合する # 「vpce-xxxxxxxxxx-1a」が1aサブネット宛に対応しているか目視確認
describe-firewall で全AZのステータスを確認します。PROVISIONINGからREADYになるまで5~10分かかることがあります。VPCのサービスクォータでエンドポイント数が上限に達していないかも確認してください。# 全AZのステータスを確認 aws network-firewall describe-firewall --firewall-name vpc-egress-firewall --query "FirewallStatus.SyncStates[*].Attachment.Status" --region ap-northeast-1
"action"フィールドが「drop」になっている通信があれば、対応するステートフルルールに許可設定が抜けています。6. よくある見落としパターン
SG・NACLの設定ミスで「繋がらない」「なぜか遮断される」が起きやすいケースをまとめます。・NACLのアウトバウンドにエフェメラルポートを設定していない:HTTPレスポンスが戻らず、SGは正しいのにタイムアウトになる
・NACLのルール番号の順序ミス:早い番号のALLOWより先に小さい番号のDENYが評価されることを忘れている(番号が小さいほど優先)
・カスタムNACLをサブネットに関連付けるのを忘れた:作成しただけでは適用されず、デフォルトNACL(全許可)のまま動いている
・SGのアウトバウンドを誤って削除した:デフォルトは全許可だが、削除後に必要なルールが存在しない状態になる
・マルチAZ構成でNACLルールをAZごとに異なるNACLに入れた:片方のAZだけルールが抜けていて、特定AZの通信だけ遮断される
・NACLのDENYルールで正常なトラフィックを誤ってブロックした:SGは全許可なのにNACLの低番号のDENYで弾かれている
本記事のまとめ
Network ACLとセキュリティグループの違いと2層防御の設計パターンをまとめます。| 比較項目 | Network ACL(NACL) | セキュリティグループ(SG) |
|---|---|---|
| 適用単位 | サブネット | ENI(インスタンス単位) |
| ステート | ステートレス(IN・OUT独立評価) | ステートフル(応答は自動許可) |
| ルール評価 | 番号順(最初にマッチで確定) | 全ルールを評価(DENYルールなし) |
| DENY設定 | 可能(不審CIDRのブロックに活用) | 不可(許可ルールのみ) |
| 主な用途 | サブネット境界の不審IPブロック | インスタンス間の通信制御 |
| デフォルト動作 | 全許可(新規NACL作成時は全DENY) | インバウンド全拒否・アウトバウンド全許可 |
| よくある落とし穴 | エフェメラルポートのアウトバウンド未許可 | SGチェーン参照のSG ID誤り |
| 検査レイヤー | L4(IPアドレス・ポート番号)のみ | L4(IPアドレス・ポート番号)のみ |
| やりたいこと | コマンド |
|---|---|
| ドメインリスト型ステートフルルールグループを作成する | aws network-firewall create-rule-group --type STATEFUL --rule-group file://rules.json --rule-group-name 名前 --capacity 100 |
| Suricata互換ルールグループを作成する | aws network-firewall create-rule-group --type STATEFUL --rule-group-name 名前 --capacity 100 --rule-group '{"RulesSource":{"RulesString":"drop tls ..."}}' |
| Firewallポリシーを作成する | aws network-firewall create-firewall-policy --firewall-policy-name 名前 --firewall-policy file://policy.json |
| Firewallポリシーを更新してルールグループを追加する | aws network-firewall update-firewall-policy --update-token トークン --firewall-policy-name 名前 --firewall-policy file://policy.json |
| Firewallを作成してAZのサブネットに配置する | aws network-firewall create-firewall --firewall-name 名前 --vpc-id VPC_ID --firewall-policy-arn ARN --subnet-mappings SubnetId=サブネットA SubnetId=サブネットB |
| FirewallステータスとエンドポイントIDを確認する | aws network-firewall describe-firewall-status --firewall-name 名前 --query "FirewallStatus.SyncStates" |
| プライベートサブネットのEgressをFirewall経由にする | aws ec2 create-route --route-table-id RTB_ID --destination-cidr-block 0.0.0.0/0 --vpc-endpoint-id vpce-xxx |
| フローログ・アラートログをCloudWatch Logsに転送する | aws network-firewall update-logging-configuration --firewall-name 名前 --logging-configuration file://logging.json |
| ブロックされたトラフィックをALERTログから絞り込む | aws logs filter-log-events --log-group-name /aws/network-firewall/alert --filter-pattern '"blocked"' |
| Transit GatewayのルートテーブルでInspection VPC設計を確認する | aws ec2 describe-transit-gateway-route-tables --filters Name=transit-gateway-id,Values=tgw-xxx |
StatefulEngineOptions は STRICT_ORDER を指定してルール評価順を明示的に制御してください。IGWエッジアソシエーションとAZ間の非対称ルーティングには特に注意してください。複数VPCやマルチアカウント環境で管理コストが増えてきた場合は、Transit Gatewayと組み合わせた検査VPC(Inspection VPC)パターンへの移行を検討してください。導入コストはFirewallエンドポイント費用(0.395 USD/時間/AZ)を事前に試算した上で判断するのが現実的です。VPCの基本設計についてはAWSのLinux環境構築とVPC設計入門を、マルチAZ冗長構成の詳細はAWS冗長設計パターン入門も合わせて参照してください。
Network ACLの設定方法だけでなく、AWSを「実務の型」として身につけませんか?
aws ec2 create-network-acl-entry のコマンドは調べれば分かります。でも「NACLとSGをどう組み合わせて設計するのか」「エフェメラルポートをどの範囲で開けるべきか」を、自信を持って答えられますか?
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:AWS VPCのサブネット設計入門|パブリック・プライベート分離とルートテーブルをCLIで実装する方法
- 前のページへ:AWS ACMとRoute 53でHTTPS化する方法|無料SSL証明書の発行からALB適用・自動更新まで
- この記事の属するカテゴリ:AWS(Amazon Linux)へ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます