I have deployed my website here:
https://curlycactus.com/
if you traverse the links, all the pages work fine. But when I copy and paste the direct link for example this:
https://curlycactus.com/work/1
I get this error:
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>40EFGXY32PKPH10V</RequestId>
<HostId>NSWqXYQGVXuN39bP9DEyqkJ8tjIDDH2xpv08l/CUwcEVUKeoRcKNnwrDm0V/eENkLczmF8935OY=</HostId>
</Error>
any ideas why this happens? Here is my CDK setup:
import * as path from "path";
import { Aws, CfnOutput, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3Deployment from "aws-cdk-lib/aws-s3-deployment";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as targets from "aws-cdk-lib/aws-route53-targets";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as iam from "aws-cdk-lib/aws-iam";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
import { CONSTRUCT_NAMES } from "./ConstructNames";
export interface IStaticWebsiteConstruct extends StackProps {
domainName: string;
}
export class StaticWebsiteConstruct extends Construct {
websiteBucket: s3.Bucket;
deploy: s3Deployment.BucketDeployment;
cloudFront: cloudfront.CloudFrontWebDistribution;
constructor(parent: Stack, id: string, props: IStaticWebsiteConstruct) {
super(parent, id);
// create bucket which holds the website data
const zone = route53.HostedZone.fromLookup(this, "Zone", {
domainName: props.domainName,
});
const siteDomain = props.domainName;
const cloudfrontOAI = new cloudfront.OriginAccessIdentity(
this,
"cloudfront-OAI",
{
comment: `OAI for ${id}`,
}
);
this.websiteBucket = new s3.Bucket(this, CONSTRUCT_NAMES.bucket.name, {
bucketName: CONSTRUCT_NAMES.bucket.name,
websiteIndexDocument: "index.html",
publicReadAccess: true,
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
// Grant access to cloudfront
this.websiteBucket.addToResourcePolicy(
new iam.PolicyStatement({
actions: ["s3:GetObject"],
resources: [this.websiteBucket.arnForObjects("*")],
principals: [
new iam.CanonicalUserPrincipal(
cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId
),
],
})
);
// TLS certificate
const certificateArn = new acm.DnsValidatedCertificate(
this,
"SiteCertificate",
{
domainName: siteDomain,
hostedZone: zone,
region: "us-east-1", // Cloudfront only checks this region for certificates.
}
).certificateArn;
// Specifies you want viewers to use HTTPS & TLS v1.1 to request your objects
const viewerCertificate = cloudfront.ViewerCertificate.fromAcmCertificate(
{
certificateArn: certificateArn,
env: {
region: Aws.REGION,
account: Aws.ACCOUNT_ID,
},
node: this.node,
stack: parent,
metricDaysToExpiry: () =>
new cloudwatch.Metric({
namespace: "TLS Viewer Certificate Validity",
metricName: "TLS Viewer Certificate Expired",
}),
},
{
sslMethod: cloudfront.SSLMethod.SNI,
securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1_1_2016,
aliases: [siteDomain],
}
);
// CloudFront distribution
const distribution = new cloudfront.CloudFrontWebDistribution(
this,
"SiteDistribution",
{
viewerCertificate,
originConfigs: [
{
s3OriginSource: {
s3BucketSource: this.websiteBucket,
originAccessIdentity: cloudfrontOAI,
},
behaviors: [
{
isDefaultBehavior: true,
compress: true,
allowedMethods:
cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
},
],
},
],
}
);
// Route53 alias record for the CloudFront distribution
new route53.ARecord(this, "SiteAliasRecord", {
recordName: siteDomain,
target: route53.RecordTarget.fromAlias(
new targets.CloudFrontTarget(distribution)
),
zone,
});
// deploy/copy the website built website to s3 bucket
this.deploy = new s3Deployment.BucketDeployment(
this,
CONSTRUCT_NAMES.bucket.deployment,
{
sources: [
s3Deployment.Source.asset(
path.join(__dirname, "..", "..", "frontend", "out")
),
],
destinationBucket: this.websiteBucket,
// distribution: this.cloudFront,
distribution,
distributionPaths: ["/*"],
}
);
}
}
CodePudding user response:
Your website doesn't feel like 100% (client side) static website. By that I mean every HTML page is pre-generated and everything is static on client side. If that's the case then /work/1 should not load any html page as it's not a html resource. For it to be HTML resource it should be like /work/1.html
With that being said, it looks like you're using React or some other technology which translates the routing when previous page is known. / -> /work/1
As you have CloudFront already in your stack. Just set the error pages to redirect back to home page and then it should work fine. Attaching the solution for my react app hosted on S3 CloudFront.
CodePudding user response:
found the issue. Http servers like Apache, nginx automatically look for .html postfix files for a URL request that doesn't have a postfix. We can do the same using cloudfront lambdas apparently, these lambdas are called lambda@edge.
https://jarredkenny.com/cloudfront-pretty-urls/
bear in mind, the lambda@edge functionality works in us-east-1 only so you have to deploy two stacks unless you move your whole stack to us-east-1

