CloudFormationでNetwork Firewall経由の通信を同一AZ内に閉じる方法
概要
CloudFormation(以下、CFn)でNetwork Firewall( AWS::NetworkFirewall::Firewall
)を作成後に Fn::GetAtt
で EndpointIds
を取得できます。
しかし、 AWS::NetworkFirewall::Firewall
の Fn::GetAtt
の EndpointIds
は順序が保証されず、単純なリスト参照ではクロスAZトラフィックが発生する可能性があります。
AWS::NetworkFirewall::Firewall Return values
EndpointIds
The unique IDs of the firewall endpoints for all of the subnets that you attached to the firewall. The subnets are not listed in any particular order. For example: ["us-west-2c:vpce-111122223333", "us-west-2a:vpce-987654321098", "us-west-2b:vpce-012345678901"].
クロスAZトラフィックを防ぐためにCFnのカスタムリソースや組み込み関数を使っていましたが、それぞれ以下の課題がありました。
方式 | 課題 |
---|---|
カスタムリソース方式 | 呼び出すLambdaの作成・テスト・運用コストの増加 |
組み込み関数方式 | CFnの可読性低下 |
このような状況の中、 AWS::NetworkFirewall::VpcEndpointAssociation
リソースが2025/8/4に公開されました。
更新履歴での説明を引用します。
Document history for the AWS CloudFormation Template Reference
Use the AWS::NetworkFirewall::VpcEndpointAssociation resource to specify a VPC endpoint association to expand the protections of a firewall endpoint. You can define VPC endpoint associations only in the Availability Zones that already have a subnet mapping defined in the Firewall resource.
この新しいリソースを使うことで、CFnで通信経路を明示してクロスAZトラフィックを防止できないか確認しました。
簡単な結論
-
VpcEndpointAssociation
を使うことでカスタムリソースや複雑な組み込み関数なしで通信をAZ内に閉じさせることが可能 - 元々作られるVPCエンドポイントに加えて追加のVPCエンドポイントが作られるので、Network Firewall Endpointの料金が嵩む
確認内容
確認に使用したCFnテンプレート
CFnテンプレート
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample AWS Network Firewall deployment with supporting VPC resources.
Parameters:
VpcCidr:
Type: String
Default: 10.0.0.0/16
Description: CIDR block for the inspection VPC.
FirewallName:
Type: String
Default: sample-fw
Description: Name to assign to the Network Firewall resources.
Resources:
InspectionVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-inspection-vpc"
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-igw"
VpcGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref InspectionVpc
FirewallSubnetAz1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVpc
CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-firewall-az1"
FirewallSubnetAz2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVpc
CidrBlock: !Select [1, !Cidr [!Ref VpcCidr, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-firewall-az2"
ProtectedSubnetAz1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVpc
CidrBlock: !Select [2, !Cidr [!Ref VpcCidr, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-protected-az1"
ProtectedSubnetAz2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVpc
CidrBlock: !Select [3, !Cidr [!Ref VpcCidr, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-protected-az2"
AdditionalSubnetAz1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVpc
CidrBlock: !Select [4, !Cidr [!Ref VpcCidr, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-additional-az1"
AdditionalSubnetAz2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVpc
CidrBlock: !Select [5, !Cidr [!Ref VpcCidr, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-additional-az2"
FirewallSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref InspectionVpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-firewall-rt"
FirewallSubnetRouteToInternet:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref FirewallSubnetRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
DependsOn:
- VpcGatewayAttachment
FirewallSubnetAz1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref FirewallSubnetRouteTable
SubnetId: !Ref FirewallSubnetAz1
FirewallSubnetAz2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref FirewallSubnetRouteTable
SubnetId: !Ref FirewallSubnetAz2
AllowAllStatefulRuleGroup:
Type: AWS::NetworkFirewall::RuleGroup
Properties:
Capacity: 100
RuleGroupName: !Sub "${FirewallName}-stateful"
Type: STATEFUL
RuleGroup:
RuleVariables: {}
ReferenceSets: {}
RulesSource:
RulesString: |
pass ip any any -> any any (sid:1; rev:1;)
StatefulRuleOptions:
RuleOrder: DEFAULT_ACTION_ORDER
NetworkFirewallPolicy:
Type: AWS::NetworkFirewall::FirewallPolicy
Properties:
FirewallPolicyName: !Sub "${FirewallName}-policy"
FirewallPolicy:
StatelessDefaultActions:
- aws:forward_to_sfe
StatelessFragmentDefaultActions:
- aws:forward_to_sfe
StatefulEngineOptions:
RuleOrder: DEFAULT_ACTION_ORDER
StatefulRuleGroupReferences:
- ResourceArn: !Ref AllowAllStatefulRuleGroup
NetworkFirewall:
Type: AWS::NetworkFirewall::Firewall
Properties:
DeleteProtection: false
FirewallName: !Ref FirewallName
FirewallPolicyArn: !Ref NetworkFirewallPolicy
SubnetChangeProtection: false
VpcId: !Ref InspectionVpc
SubnetMappings:
- SubnetId: !Ref FirewallSubnetAz1
- SubnetId: !Ref FirewallSubnetAz2
RouteTableAz1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref InspectionVpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-protected-rt-az1"
RouteTableAz2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref InspectionVpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-protected-rt-az2"
AdditionalSubnetAz1Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref FirewallSubnetRouteTable
SubnetId: !Ref AdditionalSubnetAz1
AdditionalSubnetAz2Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref FirewallSubnetRouteTable
SubnetId: !Ref AdditionalSubnetAz2
ProtectedSubnetAz1Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTableAz1
SubnetId: !Ref ProtectedSubnetAz1
ProtectedSubnetAz2Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTableAz2
SubnetId: !Ref ProtectedSubnetAz2
RouteToFirewallAz1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableAz1
DestinationCidrBlock: 0.0.0.0/0
VpcEndpointId: !GetAtt FirewallEndpointAssociationAz1.EndpointId
RouteToFirewallAz2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableAz2
DestinationCidrBlock: 0.0.0.0/0
VpcEndpointId: !GetAtt FirewallEndpointAssociationAz2.EndpointId
FirewallEndpointAssociationAz1:
Type: AWS::NetworkFirewall::VpcEndpointAssociation
Properties:
FirewallArn: !Ref NetworkFirewall
VpcId: !Ref InspectionVpc
SubnetMapping:
SubnetId: !Ref AdditionalSubnetAz1
FirewallEndpointAssociationAz2:
Type: AWS::NetworkFirewall::VpcEndpointAssociation
Properties:
FirewallArn: !Ref NetworkFirewall
VpcId: !Ref InspectionVpc
SubnetMapping:
SubnetId: !Ref AdditionalSubnetAz2
Outputs:
VpcId:
Description: VPC containing the Network Firewall deployment.
Value: !Ref InspectionVpc
FirewallArn:
Description: ARN of the deployed Network Firewall.
Value: !Ref NetworkFirewall
FirewallEndpointIds:
Description: Endpoint IDs generated for the firewall subnets.
Value: !Join [",", !GetAtt NetworkFirewall.EndpointIds]
AdditionalSubnetIds:
Description: Subnets associated via VpcEndpointAssociation for workload routing.
Value: !Join [",", [!Ref AdditionalSubnetAz1, !Ref AdditionalSubnetAz2]]
CFnテンプレートの説明
NetworkFirewallの追加のVPCエンドポイントを AdditionalSubnetAz1
に作成します。(以降ではAZ1を例示していますが、AZ2側も同様です。)
FirewallEndpointAssociationAz1:
Type: AWS::NetworkFirewall::VpcEndpointAssociation
Properties:
FirewallArn: !Ref NetworkFirewall
VpcId: !Ref InspectionVpc
SubnetMapping:
SubnetId: !Ref AdditionalSubnetAz1
全ての宛先(0.0.0.0/0)への通信を上記の FirewallEndpointAssociationAz1
で作成したVPCエンドポイントに送るようにルートを設定します。
このVPCエンドポイントに送ることでNetwork Firewallで検査されます。
RouteToFirewallAz1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableAz1
DestinationCidrBlock: 0.0.0.0/0
VpcEndpointId: !GetAtt FirewallEndpointAssociationAz1.EndpointId
EC2やECSなどが起動する ProtectedSubnetAz1
に上記の RouteTableAz1
を関連づけます。
これによりAZ1のEC2からの通信は、Network FirewallのAZ1に追加したサブネットに通信が送られ、AZ2側に通信しない設定が可能となります。
ProtectedSubnetAz1Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTableAz1
SubnetId: !Ref ProtectedSubnetAz1
トラフィックフロー図
VpcEndpointAssociation
を使用した場合の通信経路を図示します。
この構成により、AZ1のEC2インスタンスからの通信は以下の経路を辿り、AZ1内で完結します。
-
ProtectedSubnetAz1
のEC2インスタンスから発信 -
RouteTableAz1
のルートに従いVPCエンドポイント(AZ1)に転送 - Network Firewall(AZ1)で検査
- Internet Gatewayを経由してインターネットへ
各方式の比較
比較項目 | 追加エンドポイント方式 | 組み込み関数方式 | カスタムリソース方式 |
---|---|---|---|
同一AZ担保 | ◯ | ◯ | ◯ |
コスト | ×(追加のエンドポイントの費用がかかる) | ◯ | ◯ |
実装・保守性 | ◯ | △(2AZならそこまで複雑にならないが、3AZになると複雑) | ×(Lambdaの実装・テスト・保守が必要) |
3AZ以上へのスケール | ◯ | ×(CFnの可読性低下) | ◯ |
所感
以下が許容できるなら、追加エンドポイント方式はCFnの可読性、Lambdaの保守が不要というメリットがあり、実装・保守性の観点では有力な選択肢です。
- Network Firewall Endpointが増えることによる費用増
- 参考価格:東京リージョンで1エンドポイントあたりUSD 0.395/時間
- Network Firewallでデフォルト作成されるエンドポイントを未使用とする構成上の違和感
参考
Discussion