Set up internal access to a private AWS API Gateway

I have a private API Gateway that needs to be accessed by resources over VPN and Peered VPCs. According to the AWS documentation, this is easy, you invoke like so:

curl -v https://vpce-01234567abcdef012-01234567.execute-api.us-east-1.vpce.amazonaws.com/test/pets -H 'Host: 01234567ab.execute-api.us-west-2.amazonaws.com'

Where the request is formatted as:

curl -v https://{public-dns-hostname}.execute-api.{region}.vpce.amazonaws.com/{stage} -H 'Host: {api-gateway-id}.execute-api.{region}.amazonaws.com'

Interestingly, when you use this format, the VPCE’s public DNS hostname will resolve publicly, but return internal IP addresses. This lets your VPN and Peered VPCs understand which IP it needs to route to.

The inconvenience with this approach is that I need to have my applications know to pass in a custom Host header and to keep up-to-date with any possible changes with the API Gateway ID. So how can we make this easier?

My solution is to use nginx as a proxy server to my VPC endpoint and have it pass in the Host header for us. In my nginx server snippet, I include:

location /$API_GATEWAY_STAGE {
  proxy_pass https://$VPC_ENDPOINT_ID.execute-api.$REGION.vpce.amazonaws.com/$API_GATEWAY_STAGE/;
  proxy_set_header Host $API_GATEWAY_ID.execute-api.$REGION.amazonaws.com;
}

From there, I put a pretty URL on top of my nginx proxy server and it’s all done!

In my specific case, I have access to a Kubernetes cluster that can create new host names. As a result, the change for me is easy, I just create the following YAML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/server-snippet: |
      location /$API_GATEWAY_STAGE {
        proxy_pass https://$VPC_ENDPOINT_ID.execute-api.$REGION.vpce.amazonaws.com/$API_GATEWAY_STAGE/;
        proxy_set_header Host $API_GATEWAY_ID.execute-api.$REGION.amazonaws.com;
      }      
  name: $NAME_OF_INGRESS
  namespace: $KUBERNETES_NAMESPACE
spec:
  tls:
    - hosts:
        - $API_NAME.$DOMAIN.$TLD
      secretName: $API_NAME-cert
  rules:
    - host: $API_NAME.$DOMAIN.$TLD
      http:
        paths:
          - backend:
              serviceName: http-svc  # This is a dummy service that does not exist
              servicePort: 80
            path: "/(.*)"