Skip to content

fix(cloudfront): serve DefaultRootObject at the distribution root - #2560

Merged
vieiralucas merged 5 commits into
faiscadev:mainfrom
Sorttech:fix/cloudfront-default-root-object
Sep 30, 2026
Merged

vieiralucas merged 5 commits into
faiscadev:mainfrom
Sorttech:fix/cloudfront-default-root-object

Conversation

@Sorttech

@Sorttech Sorttech commented Sep 25, 2026 •

Copy link
Copy Markdown

DefaultRootObject was parsed onto the model and never read, so a viewer
request for the distribution root reached the origin as /. Against an S3
origin that returns the bucket's ListBucketResult XML instead of the SPA
shell.

Resolve it alongside the cache behavior and fetch it in place of the root
path, preserving the query string. AWS applies the default root object to
the distribution root ONLY, so a subdirectory request is never rewritten to
<dir>/<object>; a blank config value is ignored and a stray leading slash
is normalized rather than proxied as //index.html.

Test plan

Regression test: serves_default_root_object_at_the_distribution_root in crates/fakecloud-e2e/tests/cloudfront_dataplane.rs

Verification

On this exact head, rebased onto current main:

  • cargo fmt --all --check - clean.
  • cargo clippy --workspace --all-targets -- -D warnings - clean.
  • cargo test --workspace excluding fakecloud-e2e, fakecloud-conformance,
    fakecloud-tfacc and fakecloud-parity (the same set CI's test job runs),
    plus cargo test -p fakecloud-conformance --lib - 9838 passed, 0 failed
    (--no-fail-fast, --test-threads=4). An earlier run of the same command
    failed once in fakecloud-ssm
    (service::tests::rearm_in_flight_commands_settles_restored_pending) and
    passed on rerun. That test reads the last durable snapshot right after the
    in-memory status flips to Success while the save is asynchronous; it fails
    4 of 11 runs on untouched main on this machine. This branch does not touch
    SSM.
  • cargo test -p fakecloud-e2e --test cloudfront_dataplane - the regression
    test above passes against a freshly built target/debug/fakecloud.

Found by deploying a CDK CloudFront + S3 SPA against fakecloud.


Summary by cubic

Fixes CloudFront DefaultRootObject and OriginPath handling so viewer requests are served the configured objects instead of the bucket root. Previously both were stored on the model but never read: S3-backed SPAs at the root returned the bucket's ListBucketResult XML, and prefixed origins fetched from the root instead of the configured prefix.

  • The default root object applies only to the distribution root; subdirectory requests are left untouched.
  • The object is sent verbatim as AWS does (a leading slash yields //index.html) and percent-encoded so # and ? address the key rather than a fragment or query.
  • OriginPath prefixes every origin request — viewer paths, the default root object, and custom error pages — for custom, S3 REST, and S3-website origins.
  • The e2e setup was updated to expect S3 REST origins to resolve to the local endpoint after merging main.

Written for commit 8d123ee. Summary will update on new commits.

Review in cubic

`DefaultRootObject` was parsed onto the model and never read, so a viewer
request for the distribution root reached the origin as `/`. Against an S3
origin that returns the bucket's `ListBucketResult` XML instead of the SPA
shell.

Resolve it alongside the cache behavior and fetch it in place of the root
path, preserving the query string. AWS applies the default root object to
the distribution root ONLY, so a subdirectory request is never rewritten to
`<dir>/<object>`; a blank config value is ignored and a stray leading slash
is normalized rather than proxied as `//index.html`.
- Append DefaultRootObject after / exactly as configured, as AWS does (a
  leading slash yields //index.html rather than being normalized), and
  treat only an empty value as unset; percent-encode it for the origin URL
  so #, ? and spaces address the key rather than a fragment or query.
- Prefix every origin request with the origin's OriginPath, for custom,
  S3 REST and S3-website origins alike, including the default root object
  and custom error pages.
- Unit tests for root-object resolution, encoding and OriginPath; e2e for
  query-string roots, UpdateDistribution, and an OriginPath origin.
…Path tests

After merging main (which now routes S3 REST origins to this process), the
OriginPath unit tests' expected upstream base is the local endpoint.
@vieiralucas
vieiralucas merged commit bd99bfc into faiscadev:main Sep 30, 2026
157 checks passed
@vieiralucas

Copy link
Copy Markdown
Member

Thanks a lot for this, @Sorttech. Wiring DefaultRootObject into the data plane is what makes a CDK SPA distribution serve its shell at /, and the root-only scoping was right. I pushed a few follow-ups before merging: the configured value is now appended after / exactly as given, matching AWS (a leading slash yields //index.html rather than being normalized, and only an empty value means unset), and it is percent-encoded for the origin URL so characters like # and ? address the key instead of becoming a fragment or query. I also implemented OriginPath, which was stored but never applied: every origin request (custom, S3 REST and S3 website, including the default root object and custom error pages) is now prefixed with the origin's OriginPath. Added unit tests plus e2e coverage for query-string roots, UpdateDistribution, and an OriginPath origin. Merged. Really appreciate the contribution.

@Sorttech
Sorttech deleted the fix/cloudfront-default-root-object branch October 1, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants