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

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

B站视频

上一篇文章讲过了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里面,已经通过脚本自动化把这个地址抓取并且配置了。

image-20221009162957797

二、查看FortiGate防火墙配置

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

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

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

image-20221025235330712

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

2.1 FortiGate FW1 配置

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

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

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

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

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

2.2 FortiGate FW2 配置

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

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

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

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

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

三、现象测试

3.1 访问APP测试

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

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

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

运行的效果如下:image-20221025232619818

自带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

抓包命令结果如下:image-20221025233345861

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

四、部署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

五、参考文档