Reputation: 2558
I have a CloudFront distribution with a Custom Origin that points to an ALB on /api/*
and an S3 Origin for a static site on all other paths (the distribution's default behavior). This servers a Vue application stored in S3 and an API running in Fargate that is load balanced by an ALB.
In the CloudFront distribution's error configuration, I have a rule that sends 404 responses to /index.html
(this is required for SPA so that nested routes can by picked up by the javascript router). Currently when the API returns a 404 response, it is giving the /index.html
response, not the 404 response from the API. This makes sense, but it is not the behavior I want. Is there a way to get the API 404 response on /api/*
, but use /index.html
as the 404 response only for the default S3 Origin/default behavior?
I originally had the ALB served on a subdomain, so this wasn't an issue. I'm trying to simplify things by serving the API and the frontend site on the same domain (app.mydomain.com
) instead of serving the frontend on mydomain.com
and the API on api.mydomain.com
, but this has created a new bug with error responses.
I'm using CDK to build this application, here is the code I have for my CloudFront distribution that is causing the issue with 404 responses:
path_patterns = ["/api/*", "/admin/*"]
self.distribution = cloudfront.CloudFrontWebDistribution(
self,
"CloudFrontDistribution",
origin_configs=[
cloudfront.SourceConfiguration(
custom_origin_source=cloudfront.CustomOriginConfig(
domain_name=alb,
origin_protocol_policy=cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
),
behaviors=[
cloudfront.Behavior(
allowed_methods=cloudfront.CloudFrontAllowedMethods.ALL,
path_pattern=path_pattern,
forwarded_values={
"headers": ["*"],
"cookies": {"forward": "all"},
"query_string": True,
},
)
for path_pattern in path_patterns
],
),
cloudfront.SourceConfiguration(
s3_origin_source=cloudfront.S3OriginConfig(
s3_bucket_source=self.static_site_bucket
),
behaviors=[
cloudfront.Behavior(
is_default_behavior=True,
cached_methods=cloudfront.CloudFrontAllowedMethods.GET_HEAD,
)
],
),
],
alias_configuration=cloudfront.AliasConfiguration(
acm_cert_ref=certificate.certificate_arn,
names=[full_domain_name],
),
error_configurations=[
{
"errorCode": 403,
"errorCachingMinTtl": 0,
"responseCode": 200,
"responsePagePath": "/index.html",
},
{
"errorCode": 404,
"errorCachingMinTtl": 0,
"responseCode": 200,
"responsePagePath": "/index.html",
},
],
)
Edit: 2020-05-13
I have tried removing the error_configuration
from the CloudFrontWebDistribution
and have also set website_index_document
and website_error_document
on the S3 Bucket:
self.static_site_bucket = s3.Bucket(
self,
"StaticSiteBucket",
access_control=s3.BucketAccessControl.PUBLIC_READ,
bucket_name=f"{full_app_name}-frontend",
removal_policy=core.RemovalPolicy.DESTROY,
website_index_document="index.html",
website_error_document="index.html",
)
With this change I am still getting Key Error 404 responses, when I'm hoping to see the 404 page that my Vue app serves.
Is it correct to use an S3 Origin in my CloudFront configuration for the static site, or should this be a custom origin that points to the bucket's website URL?
I'm no using a custom origin for the ALB and the S3 Website. Here are all of the resources described in CDK:
from aws_cdk import (
aws_certificatemanager as acm,
aws_s3 as s3,
aws_cloudfront as cloudfront,
aws_route53 as route53,
aws_iam as iam,
aws_route53_targets as targets,
core,
)
class CloudFront(core.Construct):
def __init__(
self,
scope: core.Construct,
id: str,
hosted_zone: route53.IHostedZone,
certificate: acm.ICertificate,
alb: str,
full_domain_name: str,
full_app_name: str,
**kwargs,
) -> None:
super().__init__(scope, id, **kwargs)
self.static_site_bucket = s3.Bucket(
self,
"StaticSiteBucket",
access_control=s3.BucketAccessControl.PUBLIC_READ,
bucket_name=f"{full_app_name}-frontend",
removal_policy=core.RemovalPolicy.DESTROY,
website_index_document="index.html",
website_error_document="index.html",
)
self.policy_statement = iam.PolicyStatement(
actions=["s3:GetObject"],
resources=[f"{self.static_site_bucket.bucket_arn}/*"],
)
self.policy_statement.add_any_principal()
self.static_site_policy_document = iam.PolicyDocument(
statements=[self.policy_statement]
)
self.static_site_bucket.add_to_resource_policy(self.policy_statement)
path_patterns = ["/api/*", "/admin/*", "/flower/*"]
self.distribution = cloudfront.CloudFrontWebDistribution(
self,
"CloudFrontDistribution",
origin_configs=[
cloudfront.SourceConfiguration(
custom_origin_source=cloudfront.CustomOriginConfig(
domain_name=alb,
origin_protocol_policy=cloudfront.OriginProtocolPolicy.MATCH_VIEWER, # noqa
),
behaviors=[
cloudfront.Behavior(
allowed_methods=cloudfront.CloudFrontAllowedMethods.ALL, # noqa
path_pattern=path_pattern,
forwarded_values={
"headers": ["*"],
"cookies": {"forward": "all"},
"query_string": True,
},
)
for path_pattern in path_patterns
],
),
cloudfront.SourceConfiguration(
custom_origin_source=cloudfront.CustomOriginConfig(
domain_name=self.static_site_bucket.bucket_domain_name,
origin_protocol_policy=cloudfront.OriginProtocolPolicy.MATCH_VIEWER, # noqa
),
behaviors=[
cloudfront.Behavior(
is_default_behavior=True,
cached_methods=cloudfront.CloudFrontAllowedMethods.GET_HEAD, # noqa
)
],
),
],
alias_configuration=cloudfront.AliasConfiguration(
acm_cert_ref=certificate.certificate_arn,
names=[full_domain_name],
),
)
route53.ARecord(
self,
"AliasRecord",
target=route53.AddressRecordTarget.from_alias(
targets.CloudFrontTarget(self.distribution)
),
zone=hosted_zone.hosted_zone,
record_name=f"{full_domain_name}.",
)
Edit 2020-05-14
I was using the wrong domain name for the custom origin. I tried bucket_regional_domain_name
and bucket_website_domain_name
, but these both give me my-s3-bucket-name.s3.us-east-1.amazonaws.com
. Here's the the settings I used for the custom origin config:
cloudfront.SourceConfiguration(
custom_origin_source=cloudfront.CustomOriginConfig(
domain_name=f"{self.static_site_bucket.bucket_name}.s3-website-us-east-1.amazonaws.com",
origin_protocol_policy=cloudfront.OriginProtocolPolicy.HTTP_ONLY,
),
behaviors=[
cloudfront.Behavior(
is_default_behavior=True,
cached_methods=cloudfront.CloudFrontAllowedMethods.GET_HEAD,
)
],
),
HTTP_ONLY
is important, this will not work with MATCH_VIEWER
. Also, notice how I am using an f string to get the correct URL of the s3-website
bucket. I'm still not sure if this is something I can find automatically in CDK. I'll update this if I find more.
Upvotes: 4
Views: 2069
Reputation: 12359
👋@briancaffey, I'm glad to help you again. It's not possible to do it in CloudFront. Alternatively, I think the following approach should work.
S3Origin
to CustomOrigin
with the S3 website endpoint set as the origin domain name.Upvotes: 2