How to HTTPS Proxy integration from REST API Gateway to Network Load Balancer
Tags: aws, apigateway, ecs, devops
Published: 2023-11-28 09:24:17

If you have rest api gateway in your application to exposing EKS/ECS services and you still working with http proxy integration you need to change it right now.
here is a tutorial on how to change your **http** proxy integration to **https** proxy integration to your VPC services.
all you need is a public domain.

**So What Is The Problem?**
Now a days when you want to do a proxy integration from your rest api gateway to your VPC ECS\EKS services you have to create network load balancer and work with [this Amazon's official tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html)
in this tutorial Amazon recommend to proxy our request to the network load balancer with the load balancer's http DNS, but what if we want to keep our secured tls connection?

So here is a lot of solutions that just don't work:

_**Solution that didn't work #1**_
Create self signed / private ca - we can create route 53 private domain and self signed certificate or private ca certificate, but Amazon's rest api gateway proxy integration don't support any private ca or self signed certificate, api gateway expect public certified ca certificate as described in [Amazon's official documentation ](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-supported-certificate-authorities-for-http-endpoints.html)

_**Solution that didn't work #2**_
Create private domain and route it to network load balancer DNS - as i mentioned above api gateway expect public certificate but also it cannot get any unknown domain name like: "application.local" etc..

_**Solution that didn't work #3**_
Route to your network load balancer https url - this cannot be done because your network load balancer DNS is an Amazon's DNS and network load balancer don't have this domain certificate and therefore api gateway cannot invoke this request to an not secured connection.

_**Solution that didn't work #4**_
we have a domain and certificate for our api gateway, why not to use this? - lets assume that we have a domain and certificate, api.example.com, we cannot setup a new private hosted zone with the same name api.example.com because it is already in use for the record to point to the api gateway custom domain name, and we must have a private hosted zone so that the VPC link knows where to route to.

<u>**This is how we can solve it?**</u>

Let's assume you have rest api gateway and in your VPC you have network load balancer for your application and you're using VPC link to connect to your application.
the api gateway have a domain name for example: api.example.com and certificate for this domain, this is how you solve it:

We are creating new public certificate because api gateway demands only public certificate as i mentioned before, for this we are creating also a new public hosted zone that we can validate the certificate.
We are creating a private hosted zone with the same name as the public hosted zone and now we have a public certificate for our private hosted zone, now we can attach this certificate to our network load balancer and create a record that our api gateway can connect in https to our network load balancer.

**<u>Tutorial:</u>**

1. Create public route 53 with this name for example: app.example.com
2. Create NS record in example.com to your new subdomain app.example.com - with NS from app.example.com
3. Create certificate in certificate manager for this FQDN app.example.com and *.app.example.com and pass the validation process using route 53 records
4. Create private route 53 hosted zone with the same domain app.example.com
5. Create a record for you application network load balancer in the private hosted zone for example - application.app.example.com
6. Add the public certificate we created in section 3. to your network load balancer.
7. Change your api gateway proxy integration url to your newly created a record - https://application.app.example.com/{proxy}, and don't forget to deploy this api.

And now you can remove the http port 80 listener from your application load balancer, and your working with https from your api gateway to your application load balancer.

And here it is in CloudFormation Template:

```yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  all resources for creating your proxy integration using https

Parameters:

  SubnetName:
    Type: String
    Description: subnet name
  
  TargetGroupServiceArn:
    Type: String
    Description: target group service arn
  
  ServiceName:
    Type: String
    Description: eks/ecs service name

  DomainName:
    Type: String
    Default: example.com
    Description: youre domain name
  
  PrivateSubdomainName:
    Type: String
    Default: app
    Description: subdomain for your vpc resources
  
  PublicSubdomainName:
    Type: String
    Default: api
    Description: subdomain for your api gateway

  VPC:
    Type: String
    Description: vpc id
  
  StageName:
    Type: String
    Default: v1
    Description: api gateway stage name

Resources:

  #your public domain

  PublicDomainHostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      HostedZoneConfig:
        Comment: 'public hosted zone'
      Name: !Ref DomainName
  
  PublicDomainCertificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainName
      ValidationMethod: DNS
      SubjectAlternativeNames:
        - !Join ['.',['*', !Ref DomainName]]
      DomainValidationOptions:
        - DomainName: !Ref DomainName
          HostedZoneId: !Ref PublicDomainHostedZone
  
  #the public domain for creating certificate

  PublicApplicationDomainHostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      HostedZoneConfig:
        Comment: 'public application hosted zone'
      Name: !Join ['.',[ !Ref PrivateSubdomainName, !Ref DomainName]]
  
  PublicApplicationNSHostedZoneRecordSet:
    Type: 'AWS::Route53::RecordSet'
    Properties:
        HostedZoneId: !Ref PublicDomainHostedZone
        Name: !Join ['.',[ !Ref PrivateSubdomainName, !Ref DomainName]]
        Type: NS
        TTL: '900'
        ResourceRecords: !GetAtt PublicApplicationDomainHostedZone.NameServers
  
  PublicApplicationCertificate:
    Type: AWS::CertificateManager::Certificate
    DependsOn: PublicApplicationNSHostedZoneRecordSet
    Properties:
      DomainName: !Join ['.',[ !Ref PrivateSubdomainName, !Ref DomainName]]
      ValidationMethod: DNS
      SubjectAlternativeNames:
        - !Join ['.',['*', !Ref PrivateSubdomainName, !Ref DomainName]]
      DomainValidationOptions:
        - DomainName: !Join ['.',[ !Ref PrivateSubdomainName, !Ref DomainName]]
          HostedZoneId: !Ref PublicApplicationDomainHostedZone
  
  # private hosted zone for api gateway --> network load balancer

  PrivateApplicationDomainHostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      HostedZoneConfig:
        Comment: 'private application hosted zone'
      Name: !Join ['.',[ !Ref PrivateSubdomainName, !Ref DomainName]]
      VPCs:
        - VPCId: !Ref VPC
          VPCRegion : !Ref 'AWS::Region'
  
  # network load balancer for your application

  PrivateLoadBalancer:
    Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
    Properties:
      Scheme: internal
      Subnets:
        - !Ref SubnetName
      Type: network
  
  SecuredPrivateLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
      - Type: forward
        TargetGroupArn: !Ref TargetGroupServiceArn
      LoadBalancerArn: !Ref PrivateLoadBalancer
      Port: 443
      Protocol: TLS
      SslPolicy: 'ELBSecurityPolicy-TLS13-1-2-2021-06'
      Certificates:
        - CertificateArn: !Ref PublicApplicationCertificate
  
  LoadBalancerDNSRecordSet:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Ref PrivateApplicationDomainHostedZone
      Comment: !Sub DNS record for the ${ServiceName} service
      RecordSets:
        - Name: !Join ['.',[!Ref ServiceName, !Ref PrivateSubdomainName, !Ref DomainName]]
          Type: A
          AliasTarget:
            EvaluateTargetHealth: false
            HostedZoneId: !GetAtt PrivateLoadBalancer.CanonicalHostedZoneID
            DNSName: !GetAtt PrivateLoadBalancer.DNSName
  
  #api gateway public domain name and certificate

  ApiGatewayDomainName:
    Type: AWS::ApiGateway::DomainName
    Properties:
      RegionalCertificateArn: !Ref PublicDomainCertificate
      DomainName: !Join ['.',[!Ref PublicSubdomainName, !Ref DomainName]]
      SecurityPolicy: TLS_1_2
      EndpointConfiguration:
        Types:
         - 'REGIONAL'

  ApiGatewayDomainRoute53RecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref PublicDomainHostedZone
      Comment: record for api gateway public domain
      Name: !Join ['.',[!Ref PublicSubdomainName, !Ref DomainName]]
      Type: A
      AliasTarget:
        DNSName: !GetAtt ApiGatewayDomainName.RegionalDomainName
        HostedZoneId: !GetAtt ApiGatewayDomainName.RegionalHostedZoneId
  
  # api gateway for your application
  
  RestApiGateway:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Sub ${ServiceName}-apigw
      Description: !Sub Api Gateway for ${ServiceName}
      EndpointConfiguration:
        Types: [EDGE]
  
  ApiGatewayVpcLink:
    Type: AWS::ApiGateway::VpcLink
    Properties:
      Description: !Sub vpc link for ${ServiceName}
      Name: !Sub ${ServiceName}-private-link
      TargetArns: [!Ref PrivateLoadBalancer]
  
  ApiGatewayProxyResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApiGateway
      ParentId: !GetAtt RestApiGateway.RootResourceId
      PathPart: '{proxy+}'
  
  ApiGatewayProxyMethod:
    Type: AWS::ApiGateway::Method
    DependsOn: LoadBalancerDNSRecordSet
    Properties:
      RestApiId: !Ref RestApiGateway
      ResourceId: !Ref ApiGatewayProxyResource
      HttpMethod: ANY
      RequestParameters:
        method.request.path.proxy: true
      Integration:
        ConnectionType: VPC_LINK
        Type: HTTP_PROXY
        IntegrationHttpMethod: ANY
        ConnectionId: !Ref ApiGatewayVpcLink
        Uri: !Join ['',['https://', !Ref ServiceName, '.', !Ref PrivateSubdomainName, '.', !Ref DomainName , '/{proxy}']]
        RequestParameters:
          integration.request.path.proxy: 'method.request.path.proxy'
  
  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: ApiGatewayProxyMethod
    Properties:
      RestApiId: !Ref RestApiGateway
      Description: !Sub deployment for {ServiceName} api gateway
      StageName: !Ref StageName
      StageDescription:
        TracingEnabled: true
        LoggingLevel: INFO
        MetricsEnabled: true
        DataTraceEnabled: true
  
  ApiGatewayPublicDomainMapping:
    Type: AWS::ApiGateway::BasePathMapping
    DependsOn: ApiGatewayDeployment
    Properties: 
      BasePath: !Ref ServiceName
      DomainName: !Ref ApiGatewayDomainName
      RestApiId: !Ref RestApiGateway
      Stage: !Ref StageName

```