HAProxy – Using as Reverse Proxy for AWS API Gateway

amazon-api-gatewayamazon-web-serviceshaproxy

As the title suggest, I have an AWS API Gateway endpoint that I want to put behind HAProxy.

This is my current HAProxy configuration

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

listen  http
        bind 127.0.0.1:8080
        maxconn     18000

        acl api_gateway path_beg /api-gateway
        use_backend api-gateway-backend if api-gateway

backend api-gateway-backend
        http-request set-header Host xxxxx.execute-api.ap-southeast-2.amazonaws.com
        server api-gateway xxxxx.execute-api.ap-southeast-2.amazonaws.com:443

When I hit the /api-gateway endpoint on my HAProxy, I get 400 Bad Request. See below:

api gateway 400

I tried to change the backend to use this server api-gateway xxxxx.execute-api.ap-southeast-2.amazonaws.com:443 ssl verify none but I got 503 Service Unavailable instead.

I think this could be related to SSL SNI configuration that I need to enable on HAProxy, see this forum post https://forums.aws.amazon.com/thread.jspa?threadID=240197

Best Answer

If you don't use HTTPS then CloudFront will return the 400 Bad Request¹ error because API Gateway doesn't support HTTP.

Adding ssl verify none enables HTTPS to the back-end, CloudFront just closes the connection, causing HAProxy to log the session state at disconnection as SC-- and return the local 503 Service Unavailable error.

The solution is indeed sending Server Name Identification (SNI). Otherwise, CloudFront's front-end doesn't know which SSL certificate to offer you -- the generic *.cloudfront.net wildcard cert, or the one for *.execute-api.ap-southeast-2.amazonaws.com or one of probably hundreds of thousands of other possibilities. That's what SNI does in this case -- it tells the server what name you're connecting to.

The solution -- this is a single line, shown as multiple lines for clarity:

server api-gateway 
       xxxxx.execute-api.ap-southeast-2.amazonaws.com:443
       ssl
       verify none
       sni str(xxxxx.execute-api.ap-southeast-2.amazonaws.com)

You have to use the str() string sample fetch here, because the sni server keyword expects a sample fetch expression (for cases where you wanted to use the incoming request's SNI, for example).

The problem in the question on the forum was not actually related to SNI -- that part of the configuration was correct. The problem there was that they failed to do what you already did correctly -- http-request set-header host ... so that CloudFront sees the correct Host: header.


Note also the advice on the forum that this is a "very bad idea" seems distinctly misplaced.


¹ 400 CloudFront -- in some cases -- returns "Bad Request" in the body but the actual HTTP status code is 403 which corresponds to Forbidden.