code-server icon indicating copy to clipboard operation
code-server copied to clipboard

[Bug]: Proxying using {{port}}-{{host}} does not work

Open varunchopra opened this issue 1 year ago • 16 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

OS/Web Information

  • Web Browser: Firefox
  • Local OS: MacOS
  • Remote OS: Ubuntu 22.04
  • Remote Architecture: amd64
  • code-server --version: 4.15.0 daac46b3cf5901466410ec7df4eabfb0d9ab384a with Code 1.80.1

Steps to Reproduce

To avoid re-creating certs for different domains and sub-domains, I've tried to prepend the port to the URL instead of using it as a sub-domain.

  1. Using VSCODE_PROXY_URI=https://{{port}}-{{host}}, if code-server is running on code.example.com, then port 9000 should work on 9000-code.example.com. This is similar to how Gitpod does it.

  2. Setup nginx to read 9000-code.example.com and proxy_pass it to port 9000, NOT the port code-server is running on. If you proxy_pass it to the code_server port, it will just open code-server.

  3. Open python -m http.server 9000 inside code-server. You will see the popup with the proxy URL.

The proxy URL will work but it's only because of nginx, not because it's being proxied through code-server.

Expected

It should be proxied through code-server and require cookies and stuff, by proxying to the code-server port (and not port 9000 as seen above).

Actual

Proxying to the code-server port simply opens code-server again instead of the proxied request.

Proxying to the actual port creates a public URL which forwards the requests to the correct port but WITHOUT actually going through code-server. On the running Python server, I also see a 404 response from GET /_static/out/browser/serviceWorker.js but I don't know why this happens.

Logs

No response

Screenshot/Video

No response

Does this issue happen in VS Code or GitHub Codespaces?

  • [X] I cannot reproduce this in VS Code.
  • [X] I cannot reproduce this in GitHub Codespaces.

Are you accessing code-server over HTTPS?

  • [X] I am using HTTPS.

Notes

While this currently works for my setup, it looks like a bug and I'm not sure if the "fix" is stable long term. My only goal is to use {{ port }}-{{ host }} instead of {{ port }}.{{ host }} so if anyone has suggestions, I'm open to them.

varunchopra avatar Jul 25 '23 04:07 varunchopra

You aren't activating the internal reverse proxy functionality, therefore it does not work.

You can start code-server like this: ./code-server --proxy-domain {{port}}-{{host}}, then it should work as you expected.

smerschjohann avatar Jul 26 '23 21:07 smerschjohann

OH! That's right, it worked! I was using --proxy-domain example.

Can you tell me how the two (setting VSCODE_PROXY_URI and passing --proxy-domain) are different? . I've tried reading the code but I don't work with Typescript. :/

In the docs I see the following example, perhaps this needs to be fixed?

You can either set up a wildcard DNS entry for *.<domain> if your domain name registrar supports it, or you can create one for every port you want to access (3000.<domain>, 8080.<domain>, etc).

Now from the above I assumed <domain> is example.com.

And then it says:

To set your domain, start code-server with the --proxy-domain flag:

code-server --proxy-domain <domain>

I hope this explains why I did what I did.

Additionally, I still see the following requests being made:

127.0.0.1 - - [27/Jul/2023 01:20:16] "GET /_static/out/browser/serviceWorker.js HTTP/1.1" 404 -

And these throw a 401 Unauthorized when I try to login at https://9000-example.com/login in incognito:

https://9000-example.com/_static/src/browser/pages/global.css
https://9000-example.com/_static/src/browser/pages/login.css
https://9000-exmaple.com/_static/src/browser/media/pwa-icon-512.png

What's the reason for this?

Thank you for your help! :-D

varunchopra avatar Jul 27 '23 01:07 varunchopra

Can you tell me how the two (setting VSCODE_PROXY_URI and passing --proxy-domain) are different?

VSCODE_PROXY_URI sets the domain to use in the ports panel and --proxy-domain enables code-server's built-in proxy (and will also set VSCODE_PROXY_URI if it has not already been set).

They are separate because some folks run their own proxy instead of code-server's built-in and want that proxy to show in the ports panel, so for them they can set VSCODE_PROXY_URI.

If you are using code-server's built-in proxy then there is no need to manually set VSCODE_PROXY_URI.

Now from the above I assumed is example.com.

Yeah we should update that section to say you can use {{port}} and {{host}}. If you do not include {{port}} then code-server prepends it for you. So --domain-proxy example.com will become {{port}}.example.com.

If you check code-server's output it will show you what it ended up using for the proxy and the ports tab:

[2023-07-28T18:13:19.664Z] info    - Proxying the following domain: 
[2023-07-28T18:13:19.664Z] info      - {{port}}.example.com
[2023-07-28T18:13:19.664Z] info  Using proxy URI in PORTS tab: //{{port}}.example.com

And these throw a 401 Unauthorized when I try to login.

Good question. My guess is we have a bug where those resources are being blocked through the domain proxy but we should let them through. Not sure about the 404 though.

code-asher avatar Jul 28 '23 18:07 code-asher

Good question. My guess is we have a bug where those resources are being blocked through the domain proxy but we should let them through. Not sure about the 404 though.

At first glance, I think this is correct behavior. We are reverse proxying the target port, so these files are not available except the specific application also provides these files. But even then it would not work, because at that particular moment you are not authenticated (401).

I'm not sure if the path is generic enough to not collide with the forwarded application, so instead I think the password prompt page should either load the resources from the main domain or embed the required resources. And I think the serviceWorker is not required at all.

smerschjohann avatar Jul 28 '23 18:07 smerschjohann

Looks like this needs to change to _static; I think someone renamed the endpoint a while ago because VS Code was using static but never updated this:

https://github.com/coder/code-server/blob/e37b35278d4b529fd774469c3e1807ec8f4d1eb0/src/node/routes/domainProxy.ts#L67-L70

Is there a risk of collision? The collision that comes to my mind is if the browser caches our assets and then uses them instead of the application's but I believe our assets use etags for caching so I think that would not be possible.

I think the serviceWorker is not required at all.

Good point, we should just get rid of it on proxied addresess.

code-asher avatar Jul 28 '23 19:07 code-asher

Ah, I did not look at the code. Yes, you are right, as it is only served if unauthenticated and has etags it should be fine.

smerschjohann avatar Jul 28 '23 20:07 smerschjohann

I think letting the request through would cause a bit of confusion. Wouldn't it be better to redirect unauthenticated requests to the main domain (without the port)? - 9000.code.example.com/* redirects to code.example.com/login.

Once logged in, the user can be allowed to access the proxied domains.

On a similar note, I believe we use a cookie called coder-session-cookie (I don't remember what it is exactly), but nginx doesn't play nice with cookies with hyphens in them. You have to pull the cookie from $http_cookie instead of being able to access it directly with $cookie_coder_session_cookie. Can we rename this to coder_session_cookie?

varunchopra avatar Jul 28 '23 20:07 varunchopra

this would require a more complex flow, because of general cookie policies.

  1. 9000.code.example.com/[path]
  2. redirect to code.example.com/login
  3. user authenticates (or is already authenticated)
  4. redirect to 9000.code.example.com/code with an authorization code
  5. /code must validate that code and set a cookie accordingly
  6. redirect back to 9000.code.example.com/[path]

Benefit: For the user, the authentication process can be skipped in most cases as he is already authenticated on the coder-server editor. So no password input required. But I'm not sure if it's worth the effort.

smerschjohann avatar Jul 28 '23 20:07 smerschjohann

On a similar note, I believe we use a cookie called coder-session-cookie (I don't remember what it is exactly), but nginx doesn't play nice with cookies with hyphens in them. You have to pull the cookie from $http_cookie instead of being able to access it directly with $cookie_coder_session_cookie. Can we rename this to coder_session_cookie?

why do you tamper with the cookies at all?

smerschjohann avatar Jul 28 '23 20:07 smerschjohann

I'm not sure I understand the workflow you've pointed out.

  1. Why is there a /code endpoint on a proxied domain?
  2. Why does it redirect to 9000.code.example.com with an auth code? Why doesn't it just check the cookie set on the parent domain?

why do you tamper with the cookies at all?

To try what I suggested - if 9000.code.example.com doesn't have a auth cookie, redirect to code.example.com for it to be authenticated. Once the user's logged in, they can visit 9000.code.example.com again view the proxied data.

varunchopra avatar Jul 28 '23 20:07 varunchopra

As I said, general cookie policies. You can only set cookies downwards: code.example.com can set cookies that are accessible from 9000.code.example.com. But e.g. 9000-code.example.com cannot access any cookie from code.example.com and code.example.com cannot set cookies for 9000-code.example.com.

smerschjohann avatar Jul 28 '23 20:07 smerschjohann

This is fixable - if I use varunchopra.code.example.com, 9000-varunchopra.code.example.com, I can set the cookie on .code.example.com and it will work. We only need to specify what the cookie domain should be.

And like you said, it should already work for the child domains.

FWIW I think it's fine as it is - I'm all up for your initial suggestion.

varunchopra avatar Jul 28 '23 20:07 varunchopra

Well multiple issues:

  1. The proxy domain can be completly different from the domain where the code-server runs. The Authentication should work in the same way everywhere.
  2. Normally, it is not good practice and a potential security risk to set "Site" cookies that can be accessed by ALL subdomains. Are you really in control of all subdomains all the time? (But it seems that it is currently done this way https://github.com/coder/code-server/blob/main/src/node/http.ts#L326) @code-asher Is there a reason why "domain" is set here?
  3. Especially if you think about code-{{port}}.example.com. What about code2-{{port}} etc. They will be able to capture your cookie.

smerschjohann avatar Jul 28 '23 21:07 smerschjohann

Yes, you're right. While I'm okay with having the same cookie - I suspect most people won't and we shouldn't add anything insecure by default.

varunchopra avatar Jul 28 '23 21:07 varunchopra

You are right, that is exactly why the domain is set there, so the cookie can be accessed by all subdomains. If I recall correctly it takes the domain, finds the highest level --proxy-domain that matches, and uses that as the cookie domain value so I think we actually broke it since the proxy domain always contains {{port}} now and will never match. :laughing:

But you raise a really good point about it being a security risk, so I think it would be prudent to remove this or fix it and make it opt-in.

code-asher avatar Jul 31 '23 16:07 code-asher

Another thought: we might need to think about logout logic now that it might not all be under the same domain.

code-asher avatar Jul 31 '23 18:07 code-asher

I think we solved the original issue so I will close this and open others for the various points that were raised here.

code-asher avatar Jul 12 '24 22:07 code-asher