AWS API Gateway notes¶
How to make a private REST API using AWS API Gateway that is only accessible from inside a VPC.
Configure a
AWS::ApiGateway::RestApi
(V1) which has the default (public endpoint) disabled, the endpoint configuration set toPRIVATE
add a a resource policy enforcing API access only from a list of authorized VPC.
Front the API with an ALB in the same VPC.
Configure TLS termination on ALB with an ACM certificate.
The ALB is the only place the API can be reached via custom DNS. The TLS certificate used by the ALB is also used to configure the custom domain on the REST API since without that API gateway service has no mapping internally for requests from the ALB and will return a 403 Forbidden.
The API GW
custom domain
configuration ties the cert to the API and stage. Even with this configuration
the API gateway will never serve the custom domain certificate, it will serve
the standard *.execute- api.region.amazonaws.com
. The ALB is used to proxy
requests the VPC endpoint with the correct host SNI and thanks to ALB not doing
any kind of cert validation on its backends. The ALB Targets already provisioned
`exectute-api`` VPC ENIs.
It sounds like a lot of work to get a private REST API and it is because:
API Gateway REST API do not support custom domain names with private (this format is only available):
{public-dns-hostname}.execute-api.access.{region}.vpceamazonaws.com
API Gateway REST API do not support TLS 1.3 (yet)
Perhaps AWS will make this easier in the future since the ALB makes this expensive to do at scale.
Connecting to private API without custom domain mapping¶
This is what happens when you try and connect to an REST private API gateway
when you point its A record to the ENI of the execute-API
VCP endpoint, but
forget to create the custom domain / or map it to the API stage. (it gives a 403
Forbidden ForbiddenException
):
- Trying 10.0.0.91:443...
- Connected to restprivate.cetinich.net (10.0.0.91) port 443 (#0)
- ALPN, offering h2
- ALPN, offering http/1.1
- Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
- successfully set certificate verify locations:
- CAfile: /etc/pki/tls/certs/ca-bundle.crt
- CApath: none
- TLSv1.2 (OUT), TLS header, Certificate Status (22):
- TLSv1.2 (OUT), TLS handshake, Client hello (1):
- TLSv1.2 (IN), TLS handshake, Server hello (2):
- TLSv1.2 (IN), TLS handshake, Certificate (11):
- TLSv1.2 (IN), TLS handshake, Server key exchange (12):
- TLSv1.2 (IN), TLS handshake, Server finished (14):
- TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
- TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
- TLSv1.2 (OUT), TLS handshake, Finished (20):
- TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
- TLSv1.2 (IN), TLS handshake, Finished (20):
- SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
- ALPN, server accepted to use http/1.1
- Server certificate:
- subject: CN=\*.execute-api.ap-southeast-1.amazonaws.com
- start date: Mar 16 00:00:00 GMT
- expire date: Dec 14 23:59:59 GMT
- issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
- SSL certificate verify ok.
> GET /prod HTTP/1.1 Host: restprivate.cetinich.net User-Agent: curl/7.79.1
> Accept: _/_
- Mark bundle as not supporting multiuse < HTTP/1.1 403 Forbidden < Server:
Server < Date: Fri, 09 Jun 14:40:23 GMT < Content-Type:
application/json < Content-Length: 23 < Connection: keep-alive <
x-amzn-RequestId: 2a89d78e-a1a8-4626-bd1c-ba786a877c30 < x-amzn-ErrorType:
ForbiddenException < x-amz-apigw-id: GQX5qEuCSQ0FTnA= <
- Connection #0 to host restprivate.cetinich.net left intact
{"message":"Forbidden"}
Once you fix up the mapping on the API gateway custom domain, you will get a
response from your API gateway AccessDeniedException
. Note that API gateway
service does not (and will not) serve the ACM certificate used to configure the
custom domain, AWS only uses that information to create a mapping from the
public API gateway service to your API Gateway config, not for TLS reasons:
If you have mapped the stage name as part of the custom domain configuration, do not include the stage name in the URL as the API gateway custom domain configuration will map requests to the stage name for you.
See this for information on how to troubleshoot 403 errors.
curl -v -k https://restprivate.cetinich.net/
- Trying 10.0.0.91:443...
- Connected to restprivate.cetinich.net (10.0.0.91) port 443 (#0)
- ALPN, offering h2
- ALPN, offering http/1.1
- Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
- successfully set certificate verify locations:
- CAfile: /etc/pki/tls/certs/ca-bundle.crt
- CApath: none
- TLSv1.2 (OUT), TLS header, Certificate Status (22):
- TLSv1.2 (OUT), TLS handshake, Client hello (1):
- TLSv1.2 (IN), TLS handshake, Server hello (2):
- TLSv1.2 (IN), TLS handshake, Certificate (11):
- TLSv1.2 (IN), TLS handshake, Server key exchange (12):
- TLSv1.2 (IN), TLS handshake, Server finished (14):
- TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
- TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
- TLSv1.2 (OUT), TLS handshake, Finished (20):
- TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
- TLSv1.2 (IN), TLS handshake, Finished (20):
- SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
- ALPN, server accepted to use http/1.1
- Server certificate:
- subject: CN=\*.execute-api.ap-southeast-1.amazonaws.com
- start date: Mar 16 00:00:00 GMT
- expire date: Dec 14 23:59:59 GMT
- issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
- SSL certificate verify ok.
> GET / HTTP/1.1 Host: restprivate.cetinich.net User-Agent: curl/7.79.1
> Accept: _/_
- Mark bundle as not supporting multiuse < HTTP/1.1 403 Forbidden < Server:
Server < Date: Fri, 09 Jun 14:49:15 GMT < Content-Type:
application/json < Content-Length: 182 < Connection: keep-alive <
x-amzn-RequestId: cc0a5b55-67d8-44f4-ba78-285e735b3110 < x-amzn-ErrorType:
AccessDeniedException < x-amz-apigw-id: GQZMxHW0yQ0FlYA= <
- Connection #0 to host restprivate.cetinich.net left intact {"Message":"User:
anonymous is not authorized to perform: execute-api:Invoke on resource:
arn:aws:execute-api:ap-southeast-1:**\*\*\*\***8502:j1su55pcdb/prod/GET/
with an explicit deny"}
This is what a test integration call from API gateway produces on the other side of the NLB
10.0.9.178 - - [11/Jun 05:58:11] “GET / HTTP/1.1” 200 - INFO:root:GET request, Path: / Headers: User-Agent: AmazonAPIGateway_j1su55pcdb x-amzn-apigateway-api-id: j1su55pcdb Host: test-f7574a2b7c741001.elb.ap-southeast-1.amazonaws.com Connection: Keep-Alive
API Gateway Fargate ECS NLB with VPC link¶
You could also use VPC Link to connect to a Fargate ECS service although this still does not solve the issue of TLS.
Request URL: https://apigateway.ap-southeast-1.amazonaws.com/restapis/j1su55pcdb/resources?embed=methods&limit=500
{
"item": [
{
"id": "17ol23hv6b",
"path": "/"
},
{
"id": "rhsrhp",
"parentId": "17ol23hv6b",
"path": "/{proxy+}",
"pathPart": "{proxy+}",
"resourceMethods": {
"ANY": {
"apiKeyRequired": false,
"authorizationType": "NONE",
"httpMethod": "ANY",
"methodIntegration": {
"cacheKeyParameters": [
"method.request.path.proxy"
],
"cacheNamespace": "rhsrhp",
"connectionId": "39ulb7",
"connectionType": "VPC_LINK",
"httpMethod": "ANY",
"integrationResponses": {
"200": {
"responseTemplates": {},
"statusCode": "200"
}
},
"passthroughBehavior": "WHEN_NO_MATCH",
"requestParameters": {
"integration.request.path.proxy": "method.request.path.proxy"
},
"timeoutInMillis": 29000,
"type": "HTTP_PROXY",
"uri": "http://test-f7574a2b7c741001.elb.ap-southeast-1.amazonaws.com"
},
"requestParameters": {
"method.request.path.proxy": true
}
}
}
}
]
}
Comments
comments powered by Disqus