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

Error on Kino script loading if a livebook instance is behind cookie based authentication proxy #954

Closed
en30 opened this issue Jan 30, 2022 · 22 comments · Fixed by #975
Closed

Comments

@en30
Copy link
Contributor

en30 commented Jan 30, 2022

Environment

  • Elixir & Erlang/OTP versions (elixir --version):
  • Operating system:
  • How have you started Livebook (mix phx.server, livebook CLI, Docker, etc): Docker
  • Livebook version (use git rev-parse HEAD if running with mix): livebook/livebook@sha256:4623d37ca4d4ac7d06a48a12d0745e02c301abb42baac2674d0a1771f0dc5be7
  • Browsers that reproduce this bug (the more the merrier): Google Chrome 97.0.4692.71
  • Include what is logged in the browser console:
  • Include what is logged to the server console:

Current behavior

Step to reproduce

  1. Open Cloud Shell on GCP console
  2. docker run -p 8080:8080 --pull always livebook/livebook:edge
  3. Open Web Preview and pass the generated token
  4. Open the "Introduction to Kino" notebook and run Kino.DataTable section

Without GCP: https://github.com/en30/livebook-issue-954

Result

DataTable widget does not show up.

Error message in the browser console:
Access to script at '*/main.js' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

The HTTP request does not include Cookie header and the response redirects to an authentication page that is of a different origin in this case.

Expected behavior

DataTable widget shows up if possible.

@en30 en30 changed the title CORS error on Kino script loading if a livebook instance is behind cookie based authentication proxy Error on Kino script loading if a livebook instance is behind cookie based authentication proxy Jan 30, 2022
@josevalim
Copy link
Contributor

We had a similar bug on v0.5.0 but it should be fixed on v0.5.1. I couldn't find your tag on Docker, so can you please make sure you are on latest? Unless you pulled yesterday, which means you are on latest. :)

@en30
Copy link
Contributor Author

en30 commented Jan 30, 2022

I don't know why the tag is not there...
Here is the result for livebook version

$ docker run --pull always livebook/livebook@sha256:4623d37ca4d4ac7d06a48a12d0745e02c301abb42baac2674d0a1771f0dc5be7 /app/bin/livebook version
docker.io/livebook/livebook@sha256:4623d37ca4d4ac7d06a48a12d0745e02c301abb42baac2674d0a1771f0dc5be7: Pulling from livebook/livebook
Digest: sha256:4623d37ca4d4ac7d06a48a12d0745e02c301abb42baac2674d0a1771f0dc5be7
Status: Image is up to date for livebook/livebook@sha256:4623d37ca4d4ac7d06a48a12d0745e02c301abb42baac2674d0a1771f0dc5be7
livebook 0.5.2

Is there any possibility that the HTTP request to load script does not include credentials because it is sent from the sandbox iframe?

@josevalim
Copy link
Contributor

josevalim commented Jan 30, 2022

Thanks, I can reproduce it now. I was incorrectly assuming it was generally related to authentication but it actually happens only on token authentication, not password one. Commenting config :livebook, :authentication_mode, :disabled in config/dev.exs is enough to reproduce it.

@en30
Copy link
Contributor Author

en30 commented Jan 31, 2022

I am not sure we are looking at the same problem because I cannot reproduce it by changing config/dev.exs. Just in case, I created a repository to reproduce the behavior without GCP.
https://github.com/en30/livebook-issue-954

@jonatanklosko
Copy link
Member

@en30 thanks for the detailed reproduction. So the relevant Cookie is for the proxy server, not Livebook, and it isn't attached when sending requests from within the iframe, so the request redirects to the proxy auth page. This is a huge concern, because in order to send the cookie we would need sandbox="allow-same-origin" on the iframe, but we cannot do that together with sandbox="allow-scripts" for security reasons 🤔

@josevalim
Copy link
Contributor

@jonatanklosko can the iframe sets its own cookie? If it can, then we can pass all cookies to the iframe, except the ones that are important for us.

@jonatanklosko
Copy link
Member

We cannot access HttpOnly cookies from JavaScript and I imagine a proxy like that would set that (either way we shouldn't assume).

@josevalim
Copy link
Contributor

@jonatanklosko we have all cookies in the server, which we can pass down. We can also make it an explicit configuration if we don't want to assume.

@jonatanklosko
Copy link
Member

It's the proxy that sets the cookie though, so we don't have direct access to it on the server, maybe on a subsequent request. And even then, if the cookie is HttpOnly, passing it down doesn't help because the iframe won't be able to set it.

@jonatanklosko
Copy link
Member

@josevalim on a related note, I'm looking into camera access and there are also issues, because our setup is too strict. I have an idea how we can solve both, though it's definitely not pretty.

@josevalim
Copy link
Contributor

@jonatanklosko the httponly is a server setting, the browser will definitely send it later if it is set via JavaScript and iirc the server has no way of knowing. But I am glad to hear alternatives.

@en30
Copy link
Contributor Author

en30 commented Feb 1, 2022

@jonatanklosko Thank you for clarifying the situation.
@josevalim I am sorry for my confusing description.

Since import consists of fetch and evaluation, may it be possible to work around this by doing the fetch outside the iframe to get the code as text and then doing the evaluation inside the iframe?
fetch sends cookies without touching them and evaluation is done inside sandbox iframe.
Though it is a bit complicated.

@jonatanklosko
Copy link
Member

@en30 actually this should be fairly straightforward, we can load the module outside iframe and inside iframe we would still use import(url), but use a dataurl with module contents.

That said, I think the issue is more broad and solving the camera access issue will automatically solve this as well. Otherwise, this sounds like the best alternative!

@en30
Copy link
Contributor Author

en30 commented Feb 1, 2022

@jonatanklosko
Oh, sure! It's definitely much better to use import as-is.

I hope your solution (Feature Policy or something?) is successful🤞

@jonatanklosko
Copy link
Member

@en30 could you try the latest :edge docker image? I think it should be solved with the changes in #968 :)

@en30
Copy link
Contributor Author

en30 commented Feb 2, 2022

@jonatanklosko I tried it both on GCP and using en30/livebook-issue-954, but it still doesn't seem to be working.

To make sure I use the latest image, I specified sha256 digest.
livebook/livebook@sha256:d3f6f80f60d9419465552b12b8afe0b220cf8303269991212f6430f976e6625c
from https://hub.docker.com/layers/livebook/livebook/edge/images/sha256-d3f6f80f60d9419465552b12b8afe0b220cf8303269991212f6430f976e6625c?context=explore

@jonatanklosko
Copy link
Member

@en30 thanks for checking. So the cookies are indeed not sent when importing the module, that's because this is a non-simple cross-origin request (from livebook.space iframe to the livebook endpoint). So far I looked into two ways of addressing it, but neither seems good enough:

  1. We load the module contents in Livebook and import as data URL in iframe (as mentioned above). This successfully loads the initial main.js file, however more complex components may load relative resources like fetch("./my-data.json") and such requests wouldn't include cookies and fail the proxy auth.

  2. We use <script crossorigin="use-credentials"> inside the iframe and a couple additional headers on the Livebook side, which means that import(...) would include credentials, however this doesn't help with fetch("./my-data.json") and breaks other external requests, for instance to a CDN.

One thing we could do is to require the proxy to pass requests to public resources without authentication. We could namespace those routes under /public. We could probably detect this case and show a clear information in the output area or in the console. This is still not ideal, since we leave this configuration to the user, but it may be the only solid solution. @en30 in your use case, how hard would it be to expose /public/* without auth?

@en30
Copy link
Contributor Author

en30 commented Feb 3, 2022

@jonatanklosko

in your use case, how hard would it be to expose /public/* without auth?

  • For Cloud Shell's Web Preview (the original use case of this PR) which launches an authentication proxy implicitly, I could not find a way to configure passthrough paths.
  • For Google Cloud's Identity-Aware Proxy, a managed authentication proxy service, there is a way to configure passthrough paths

I think the solution is reasonable. At least there are ways to work around this issue.

@en30
Copy link
Contributor Author

en30 commented Feb 3, 2022

I haven't done a proper feasibility check yet, but is it possible to do something like this?

  • Use Service Worker in an iframe, intercept fetch.
    • Can iframe have Service Worker?
    • Can import also be intercepted?
  • In the case of a request to livebook, use postMessage and make parent frame to fetch the request, and return the response.

@jonatanklosko
Copy link
Member

Using service workers is likely asking for trouble. We would need to proxy the request as sw -> iframe -> livebook -> iframe -> sw. But even then I don't think it's possible, because passing objects, such as Request as messages is not possible (results in an error like "object cannot be cloned"). For the request specifically maybe we could do manual serialization and recreation, however for response I'm not so sure. Either way, I think this would be an overkill complexity-wise.

@jonatanklosko
Copy link
Member

@en30 thank you for the discussion! I added an error message that should clarify the problem and suggest exposing /public/* as the expected way to address it (#975).

@en30
Copy link
Contributor Author

en30 commented Feb 3, 2022

@jonatanklosko Thank you for addressing the issue. And thank you for all of your hard work and the great product.

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 a pull request may close this issue.

3 participants