Forwarding CloudFront Host Header to API Gateway
H

2

17

We have a wildcard(*) subdomain pointing to a CloudFront distribution. The origin is API Gateway.

We need to know the original Host header within API Gateway so we can route the requests.

Simply whitelisting the Host header in CloudFront returns an error when accessing the CloudFront distribution via HTTP - presumably because API Gateway needs the Host header to know which API to invoke.

If this is the case, is it possible to forward the Host header via X-Forwarded-Host from CloudFront to the API Gateway? Or... is there an alternative way to use wildcard subdomains with API Gateway?

Halftimbered answered 30/8, 2016 at 8:16 Comment(10)
If you whitelist the Host header, it should pass the Host header through to the origin as Host (i.e. not as X-Forwarded-Host). Can you post the actual error you receive? Is it just a 500 with no content, or is there a body or error message? May also be worth reviewing the Cloudwatch Logs for the API Gateway for a more detailed error message.Sphygmic
@ChrisSimon passing the original request's host header as Host: would result in the request never arriving at API Gateway, which expects a single, assigned value in Host -- so there would be no logs generated there. The question here is how to funnel multiple request host values (wildcard) through to one target, without losing track of the original hostname, by sending it as an alternate header, like X-Forwarded-Host, created automatically by CloudFront.Marmite
@Michael-sqlbot precisely what I want (do you know if it's possible?!).Also, I can confirm that I get a 403 from CloudFront (ERROR. The request could not be satisfied. Bad request.) and that API Gateway never gets hit (and doesn't generate any logs in CloudWatch... but it does if you hit the API directly, not going via CloudFront).Halftimbered
It is possible to do it one-off, but not wildcard, natively. I'll write a full answer later today as time permits, but your best choices are (a) individual, almost-identical distributions, one per incoming hostname, using a Custom Origin Header to add a static X-Forwarded-Host: (easy to automate, though), or (b) EC2-based proxy between CF and API-GW to copy Host: to X-Forwarded-Host: before changing Host: and sending to API-GW. I have both scenarios in my systems today.Marmite
EC2-based proxy sounds best: the only reason we're needing to use wildcards is because CF distributions take so long to create and modify (~30 minutes), so ideally we want 1 CF distribution that we can re-use for all our live, test, dev and staging instances, which we can spin-up and tear-down without having to wait for CF to apply its changes. We thought wildcard CNAMEs in CF would solve this problem... looks like it does, just not if you're using API Gateway / Lambda as your origin :( thank you greatly for your time in answering :)Halftimbered
Wildcards would solve the problem if not for the fact that you need to funnel them down to a single target hostname... but if you think about it, you really don't want a single distro for live, test, dev... particularly not live + the others. Also, play with CloudFront some more... you'll find that the console's status often lags behind the actual speed of changes propagating, and once you have it going, changes should be rare. I'll include some proxy config in the answer, though.Marmite
It's actually the CloudFormation time I'm going off: if there are any updates to CloudFront in a change, it adds an additional 30 mins onto the stack update. In this particular instance, we're wanting to automatically spin-up full versions of our site for each branch pushed into GIT. We're doing that because it's a real 'nice to have' for development, and since we're using Lambda, there's no waste/cost in doing so. We're actually in a place now where we can do this... but adding CloudFront makes our 1 min stack deployment close to 35 mins (hence wanting to share a CF distro).Halftimbered
Ah, the conversation has clarified it - I see the dilemma now! Spinning up a new env for each branch is very nice - how much is cloudfront doing for you? Sharing a CF dist between environments is already varying from production... assuming branch deployments are mainly for automated testing, could you have a cfn parameter to disable cloudfront for branches, and configure your test clients to target the appropriate api gateway domain name directly? You could create a new stage/deployment per branch, use the API Gateway API to discover the endpoint, test, then tear down the stage.Sphygmic
We could abandon CF for branch deployments, but that would be drifting from how we have things in production. Especially since CDNs/caching can introduce bugs, we'd really like to be able to see all the pieces working together in the branch environments exactly as they would in production (with the exception of the backend DB). We were considering sharing a CF dist between all environments, so those branch environments would actually be identical to production.Halftimbered
Posted a further suggestion as an answer, as it's too complex for a comment - even though it's not quite an answer to your original question!Sphygmic
F
9

I'm answering this late in the day and even though it has an accepted answer because this question shows up at the top of a search for this issue:

These days it's entirely possible to dynamically forward the original Host header via X-Forwarded-Host from CloudFront to the API Gateway, without having to hard-code a custom origin header as suggested.

This can be accomplished by creating a Viewer Request edge function (a Lambda@Edge or a CloudFront function) that intercepts the request before it gets to CloudFront, maps the incoming Host header to X-Forwarded-Host and then appends the new X-Forwarded-Host to the request's headers before passing it on.

Then whitelist the X-Forwarded-Host header for the API Gateway origin.

In Node.js the edge function would look like:

export function handler(event, context, callback) {
    const request = event.Records[0].cf.request;

    request.headers['x-forwarded-host'] = [{
        key: 'X-Forwarded-Host',
        value: request.headers.host[0].value
    }];

    return callback(null, request);
}
Fabiola answered 2/11, 2022 at 12:33 Comment(1)
Been looking into this for far too long and this is the most complete answer that I have found so far. However, I still haven't been able to comprehend why CloudFront can't set the X-Forwarded-Host header itself. Why do we need to write code and pay for Lambda execution for this? Seems excessive for something so basic. At this point, it's probably easier and cheaper to just add a custom header to my origin to make this work.Shiflett
S
2

This isn't quite an answer to your original question, but it might be an alternative way of achieving your goals.

Firstly, sharing a CF distribution between all environments (including prod) carries risk with it - when you need to test a change to the CF configuration you will necessarily be modifying the prod CF dist with untested changes which could have significant consequences.

Secondly, while it's wonderful if you can test the whole environment at all stages in a CI/CD pipeline, it's not always possible (and CF is particularly bad for it) - so it's about finding a balance between short feedback cycles and thoroughness of testing.

The solution is usually to introduce extra stages to your pipeline, where the early stages give rapid feedback on the most common problems, and later stages give slower feedback on less frequent problems.

In your case, I'd suggest:

  1. Branch deployments do not deploy CF - tests target the API Gateway directly
  2. Master/Default deployments DO deploy CF - to a 'staging' environment - tests target the staging CF distribution
  3. Successfully tested releases to the 'staging' environment are promoted to production

By introducing the staging environment, you get rapid feedback on branch builds, but you still have the opportunity to test things behind the cache before going into prod.

If you are making changes to the CF configuration, you could make your deployment script dynamically decide to include CF in the branch deployment off some trigger (perhaps the presence of the word 'cloudfront' in the branch name - although that could be a bit 'magical' for some!) and you could test those changes on the branch before merging to master for testing in staging.

Sphygmic answered 2/9, 2016 at 23:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.