How to call cloudfront distribution with wrong Host header

797 Views Asked by At

Prerequisites

I have configured a custom domain for an API Gateway. The endpoint type is edge, as the API Gateway (RestApi) is edge.

I also issued a certificate through cdk. This is some of the code used:

  const zone = route53.HostedZone.fromLookup(stack, 'Z0557019************', {
    domainName: 'sub.example.com' // changed for SO post
  })

  const certificate = new DnsValidatedCertificate(stack, 'lws-api-certificate', {
    domainName: 'mysub.sub.example.com', // changed for SO post
    region: 'us-east-1',
    hostedZone: zone
  })

  const api = new apigateway.RestApi(stack, 'legacy-wrapper-api', {
    restApiName: 'My API',
    domainName: {
      domainName: 'mysub.sub.example.com',
      certificate: certificate,
      endpointType: EndpointType.EDGE
      
    },
    policy: apiResourcePolicy
  })

  new route53.ARecord(stack, 'ApiGatewayAliasRecord', {
    zone: zone,
    recordName: 'mysub',
    target: route53.RecordTarget.fromAlias(new targets.ApiGateway(api))
  })

So, I understand that a Cloudfront distribution is created. However, it is not listed in CloudFront.

In ApiGateway console:

enter image description here

What works

The custom domain was successfully created and applied and I am able to access my API gateway through mysub.sub.example.com.

What is not working

My issue is the following. I have clients which send a wrong Host Header. We can call our api fine when Host Header is mysub.sub.example.com. However, one client sends erroneously sub.another-example.com.

I know that this is a wrong header. But there is nothing we can do about this client sending the wrong header. Unfortunately!

What are my options?

  1. switch to http
  2. add foreign Host to allowed Hosts (certificate)?
  3. Create some sort of proxy instance which allows incoming traffic from sub.another-example.com and redirect to

Regarding 1.

How can I allow http? I am seeing an option to set a minimum security level, but the minimum is TSL1.0. Can I even create a CloudFront distribution that accepts http? Again, in console I cannot see the distribution so I have to do it via CDK.

Regarding 2.

How can I do that, specifically in CDK?

Regarding 3.

Any creative ideas?

Help is much appreciated.

Additional resource: A curl call to the custom domain with wrong header brings this:

$ curl --header "Authorization: Bearer 9c904d3##########################" --header "Host: sub.another-example.com" -v https://mysub.sub.example.com/v2.2/api/posts/list
* TCP_NODELAY set
* Connected to mysub.sub.example.com (18.66.###########) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=mysub.sub.example.com
*  start date: May 27 00:00:00 2022 GMT
*  expire date: Jun 25 23:59:59 2023 GMT
*  subjectAltName: host "mysub.sub.example.com" matched cert's "mysub.sub.example.com"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5627934cd8c0)
> GET /v2.2/api/posts/list HTTP/2
> Host: sub.another-example.com                    <----------------- This is the problematic value
> user-agent: curl/7.68.0
> accept: */*
> authorization: Bearer 9c904d3########################## 
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 403
< server: CloudFront
< date: Mon, 30 May 2022 11:21:50 GMT
< content-type: text/html
< content-length: 915
< x-cache: Error from cloudfront
< via: 1.1 f7d063##########################.cloudfront.net (CloudFront)
< x-amz-cf-pop: FRA56-P5
< x-amz-cf-id: _KTRiNK8z74PB4qCwS##################################################==
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Bad request.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: _KTRiNK8z74PB4qCwS##################################################==
</PRE>
<ADDRESS>
</ADDRESS>
* Connection #0 to host mysub.sub.example.com left intact
</BODY></HTML>
1

There are 1 best solutions below

0
On

As often when the questions are so specific I found a solution myself.

It is option 2! All with CDK.

Those are the steps:

  1. As I mentioned the Cloudfront distribution created for the custom domain is hidden in console, so you need to create an new one.
  2. you want an "Alternate domain name" with the value of "sub.another-example.com" to be appearing on the distributions settings
  3. In order for that to work, you will need to create a certificate which also includes the foreign domain
  4. for that certificate to validate you will need to put a CNAME to the DNS settings of that domain (so you need to be able to do that).
  5. your certificate will not auto-validate by DNS of course! I found a hack for that which requires deploying two times.

Cloudfront distribution with alternate domain and cert

This is the final code.

import { CloudFrontToApiGateway } from '@aws-solutions-constructs/aws-cloudfront-apigateway'

let certificate
if (buildConfig.isFirstDeploy) {
  // This is a helper for an otherwise manual step. Unfortunately, there is no better fix for foreign host certs.
  // The first deploy will fail with message '*.another-example.com. is not permitted in zone *.sub.example.com
  // However, you can then manually validate the *.another-example.com domain and use the ARN for following deploys
  // https://stackoverflow.com/questions/58101817/cdk-dnsvalidatedcertificate-can-create-a-certificate-in-a-linked-aws-account-w
  certificate = new DnsValidatedCertificate(stack, 'api-certificate', {
    domainName: `mysub.sub.example.com`,
    region: 'us-east-1', // required for edge https://docs.aws.amazon.com/acm/latest/userguide/acm-services.html
    hostedZone: zone,
    subjectAlternativeNames: ['*.another-example.com']
  })
} else {
  certificate = Certificate.fromCertificateArn(
    stack,
    'api-certificate',
    'arn:aws:acm:us-east-1:#############:certificate/#################' // you only know that after first deploy, copy from console
  )
}

const cloudfront = new CloudFrontToApiGateway(stack, 'MyCloudFrontDistribution', {
  existingApiGatewayObj: api,
  cloudFrontDistributionProps: {
    certificate: certificate,
    domainNames: [
      `mysub.sub.example.com`,
      '*.another-example.com'
    ]
  }
})