AWSTemplateFormatVersion: '2010-09-09'
Description: >
  EG334S VPN #1 - SIMULATED ON-PREM SIDE (deploy in us-east-1 / N. Virginia).
  Builds VPC1 (10.0.0.0/16), a strongSwan EC2 acting as the Customer Gateway
  device, an Elastic IP (this IP becomes the Customer Gateway address on the
  AWS-public side), one on-prem test server, the retained central audit bucket
  and this VPC's S3 Flow Log.
  DEPLOY THIS STACK FIRST - its StrongSwanElasticIP output is an input to the
  eu-west-1 stack.

# ---------------------------------------------------------------------------
# Parameters
# ---------------------------------------------------------------------------
Parameters:
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Existing EC2 key pair in us-east-1 for SSH access.

  AdminCidr:
    Type: String
    Description: >
      Required administrator public IPv4 address as a single-host /32. There is
      deliberately no permissive default: every deployment must make the
      administrative source explicit.
    AllowedPattern: '^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])/32$'
    ConstraintDescription: Supply one valid public IPv4 address followed by /32; 0.0.0.0/0 is rejected.

  AwsPublicCidr:
    Type: String
    Default: 10.1.0.0/16
    Description: CIDR of the remote AWS-public VPC (eu-west-1). Used for routing and SG rules.

  Vpn1Tunnel1Cidr:
    Type: String
    Default: 192.0.2.1/32
    AllowedPattern: '^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])/32$'
    Description: >
      First AWS VPN #1 outside address followed by /32. The TEST-NET default
      intentionally admits no production peer; update after VPN #1 is created.

  Vpn1Tunnel2Cidr:
    Type: String
    Default: 192.0.2.2/32
    AllowedPattern: '^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])/32$'
    Description: >
      Second AWS VPN #1 outside address followed by /32. The TEST-NET default
      intentionally admits no production peer; update after VPN #1 is created.

  InstanceType:
    Type: String
    Default: t3.micro
    AllowedValues: [t3.micro, t3.small]
    Description: Free-tier-friendly instance size.

  CreateCentralLogBucket:
    Type: String
    Default: 'true'
    AllowedValues: ['true', 'false']
    Description: >
      Create the retained S3 bucket used by both regional VPC Flow Logs and
      the multi-region CloudTrail. Set false only when updating an existing
      environment whose logging bucket is supplied below.

  ExistingCentralLogBucketName:
    Type: String
    Default: ''
    Description: >
      Existing logging bucket name. Required only when CreateCentralLogBucket
      is false; the bucket must already allow VPC Flow Logs and CloudTrail.

Conditions:
  ShouldCreateCentralLogBucket: !Equals [!Ref CreateCentralLogBucket, 'true']

# ---------------------------------------------------------------------------
# Resources
# ---------------------------------------------------------------------------
Resources:

  # ---- Network ----
  Vpc1:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - { Key: Name, Value: EG334S-onprem-vpc1 }

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - { Key: Name, Value: EG334S-onprem-igw }

  IgwAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc1
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc1
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - { Key: Name, Value: EG334S-onprem-subnet }

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc1
      Tags:
        - { Key: Name, Value: EG334S-onprem-rt }

  SubnetRouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref RouteTable

  DefaultRoute:
    Type: AWS::EC2::Route
    DependsOn: IgwAttachment
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Send traffic destined for the AWS-public VPC through the strongSwan box.
  RouteToAwsPublic:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: !Ref AwsPublicCidr
      InstanceId: !Ref StrongSwanInstance

  # ---- Security groups ----
  StrongSwanSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: strongSwan IPSec endpoint
      VpcId: !Ref Vpc1
      SecurityGroupIngress:
        # IKE + NAT-T are restricted to the two generated AWS endpoints.
        - { IpProtocol: udp, FromPort: 500,  ToPort: 500,  CidrIp: !Ref Vpn1Tunnel1Cidr, Description: IKE from VPN1 tunnel 1 }
        - { IpProtocol: udp, FromPort: 4500, ToPort: 4500, CidrIp: !Ref Vpn1Tunnel1Cidr, Description: NAT-T from VPN1 tunnel 1 }
        - { IpProtocol: udp, FromPort: 500,  ToPort: 500,  CidrIp: !Ref Vpn1Tunnel2Cidr, Description: IKE from VPN1 tunnel 2 }
        - { IpProtocol: udp, FromPort: 4500, ToPort: 4500, CidrIp: !Ref Vpn1Tunnel2Cidr, Description: NAT-T from VPN1 tunnel 2 }
        # Tunneled traffic returning from the AWS-public side.
        - { IpProtocol: -1, CidrIp: !Ref AwsPublicCidr, Description: Tunneled traffic from AWS-public }
        # In-VPC traffic (test server -> strongSwan).
        - { IpProtocol: -1, CidrIp: 10.0.0.0/16, Description: Local VPC }
        - { IpProtocol: tcp, FromPort: 22, ToPort: 22, CidrIp: !Ref AdminCidr, Description: SSH }
      Tags:
        - { Key: Name, Value: EG334S-onprem-strongswan-sg }

  ServerSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: On-prem test server
      VpcId: !Ref Vpc1
      SecurityGroupIngress:
        - { IpProtocol: icmp, FromPort: -1, ToPort: -1, CidrIp: !Ref AwsPublicCidr, Description: Ping from AWS-public }
        - { IpProtocol: -1, CidrIp: 10.0.0.0/16, Description: Local VPC }
        - { IpProtocol: tcp, FromPort: 22, ToPort: 22, CidrIp: !Ref AdminCidr, Description: SSH }
      Tags:
        - { Key: Name, Value: EG334S-onprem-server-sg }

  # ---- strongSwan customer-gateway device ----
  StrongSwanInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      # Region-agnostic latest Amazon Linux 2 AMI via SSM public parameter.
      ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}'
      KeyName: !Ref KeyPairName
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds: [ !Ref StrongSwanSg ]
      # CRITICAL: a router must forward packets that are not addressed to itself.
      SourceDestCheck: false
      UserData:
        Fn::Base64: |
          #!/bin/bash
          set -euxo pipefail
          yum update -y
          amazon-linux-extras install -y epel
          yum install -y strongswan
          # A router forwards; enable IPv4 forwarding persistently.
          echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-ipforward.conf
          sysctl -p /etc/sysctl.d/99-ipforward.conf
          systemctl enable strongswan || true
          # Tunnel-specific config (PSKs + AWS tunnel IPs) is applied by hand in the
          # runbook AFTER the eu-west-1 VPN connection exists.
      Tags:
        - { Key: Name, Value: EG334S-onprem-strongswan }

  StrongSwanEip:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - { Key: Name, Value: EG334S-onprem-strongswan-eip }

  StrongSwanEipAssoc:
    Type: AWS::EC2::EIPAssociation
    Properties:
      AllocationId: !GetAtt StrongSwanEip.AllocationId
      InstanceId: !Ref StrongSwanInstance

  # ---- On-prem test server (what you ping FROM) ----
  OnPremServer:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}'
      KeyName: !Ref KeyPairName
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds: [ !Ref ServerSg ]
      Tags:
        - { Key: Name, Value: EG334S-onprem-server }

  # ---- Central audit storage and us-east-1 VPC Flow Logs ----
  # This bucket is created in the first stack so the later eu-west-1 stack can
  # immediately attach its Flow Log and create the multi-region CloudTrail.
  CentralLogBucket:
    Type: AWS::S3::Bucket
    Condition: ShouldCreateCentralLogBucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        IgnorePublicAcls: true
        BlockPublicPolicy: true
        RestrictPublicBuckets: true
      Tags:
        - { Key: Name, Value: EG334S-central-audit-logs }
        - { Key: Project, Value: EG334S }

  CentralLogBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Condition: ShouldCreateCentralLogBucket
    Properties:
      Bucket: !Ref CentralLogBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: FlowLogsWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub '${CentralLogBucket.Arn}/AWSLogs/${AWS::AccountId}/*'
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
                s3:x-amz-acl: bucket-owner-full-control
              ArnLike:
                aws:SourceArn: !Sub 'arn:${AWS::Partition}:logs:*:${AWS::AccountId}:*'
          - Sid: FlowLogsAclCheck
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action:
              - s3:GetBucketAcl
              - s3:ListBucket
            Resource: !GetAtt CentralLogBucket.Arn
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub 'arn:${AWS::Partition}:logs:*:${AWS::AccountId}:*'
          - Sid: CloudTrailAclCheck
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !GetAtt CentralLogBucket.Arn
            Condition:
              StringEquals:
                aws:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:eu-west-1:${AWS::AccountId}:trail/EG334S-audit-trail'
          - Sid: CloudTrailWrite
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub '${CentralLogBucket.Arn}/AWSLogs/${AWS::AccountId}/*'
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
                aws:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:eu-west-1:${AWS::AccountId}:trail/EG334S-audit-trail'

  OnPremVpcFlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      ResourceId: !Ref Vpc1
      ResourceType: VPC
      TrafficType: ALL
      LogDestinationType: s3
      LogDestination: !If
        - ShouldCreateCentralLogBucket
        - !GetAtt CentralLogBucket.Arn
        - !Sub 'arn:${AWS::Partition}:s3:::${ExistingCentralLogBucketName}'
      MaxAggregationInterval: 600
      Tags:
        - { Key: Name, Value: EG334S-onprem-vpc-flow-log }
        - { Key: Project, Value: EG334S }

# ---------------------------------------------------------------------------
# Outputs
# ---------------------------------------------------------------------------
Outputs:
  StrongSwanElasticIP:
    Description: >
      >>> COPY THIS <<< It is the Customer Gateway IP for the eu-west-1 stack
      (parameter OnPremStrongSwanEIP).
    Value: !Ref StrongSwanEip

  StrongSwanPrivateIp:
    Description: Private IP of the strongSwan box (its leftid inside the tunnel is the EIP).
    Value: !GetAtt StrongSwanInstance.PrivateIp

  OnPremServerPrivateIp:
    Description: Private IP of the on-prem test server - ping this FROM the AWS-public server.
    Value: !GetAtt OnPremServer.PrivateIp

  Vpc1Id:
    Value: !Ref Vpc1

  PublicSubnetId:
    Description: Pass to any school-account integration stack that imports the Virginia subnet.
    Value: !Ref PublicSubnet

  RouteTableId:
    Description: Exported for approved future routing integrations.
    Value: !Ref RouteTable

  StrongSwanInstanceId:
    Description: Exported for approved future routing integrations.
    Value: !Ref StrongSwanInstance

  StrongSwanSecurityGroupId:
    Description: Exported for approved future routing integrations.
    Value: !Ref StrongSwanSg

  TestServerInstanceId:
    Description: Virginia private-path test host.
    Value: !Ref OnPremServer

  TestServerSecurityGroupId:
    Description: Exported for approved future test-path integrations.
    Value: !Ref ServerSg

  CentralLogBucketName:
    Description: >
      Pass this value to the eu-west-1 stack as CentralLogBucketName.
      The bucket is retained when this stack is deleted.
    Value: !If
      - ShouldCreateCentralLogBucket
      - !Ref CentralLogBucket
      - !Ref ExistingCentralLogBucketName

  CentralLogBucketArn:
    Value: !If
      - ShouldCreateCentralLogBucket
      - !GetAtt CentralLogBucket.Arn
      - !Sub 'arn:${AWS::Partition}:s3:::${ExistingCentralLogBucketName}'

  OnPremVpcFlowLogId:
    Value: !Ref OnPremVpcFlowLog
