AWS VPC 流量集中检测系列--(4)利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙

B站视频:​ ​​https://www.bilibili.com/video/BV1LG411j7v4/?spm_id_from=333.999.0.0​

欢迎大家关注我的微信公众号:自刘地

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS


上一篇文章讲过了AWS GWLB如何集成FortiGate防火墙,来对流量做集中检测。上一次实验是通过AWS 控制台操作的,这里分享一下实验环境的CloudFormation代码,帮助大家快速部署一下实验环境。

一、CloudFormation代码部署

这里的CloudFormation代码在Tokyo区域部署的,如果要在其他Region部署,请修改FortiGate和Windows2022Base的AMI ID(参考我之前的文章《如何寻找EC2特定版本的AMI ID》)。

这次CloudFormation是全自动化代码,堆栈运行完成以后,可以直接测试现象,不需要再做任何额外的配置。默认防火墙使用​​6.4.10​​的版本部署,如果要使用​​7.2.2​​参考第四部分修改关于防火墙的代码。

AWSTemplateFormatVersion: "2010-09-09"

Mappings:
RegionMap:
ap-northeast-1:
FortiGate722: ami-08479d0bce02ca48b
FortiGate6410: ami-0abf1a002258e8077
Windows2022Base: ami-06ac5e650e049a48f
FirewallInstanceType:
FortiGate722:
InstanceType: c6i.xlarge
FortiGate6410:
InstanceType: c6i.xlarge

Parameters:
EC2InstanceAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
Environment:
Type: String
AllowedValues:
- dev
- prod
Default: dev
MyKeyPair:
Description: Amazon EC2 Key Pair
Type: AWS::EC2::KeyPair::KeyName
Default: Global_Tokyo_KeyPair

WebServerPort:
Description: Apache Http Server Port
Type: String
Default: 8443
AllowedValues:
- 8443
- 8888
- 8088
FortigateVersion:
Description: Choice Fortigate Firewall Version Type
Type: String
Default: FortiGate722
AllowedValues:
- FortiGate722
- FortiGate6410

Resources:
#=========================================创建VPC、IGW========================================#
# 创建一SecVpc
SecVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.20.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: SecVpc

# 创建IGW并且关联到VPC
SecVpcIGW:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: SecVpcIGW

SecVpcAttachIgw:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
VpcId: !Ref SecVpc
InternetGatewayId: !Ref SecVpcIGW

#---------------------------SecVpc创建4个子网-------------------------------------#

# SecVpc AZ1内创建GWLB子网
Az1GwlbSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecVpc
CidrBlock: 10.20.10.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: SecVpc-GWLB-AZ1-GWLB-Subnet

# SecVpc AZ2内创建GWLB子网
Az2GwlbSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecVpc
CidrBlock: 10.20.30.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: SecVpc-GWLB-AZ2-GWLB-Subnet

# SecVpc AZ1内创建MGT子网
Az1MgtSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecVpc
CidrBlock: 10.20.20.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: SecVpc-GWLB-AZ1-MGT-Subnet

# SecVpc AZ2内创建MGT子网
Az2MgtSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecVpc
CidrBlock: 10.20.40.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: SecVpc-GWLB-AZ2-MGT-Subnet

#---------------------------SecVpc创建路由表-------------------------------------#

# SecVpc创建管理网段的路由表
MgtRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref SecVpc
Tags:
- Key: Name
Value: SecVpc-Mgt-route-table

# Mgt路由表关联子网
Az1MgtSubnetAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref MgtRouteTable
SubnetId: !Ref Az1MgtSubnet

Az2MgtSubnetAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref MgtRouteTable
SubnetId: !Ref Az2MgtSubnet

# SecVpc创建Gwlb的路由表
GwlbRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref SecVpc
Tags:
- Key: Name
Value: SecVpc-Gwlb-route-table

# Gwlb路由表关联子网
Az1GwlbSubnetAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref GwlbRouteTable
SubnetId: !Ref Az1GwlbSubnet

Az2GwlbSubnetAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref GwlbRouteTable
SubnetId: !Ref Az2GwlbSubnet


# 管理网段添加默认路由去往IGW
MgtToInternetRoute:
Type: "AWS::EC2::Route"
DependsOn: SecVpcIGW
Properties:
RouteTableId: !Ref MgtRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref SecVpcIGW

#---------------------------SecVpc创建安全组------------------------------------#

# 在SEC VPC内创建一个安全组
SecVpcSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG to test ping
VpcId: !Ref SecVpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 8443
ToPort: 8443
CidrIp: 0.0.0.0/0
- IpProtocol: -1
FromPort: -1
ToPort: -1
CidrIp: 10.20.0.0/16
- IpProtocol: -1
FromPort: -1
ToPort: -1
CidrIp: 10.10.0.0/16
- IpProtocol: tcp
FromPort: 3389
ToPort: 3389
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: SecVpcSg

#---------------------------SecVpc创建Fortigate接口------------------------------------#

Fortigate1MgmtEip:
Type: "AWS::EC2::EIP"
Properties:
Tags:
- Key: Name
Value: SecVpc-fortigate1-mgmt-eip

Fortigate1MgmtEni: # 创建Fortigate1管理接口
Type: "AWS::EC2::NetworkInterface"
Properties:
SourceDestCheck: false
PrivateIpAddress: 10.20.20.100
GroupSet:
- Ref: "SecVpcSg"
SubnetId:
Ref: "Az1MgtSubnet"
Tags:
- Key: Name
Value: SecVpc-fortigate1-mgmt-eni

Fortigate1MgmtEniAssociation: # 关联公网IP到Mgt弹性接口
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt Fortigate1MgmtEip.AllocationId # 这里是EIP
NetworkInterfaceId: !Ref Fortigate1MgmtEni

Fortigate1DataEni: # 创建Fortigate1数据接口
Type: "AWS::EC2::NetworkInterface"
Properties:
SourceDestCheck: false
PrivateIpAddress: 10.20.10.100
GroupSet:
- Ref: "SecVpcSg"
SubnetId:
Ref: "Az1GwlbSubnet"
Tags:
- Key: Name
Value: SecVpc-fortigate1-data-eni

Fortigate2MgmtEip:
Type: "AWS::EC2::EIP"
Properties:
Tags:
- Key: Name
Value: SecVpc-fortigate2-mgmt-eip

Fortigate2MgmtEni: # 创建Fortigate2管理接口
Type: "AWS::EC2::NetworkInterface"
Properties:
SourceDestCheck: false
PrivateIpAddress: 10.20.40.100
GroupSet:
- Ref: "SecVpcSg"
SubnetId:
Ref: "Az2MgtSubnet"
Tags:
- Key: Name
Value: SecVpc-fortigate2-mgmt-eni

Fortigate2MgmtEniAssociation: # 关联公网IP到Mgt弹性接口
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt Fortigate2MgmtEip.AllocationId # 这里是EIP
NetworkInterfaceId: !Ref Fortigate2MgmtEni

Fortigate2DataEni: # 创建Fortigate2数据接口
Type: "AWS::EC2::NetworkInterface"
Properties:
SourceDestCheck: false
PrivateIpAddress: 10.20.30.100
GroupSet:
- Ref: "SecVpcSg"
SubnetId:
Ref: "Az2GwlbSubnet"
Tags:
- Key: Name
Value: SecVpc-fortigate2-data-eni

#---------------------------SecVpc创建Fortigate实例------------------------------------#

# Fortigate1
Fortigate1:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", !Ref FortigateVersion]
KeyName: !Ref MyKeyPair
InstanceType: !FindInMap [FirewallInstanceType, !Ref "FortigateVersion", InstanceType]
NetworkInterfaces:
-
NetworkInterfaceId: !Ref Fortigate1DataEni
DeviceIndex: 0
-
NetworkInterfaceId: !Ref Fortigate1MgmtEni
DeviceIndex: 1
UserData:
Fn::Base64:
Fn::Sub:
- |
config system admin
edit "labuser"
set accprofile "super_admin"
set vdom "root"
set password FSr1Lliu1qiang2long3DemoZJG5
next
end
end
config system interface
edit "port1"
set defaultgw disable
set allowaccess ping https ssh fgfm probe-response
next
edit "port2"
set mode dhcp
set allowaccess ping https ssh
set defaultgw enable
next
end
config system global
set vdom-mode split-vdom
end
config global
config system interface
edit "port1"
set vdom "FG-traffic"
end
end
config vdom
edit FG-traffic
config system geneve
edit "geneve1"
set interface "port1"
set type ppp
set remote-ip ${Az1GwlbIp}
next
edit "geneve2"
set interface "port1"
set type ppp
set remote-ip ${Az2GwlbIp}
next
end
config firewall policy
edit 1
set name "1"
set srcintf "geneve1"
set dstintf "geneve1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
edit 2
set name "2"
set srcintf "geneve2"
set dstintf "geneve2"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
end
config log setting
set local-in-allow enable
set local-in-deny-unicast enable
set local-in-deny-broadcast enable
set local-out enable
next
end
config router static
edit 1
set device "geneve1"
next
edit 2
set device "geneve2"
next
edit 3
set dst 10.20.0.0 255.255.0.0
set gateway 10.20.10.1
set device "port1"
next
end
config router policy
edit 1
set input-device "geneve1"
set gateway ${Az1GwlbIp}
set output-device "geneve1"
next
edit 2
set input-device "geneve2"
set gateway ${Az2GwlbIp}
set output-device "geneve2"
next
end
- Az1GwlbIp: !GetAtt 'CustomGwlbIpResource.Az1GwlbIp'
Az2GwlbIp: !GetAtt 'CustomGwlbIpResource.Az2GwlbIp'
Tags:
- Key: Name
Value: Fortigate-FW1

Fortigate2:
Type: AWS::EC2::Instance
DependsOn: CustomGwlbIpResource
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", !Ref FortigateVersion]
KeyName: !Ref MyKeyPair
InstanceType: !FindInMap [FirewallInstanceType, !Ref "FortigateVersion", InstanceType]
NetworkInterfaces:
-
NetworkInterfaceId: !Ref Fortigate2DataEni
DeviceIndex: 0
-
NetworkInterfaceId: !Ref Fortigate2MgmtEni
DeviceIndex: 1
UserData:
Fn::Base64:
Fn::Sub:
- |
config system admin
edit "labuser"
set accprofile "super_admin"
set vdom "root"
set password FSr1Lliu1qiang2long3DemoZJG5
next
end
end
config system interface
edit "port1"
set defaultgw disable
set allowaccess ping https ssh fgfm probe-response
next
edit "port2"
set mode dhcp
set allowaccess ping https ssh
set defaultgw enable
next
end
config system global
set vdom-mode split-vdom
end
config global
config system interface
edit "port1"
set vdom "FG-traffic"
end
end
config vdom
edit FG-traffic
config system geneve
edit "geneve1"
set interface "port1"
set type ppp
set remote-ip ${Az2GwlbIp}
next
edit "geneve2"
set interface "port1"
set type ppp
set remote-ip ${Az1GwlbIp}
next
end
config firewall policy
edit 1
set name "1"
set srcintf "geneve1"
set dstintf "geneve1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
edit 2
set name "2"
set srcintf "geneve2"
set dstintf "geneve2"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
end
config log setting
set local-in-allow enable
set local-in-deny-unicast enable
set local-in-deny-broadcast enable
set local-out enable
next
end
config router static
edit 1
set device "geneve1"
next
edit 2
set device "geneve2"
next
edit 3
set dst 10.20.0.0 255.255.0.0
set gateway 10.20.30.1
set device "port1"
next
end
config router policy
edit 1
set input-device "geneve1"
set gateway ${Az2GwlbIp}
set output-device "geneve1"
next
edit 2
set input-device "geneve2"
set gateway ${Az1GwlbIp}
set output-device "geneve2"
next
end
- Az1GwlbIp: !GetAtt 'CustomGwlbIpResource.Az1GwlbIp'
Az2GwlbIp: !GetAtt 'CustomGwlbIpResource.Az2GwlbIp'
Tags:
- Key: Name
Value: Fortigate-FW2

#---------------------------创建GWLB------------------------------------#

Gwlb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
LoadBalancerAttributes:
- Key: load_balancing.cross_zone.enabled
Value: true
Name: Fortigate-GWLB
Type: gateway
Subnets:
- !Ref Az1GwlbSubnet
- !Ref Az2GwlbSubnet
Tags:
- Key: Name
Value: SecVpc-Fortigate-gwlb

TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 5
Name: Fortigate-Target
Port: 6081
Protocol: GENEVE
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 20
VpcId: !Ref SecVpc
HealthCheckPort: 443
HealthCheckProtocol: HTTPS
TargetType: instance
Targets:
- Id: !Ref Fortigate1
- Id: !Ref Fortigate2
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-gwlbtg"

Listener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref Gwlb

#---------------------------创建Endpoint Service------------------------------------#

VpcEndpointService:
Type: AWS::EC2::VPCEndpointService
Properties:
GatewayLoadBalancerArns:
- !Ref Gwlb
AcceptanceRequired: false

# Create Lambda Custom Resource to retrieve VPC Endpoint Service Name:

VpceServiceLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- ec2:DescribeVpcEndpointServiceConfigurations
- ec2:DescribeVpcEndpointServicePermissions
- ec2:DescribeVpcEndpointServices
Resource: "*"

# Lambda creates CloudWatch Log Group.
# Since CF stack didn't explicitly create the Log Group, Log Group doesn't get deleted when stack is deleted.
# Hence creating Log Group though the stack for Lambda specific funciton.
# Their are few things to consider. For more details refer to: https://github.com/aws/serverless-application-model/issues/1216
VpceServiceLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${AWS::StackName}-service
RetentionInDays: 1

VpceServiceName:
Type: AWS::Lambda::Function
DependsOn: VpceServiceLogGroup
Properties:
FunctionName: !Sub ${AWS::StackName}-service
Handler: "index.handler"
Role: !GetAtt VpceServiceLambdaExecutionRole.Arn
Code:
ZipFile: |
import json
import logging
import time

import boto3
import cfnresponse
from botocore.exceptions import ClientError

try:
ec2 = boto3.client('ec2')
except ClientError as e:
logger.error(f"ERROR: failed to connect to EC2 client: {e}")
sys.exit(1)

def handler(event, context):
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.info('Received event: {}'.format(json.dumps(event)))

responseData = {}
responseStatus = cfnresponse.FAILED

try:
serviceid = event["ResourceProperties"]["VpceServiceId"]
except Exception as e:
logger.info('Attribute retrival failure: {}'.format(e))

try:
if event["RequestType"] == "Delete":
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
except Exception:
logger.exception("Signaling failure to CloudFormation.")
cfnresponse.send(event, context, cfnresponse.FAILED, {})

if event["RequestType"] == "Create":
logger.info("Retrieving VPC Endpoint Service Name:")
try:
response = ec2.describe_vpc_endpoint_service_configurations(
Filters=[
{
'Name': 'service-id',
'Values': [serviceid]
}
]
)
except Exception as e:
logger.info('ec2.describe_vpc_endpoint_service_configurations failure: {}'.format(e))

service_name = response['ServiceConfigurations'][0]['ServiceName']

time.sleep(120)

responseData['ServiceName'] = service_name
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
Runtime: python3.7
Timeout: 150


RetrieveVpceServiceName:
Type: Custom::RetrieveAttributes
Properties:
ServiceToken: !GetAtt VpceServiceName.Arn
VpceServiceId: !Ref VpcEndpointService

App1Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref AppVpc
ServiceName: !GetAtt RetrieveVpceServiceName.ServiceName
VpcEndpointType: GatewayLoadBalancer
SubnetIds:
- !Ref Gwlbe1Subnet

App2Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref AppVpc
ServiceName: !GetAtt RetrieveVpceServiceName.ServiceName
VpcEndpointType: GatewayLoadBalancer
SubnetIds:
- !Ref Gwlbe2Subnet

#==============================创建App VPC、IGW==============================#
# 创建一APP VPC
AppVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.10.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: AppVpc

# 创建IGW并且关联到VPC
AppVpcIGW:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: AppVpc-IGW

AppVpcAttachIgw:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
VpcId: !Ref AppVpc
InternetGatewayId: !Ref AppVpcIGW

#---------------------------AppVpc创建2个子网-------------------------------------#

# AppVpc创建GWLB1子网
Gwlbe1Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref AppVpc
CidrBlock: 10.10.10.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: AppVpc-GWLBe1-Subnet


# AppVpc创建App1子网
App1Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref AppVpc
CidrBlock: 10.10.20.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: AppVpc-App1-Subnet

# AppVpc创建GWLB2子网
Gwlbe2Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref AppVpc
CidrBlock: 10.10.30.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: AppVpc-GWLBe2-Subnet

# AppVpc创建App2子网
App2Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref AppVpc
CidrBlock: 10.10.40.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: AppVpc-App2-Subnet

#---------------------------AppVpc创建路由表-------------------------------------#

#---------------IGW路由---------------#

# AppVpc创建IGW的路由表
IgwIngressRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref AppVpc
Tags:
- Key: Name
Value: AppVpc-Igw-Ingress-route-table

# IgwIngress路由表关联IGW
IgwIngressAssociation:
Type: "AWS::EC2::GatewayRouteTableAssociation"
Properties:
RouteTableId: !Ref IgwIngressRouteTable
GatewayId: !Ref AppVpcIGW

# IgwIngress去往App1网段的路由
IgwIngressToApp1:
Type: "AWS::EC2::Route"
DependsOn: App1Endpoint
Properties:
RouteTableId: !Ref IgwIngressRouteTable
DestinationCidrBlock: 10.10.20.0/24
VpcEndpointId: !Ref App1Endpoint

# IgwIngress去往App2网段的路由
IgwIngressToApp2:
Type: "AWS::EC2::Route"
DependsOn: App2Endpoint
Properties:
RouteTableId: !Ref IgwIngressRouteTable
DestinationCidrBlock: 10.10.40.0/24
VpcEndpointId: !Ref App2Endpoint

#---------------GWLBe路由---------------#

# AppVpc创建Gwlbe的路由表
GwlbeRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref AppVpc
Tags:
- Key: Name
Value: AppVpc-Gwlbe-route-table

# 路由表关联子网Gwlbe1
Gwlbe1RouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref GwlbeRouteTable
SubnetId: !Ref Gwlbe1Subnet

# 路由表关联子网Gwlbe2
Gwlbe2RouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref GwlbeRouteTable
SubnetId: !Ref Gwlbe2Subnet

# 管理网段添加默认路由去往IGW
Gwlbe1ToInternetRoute:
Type: "AWS::EC2::Route"
DependsOn: AppVpcIGW
Properties:
RouteTableId: !Ref GwlbeRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref AppVpcIGW

#---------------App路由---------------#

# AppVpc创建App1的路由表
App1RouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref AppVpc
Tags:
- Key: Name
Value: AppVpc-App1-route-table

# AppVpc创建App2的路由表
App2RouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref AppVpc
Tags:
- Key: Name
Value: AppVpc-App2-route-table

# App1路由表关联子网
App1RouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref App1RouteTable
SubnetId: !Ref App1Subnet

# App2路由表关联子网
App2RouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
RouteTableId: !Ref App2RouteTable
SubnetId: !Ref App2Subnet

# App1网段添加默认路由去往Endpoint
App1ToEndpoint:
Type: "AWS::EC2::Route"
DependsOn: App1Endpoint
Properties:
RouteTableId: !Ref App1RouteTable
DestinationCidrBlock: 0.0.0.0/0
VpcEndpointId: !Ref App1Endpoint

# App2网段添加默认路由去往Endpoint
App2ToEndpoint:
Type: "AWS::EC2::Route"
DependsOn: App2Endpoint
Properties:
RouteTableId: !Ref App2RouteTable
DestinationCidrBlock: 0.0.0.0/0
VpcEndpointId: !Ref App2Endpoint

#---------------------------AppVpc创建安全组------------------------------------#

# 在SEC VPC内创建一个安全组
AppVpcSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG to test ping
VpcId: !Ref AppVpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 8443
ToPort: 8443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: -1
FromPort: -1
ToPort: -1
CidrIp: 10.20.0.0/16
- IpProtocol: -1
FromPort: -1
ToPort: -1
CidrIp: 10.10.0.0/16
- IpProtocol: tcp
FromPort: 3389
ToPort: 3389
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: AppVpcSg

#---------------------------AppVpc创建EC2实例------------------------------------#

# App1 Linux AMI
App1Linux:
Type: AWS::EC2::Instance
DependsOn: [App1ToEndpoint, App2ToEndpoint]
Properties:
ImageId: !Ref EC2InstanceAmiId
KeyName: !Ref MyKeyPair
InstanceType: t2.micro
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: 0
GroupSet:
- Ref: AppVpcSg
SubnetId: !Ref App1Subnet
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
yum update -y
yum install -y httpd
sed -i.bak 's/Listen 80/Listen 8443/g' /etc/httpd/conf/httpd.conf
echo "<h2>Hello World from $(hostname -f)</h2>" > /var/www/html/index.html
systemctl start httpd.service
systemctl enable httpd.service
Tags:
- Key: Name
Value: App1-Linux

# App2 Windows AMI
App2Windows:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", Windows2022Base]
KeyName: !Ref MyKeyPair
InstanceType: t2.xlarge
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: 0
GroupSet:
- Ref: AppVpcSg
SubnetId: !Ref App2Subnet
Tags:
- Key: Name
Value: App2-Windows
UserData:
Fn::Base64:
!Sub |
<powershell>
$PASSWORD = convertto-securestring "FSr1Lliu1qiang2long3DemoZJG5" -asplaintext -force
New-LocalUser -Name "labuser" -Description "rdp user" -Password $Password
Add-LocalGroupMember -Group 'Administrators' -Member 'labuser'
net localgroup "Remote Desktop Users" /add labuser
$file = "C:\" + (Get-Date).ToString("MM-dd-yy-hh-mm")
New-Item $file -ItemType file
</powershell>

GwlbIpLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: GwlbIpAccess
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- ec2:DescribeNetworkInterfaces
Resource: "*"

CustomGwlbIpResource:
DependsOn: GwlbIpAddressFunction
Type: Custom::GwlbIp
Properties:
ServiceToken: !GetAtt 'GwlbIpAddressFunction.Arn'

GwlbIpAddressFunction:
Type: AWS::Lambda::Function
DependsOn: Gwlb
Properties:
FunctionName: GwlbIpAddressFunction
Handler: "index.handler"
Role: !GetAtt GwlbIpLambdaExecutionRole.Arn
Code:
ZipFile: |
import json
import logging
import time

import boto3
import cfnresponse
from botocore.exceptions import ClientError

try:
ec2 = boto3.client('ec2')
except ClientError as e:
logger.error(f"ERROR: failed to connect to EC2 client: {e}")
sys.exit(1)

def handler(event, context):
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.info('Received event: {}'.format(json.dumps(event)))

responseData = {}
responseStatus = cfnresponse.FAILED

try:
serviceid = event["ResourceProperties"]["VpceServiceId"]
except Exception as e:
logger.info('Attribute retrival failure: {}'.format(e))

try:
if event["RequestType"] == "Delete":
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
except Exception:
logger.exception("Signaling failure to CloudFormation.")
cfnresponse.send(event, context, cfnresponse.FAILED, {})

if event["RequestType"] == "Create":
logger.info("Retrieving VPC Endpoint Service Name:")
try:
response = ec2.describe_network_interfaces(
Filters=[
{
'Name': 'interface-type',
'Values': [
'gateway_load_balancer',
]
},
]
)
for x in response['NetworkInterfaces']:
if x['AvailabilityZone'] == 'ap-northeast-1a':
az1_gwlb_ip = x['PrivateIpAddress']
if x['AvailabilityZone'] == 'ap-northeast-1c':
az2_gwlb_ip = x['PrivateIpAddress']

except Exception as e:
logger.info('ec2.describe_network_interfaces failure: {}'.format(e))

time.sleep(120)

responseData = {}
responseData['Az1GwlbIp'] = az1_gwlb_ip
responseData['Az2GwlbIp'] = az2_gwlb_ip

responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
Runtime: python3.7
Timeout: 150

Outputs:
Az1GwlbIp:
Description: Firewall AZ1 GWLB IP Address
Value: !GetAtt 'CustomGwlbIpResource.Az1GwlbIp'
Az2GwlbIp:
Description: Firewall AZ2 GWLB IP Address
Value: !GetAtt 'CustomGwlbIpResource.Az2GwlbIp'

堆栈创建完成之后,会输出GWLB的弹性接口IP地址。如果手动配置Fortigate防火墙,配置geneve时需要这个信息。在CloudFormation里面,已经通过脚本自动化把这个地址抓取并且配置了。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_02

二、查看FortiGate防火墙配置

FortiGate防火墙的配置在CloudFormation代码里面已经配置完成了,这里通过图形化界面来看一下配置结果。

首次登陆FortiGate防火墙,默认的用户名是​​admin​​,默认的密码是实例id。

我在CloudFormation代码里面,为防火墙新建了一个测试用户,用户名为​​labuser​​,密码为​​FSr1Lliu1qiang2long3DemoZJG5​​。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_03

这里可以查看实例ID信息。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_04

2.1 FortiGate FW1 配置

FortiGate-FW1启用了Split-VDOM,有两个VDOM,root用于防火墙管理,FG-traffic用于转发数据流量。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_GWLB_05

port1是主接口,用于转发数据流量。port2作为管理接口,用于管理流量。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_CloudFormation_06

静态路由配置,默认路由从geneve1和geneve2接口出去,配置本VPC CIDR网段从port1数据接口出去,下一跳指向子网网关。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_Fortigate_07

因为配置了两条默认路由,有可能造成流量来回路径不一致的问题。所以通过配置策略路由,让来自geneve1的流量从genven1接口出去,来自geneve2的流量从genven2接口出去。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_08

测试环境下,放行所有流量。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_GWLB_09

2.2 FortiGate FW2 配置

FortiGate-FW2启用了Split-Task VDOM,有两个VDOM,root和FG-traffic。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_10

port1是主接口,用于转发数据流量。port2作为管理接口,用于管理流量。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_CloudFormation_11

静态路由配置,默认路由从geneve1和geneve2接口出去,配置本VPC CIDR网段从port1数据接口出去,下一跳指向子网网关。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_12

配置策略路由,因为配置了两条默认路由,有可能造成流量来回路径不一致的问题。所以通过配置策略路由,让来自geneve1的流量从genven1接口出去,来自geneve2的流量从genven2接口出去。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_Gateway Load Balance_13

测试环境下,放行所有流量。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_GWLB_14

三、现象测试

3.1 访问APP测试

连接上APP2 Window RDP桌面。可以使用我在代码里面新建的用户名​​labuser​​,密码​​FSr1Lliu1qiang2long3DemoZJG5​​进行连接。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_Gateway Load Balance_15

查看防火墙连接日志信息。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_CloudFormation_16

3.2 防火墙抓包

FortiGate防火墙可以通过Debug packet flow[1]来查看防火墙对数据包执行的动作,这个抓包方法一般用于排错,输出的信息比较多。也可以通过自带的sniffer工具[2]单纯的抓包,输出的信息比较简洁。

Debug packet flow

例如:这里首先暂停了之前的抓包进程,然后抓取地址包含114.114.114.114的10个报文。

diagnose debug flow trace stop

diagnose debug enable
diagnose debug flow filter addr 114.114.114.114
diagnose debug flow show function-name enable
diagnose debug flow trace start 10

也可以通过协议来进行过滤,这里只查看​​icmp​​协议的报文。

diagnose debug flow trace stop

diagnose debug enable
diagnose debug flow filter proto 1
diagnose debug flow show function-name enable
diagnose debug flow trace start 10

运行的效果如下:

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_CloudFormation_17

自带sniffer工具抓包

例如,这里抓取来自port1端口,端口号为6081的报文,其实就是GENVEN报文。

FGT-GWLB-1 (FG-traffic) # diagnose sniffer packet port1 'port 6081'

例如,这里抓取来所有的icmp报文。

FGT-GWLB-1 (FG-traffic) # diagnose sniffer packet any icmp 4

抓包命令结果如下:

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_CloudFormation_18

可以利用sniff抓包来确认流量目前走哪一台防火墙,然后stop这个防火墙实例,查看流量多长时间能够切换成功,测试防火墙的高可用切换。我测试大致会在40秒左右切换成功。

利用CloudFormation自动化部署AWS GWLB集成FortiGate防火墙_AWS_19

四、部署Fortigate7.2.2版本代码参考

上述的CloudFormation代码中FortiGate防火墙的开机启动配置,是基于​​FortiGate6.4.10​​版本配置的。如果选择​​7.2.2​​版本部署,防火墙无法加载对应配置,可以将下面的代码替换上面关于防火墙的部分。

# Fortigate1
Fortigate1:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", !Ref FortigateVersion]
KeyName: !Ref MyKeyPair
InstanceType: !FindInMap [FirewallInstanceType, !Ref "FortigateVersion", InstanceType]
NetworkInterfaces:
-
NetworkInterfaceId: !Ref Fortigate1DataEni
DeviceIndex: 0
-
NetworkInterfaceId: !Ref Fortigate1MgmtEni
DeviceIndex: 1
UserData:
Fn::Base64:
Fn::Sub:
- |
config system admin
edit "labuser"
set accprofile "super_admin"
set vdom "root"
set password FSr1Lliu1qiang2long3DemoZJG5
next
end
end
config system interface
edit "port1"
set defaultgw disable
set allowaccess ping https ssh fgfm probe-response
next
edit "port2"
set mode dhcp
set allowaccess ping https ssh fgfm probe-response
set defaultgw enable
next
end
config system global
set vdom-mode multi-vdom
set timezone 08
end
end
config vdom
edit root
config system settings
set vdom-type admin
end
end
config vdom
edit FG-traffic
config system settings
set vdom-type traffic
end
end
config global
config system interface
edit "port1"
set vdom "FG-traffic"
set defaultgw disable
set allowaccess ping https ssh fgfm probe-response
next
edit "port2"
set vdom "root"
set mode dhcp
set allowaccess ping https ssh fgfm probe-response
set defaultgw enable
next
end
end
config vdom
edit FG-traffic
config system geneve
edit "geneve1"
set interface "port1"
set type ppp
set remote-ip ${Az1GwlbIp}
next
edit "geneve2"
set interface "port1"
set type ppp
set remote-ip ${Az2GwlbIp}
next
end
config firewall policy
edit 1
set name "1"
set srcintf "geneve1"
set dstintf "geneve1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
edit 2
set name "2"
set srcintf "geneve2"
set dstintf "geneve2"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
end
config log setting
set local-in-allow enable
set local-in-deny-unicast enable
set local-in-deny-broadcast enable
set local-out enable
next
end
config router static
edit 1
set device "geneve1"
next
edit 2
set device "geneve2"
next
edit 3
set dst 10.20.0.0 255.255.0.0
set gateway 10.20.10.1
set device "port1"
next
end
config router policy
edit 1
set input-device "geneve1"
set gateway ${Az1GwlbIp}
set output-device "geneve1"
next
edit 2
set input-device "geneve2"
set gateway ${Az2GwlbIp}
set output-device "geneve2"
next
end
- Az1GwlbIp: !GetAtt 'CustomGwlbIpResource.Az1GwlbIp'
Az2GwlbIp: !GetAtt 'CustomGwlbIpResource.Az2GwlbIp'
Tags:
- Key: Name
Value: Fortigate-FW1

Fortigate2:
Type: AWS::EC2::Instance
DependsOn: CustomGwlbIpResource
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", !Ref FortigateVersion]
KeyName: !Ref MyKeyPair
InstanceType: !FindInMap [FirewallInstanceType, !Ref "FortigateVersion", InstanceType]
NetworkInterfaces:
-
NetworkInterfaceId: !Ref Fortigate2DataEni
DeviceIndex: 0
-
NetworkInterfaceId: !Ref Fortigate2MgmtEni
DeviceIndex: 1
UserData:
Fn::Base64:
Fn::Sub:
- |
config system admin
edit "labuser"
set accprofile "super_admin"
set vdom "root"
set password FSr1Lliu1qiang2long3DemoZJG5
next
end
end
config system interface
edit "port1"
set defaultgw disable
set allowaccess ping https ssh fgfm probe-response
next
edit "port2"
set mode dhcp
set allowaccess ping https ssh fgfm probe-response
set defaultgw enable
next
end
config system global
set vdom-mode multi-vdom
set timezone 08
end
end
config vdom
edit root
config system settings
set vdom-type admin
end
end
config vdom
edit FG-traffic
config system settings
set vdom-type traffic
end
end
config global
config system interface
edit "port1"
set vdom "FG-traffic"
set defaultgw disable
set allowaccess ping https ssh fgfm probe-response
next
edit "port2"
set vdom "root"
set mode dhcp
set allowaccess ping https ssh fgfm probe-response
set defaultgw enable
next
end
end
config vdom
edit FG-traffic
config system geneve
edit "geneve1"
set interface "port1"
set type ppp
set remote-ip ${Az2GwlbIp}
next
edit "geneve2"
set interface "port1"
set type ppp
set remote-ip ${Az1GwlbIp}
next
end
config firewall policy
edit 1
set name "1"
set srcintf "geneve1"
set dstintf "geneve1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
edit 2
set name "2"
set srcintf "geneve2"
set dstintf "geneve2"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
set logtraffic-start enable
next
end
config log setting
set local-in-allow enable
set local-in-deny-unicast enable
set local-in-deny-broadcast enable
set local-out enable
next
end
config router static
edit 1
set device "geneve1"
next
edit 2
set device "geneve2"
next
edit 3
set dst 10.20.0.0 255.255.0.0
set gateway 10.20.30.1
set device "port1"
next
end
config router policy
edit 1
set input-device "geneve1"
set gateway ${Az2GwlbIp}
set output-device "geneve1"
next
edit 2
set input-device "geneve2"
set gateway ${Az1GwlbIp}
set output-device "geneve2"
next
end
end
- Az1GwlbIp: !GetAtt 'CustomGwlbIpResource.Az1GwlbIp'
Az2GwlbIp: !GetAtt 'CustomGwlbIpResource.Az2GwlbIp'
Tags:
- Key: Name
Value: Fortigate-FW2

五、参考文档

  • [1] FortiGate / FortiOS 7.2.1 Administration Guide Debugging the packet flow:https://docs.fortinet.com/document/fortigate/7.2.1/administration-guide/054688/debugging-the-packet-flow
  • [2] Troubleshooting Tip: Using the FortiOS built-in packet sniffer:https://community.fortinet.com/t5/FortiGate/Troubleshooting-Tip-Using-the-FortiOS-built-in-packet-sniffer/ta-p/194222