Skip to content

Conversation

bentsku
Copy link
Contributor

@bentsku bentsku commented Mar 18, 2023

This is a follow up from #7554, from an idea proposed by @whummer (in this comment) , with dynamic handling of CORS origin for web resource served by LocalStack.
This is not mergeable as is, as it still has some logging regarding performance, but I'd like to have some comments / opinions.

Scenario:

LocalStack is serving a resource meant to be accessed from the browser (CloudFront or S3 website).

But this resource needs to access LocalStack resources as well. Right now, the only way is to set the environment variables of LocalStack to allow the Origin (let’s say, http://<bucket>.s3-website.localhost.localstack.cloud:4566).

Fix

This PR adds a dynamic check of the origin, to validate if it follows some regex matching 2 service domains known for serving content accessible from a user browser (thus enforcing CORS). We can then add a regex for every resource subdomain in that situation.

The performance right now is around 4µs on my local machine when a request would be either rejected or matching s3-website or cloudfront. The performance would not be affected for most requests, as we either don't match CORS because of no Origin header, or the request is normally allowed.

\cc @whummer @dfangl @alexrashed

@bentsku bentsku temporarily deployed to localstack-ext-tests March 18, 2023 20:36 — with GitHub Actions Inactive
@bentsku bentsku temporarily deployed to localstack-ext-tests March 18, 2023 20:39 — with GitHub Actions Inactive
@coveralls
Copy link

coveralls commented Mar 18, 2023

Coverage Status

Coverage: 81.747% (-0.007%) from 81.753% when pulling 711510b on allow-dynamic-internal-cors into 00d3a59 on master.

@github-actions
Copy link

github-actions bot commented Mar 18, 2023

LocalStack integration with Pro

       3 files  ±  0         3 suites  ±0   1h 33m 40s ⏱️ + 1m 9s
1 832 tests +18  1 441 ✔️ +12  391 💤 +  6  0 ±0 
2 560 runs  +28  1 808 ✔️ +13  752 💤 +15  0 ±0 

Results for commit a693385. ± Comparison against base commit 5949082.

♻️ This comment has been updated with latest results.

@bentsku bentsku marked this pull request as ready for review March 18, 2023 22:54
@bentsku bentsku requested a review from thrau as a code owner March 18, 2023 22:55
@bentsku bentsku requested review from whummer and thrau and removed request for thrau March 18, 2023 22:55
Copy link
Member

@whummer whummer left a comment

Choose a reason for hiding this comment

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

Cool! Nice approach that can further streamline the CORS UX. 🎉

Was able to replicate with the following steps:

  1. create a CloudFront distribution, as per our docs:
$ awslocal s3 mb s3://test
$ echo 'Hello World' > /tmp/hello.txt
$ awslocal s3 cp /tmp/hello.txt s3://test/hello.txt --acl public-read

$ domain=$(awslocal cloudfront create-distribution --origin-domain-name test.s3.amazonaws.com | jq -r '.Distribution.DomainName')
$ echo $domain
ce6554a5.cloudfront.localhost.localstack.cloud
  1. create a simple index.html file that connects to the enpoint:
<html>
<script>
fetch('http://ce6554a5.cloudfront.localhost.localstack.cloud:4566/hello.txt');
</script>
</html>
  1. upload the file to a bucket and expose it as an S3 website:
awslocal s3 cp /tmp/index.html s3://test/index.html --content-type text/html
awslocal s3api put-bucket-website --bucket test --website-configuration 'ErrorDocument={Key=index.html},IndexDocument={Suffix=index.html}'
  1. open http://test.s3-website.localhost.localstack.cloud:4566/index.html in the browser, observe the Network console (request to http://ce6554a5.cloudfront.localhost.localstack.cloud:4566/hello.txt)

Result:

  • returns a 403 error on current master 🟥
  • returns 200 and expected file content with the changes in this PR ✅

To me, this achieves a decent balance between improving convenience (works out of the box without extra configs), while not sacrificing security (requests still only allowed from *.localhost.localstack.cloud pages in the browser). 👍 As usual with CORS or security-critical configurations, would be good to get a second pair of eyes on it, though.. 👀 /cc @dfangl @alexrashed @thrau

for protocol in {"http", "https"}:
for port in _ports:
for port in _get_allowed_cors_ports():
Copy link
Member

Choose a reason for hiding this comment

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

nit: we could also use _ALLOWED_INTERNAL_PORTS here, for consistency. (although the performance impact is probably negligible).

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 what I thought and did at first, but then it would be generated only once, and in your other tests where you monkey patch ALLOWED_CORS_ORIGINS, you'd have to monkey patch _ALLOWED_INTERNAL_PORTS before. I thought it'd make your tests a bit more complicated.

# test allowed origins for extended config (HTTPS edge port 443, HTTP edge port 4566)
monkeypatch.setattr(config, "EDGE_PORT", 443)
monkeypatch.setattr(config, "EDGE_PORT_HTTP", 4566)
origins = cors._get_allowed_cors_origins()

@bentsku
Copy link
Contributor Author

bentsku commented Mar 20, 2023

Thanks @whummer for creating a reproducible sample!

Just a note:

(requests still only allowed from *.localhost.localstack.cloud pages in the browser).

The requests are only allowed from *.s3-website.localhost.localstack.cloud and *.cloudfront.localhost.localstack.cloud for now.
Also, the webpage served by those 2 "dynamic origins" would be able to not only access cloudfront, but also for example the SQS API, or any API using the default LocalStack configuration.
Just giving precision for the second pair of eyes 👀

@bentsku bentsku temporarily deployed to localstack-ext-tests March 21, 2023 16:37 — with GitHub Actions Inactive
@bentsku bentsku changed the title wip: add dynamic check of origin for CORS add dynamic check of origin for CORS Mar 21, 2023
@thrau thrau requested review from dfangl and removed request for thrau March 21, 2023 17:36
@bentsku bentsku force-pushed the allow-dynamic-internal-cors branch from a693385 to c87decd Compare March 24, 2023 22:31
@thrau thrau force-pushed the allow-dynamic-internal-cors branch from c87decd to f61cd88 Compare March 25, 2023 12:47
Copy link
Member

@thrau thrau left a comment

Choose a reason for hiding this comment

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

I think this is fantastic 🎉 I don't see a big issue right now 👍

@@ -166,11 +196,25 @@ def is_cors_origin_allowed(headers: Headers) -> bool:
return True

@staticmethod
@log_duration(min_ms=0)
Copy link
Member

Choose a reason for hiding this comment

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

nit: could we remove this again?

Comment on lines 22 to 23
from ...constants import LOCALHOST, LOCALHOST_HOSTNAME, PATH_USER_REQUEST
from ...utils.bootstrap import log_duration
Copy link
Member

Choose a reason for hiding this comment

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

nit: ideally we don't use relative imports of modules that are outside the scope of the component we are building. basically: relative imports are used when things belong together in a component, absolute imports are used when importing code that represents a dependency to another component

Copy link
Contributor Author

@bentsku bentsku Mar 25, 2023

Choose a reason for hiding this comment

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

Right, I'm not sure why there are dynamic imports here, I can change them 👍

@whummer whummer merged commit 987a4d7 into master Mar 29, 2023
@whummer whummer deleted the allow-dynamic-internal-cors branch March 29, 2023 12:52
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