
service: 'tcf-prod-vpc'

provider:
  name: aws
  runtime: python3.7
  profile: 'tcf-prod'
  stackName: 'tcf-prod-vpc'
  region: 'af-south-1'
  stage: ${self:provider.profile}
  deploymentBucket:
    name: ${self:custom.ServerlessBucketName}
    serverSideEncryption: AES256
    blockPublicAccess: true
plugins:
  - serverless-deployment-bucket
  - serverless-parameters

custom:
  CustomerName: tcf
  StageName: prod
  # Combination of profile and function name used as bucket name:
  ResourceName: serverless-framework # Only lowercase letters and numbers allowed. Used to populate bucket name.
  ServerlessBucketName: '${self:custom.ResourceName}-${self:provider.stage}-${self:provider.region}'
  FlowLogBucketName: '${self:custom.ResourceName}-${self:provider.stage}-${self:provider.region}-flowlog'

  # CloudFormation parameters templates
  parameters:
    paramCustomerName:
      Description: Enter the Customer Name, used to tag everything
      Type: String
      Default: '${self:custom.CustomerName}'

    paramVpcCIDR:
      Description: Enter the IP range (CIDR notation) for VPC
      Type: String
      Default: 10.0.0.0/16

    AZRegions:
      Description: Enter the IP range (CIDR notation) for VPC
      Type: CommaDelimitedList
      Default: 'af-south-1a, af-south-1b, af-south-1c'

    paramPublicSubnetIpBlocks:
      Description: 'Comma-delimited list of three _Public_ CIDR blocks'
      Type: CommaDelimitedList
      Default: '10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24'

    paramPrivateSubnet1IpBlocks:
      Description: 'Comma-delimited list of three _Private_ CIDR blocks'
      Type: CommaDelimitedList
      Default: '10.0.5.0/24, 10.0.6.0/24, 10.0.7.0/24'

    paramPrivateSubnet2IpBlocks:
      Description: 'Comma-delimited list of three _Private_ CIDR blocks'
      Type: CommaDelimitedList
      Default: '10.0.10.0/24, 10.0.11.0/24, 10.0.12.0/24'

    # FlowLog section

    # FLowLog - Cloud Watch

    PermissionsBoundary:
      Description: 'Optional ARN for a policy that will be used as the permission boundary for all roles created by this template.'
      Type: String
      Default: ''

    RetentionInDays:
      Description: 'Specifies the number of days you want to retain log events.'
      Type: Number
      Default: 365
      AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]

    # FlowLog - S3

    InternalLogBucket:
      Description: 'Optional The name of an S3 bucket where you want to store flow logs. If you leave this empty, the Amazon S3 bucket is created for you.'
      Type: String
      Default: '${self:custom.FlowLogBucketName}'

    LogFilePrefix:
      Description: 'Optional The log file prefix.'
      Type: String
      Default: ''

    TrafficType:
      Description: 'The type of traffic to log.'
      Type: String
      Default: ALL
      AllowedValues:
        - ACCEPT
        - REJECT
        - ALL

resources:

  # CloudFormation resource templates

  Conditions:

    HasPermissionsBoundary: !Not [ !Equals [ !Ref PermissionsBoundary, '' ] ]
    InternalBucket: !Not [ !Equals [ !Ref InternalLogBucket, '' ] ]
    HasLogFilePrefix: !Not [ !Equals [ !Ref LogFilePrefix, '' ] ]

  Resources:

    # Create a Public/Private VPC

    theVpc:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: !Ref paramVpcCIDR
        EnableDnsSupport: true
        EnableDnsHostnames: true
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'VPC'

    ## Create Flow Logs

    # Create the S3 Internal private bucket

    theFlowLogS3Bucket:
      Condition: InternalBucket
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Ref InternalLogBucket
        PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
        VersioningConfiguration:
          Status: Enabled
        BucketEncryption:
          ServerSideEncryptionConfiguration:
            - ServerSideEncryptionByDefault:
                SSEAlgorithm: AES256
              BucketKeyEnabled: true
        LifecycleConfiguration:
          Rules:
            - Id: ExpireEverythingInOneYear
              Prefix: ""
              Status: Enabled
              ExpirationInDays: 365
        Tags:
          - Key: Name
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'bucket'
                - 'theFlowLogS3'

    # Create the role for Cloud Watch Log

    FlowLogRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service: 'vpc-flow-logs.amazonaws.com'
              Action: 'sts:AssumeRole'
        PermissionsBoundary: !If [HasPermissionsBoundary, !Ref PermissionsBoundary, !Ref 'AWS::NoValue']
        Policies:
          - PolicyName: 'flowlogs-policy'
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - 'logs:CreateLogStream'
                    - 'logs:PutLogEvents'
                    - 'logs:DescribeLogGroups'
                    - 'logs:DescribeLogStreams'
                  Resource: !GetAtt 'LogGroup.Arn'
        Tags:
          - Key: Name
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'iam'
                - 'role'
                - 'FlowLog'

    # Create the S3 bucket policy

    theFlowLogS3BucketPolicy:
      Condition: InternalBucket
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref theFlowLogS3Bucket
        PolicyDocument:
          Version: '2012-10-17'
          Statement: # https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-s3.html#flow-logs-s3-permissions
            - Sid: AWSLogDeliveryWrite
              Effect: Allow
              Principal:
                Service: 'delivery.logs.amazonaws.com'
              Action: 's3:PutObject'
              Resource: !If [ HasLogFilePrefix, !Sub '${theFlowLogS3Bucket.Arn}/${LogFilePrefix}/AWSLogs/${AWS::AccountId}/*', !Sub '${theFlowLogS3Bucket.Arn}/AWSLogs/${AWS::AccountId}/*' ]
              Condition:
                StringEquals:
                  's3:x-amz-acl': 'bucket-owner-full-control'
            - Sid: AWSLogDeliveryAclCheck
              Effect: Allow
              Principal:
                Service: 'delivery.logs.amazonaws.com'
              Action: 's3:GetBucketAcl'
              Resource: !GetAtt 'theFlowLogS3Bucket.Arn'
            - Sid: AllowSSLRequestsOnly
              Effect: Deny
              Principal: '*'
              Action: 's3:*'
              Resource:
                - !GetAtt 'theFlowLogS3Bucket.Arn'
                - !Sub '${theFlowLogS3Bucket.Arn}/*'
              Condition:
                Bool:
                  'aws:SecureTransport': false

    # Create the log group

    LogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        LogGroupName: '${self:custom.CustomerName}-${self:custom.StageName}-VPC-LogGroup'
        RetentionInDays: !Ref RetentionInDays
        Tags:
          - Key: Name
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'logs'
                - 'loggroup'

    # Create the FlowLog to Cloud Watch

    FlowLogDeliveringToCW:
      Type: AWS::EC2::FlowLog
      Properties:
        DeliverLogsPermissionArn: !GetAtt 'FlowLogRole.Arn'
        LogGroupName: !Ref LogGroup
        ResourceId: !Ref theVpc
        ResourceType: 'VPC'
        TrafficType: !Ref TrafficType
        Tags:
          - Key: Name
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'flowlog'
                - 'cloudwatch'

    # Create the FlowLog to S3

    FlowLogDeliveringToS3:
      Condition: InternalBucket
      DependsOn: theFlowLogS3BucketPolicy
      Type: AWS::EC2::FlowLog
      Properties:
        ResourceId: !Ref theVpc
        ResourceType: 'VPC'
        TrafficType: !Ref TrafficType
        LogDestination: !GetAtt theFlowLogS3Bucket.Arn
        LogDestinationType: s3
        MaxAggregationInterval: 60
        Tags:
          - Key: Name
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'flowlog'
                - 'S3'

    # Create an Internet Gateway

    theInternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties:
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'Internet Gateway'

    # Attach the Internet Gateway to VPC

    theVpcGatewayAttachment:
      Type: AWS::EC2::VPCGatewayAttachment
      DependsOn:
        - theInternetGateway
        - theVpc
      Properties:
        VpcId: !Ref theVpc
        InternetGatewayId: !Ref theInternetGateway

    # Create a public route table for the VPC (will be public once it is associated with the Internet Gateway)

    thePublicRouteTable:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId: !Ref theVpc
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'public'
                - 'route table'

    # Create a default route in the public route table and assign the Internet Gateway as the gateway

    thePublicRoute:
      Type: AWS::EC2::Route
      DependsOn:
        - theVpcGatewayAttachment
        - thePublicRouteTable
      Properties:
        RouteTableId: !Ref thePublicRouteTable
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId: !Ref theInternetGateway

    ## Create public subnets

    # Create public subnet A1

    thePublicSubnetA1:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref theVpc
        AvailabilityZone: !Select [ 0, !Ref AZRegions ]
        # Note: MapPublicIpOnLaunch: true for public subnet
        MapPublicIpOnLaunch: true
        CidrBlock: !Select [ 0, !Ref paramPublicSubnetIpBlocks ]
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'public'
                - 'subnet'
                - 'A1'

    # Create public subnet B1

    thePublicSubnetB1:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref theVpc
        AvailabilityZone: !Select [ 1, !Ref AZRegions ]
        # Note: MapPublicIpOnLaunch: true for public subnet
        MapPublicIpOnLaunch: true
        CidrBlock: !Select [ 1, !Ref paramPublicSubnetIpBlocks ]
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'public'
                - 'subnet'
                - 'B1'

    # Associate the public route table A1 with the public A1 subnet

    thePublicSubnetA1RouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref thePublicRouteTable
        SubnetId: !Ref thePublicSubnetA1

    # Associate the public route table B1 with the public B1 subnet

    thePublicSubnetB1RouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref thePublicRouteTable
        SubnetId: !Ref thePublicSubnetB1

    ## Create NAT Gateway (4 components: EIP, NAT, Route, Associate Route)

    # 1: Specify an Elastic IP (EIP) address for the NAT gateway in A1

    theEIPforNatGatewayA1:
      Type: AWS::EC2::EIP
      DependsOn: theVpcGatewayAttachment
      Properties:
        Domain: vpc
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'eip'
                - 'nat-gw'

    # 1: Specify an Elastic IP (EIP) address for the NAT gateway in B1

    #  theEIPforNatGatewayB1:
    #    Type: AWS::EC2::EIP
    #    DependsOn: theVpcGatewayAttachment
    #    Properties:
    #      Domain: vpc

    # 2: Create a NAT Gateway in the public subnet for A1

    theNatGatewayA1:
      Type: AWS::EC2::NatGateway
      Properties:
        AllocationId: !GetAtt theEIPforNatGatewayA1.AllocationId
        SubnetId: !Ref thePublicSubnetA1
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'NatGateway'
                - 'A1'

    # 2: Create a NAT Gateway in the public subnet for B1

    #  theNatGatewayB1:
    #    Type: AWS::EC2::NatGateway
    #    Properties:
    #      AllocationId: !GetAtt theEIPforNatGatewayB1.AllocationId
    #      SubnetId: !Ref thePublicSubnetB1

    # 3: Create a private route table for A1

    thePrivateRouteTableA1:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId: !Ref theVpc
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'private'
                - 'route table'
                - 'A1'

    # 3: Create a private route table for B1

    thePrivateRouteTableB1:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId: !Ref theVpc
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'private'
                - 'route table'
                - 'B1'

    # 4: Associate the private route table with the Nat Gateway in A1

    thePrivateRouteForA1:
      Type: AWS::EC2::Route
      DependsOn: theVpcGatewayAttachment
      Properties:
        RouteTableId: !Ref thePrivateRouteTableA1
        DestinationCidrBlock: 0.0.0.0/0
        NatGatewayId: !Ref theNatGatewayA1

    # 4: Associate the private route table with the Nat Gateway in B1

    thePrivateRouteForB2:
      Type: AWS::EC2::Route
      DependsOn: theVpcGatewayAttachment
      Properties:
        RouteTableId: !Ref thePrivateRouteTableB1
        DestinationCidrBlock: 0.0.0.0/0
        NatGatewayId: !Ref theNatGatewayA1

    ## Create private subnets A1 and B1

    # Create private subnet A1

    thePrivateSubnetA1:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref theVpc
        AvailabilityZone: !Select [ 0, !Ref AZRegions ]
        MapPublicIpOnLaunch: false
        CidrBlock: !Select [ 0, !Ref paramPrivateSubnet1IpBlocks ]
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'private'
                - 'subnet'
                - 'A1'

    # Create private subnet B1

    thePrivateSubnetB1:
      Type: AWS::EC2::Subnet
      DependsOn: theVpc
      Properties:
        VpcId: !Ref theVpc
        AvailabilityZone: !Select [ 1, !Ref AZRegions ]
        MapPublicIpOnLaunch: false
        CidrBlock: !Select [ 1, !Ref paramPrivateSubnet1IpBlocks ]
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'private'
                - 'subnet'
                - 'B1'

    # Associate the private route table A1 with the private subnet in A1 (NAT GW)

    thePrivateSubnetA1RouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref thePrivateRouteTableA1
        SubnetId: !Ref thePrivateSubnetA1

    #  Associate the private route table B1 with the private subnet in B1 (NAT GW)

    thePrivateSubnetB1RouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref thePrivateRouteTableB1
        SubnetId: !Ref thePrivateSubnetB1

    ## Create private subnets A2 and B2

    # Create private subnet A2

    thePrivateSubnetA2:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref theVpc
        AvailabilityZone: !Select [ 0, !Ref AZRegions ]
        MapPublicIpOnLaunch: false
        CidrBlock: !Select [ 0, !Ref paramPrivateSubnet2IpBlocks ]
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'private'
                - 'subnet'
                - 'A2'

    # Create private subnet B2

    thePrivateSubnetB2:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref theVpc
        AvailabilityZone: !Select [ 1, !Ref AZRegions ]
        MapPublicIpOnLaunch: false
        CidrBlock: !Select [ 1, !Ref paramPrivateSubnet2IpBlocks ]
        Tags:
          - Key: 'Name'
            Value: !Join
              - '-'
              - - '${self:custom.CustomerName}'
                - '${self:custom.StageName}'
                - 'private'
                - 'subnet'
                - 'B2'

    # Associate the private route table A1 with the private subnet in A2 (NAT GW)

    thePrivateSubnetA2RouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref thePrivateRouteTableA1
        SubnetId: !Ref thePrivateSubnetA2

    #  Associate the private route table B1 with the private subnet in B2 (NAT GW)

    thePrivateSubnet2RouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref thePrivateRouteTableB1
        SubnetId: !Ref thePrivateSubnetB2

  ## Outputs

  Outputs:
    exportCustomerName:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - Customer Name'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-CustomerName'
      Value: !Ref paramCustomerName

    clickstagingVPC:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - VPC'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-VPC'
      Value: !Ref theVpc

    clickstagingPublicSubnetA1:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PublicSubnetA1'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PublicSubnetA1'
      Value: !Ref thePublicSubnetA1

    clickstagingPublicSubnetB1:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PublicSubnetB1'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PublicSubnetB1'
      Value: !Ref thePublicSubnetB1

    clickstagingPrivateSubnetA1:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PrivateSubnetA1'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PrivateSubnetA1'
      Value: !Ref thePrivateSubnetA1

    clickstagingPrivateSubnetB1:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PrivateSubnetB1'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PrivateSubnetB1'
      Value: !Ref thePrivateSubnetB1

    clickstagingPrivateSubnetA2:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PrivateSubnetA2'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PrivateSubnetA2'
      Value: !Ref thePrivateSubnetA2

    clickstagingPrivateSubnetB2:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PrivateSubnetB2'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PrivateSubnetB2'
      Value: !Ref thePrivateSubnetB2

    clickstagingPrivateSubnetA1CidrIp:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PrivateSubnetA1CidrIp'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PrivateSubnetA1CidrIp'
      Value: !GetAtt thePrivateSubnetA1.CidrBlock

    clickstagingPrivateSubnetB1CidrIp:
      Description: '${self:custom.CustomerName}-${self:custom.StageName} - PrivateSubnetB1CidrIp'
      Export:
        Name: '${self:custom.CustomerName}-${self:custom.StageName}-PrivateSubnetB1CidrIp'
      Value: !GetAtt thePrivateSubnetB1.CidrBlock

#-----------------------------------------------------------------------#
