Serve the site over HTTPS from CloudFront #5

Merged
liamjd merged 1 commit from add-cloudfront-https into main 2026-07-28 06:33:19 +01:00
Owner

The bucket was public and served over plain HTTP from an S3 website
endpoint, which terminates no TLS and can carry no custom headers. A
network attacker could rewrite the app on its way to the browser.

Put CloudFront in front of the bucket's REST endpoint, signed with an
origin access control, and close the bucket: website hosting off, all
four public-access-block switches on, and s3:GetObject granted only to
cloudfront.amazonaws.com conditioned on this distribution's ARN. The
website endpoint is not kept as a fallback — it cannot require a
signature, so it would be a second way in that bypasses everything
below it.

The distribution redirects HTTP to HTTPS and adds HSTS, CSP, nosniff,
X-Frame-Options, Referrer-Policy, Permissions-Policy and the COOP/CORP
pair. adapter-static's fallback: 'index.html' becomes a pair of custom
error responses: a REST origin answers an unknown key with 403 rather
than 404 when the policy grants no s3:ListBucket, so both are mapped.

CSP needs two halves. SvelteKit's bootstrap is an inline script whose
hash changes with every build, so nothing static in OpenTofu can pin it
and the header settles for 'unsafe-inline'. csp: { mode: 'hash' } in
vite.config.ts emits a policy carrying the real hash, and since
both policies are enforced the hashes are the effective ceiling. That
let style-src drop 'unsafe-inline', which meant replacing app.html's
inline style attribute with an #app rule in app.css.

domain_name is empty by default, so the site uses CloudFront's own
certificate. Setting it requests an ACM certificate in us-east-1 and
publishes the validation records as an output.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

The bucket was public and served over plain HTTP from an S3 website endpoint, which terminates no TLS and can carry no custom headers. A network attacker could rewrite the app on its way to the browser. Put CloudFront in front of the bucket's REST endpoint, signed with an origin access control, and close the bucket: website hosting off, all four public-access-block switches on, and s3:GetObject granted only to cloudfront.amazonaws.com conditioned on this distribution's ARN. The website endpoint is not kept as a fallback — it cannot require a signature, so it would be a second way in that bypasses everything below it. The distribution redirects HTTP to HTTPS and adds HSTS, CSP, nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy and the COOP/CORP pair. adapter-static's `fallback: 'index.html'` becomes a pair of custom error responses: a REST origin answers an unknown key with 403 rather than 404 when the policy grants no s3:ListBucket, so both are mapped. CSP needs two halves. SvelteKit's bootstrap is an inline script whose hash changes with every build, so nothing static in OpenTofu can pin it and the header settles for 'unsafe-inline'. `csp: { mode: 'hash' }` in vite.config.ts emits a <meta> policy carrying the real hash, and since both policies are enforced the hashes are the effective ceiling. That let style-src drop 'unsafe-inline', which meant replacing app.html's inline style attribute with an #app rule in app.css. `domain_name` is empty by default, so the site uses CloudFront's own certificate. Setting it requests an ACM certificate in us-east-1 and publishes the validation records as an output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bucket was public and served over plain HTTP from an S3 website
endpoint, which terminates no TLS and can carry no custom headers. A
network attacker could rewrite the app on its way to the browser.

Put CloudFront in front of the bucket's REST endpoint, signed with an
origin access control, and close the bucket: website hosting off, all
four public-access-block switches on, and s3:GetObject granted only to
cloudfront.amazonaws.com conditioned on this distribution's ARN. The
website endpoint is not kept as a fallback — it cannot require a
signature, so it would be a second way in that bypasses everything
below it.

The distribution redirects HTTP to HTTPS and adds HSTS, CSP, nosniff,
X-Frame-Options, Referrer-Policy, Permissions-Policy and the COOP/CORP
pair. adapter-static's `fallback: 'index.html'` becomes a pair of custom
error responses: a REST origin answers an unknown key with 403 rather
than 404 when the policy grants no s3:ListBucket, so both are mapped.

CSP needs two halves. SvelteKit's bootstrap is an inline script whose
hash changes with every build, so nothing static in OpenTofu can pin it
and the header settles for 'unsafe-inline'. `csp: { mode: 'hash' }` in
vite.config.ts emits a <meta> policy carrying the real hash, and since
both policies are enforced the hashes are the effective ceiling. That
let style-src drop 'unsafe-inline', which meant replacing app.html's
inline style attribute with an #app rule in app.css.

`domain_name` is empty by default, so the site uses CloudFront's own
certificate. Setting it requests an ACM certificate in us-east-1 and
publishes the validation records as an output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
liamjd merged commit f738874323 into main 2026-07-28 06:33:19 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
liamjd/SBOMViewer!5
No description provided.