CloudFront not sending custom headers to origin for additional behavior
Situation
I am currently in the process of migrating on of my pet projects from another provider to AWS. As a first step, I have created a CloudFront distribution sending all requests as-is to the loadbalancer my application is currently running on (external provider).
The CDK stack I started with looks like follows:
package mypackagename; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.services.certificatemanager.Certificate; import software.amazon.awscdk.services.cloudfront.*; import software.amazon.awscdk.services.cloudfront.origins.HttpOrigin; import software.constructs.Construct; import java.util.List; import java.util.Map; public class MyServiceNameCloudfrontCdkStack extends Stack { public MyServiceNameCloudfrontCdkStack(final Construct scope, final String id, final StackProps props, final Config config) { super(scope, id, props); Distribution.Builder.create(this, "cloudfront") .priceClass(PriceClass.PRICE_CLASS_ALL) .httpVersion(HttpVersion.HTTP2) .enableIpv6(true) .domainNames(List.of(config.domain())) .certificate(Certificate.fromCertificateArn(this, "sslcert", config.sslCertArn())) .minimumProtocolVersion(SecurityPolicyProtocol.TLS_V1_2_2021) .defaultBehavior( BehaviorOptions.builder() .origin( HttpOrigin.Builder.create("<hostname of the LB at external provider>") .protocolPolicy(OriginProtocolPolicy.HTTP_ONLY) .httpPort(8080) .customHeaders(Map.of( "Forwarded", String.format("host=%s;proto=https", config.domain()), "X-Forwarded-Host", config.domain(), "X-Forwarded-Proto", "https", "X-Forwarded-Port", "443" )) .build() ) .compress(true) .viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS) .allowedMethods(AllowedMethods.ALLOW_ALL) .cachePolicy(CachePolicy.CACHING_DISABLED) .originRequestPolicy(OriginRequestPolicy.ALL_VIEWER) .build() ) .enableLogging(false) .enabled(true) .build(); } public record Config(String domain, String sslCertArn) {} }
With this stack, everything works as expected.
As a second step, I updated the CDK stack to have separate behaviors for each of the components I'm planning to split my logic to in the future. They all still use the same origin but with some minor changes to the behavior.
The updated CDK stack looks like follows:
package mypackagename; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.services.certificatemanager.Certificate; import software.amazon.awscdk.services.cloudfront.*; import software.amazon.awscdk.services.cloudfront.origins.HttpOrigin; import software.constructs.Construct; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class MyServiceNameCloudfrontCdkStack extends Stack { public MyServiceNameCloudfrontCdkStack(final Construct scope, final String id, final StackProps props, final Config config) { super(scope, id, props); // region custom response headers for the fallthrough behaviour (ui stuff) final ResponseHeadersPolicy uiResponseHeadersPolicy = ResponseHeadersPolicy.Builder.create(this, "ui-response-headers-policy") .securityHeadersBehavior( ResponseSecurityHeadersBehavior.builder() .frameOptions( ResponseHeadersFrameOptions.builder() .frameOption(HeadersFrameOption.DENY) .override(true) .build() ) .contentSecurityPolicy( ResponseHeadersContentSecurityPolicy.builder() .contentSecurityPolicy(String.join("; ", "default-src 'self'", "connect-src 'self' https://api.guildwars2.com", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' https://icons-gw2.darthmaim-cdn.com/ data:", "frame-src https://www.youtube.com/embed/" )) .override(true) .build() ) .build() ) .build(); // endregion // region the external loadbalancer origin config final IOrigin externalLBOrigin = HttpOrigin.Builder.create("<hostname of the LB at external provider>") .protocolPolicy(OriginProtocolPolicy.HTTP_ONLY) .httpPort(8080) .customHeaders(Map.of( "Forwarded", String.format("host=%s;proto=https", config.domain()), "X-Forwarded-Host", config.domain(), "X-Forwarded-Proto", "https", "X-Forwarded-Port", "443" )) .build(); // endregion // region additional behaviours (everything except ui) final Map <String , BehaviorOptions> additionalBehaviors = new LinkedHashMap<>(); additionalBehaviors.put( "/api*", BehaviorOptions.builder() .origin(externalLBOrigin) .compress(true) .viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS) .allowedMethods(AllowedMethods.ALLOW_ALL) .cachePolicy(CachePolicy.CACHING_DISABLED) .originRequestPolicy(OriginRequestPolicy.ALL_VIEWER) .build() ); additionalBehaviors.put( "/oauth2*", BehaviorOptions.builder() .origin(externalLBOrigin) .compress(true) .viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS) .allowedMethods(AllowedMethods.ALLOW_ALL) .cachePolicy(CachePolicy.CACHING_DISABLED) .originRequestPolicy(OriginRequestPolicy.ALL_VIEWER) .build() ); additionalBehaviors.put( "/.well-known/oauth-authorization-server", BehaviorOptions.builder() .origin(externalLBOrigin) .compress(true) .viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS) .allowedMethods(AllowedMethods.ALLOW_GET_HEAD) .cachePolicy(CachePolicy.CACHING_DISABLED) .build() ); // endregion Distribution.Builder.create(this, "cloudfront") .priceClass(PriceClass.PRICE_CLASS_ALL) .httpVersion(HttpVersion.HTTP2) .enableIpv6(true) .domainNames(List.of(config.domain())) .certificate(Certificate.fromCertificateArn(this, "sslcert", config.sslCertArn())) .minimumProtocolVersion(SecurityPolicyProtocol.TLS_V1_2_2021) .defaultBehavior( BehaviorOptions.builder() .origin(externalLBOrigin) .compress(true) .viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS) .allowedMethods(AllowedMethods.ALLOW_GET_HEAD) .cachePolicy(CachePolicy.CACHING_OPTIMIZED) .originRequestPolicy(OriginRequestPolicy.ALL_VIEWER) .responseHeadersPolicy(uiResponseHeadersPolicy) .build() ) .additionalBehaviors(additionalBehaviors) .enableLogging(false) .enabled(true) .build(); } public record Config(String domain, String sslCertArn) {} }
Issue
Most of the changes work as expected (for example, I see that caching now takes place for the default behavior).
BUT: For /oauth2*
requests, CloudFront does not send all or at least some of the defined customHeaders
to the origin server.
I don't know if it also affects the other behaviors, but I know for sure it does affect the /oauth2*
behavior.
This is especially weird because (as expected) the resulting CloudFront Distribution shows only one Origin, which correctly lists the Custom Headers I have set in CDK code.
When rolling back to the previous version of my CDK stack everything works as expected again.
EDIT:
I further tested this weird behavior using a HTTP Echo server instead of the Loadbalancer as a origin.
CloudFront does not always send the custom header X-Forwarded-Proto
to the origin. This is can be seen almost 100% on the default behavior in the updated CDK stack (since I enabled caching for this behavior there). For all other behaviors (where caching is disabled), the X-Forwarded-Proto
is being sent by CloudFront frequently, but not always.
Versions
Maven Versions:
<cdk.version>2.46.0</cdk.version>
<constructs.version>[10.0.0,11.0.0)</constructs.version>
cdk.out
:
{"version":"21.0.0"}
- 최신
- 최다 투표
- 가장 많은 댓글
According to the docs (https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html), the X-Forwarded-Proto
header is not officially supported.
I was able to migrate to the Forwarded
header by changing by Spring configuration from
server: forward-headers-strategy: native
to
server: forward-headers-strategy: framework
The Framework-Strategy makes use of the Forwarded
header, which is consistently being sent by CloudFront.
관련 콘텐츠
- 질문됨 3달 전lg...
- 질문됨 일 년 전lg...
- AWS 공식업데이트됨 2년 전