Transit Gatewayピアリング
Transit Gateway(TGW)ピアリングは、同一リージョン内(Intra-Region)および異なるリージョン間(Inter-Region)のTransit Gatewayを相互接続し、IPv4/IPv6トラフィックをルーティングする機能です。ピアリングは自アカウント内のTGW同士だけでなく、別のAWSアカウントが所有するTGWとの間でも確立可能です。 ピアリング設定は以下の流れで行います。
- 一方のTGWでピアリングアタッチメントを作成し相手先のTGWを指定します
- 相手先のTGWでピアリングリクエストを承認します
- 通信に必要なルートテーブルの修正を行います
ピアリングの制約
TGWピアリングには、以下の制約があります。
- 動的ルーティングに非対応:ピアリングアタッチメント間ではルート伝播はサポートされていないため、静的ルートをTGWのルートテーブルに手動で追加する必要があります(ピアリング越しのBGPによるルート伝播もサポートされません)
- ECMPに非対応:TGWピアリングでは動的ルーティングをサポートしていない、かつ、同じ静的ルートを2つの異なるターゲットに対して設定できないためECMPを利用することができません
- マルチキャストに非対応:ピアリング越しのマルチキャストには対応していません
現状はBGPピアリングがサポートされないため、ピアリングを行うTransit Gateway同士のASNは重複しても問題ありません。ただし、ルート伝播がサポートされた場合を考慮してASNは重複しないように設計することが推奨されています。
試してみる
それでは実際にTGWピアリングを試してみます。今回は、同様のアカウントで別々のリージョンのTGW環境を用意します。
検証環境構成
検証のため以下の環境を用意します。
本構成では既に以下の通信が可能な状態です。
- us-east-1(左下)のEC2からNetwork Firewallでパケット検査し、VPC-BoundaryのNat Gateway経由でインターネット接続
- us-east-2(右側)のVPC間の通信
※構成図には書いていませんが、us-east-2(右側)のEC2は動作確認用にVPCエンドポイントでSSMアクセス可能な状態にしています
検証環境は、サクッとCloudFormationで用意しました。(ほとんどAIに書いてもらいました。本当に優秀ですね。。。)
us-east-1リソース
AWSTemplateFormatVersion: '2010-09-09'
Description: |
Centralized Network Firewall architecture (Transit Gateway attachment / 2-VPC design)
Network Firewall is attached directly to Transit Gateway,
eliminating Firewall subnets and VPC endpoints from the VPCs.
Traffic flow:
[Egress] EC2 -> TGW(Private RT) -> NF(inspect) -> TGW(NF RT) -> VPC-Boundary TGW Subnet
-> NAT GW -> IGW -> Internet
[Ingress] Internet -> IGW -> NAT GW -> TGW(Boundary RT) -> NF(inspect) -> TGW(NF RT) -> EC2
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Description: Newest Amazon Linux 2023 AMI (Systems Manager Parameter Store)
Mappings:
# AZ ID mapping required for Network Firewall TGW attachment.
# AZ IDs are globally unique, but the mapping from AZ name (e.g. us-east-1a)
# to AZ ID varies per AWS account.
RegionFirstAZId:
us-east-1: { ZoneId: use1-az1 }
us-east-2: { ZoneId: use2-az1 }
us-west-1: { ZoneId: usw1-az1 }
us-west-2: { ZoneId: usw2-az1 }
ap-northeast-1: { ZoneId: apne1-az1 }
ap-northeast-2: { ZoneId: apne2-az1 }
ap-northeast-3: { ZoneId: apne3-az1 }
ap-southeast-1: { ZoneId: apse1-az1 }
ap-southeast-2: { ZoneId: apse2-az1 }
ap-south-1: { ZoneId: aps1-az1 }
eu-west-1: { ZoneId: euw1-az1 }
eu-west-2: { ZoneId: euw2-az1 }
eu-west-3: { ZoneId: euw3-az1 }
eu-central-1: { ZoneId: euc1-az1 }
eu-north-1: { ZoneId: eun1-az1 }
sa-east-1: { ZoneId: sae1-az1 }
ca-central-1: { ZoneId: cac1-az1 }
Resources:
# ================================================================
# VPC-Boundary (10.1.0.0/16)
# - TGW subnet (10.1.1.0/24) : Transit Gateway dedicated
# - Public subnet (10.1.2.0/24) : NAT Gateway
# ================================================================
VPCBoundary:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.1.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: VPC-Boundary
IGWBoundary:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: IGW-Boundary
IGWBoundaryAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPCBoundary
InternetGatewayId: !Ref IGWBoundary
SubnetBoundaryTGW:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCBoundary
CidrBlock: 10.1.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Boundary-TGW
SubnetBoundaryPublic:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCBoundary
CidrBlock: 10.1.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Boundary-Public
EIPNatGW:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGateway:
Type: AWS::EC2::NatGateway
DependsOn: IGWBoundaryAttachment
Properties:
AllocationId: !GetAtt EIPNatGW.AllocationId
SubnetId: !Ref SubnetBoundaryPublic
Tags:
- Key: Name
Value: NatGW-Boundary
# ------------------------------------------------------------------
# Route table: Public subnet
# 0.0.0.0/0 -> IGW (internet egress)
# 10.2.0.0/16 -> TGW (return packets from NAT GW forwarded to TGW)
# TGW(Boundary RT) -> NF(inspect) -> TGW(NF RT) -> Private
# ------------------------------------------------------------------
RTBoundaryPublic:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCBoundary
Tags:
- Key: Name
Value: RT-Boundary-Public
RouteBoundaryPublicDefault:
Type: AWS::EC2::Route
DependsOn: IGWBoundaryAttachment
Properties:
RouteTableId: !Ref RTBoundaryPublic
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGWBoundary
RouteBoundaryPublicToPrivate:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentBoundary
Properties:
RouteTableId: !Ref RTBoundaryPublic
DestinationCidrBlock: 10.2.0.0/16
TransitGatewayId: !Ref TransitGateway
# ------------------------------------------------------------------
# Route table: TGW subnet
# 0.0.0.0/0 -> NAT GW (NF-inspected egress packets forwarded to NAT GW)
# ------------------------------------------------------------------
RTBoundaryTGW:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCBoundary
Tags:
- Key: Name
Value: RT-Boundary-TGW
RouteBoundaryTGWDefault:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RTBoundaryTGW
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
RTABoundaryPublic:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetBoundaryPublic
RouteTableId: !Ref RTBoundaryPublic
RTABoundaryTGW:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetBoundaryTGW
RouteTableId: !Ref RTBoundaryTGW
# ================================================================
# Network Firewall (attached directly to Transit Gateway)
# ================================================================
FirewallStatefulRuleGroup:
Type: AWS::NetworkFirewall::RuleGroup
Properties:
RuleGroupName: !Sub "${AWS::StackName}-StatefulAllowAll"
Type: STATEFUL
Capacity: 100
RuleGroup:
StatefulRuleOptions:
RuleOrder: STRICT_ORDER
RulesSource:
RulesString: |
pass ip any any -> any any (msg:"Allow all traffic"; sid:1; rev:1;)
Tags:
- Key: Name
Value: StatefulAllowAll
FirewallPolicy:
Type: AWS::NetworkFirewall::FirewallPolicy
Properties:
FirewallPolicyName: !Sub "${AWS::StackName}-CentralizedInspectionPolicy"
FirewallPolicy:
StatelessDefaultActions:
- aws:forward_to_sfe
StatelessFragmentDefaultActions:
- aws:forward_to_sfe
StatefulEngineOptions:
RuleOrder: STRICT_ORDER
StatefulDefaultActions:
- aws:drop_strict
StatefulRuleGroupReferences:
- ResourceArn: !GetAtt FirewallStatefulRuleGroup.RuleGroupArn
Priority: 1
Tags:
- Key: Name
Value: CentralizedInspectionPolicy
# Specifying TransitGatewayId attaches the firewall directly to TGW.
# VpcId and SubnetMappings are not required (no Firewall subnet or VPC endpoint).
NetworkFirewall:
Type: AWS::NetworkFirewall::Firewall
Properties:
FirewallName: !Sub "${AWS::StackName}-CentralizedFirewall"
FirewallPolicyArn: !GetAtt FirewallPolicy.FirewallPolicyArn
TransitGatewayId: !Ref TransitGateway
AvailabilityZoneMappings:
- AvailabilityZone: !FindInMap [RegionFirstAZId, !Ref AWS::Region, ZoneId]
DeleteProtection: false
FirewallPolicyChangeProtection: false
Tags:
- Key: Name
Value: CentralizedFirewall
# ================================================================
# VPC-Private (10.2.0.0/16)
# - TGW subnet (10.2.1.0/24) : Transit Gateway dedicated
# - EC2 subnet (10.2.2.0/24) : EC2 instance
# ================================================================
VPCPrivate:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.2.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: VPC-Private
SubnetPrivateTGW:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCPrivate
CidrBlock: 10.2.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Private-TGW
SubnetPrivateEC2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCPrivate
CidrBlock: 10.2.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Private-EC2
# ------------------------------------------------------------------
# Route table: EC2 subnet
# 0.0.0.0/0 -> TGW (all egress traffic forwarded to TGW)
# TGW(Private RT) -> NF(inspect) -> TGW(NF RT) -> Boundary
# ------------------------------------------------------------------
RTPrivateEC2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCPrivate
Tags:
- Key: Name
Value: RT-Private-EC2
RoutePrivateEC2Default:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentPrivate
Properties:
RouteTableId: !Ref RTPrivateEC2
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref TransitGateway
# Route table: TGW subnet (local routes only)
RTPrivateTGW:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCPrivate
Tags:
- Key: Name
Value: RT-Private-TGW
RTAPrivateEC2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetPrivateEC2
RouteTableId: !Ref RTPrivateEC2
RTAPrivateTGW:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetPrivateTGW
RouteTableId: !Ref RTPrivateTGW
# ================================================================
# EC2 instance (VPC-Private)
# ================================================================
SGPrivateEC2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for Private EC2 instance
VpcId: !Ref VPCPrivate
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic
Tags:
- Key: Name
Value: SG-Private-EC2
EC2InstanceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-EC2InstanceRole"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2InstanceRole
PrivateEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref SubnetPrivateEC2
SecurityGroupIds:
- !Ref SGPrivateEC2
IamInstanceProfile: !Ref EC2InstanceProfile
Tags:
- Key: Name
Value: EC2-Private
# ================================================================
# Transit Gateway
# ================================================================
TransitGateway:
Type: AWS::EC2::TransitGateway
Properties:
Description: Centralized inspection Transit Gateway
# ASN must differ from the peer TGW (tgw-peering-remote uses 64513)
AmazonSideAsn: 64512
DefaultRouteTableAssociation: disable
DefaultRouteTablePropagation: disable
AutoAcceptSharedAttachments: disable
Tags:
- Key: Name
Value: TGW-Central
# TGW attachments for VPCs only; NF attachment is created automatically by NetworkFirewall resource
TGWAttachmentBoundary:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref VPCBoundary
SubnetIds:
- !Ref SubnetBoundaryTGW
Tags:
- Key: Name
Value: TGWAttachment-Boundary
TGWAttachmentPrivate:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref VPCPrivate
SubnetIds:
- !Ref SubnetPrivateTGW
Tags:
- Key: Name
Value: TGWAttachment-Private
# ----------------------------------------------------------------
# TGW route table 1: Private (for VPC-Private attachment)
# All traffic -> Network Firewall attachment (for inspection)
# ----------------------------------------------------------------
TGWRouteTablePrivate:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: TGW-RT-Private
TGWAssociationPrivate:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTablePrivate
TransitGatewayAttachmentId: !Ref TGWAttachmentPrivate
TGWRoutePrivateDefault:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTablePrivate
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayAttachmentId: !GetAtt NetworkFirewall.TransitGatewayAttachmentId
# ----------------------------------------------------------------
# TGW route table 2: NF (for Network Firewall attachment)
# Routes post-inspection packets based on destination
# 0.0.0.0/0 -> Boundary (internet-bound egress)
# 10.2.0.0/16 -> Private (return traffic to VPC-Private)
# ----------------------------------------------------------------
TGWRouteTableNF:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: TGW-RT-NF
TGWAssociationNF:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableNF
TransitGatewayAttachmentId: !GetAtt NetworkFirewall.TransitGatewayAttachmentId
TGWRouteNFDefault:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableNF
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayAttachmentId: !Ref TGWAttachmentBoundary
TGWRouteNFToPrivate:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableNF
DestinationCidrBlock: 10.2.0.0/16
TransitGatewayAttachmentId: !Ref TGWAttachmentPrivate
# ----------------------------------------------------------------
# TGW route table 3: Boundary (for VPC-Boundary attachment)
# Return traffic -> Network Firewall attachment (bidirectional inspection)
# ----------------------------------------------------------------
TGWRouteTableBoundary:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: TGW-RT-Boundary
TGWAssociationBoundary:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableBoundary
TransitGatewayAttachmentId: !Ref TGWAttachmentBoundary
TGWRouteBoundaryToPrivate:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableBoundary
DestinationCidrBlock: 10.2.0.0/16
TransitGatewayAttachmentId: !GetAtt NetworkFirewall.TransitGatewayAttachmentId
# ================================================================
# Outputs
# ================================================================
Outputs:
VPCBoundaryId:
Description: VPC-Boundary ID
Value: !Ref VPCBoundary
VPCPrivateId:
Description: VPC-Private ID
Value: !Ref VPCPrivate
TransitGatewayId:
Description: Transit Gateway ID
Value: !Ref TransitGateway
NetworkFirewallArn:
Description: Network Firewall ARN
Value: !Ref NetworkFirewall
NetworkFirewallTGWAttachmentId:
Description: Network Firewall Transit Gateway Attachment ID
Value: !GetAtt NetworkFirewall.TransitGatewayAttachmentId
NatGatewayPublicIP:
Description: NAT Gateway Elastic IP
Value: !Ref EIPNatGW
PrivateEC2InstanceId:
Description: Private EC2 Instance ID (accessible via SSM Session Manager)
Value: !Ref PrivateEC2Instance
us-east-2リソース
AWSTemplateFormatVersion: '2010-09-09'
Description: |
TGW peering verification template (deploy to remote region)
VPC-Private2 and VPC-Private3 communicate with each other via TGW.
TGW peering itself must be configured manually from the console.
VPC-Private2: 172.16.0.0/16
VPC-Private3: 172.17.0.0/16
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Description: Newest Amazon Linux 2023 AMI (Systems Manager Parameter Store)
Resources:
# ================================================================
# VPC-Private2 (172.16.0.0/16)
# - TGW subnet (172.16.1.0/24) : Transit Gateway dedicated
# - EC2 subnet (172.16.2.0/24) : EC2 instance
# ================================================================
VPCPrivate2:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.16.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: VPC-Private2
SubnetPrivate2EC2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCPrivate2
CidrBlock: 172.16.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Private2-EC2
SubnetPrivate2TGW:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCPrivate2
CidrBlock: 172.16.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Private2-TGW
# ------------------------------------------------------------------
# Route table: EC2 subnet (VPC-Private2)
# 172.17.0.0/16 -> TGW (traffic to VPC-Private3)
# ------------------------------------------------------------------
RTPrivate2EC2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCPrivate2
Tags:
- Key: Name
Value: RT-Private2-EC2
RoutePrivate2ToPrivate3:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentPrivate2
Properties:
RouteTableId: !Ref RTPrivate2EC2
DestinationCidrBlock: 172.17.0.0/16
TransitGatewayId: !Ref TransitGateway
RTAPrivate2EC2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetPrivate2EC2
RouteTableId: !Ref RTPrivate2EC2
# Route table: TGW subnet (local routes only)
RTPrivate2TGW:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCPrivate2
Tags:
- Key: Name
Value: RT-Private2-TGW
RTAPrivate2TGW:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetPrivate2TGW
RouteTableId: !Ref RTPrivate2TGW
# ================================================================
# VPC-Private3 (172.17.0.0/16)
# - TGW subnet (172.17.1.0/24) : Transit Gateway dedicated
# - EC2 subnet (172.17.2.0/24) : EC2 instance
# ================================================================
VPCPrivate3:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.17.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: VPC-Private3
SubnetPrivate3EC2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCPrivate3
CidrBlock: 172.17.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Private3-EC2
SubnetPrivate3TGW:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPCPrivate3
CidrBlock: 172.17.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Subnet-Private3-TGW
# ------------------------------------------------------------------
# Route table: EC2 subnet (VPC-Private3)
# 172.16.0.0/16 -> TGW (traffic to VPC-Private2)
# ------------------------------------------------------------------
RTPrivate3EC2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCPrivate3
Tags:
- Key: Name
Value: RT-Private3-EC2
RoutePrivate3ToPrivate2:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentPrivate3
Properties:
RouteTableId: !Ref RTPrivate3EC2
DestinationCidrBlock: 172.16.0.0/16
TransitGatewayId: !Ref TransitGateway
RTAPrivate3EC2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetPrivate3EC2
RouteTableId: !Ref RTPrivate3EC2
# Route table: TGW subnet (local routes only)
RTPrivate3TGW:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCPrivate3
Tags:
- Key: Name
Value: RT-Private3-TGW
RTAPrivate3TGW:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetPrivate3TGW
RouteTableId: !Ref RTPrivate3TGW
# ================================================================
# SSM VPC endpoints (enables Session Manager without internet access)
# ================================================================
SGEndpointPrivate2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for SSM VPC endpoints in VPC-Private2
VpcId: !Ref VPCPrivate2
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 172.16.0.0/16
Description: Allow HTTPS from VPC-Private2
Tags:
- Key: Name
Value: SG-Endpoint-Private2
VPCESSMPrivate2:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCPrivate2
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
VpcEndpointType: Interface
SubnetIds:
- !Ref SubnetPrivate2EC2
SecurityGroupIds:
- !Ref SGEndpointPrivate2
PrivateDnsEnabled: true
VPCESSMMessagesPrivate2:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCPrivate2
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
VpcEndpointType: Interface
SubnetIds:
- !Ref SubnetPrivate2EC2
SecurityGroupIds:
- !Ref SGEndpointPrivate2
PrivateDnsEnabled: true
VPCEEC2MessagesPrivate2:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCPrivate2
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2messages"
VpcEndpointType: Interface
SubnetIds:
- !Ref SubnetPrivate2EC2
SecurityGroupIds:
- !Ref SGEndpointPrivate2
PrivateDnsEnabled: true
SGEndpointPrivate3:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for SSM VPC endpoints in VPC-Private3
VpcId: !Ref VPCPrivate3
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 172.17.0.0/16
Description: Allow HTTPS from VPC-Private3
Tags:
- Key: Name
Value: SG-Endpoint-Private3
VPCESSMPrivate3:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCPrivate3
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
VpcEndpointType: Interface
SubnetIds:
- !Ref SubnetPrivate3EC2
SecurityGroupIds:
- !Ref SGEndpointPrivate3
PrivateDnsEnabled: true
VPCESSMMessagesPrivate3:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCPrivate3
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
VpcEndpointType: Interface
SubnetIds:
- !Ref SubnetPrivate3EC2
SecurityGroupIds:
- !Ref SGEndpointPrivate3
PrivateDnsEnabled: true
VPCEEC2MessagesPrivate3:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCPrivate3
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2messages"
VpcEndpointType: Interface
SubnetIds:
- !Ref SubnetPrivate3EC2
SecurityGroupIds:
- !Ref SGEndpointPrivate3
PrivateDnsEnabled: true
# ================================================================
# EC2 instances
# ================================================================
EC2InstanceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-EC2InstanceRole"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2InstanceRole
SGPrivate2EC2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 in VPC-Private2
VpcId: !Ref VPCPrivate2
SecurityGroupIngress:
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: 172.17.0.0/16
Description: Allow ICMP from VPC-Private3
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: Allow all outbound
Tags:
- Key: Name
Value: SG-Private2-EC2
EC2Private2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref SubnetPrivate2EC2
SecurityGroupIds:
- !Ref SGPrivate2EC2
IamInstanceProfile: !Ref EC2InstanceProfile
Tags:
- Key: Name
Value: EC2-Private2
SGPrivate3EC2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 in VPC-Private3
VpcId: !Ref VPCPrivate3
SecurityGroupIngress:
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: 172.16.0.0/16
Description: Allow ICMP from VPC-Private2
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: Allow all outbound
Tags:
- Key: Name
Value: SG-Private3-EC2
EC2Private3:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref SubnetPrivate3EC2
SecurityGroupIds:
- !Ref SGPrivate3EC2
IamInstanceProfile: !Ref EC2InstanceProfile
Tags:
- Key: Name
Value: EC2-Private3
# ================================================================
# Transit Gateway
# ================================================================
TransitGateway:
Type: AWS::EC2::TransitGateway
Properties:
Description: Transit Gateway for peering verification
# ASN must differ from the peer TGW (network-firewall-centralized uses default 64512)
AmazonSideAsn: 64513
DefaultRouteTableAssociation: disable
DefaultRouteTablePropagation: disable
AutoAcceptSharedAttachments: disable
Tags:
- Key: Name
Value: TGW-Peering
TGWAttachmentPrivate2:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref VPCPrivate2
SubnetIds:
- !Ref SubnetPrivate2TGW
Tags:
- Key: Name
Value: TGWAttachment-Private2
TGWAttachmentPrivate3:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref VPCPrivate3
SubnetIds:
- !Ref SubnetPrivate3TGW
Tags:
- Key: Name
Value: TGWAttachment-Private3
# ----------------------------------------------------------------
# TGW route table: for Private2 attachment
# 172.17.0.0/16 -> TGWAttachment-Private3
# ----------------------------------------------------------------
TGWRouteTablePrivate2:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: TGW-RT-Private2
TGWAssociationPrivate2:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTablePrivate2
TransitGatewayAttachmentId: !Ref TGWAttachmentPrivate2
TGWRoutePrivate2ToPrivate3:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTablePrivate2
DestinationCidrBlock: 172.17.0.0/16
TransitGatewayAttachmentId: !Ref TGWAttachmentPrivate3
# ----------------------------------------------------------------
# TGW route table: for Private3 attachment
# 172.16.0.0/16 -> TGWAttachment-Private2
# ----------------------------------------------------------------
TGWRouteTablePrivate3:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: TGW-RT-Private3
TGWAssociationPrivate3:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTablePrivate3
TransitGatewayAttachmentId: !Ref TGWAttachmentPrivate3
TGWRoutePrivate3ToPrivate2:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTablePrivate3
DestinationCidrBlock: 172.16.0.0/16
TransitGatewayAttachmentId: !Ref TGWAttachmentPrivate2
# ================================================================
# Outputs
# ================================================================
Outputs:
VPCPrivate2Id:
Description: VPC-Private2 ID
Value: !Ref VPCPrivate2
VPCPrivate3Id:
Description: VPC-Private3 ID
Value: !Ref VPCPrivate3
TransitGatewayId:
Description: Transit Gateway ID (used when configuring TGW peering)
Value: !Ref TransitGateway
EC2Private2InstanceId:
Description: EC2-Private2 Instance ID (accessible via SSM Session Manager)
Value: !Ref EC2Private2
EC2Private3InstanceId:
Description: EC2-Private3 Instance ID (accessible via SSM Session Manager)
Value: !Ref EC2Private3
今回の検証にあたり調べていたところ、Network FirewallのTransit Gateway統合機能がサポートされていました。Firewall用のVPC作成が不要ということで、便利そうだったので本機能を採用しています。
設定手順
今回は、ピアリングを作成した後、VPC-Private2のEC2をFWで検査しNat Gateway経由でインターネット接続ができる状態を目指します。
①ピアリングアタッチメントの作成
まずは、TGW同士を繋ぐためピアリングを構成します。ピアリングはのアタッチメントを作成することで実現します。今回は、us-east-1から作業します。
※本記事の画像に含まれるリソースはすべて削除済みのため、リソースID等はマスクしておりません
アタッチメントタイプは「peering connection」を選びます。ピア接続アタッチメントに対向のTGWを指定します。
アタッチメントを作成すると、Initialing request状態となります。
②ピアリングリクエストの承認
ピアリングアタッチメントを作成すると、対向のTGWで承諾が必要です。us-east-2のアタッチメントの画面に新規のアタッチメントが作成されているため、アタッチメントを承諾します。(デフォルトでは名前がついてないようです)
③ルートテーブルの修正
ピアリングが構成できたため、実現したい通信内容に応じてルートテーブル(TGW、サブネット)を修正します。今回は、以下のルートテーブルを編集します。
※本記事では、ルートテーブルの設定に関する記載は割愛します
ポイントとしては、以下の2点です。
- ピアリング用に作成したアタッチメントにルートテーブルを関連付けます。新規ルートテーブルを作成した後、関連付けまで忘れずに行います。ピアリングアタッチメントは、双方のリージョンで作成されているため、それぞれのアタッチメントで関連付けが必要です。
- 特定のアタッチメントからのルーティング先(次のターゲット)がピアリングアタッチメントであった場合、参照されるのは対向のTGW側のピアリングアタッチメントのルートテーブルです。そのため、ピアリングアタッチメントのルートテーブルには自TGW側のルートを設定します。イメージについては、以下を参照してください。
動作確認
それでは動作確認してみます。右上のEC2インスタンスから8.8.8.8にpingをうちます。問題なく繋がっています。
ちょっと分かりづらいですが、送信元のIPアドレスも確認してみるとNat GatewayのEIPであることが確認できます。
Network Firewallがきいているかも確認したいので、ICMPをdropするルールを追加します。
すると、pingが届かなくなることを確認できます。ファイアウォールが正しく機能していそうです。
さいごに
AWS-ANSの更新があったので、復習もかねて久々にTGWを検証してみました。やっぱり、ネットワークはおもしろいですね。また気が向いたらTGWの記事を書きたいと思います。












