AWSTemplateFormatVersion: '2010-09-09'
Description: |
  Creates VPC Interface Endpoints pointing at Traversal's PrivateLink Endpoint
  Services. Attach your own security groups to each endpoint to control access
  independently per service.

  All four service classes (api, app, edge, ingest) are supported. Provide the
  Endpoint Service name for whichever services Traversal has shared with you;
  leave others blank. Update the stack later to enable additional services as
  they come online.

  Prerequisites:
  - Traversal has delegated the customer subdomain (e.g. traversal.<your>.zone)
    to Traversal's Route53 nameservers (you added an NS record in your parent
    zone). Domain verification then happens automatically on Traversal's side.

  After deployment, your VPC will resolve queries for the Traversal-supplied
  subdomains to the private IPs of these endpoints, keeping all traffic on the
  AWS network.

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Network - required for any endpoint"
        Parameters:
          - VpcId
          - SubnetIds
      - Label:
          default: "Endpoint Service Names - provide one per service Traversal has enabled"
        Parameters:
          - ApiEndpointServiceName
          - AppEndpointServiceName
          - EdgeEndpointServiceName
          - IngestEndpointServiceName
      - Label:
          default: "Security Groups - provide one or more per service endpoint"
        Parameters:
          - ApiSecurityGroupIds
          - AppSecurityGroupIds
          - EdgeSecurityGroupIds
          - IngestSecurityGroupIds
      - Label:
          default: "Optional"
        Parameters:
          - EnablePrivateDns

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC in which to create the Interface Endpoints.

  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Description: |
      Subnets (one per AZ recommended) where the endpoint network interfaces
      will be placed. Must be in the same VPC as VpcId.

  ApiEndpointServiceName:
    Type: String
    Default: ""
    Description: |
      Traversal-supplied Endpoint Service name for api traffic
      (com.amazonaws.vpce.<region>.vpce-svc-xxxxxxxxxxxxxxxxx). Resolves
      api.<your-subdomain> inside this VPC. Leave blank to skip.

  AppEndpointServiceName:
    Type: String
    Default: ""
    Description: |
      Endpoint Service name for app (UI) traffic. Resolves app.<your-subdomain>
      inside this VPC. Leave blank to skip.

  EdgeEndpointServiceName:
    Type: String
    Default: ""
    Description: |
      Endpoint Service name for edge/relay traffic with mTLS. Resolves
      edge.<your-subdomain> inside this VPC. Leave blank to skip.

  IngestEndpointServiceName:
    Type: String
    Default: ""
    Description: |
      Endpoint Service name for ingest traffic with mTLS. Resolves
      ingest.<your-subdomain> inside this VPC. Leave blank to skip.

  ApiSecurityGroupIds:
    Type: List<AWS::EC2::SecurityGroup::Id>
    Description: |
      Security group IDs to attach to the api endpoint. Must allow inbound HTTPS
      (443) from sources that need to reach the api service. Required when
      ApiEndpointServiceName is provided; supply any valid SG ID otherwise.

  AppSecurityGroupIds:
    Type: List<AWS::EC2::SecurityGroup::Id>
    Description: |
      Security group IDs to attach to the app endpoint. Must allow inbound HTTPS
      (443) from sources that need to reach the app service. Required when
      AppEndpointServiceName is provided; supply any valid SG ID otherwise.

  EdgeSecurityGroupIds:
    Type: List<AWS::EC2::SecurityGroup::Id>
    Description: |
      Security group IDs to attach to the edge endpoint. Must allow inbound HTTPS
      (443) from sources that need to reach the edge service. Required when
      EdgeEndpointServiceName is provided; supply any valid SG ID otherwise.

  IngestSecurityGroupIds:
    Type: List<AWS::EC2::SecurityGroup::Id>
    Description: |
      Security group IDs to attach to the ingest endpoint. Must allow inbound HTTPS
      (443) from sources that need to reach the ingest service. Required when
      IngestEndpointServiceName is provided; supply any valid SG ID otherwise.

  EnablePrivateDns:
    Type: String
    Default: "false"
    AllowedValues: ["true", "false"]
    Description: |
      Enable private DNS for the endpoints. When true, AWS auto-resolves the
      Traversal-supplied subdomains to the endpoint private IPs inside this VPC.
      Requires the Traversal Endpoint Service domains to be verified first.

Conditions:
  HasApi:    !Not [!Equals [!Ref ApiEndpointServiceName,    ""]]
  HasApp:    !Not [!Equals [!Ref AppEndpointServiceName,    ""]]
  HasEdge:   !Not [!Equals [!Ref EdgeEndpointServiceName,   ""]]
  HasIngest: !Not [!Equals [!Ref IngestEndpointServiceName, ""]]

Resources:
  ApiEndpoint:
    Condition: HasApi
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Ref ApiEndpointServiceName
      VpcId: !Ref VpcId
      SubnetIds: !Ref SubnetIds
      SecurityGroupIds: !Ref ApiSecurityGroupIds
      PrivateDnsEnabled: !Ref EnablePrivateDns
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-traversal-api"

  AppEndpoint:
    Condition: HasApp
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Ref AppEndpointServiceName
      VpcId: !Ref VpcId
      SubnetIds: !Ref SubnetIds
      SecurityGroupIds: !Ref AppSecurityGroupIds
      PrivateDnsEnabled: !Ref EnablePrivateDns
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-traversal-app"

  EdgeEndpoint:
    Condition: HasEdge
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Ref EdgeEndpointServiceName
      VpcId: !Ref VpcId
      SubnetIds: !Ref SubnetIds
      SecurityGroupIds: !Ref EdgeSecurityGroupIds
      PrivateDnsEnabled: !Ref EnablePrivateDns
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-traversal-edge"

  IngestEndpoint:
    Condition: HasIngest
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Ref IngestEndpointServiceName
      VpcId: !Ref VpcId
      SubnetIds: !Ref SubnetIds
      SecurityGroupIds: !Ref IngestSecurityGroupIds
      PrivateDnsEnabled: !Ref EnablePrivateDns
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-traversal-ingest"

Outputs:
  ApiEndpointId:
    Condition: HasApi
    Description: ID of the api VPC Interface Endpoint
    Value: !Ref ApiEndpoint

  AppEndpointId:
    Condition: HasApp
    Description: ID of the app VPC Interface Endpoint
    Value: !Ref AppEndpoint

  EdgeEndpointId:
    Condition: HasEdge
    Description: ID of the edge VPC Interface Endpoint
    Value: !Ref EdgeEndpoint

  IngestEndpointId:
    Condition: HasIngest
    Description: ID of the ingest VPC Interface Endpoint
    Value: !Ref IngestEndpoint

  ApiEndpointDnsName:
    Condition: HasApi
    Description: Regional DNS name for the api VPC Interface Endpoint
    Value: !Select [1, !Split [":", !Select [0, !GetAtt ApiEndpoint.DnsEntries]]]

  AppEndpointDnsName:
    Condition: HasApp
    Description: Regional DNS name for the app VPC Interface Endpoint
    Value: !Select [1, !Split [":", !Select [0, !GetAtt AppEndpoint.DnsEntries]]]

  EdgeEndpointDnsName:
    Condition: HasEdge
    Description: Regional DNS name for the edge VPC Interface Endpoint
    Value: !Select [1, !Split [":", !Select [0, !GetAtt EdgeEndpoint.DnsEntries]]]

  IngestEndpointDnsName:
    Condition: HasIngest
    Description: Regional DNS name for the ingest VPC Interface Endpoint
    Value: !Select [1, !Split [":", !Select [0, !GetAtt IngestEndpoint.DnsEntries]]]
