Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ensure we do not rewrite external Origin headers in wrangler dev #4950

Merged
merged 4 commits into from
Feb 9, 2024

Conversation

petebacondarwin
Copy link
Contributor

In #4812 we tried to fix the Origin headers to match the Host header but were overzealous and rewrote Origin headers for external origins (outside of the proxy server's origin).

This is now fixed, and moreover we rewrite any headers that refer to the proxy server on the request with the configured host and vice versa on the response.

This should ensure that CORS is not broken in browsers when a different host is being simulated based on routes in the Wrangler configuration.

Author has addressed the following:

  • Tests
    • Included
    • Not necessary because:
  • Changeset (Changeset guidelines)
    • Included
    • Not necessary because:
  • Associated docs
    • Issue(s)/PR(s):
    • Not necessary because: bug fix

@petebacondarwin petebacondarwin requested a review from a team as a code owner February 7, 2024 22:48
Copy link

changeset-bot bot commented Feb 7, 2024

🦋 Changeset detected

Latest commit: ded262e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
miniflare Patch
wrangler Patch
@cloudflare/pages-shared Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Feb 7, 2024

A wrangler prerelease is available for testing. You can install this latest build in your project with:

npm install --save-dev https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/7843359492/npm-package-wrangler-4950

You can reference the automatically updated head of this PR with:

npm install --save-dev https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/prs/4950/npm-package-wrangler-4950

Or you can use npx with this latest build directly:

npx https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/7843359492/npm-package-wrangler-4950 dev path/to/script.js
Additional artifacts:
npx https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/7843359492/npm-package-create-cloudflare-4950 --no-auto-update
npm install https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/7843359492/npm-package-miniflare-4950
npm install https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/7843359492/npm-package-cloudflare-pages-shared-4950

Note that these links will no longer work once the GitHub Actions artifact expires.


[email protected] includes the following runtime dependencies:

Package Constraint Resolved
miniflare workspace:* 3.20240129.1
workerd 1.20240129.0 1.20240129.0
workerd --version 1.20240129.0 2024-01-29

Please ensure constraints are pinned, and miniflare/workerd minor versions match.

Copy link

codecov bot commented Feb 7, 2024

Codecov Report

Attention: 2 lines in your changes are missing coverage. Please review.

Comparison is base (ffafe8a) 70.43% compared to head (ded262e) 70.52%.
Report is 2 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4950      +/-   ##
==========================================
+ Coverage   70.43%   70.52%   +0.08%     
==========================================
  Files         295      295              
  Lines       15407    15419      +12     
  Branches     3948     3953       +5     
==========================================
+ Hits        10852    10874      +22     
+ Misses       4555     4545      -10     
Files Coverage Δ
packages/wrangler/src/api/startDevWorker/events.ts 15.38% <ø> (ø)
packages/wrangler/src/dev/remote.tsx 7.92% <0.00%> (ø)

... and 8 files with indirect coverage changes

headers: {
"cf-workers-preview-token": workerPreviewToken.value,
Cookie: accessToken && `CF_Authorization=${accessToken}`,
...(accessToken ? { Cookie: `CF_Authorization=${accessToken}` } : {}),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noted that we had to make the headers record allow undefined values just to accommodate this Cookie assignment. But in reality we don't actually want to assign undefined to Cookie, since the code later one would result in the Cookie looking like: previous-cookies;undefined.

So I fixed this up here, removed the undefined from the type and was able to avoid a ! operator later on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fair enough. And is probably the "correct" solution. But this type will eventually be exposed to users to write themselves, and I believe allowing undefined will be more ergonomic and subtly improve DX

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced that allowing undefined here adds to user DX. If anything it creates a mental overhead because the user has to think about what setting to undefined actually means (when it actually means ignore this header and not remove the original header).

Comment on lines +443 to +444
hostname: props.host,
port: props.port.toString(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding these here also fixes the remote dev mode Origin header rewriting!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit side-effecty... This will add a MF-Original-URL header which won't be interpreted and removed by miniflare in remote mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was a bit worried about that. Should we create new override properties that are specifically for header rewrites?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, as far as I can tell MF-Original-URL and MF-Disable-Pretty-Error are always set on every proxy worker request whether in local or remote mode. So I don't think that this change actually introduces any new side effect.

Whether those headers should be added in remote mode is a question for another PR I think.

Copy link
Contributor

@RamIdeas RamIdeas Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let's follow up in another PR if needed.

I think in remote mode, the inner url overrides aren't set so the header wouldn't have been set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +443 to +444
hostname: props.host,
port: props.port.toString(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit side-effecty... This will add a MF-Original-URL header which won't be interpreted and removed by miniflare in remote mode.

headers: {
"cf-workers-preview-token": workerPreviewToken.value,
Cookie: accessToken && `CF_Authorization=${accessToken}`,
...(accessToken ? { Cookie: `CF_Authorization=${accessToken}` } : {}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fair enough. And is probably the "correct" solution. But this type will eventually be exposed to users to write themselves, and I believe allowing undefined will be more ergonomic and subtly improve DX

/**
* Rewrite references to URLs in request headers.
*
* This function is used to map the URLs in headers like Origin and Access-Control-Allow-Origin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access-Control-Allow-Origin is a response header so I assume you expect this function to be used in both directions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll tweak the description.

@@ -127,6 +130,8 @@ export class ProxyWorker implements DurableObject {
// if we decide to await, we should include a timeout (~100ms) in case the user worker has long-running/parellel requests
void fetch(userWorkerUrl, new Request(request, { headers }))
.then((res) => {
rewriteUrlRelatedHeaders(headers, innerUrl, outerUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes both directions! This is clever. Now the browser will have no idea that the URL was faked inside the worker either 😄

Small bug/typo:

Suggested change
rewriteUrlRelatedHeaders(headers, innerUrl, outerUrl);
rewriteUrlRelatedHeaders(res.headers, innerUrl, outerUrl);

But you might not be able to mutate response headers without:

Suggested change
rewriteUrlRelatedHeaders(headers, innerUrl, outerUrl);
res = new Response(res);
rewriteUrlRelatedHeaders(res.headers, innerUrl, outerUrl);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a typo! It's a bona fide mistake! Thanks for spotting that.

Copy link
Contributor

@RamIdeas RamIdeas Feb 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think this is a "new"-ish feature that should get its own changeset and documentation. It's really cool that a dev could write Response.redirect(`${url.origin}/redirect/path`) and that would "just work"™️ in wrangler dev [--remote] – we should shout from the rooftops!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly this was already part of the old proxy:

function rewriteRemoteHostToLocalHostInHeaders(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this problem in a fixup commit and added a test to prove it.

Copy link
Contributor Author

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review

headers: {
"cf-workers-preview-token": workerPreviewToken.value,
Cookie: accessToken && `CF_Authorization=${accessToken}`,
...(accessToken ? { Cookie: `CF_Authorization=${accessToken}` } : {}),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced that allowing undefined here adds to user DX. If anything it creates a mental overhead because the user has to think about what setting to undefined actually means (when it actually means ignore this header and not remove the original header).

Comment on lines +443 to +444
hostname: props.host,
port: props.port.toString(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was a bit worried about that. Should we create new override properties that are specifically for header rewrites?

@@ -127,6 +130,8 @@ export class ProxyWorker implements DurableObject {
// if we decide to await, we should include a timeout (~100ms) in case the user worker has long-running/parellel requests
void fetch(userWorkerUrl, new Request(request, { headers }))
.then((res) => {
rewriteUrlRelatedHeaders(headers, innerUrl, outerUrl);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a typo! It's a bona fide mistake! Thanks for spotting that.

/**
* Rewrite references to URLs in request headers.
*
* This function is used to map the URLs in headers like Origin and Access-Control-Allow-Origin
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll tweak the description.

In #4812 we tried to fix the Origin headers to match the Host header but were overzealous and rewrote Origin headers for external origins (outside of the proxy server's origin).

This is now fixed, and moreover we rewrite any headers that refer to the proxy server on the request with the configured host and vice versa on the response.

This should ensure that CORS is not broken in browsers when a different host is being simulated based on routes in the Wrangler configuration.
Copy link
Contributor

@RamIdeas RamIdeas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to merge. Might want to hold off merging since I believe we want to get a hot fix out today.

One remaining point (you decide whether to address or not): should we print something to the console to let the user know the origin/host is being rewritten across all request/response headers inc Set-Cookie (and not the body) and let them know they can opt-out by...?

@petebacondarwin
Copy link
Contributor Author

petebacondarwin commented Feb 9, 2024

Happy to merge. Might want to hold off merging since I believe we want to get a hot fix out today.

One remaining point (you decide whether to address or not): should we print something to the console to let the user know the origin/host is being rewritten across all request/response headers inc Set-Cookie (and not the body) and let them know they can opt-out by...?

Yeah good question... we have not previously had a message for rewriting the request.url, and prior to 3.18, I believe that we would have been rewriting response headers in this way anyway.

I've put some debug logging messages in, so at least if the user is getting confused it could be analysed via increasing the logging level. I forgot this is workerd and can't use the Wrangler logger here...

Copy link
Contributor

@penalosa penalosa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The edge-preview-authenticated-proxy stuff here looks good!

@RamIdeas
Copy link
Contributor

RamIdeas commented Feb 9, 2024

I've put some debug logging messages in, so at least if the user is getting confused it could be analysed via increasing the logging level. I forgot this is workerd and can't use the Wrangler logger here...

In remote mode, yeah no, but locally workerd logs now reach the log file (not the terminal but that's what you were going for anyway)

@petebacondarwin
Copy link
Contributor Author

In remote mode, yeah no, but locally workerd logs now reach the log file (not the terminal but that's what you were going for anyway)

Hmm I tried adding console.debug() logs to the ProxyWorker but these always appeared in the output of wrangler dev (whatever the logging level). So I think this is too much noise. Perhaps we need to tweak the Proxy Worker's Miniflare configuration?

I think we can leave this to another time, or if someone gets confused by this rewriting.

@petebacondarwin petebacondarwin merged commit 05360e4 into main Feb 9, 2024
31 checks passed
@petebacondarwin petebacondarwin deleted the fix-host-origin-header branch February 9, 2024 12:18
@workers-devprod workers-devprod mentioned this pull request Feb 9, 2024
@penalosa penalosa mentioned this pull request Feb 15, 2024
6 tasks
@jfnault-seedbox
Copy link

@petebacondarwin Can you please take a look at
#5221

I think this PR is related, I had couple of notes:
#5221 (comment)

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.

4 participants