coder / code-server

VS Code in the browser
https://coder.com
MIT License
67.98k stars 5.58k forks source link

[Bug]: Port forwarding with suburl makes the forwarded links corrupted . #5792

Closed a279437145 closed 1 year ago

a279437145 commented 1 year ago

Is there an existing issue for this?

OS/Web Information

Steps to Reproduce

  1. Open code-server
  2. Run jupyter notebook in terminal , and forward the port (should be forwarded automatically)
  3. Open the forwarded url

Expected

Jupyter notebook shows up a correct UI and works without problems.

Actual

The jupyter notebook showed up with a unformatted UI (because css and js files are requested with a corrupted URL, only the https://192.168.0.114:28080/proxy/8888/login?next=%2Ftree is requested correctly) if logging in to the notebook it redirected me to https://192.168.0.114:28080/?next=/tree , which is obviously wrong (code-server is serving there) eg , for the logo.png , it requested for https://192.168.0.114:28080/static/base/images/logo.png?v=<a long string> , but it should request for https://192.168.0.114:28080/proxy/8888/static/base/images/logo.png?v=<a long string>

Logs

Logs ```plain [IPC Library: Pty Host] DEBUG CommandDetectionCapability#handleCommandExecuted 0 1 [IPC Library: Pty Host] DEBUG CommandDetectionCapability#setCommandLine jupyter notebook [IPC Library: Pty Host] TRACE IPty#onData [I 15:06:55.184 NotebookApp] Serving notebooks from local directory: /home/ubuntu/workspace [IPC Library: Pty Host] TRACE IPty#onData [I 15:06:55.184 NotebookApp] Jupyter Notebook 6.5.2 is running at: [I 15:06:55.184 NotebookApp] http://localhost:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8 [I 15:06:55.184 NotebookApp] or http://127.0.0.1:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8 [I 15:06:55.184 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). [IPC Library: Pty Host] TRACE IPty#onData [C 15:06:55.197 NotebookApp] To access the notebook, open this file in a browser: file:///home/ubuntu/.local/share/jupyter/runtime/nbserver-13720-open.html Or copy and paste one of these URLs: http://localhost:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8 or http://127.0.0.1:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8 [IPC Library: Pty Host] DEBUG ChildProcessMonitor: Has child processes changed true [IPC Library: Pty Host] TRACE IPty#onData [I 15:07:03.317 NotebookApp] 302 GET / (127.0.0.1) 1.440000ms [IPC Library: Pty Host] TRACE IPty#onData [I 15:07:03.407 NotebookApp] 302 GET /tree (127.0.0.1) 1.770000ms [15:07:15] [][29df24bf][ManagementConnection] The client has disconnected, will wait for reconnection 3h before disposing... [15:07:17] [][29df24bf][ManagementConnection] Another client has connected, will shorten the wait for reconnection 5m before disposing... [15:07:17] [][3473a0f6][ExtensionHostConnection] - startParams language: en [15:07:17] [][3473a0f6][ExtensionHostConnection] - startParams env: {"VSCODE_PROXY_URI":"https://192.168.0.114:28080/proxy/{{port}}/"} [15:07:17] [][3473a0f6][ExtensionHostConnection] The client has reconnected. [15:07:17] [][29df24bf][ManagementConnection] The client has reconnected. [2022-11-18T15:07:21.474Z] debug 2 active connections Trace: [2022-11-18T15:07:21.477Z] trace heartbeat at doLog (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:57:28) at ServerFormatter.doWrite (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:200:20) at ServerFormatter.write (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:119:14) at Logger.handle (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:339:25) at Logger.trace (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:268:14) at Heart. (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:41:29) at Generator.next () at /home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:8:71 at new Promise () at __awaiter (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:4:12) at Heart.beat (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:37:16) at /home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:76:17 at Generator.next () at fulfilled (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:5:58) at runMicrotasks () at processTicksAndRejections (node:internal/process/task_queues:96:5) ```

Screenshot/Video

Behavior Run jupyter: ![image](https://user-images.githubusercontent.com/75509566/202737562-abdb51fd-313c-4363-bef0-37c2f2864b34.png) Forward port: ![image](https://user-images.githubusercontent.com/75509566/202737603-296f6cfa-5389-4a53-9c8b-4f809a47c320.png) Access it: ![image](https://user-images.githubusercontent.com/75509566/202737709-0df62488-7ade-4749-ba08-a74ef612dfd3.png)
Browser console output ![image](https://user-images.githubusercontent.com/75509566/202737881-b2685a97-0027-44f6-b051-7c296ee219be.png)
Explaination The correct URL: ![image](https://user-images.githubusercontent.com/75509566/202738666-8a87ea92-24cb-42dd-866b-53a2530d8880.png) The corrupted URL: ![image](https://user-images.githubusercontent.com/75509566/202738695-7d624913-6fb6-4817-895c-f4d7aa1d675f.png)

Does this issue happen in VS Code or GitHub Codespaces?

Are you accessing code-server over HTTPS?

Notes

Code-server is running on a lxc container , it exposed the port 8888 to the server host , and I used iptables(iptables -t nat -A PREROUTING -p tcp --dport 28080 -j DNAT --to-destination 10.154.46.4:8080 , where 28080 is the server exposed port , and 10.154.46.4:8080 is the container exposed port). It is using a self-signed certificate with cert and cert-key params in the config file (not serving through nginx/caddy)

Also , this bug makes the pdf viewer from LaTeX workshop not working properly.

Is it because of relative path ? But jupyter and LaTeX workshop aren't my project , i don't know where I can make changes. Subdomains may work , but I don't have a domain for my server.

frank8922 commented 1 year ago

I'm having the same browser output when I try using jupyter-lab as well. I'm using code-server in a docker container behind a reverse proxy (caddy). I noticed when I prepend the path of the 404 requests with the path '/proxy/8888/' it fetches the desired resource successfully. In my case it looks like because code-server's proxy strips the '/proxy//' portion of the path it fails.

jsjoeio commented 1 year ago

Hmm 🤔 Can this be fixed via the environment variable? Here are the notes from the release about this feature:

Support for the Ports panel which leverages code-server's built-in proxy. It also uses VSCODE_PROXY_URI where {{port}} is replace when forwarding a port. Example: VSCODE_PROXY_URI=https://{{port}}.kyle.dev would forward an application running on localhost:3000 to https://3000.kyle.dev/

I don't work with Jupyter notebooks or jupyter-lab so I'm not sure what to expect here.

If you can provide some tests cases and what the expected URLs would be, I can help more.

a279437145 commented 1 year ago

I tried subdomain , I don't have a registered domain so I added 192.168.0.114 raspberrypi and 192.168.0.114 8888.raspberrypi to my /etc/hosts , and I launched with VSCODE_PROXY_URI=https://{{port}}.raspberrypi:28080 nohup ./code/code-server-4.8.3-linux-arm64/bin/code-server --verbose & (I've changed the code-server config to serve on port 28080) Forwarded like this:

Screenshot ![image](https://user-images.githubusercontent.com/75509566/205086142-f1a9eb19-4f76-47ce-957c-190b6852082e.png)

But when I try to access it , it shows the code-server , not the jupyter

Screenshot ![image](https://user-images.githubusercontent.com/75509566/205086412-5c7ae838-8d31-4169-b785-1b3ca419cd88.png) Note : I forgot to add DNS.*=*.raspberrypi when I signed my certificate , so it shows a `Not secure` mark (Does this matter?)

Seems the server does know it's being accessed via 8888.raspberrypi:28080 , because when extension are scanned , they showed this in the authority block , eg

Logs ```plain [IPC Library: Pty Host] TRACE ptyService#getLayoutInfo { workspaceId: '-392037db' } [15:00:45] Scanned user extensions: 7 [15:00:45] Scanned system extensions: 88 [15:00:45] Scanned Extensions [ { id: 'vscode.bat', identifier: b { value: 'vscode.bat', _lower: 'vscode.bat' }, isBuiltin: true, isUserBuiltin: false, isUnderDevelopment: false, extensionLocation: { '$mid': 1, path: '/home/ubuntu/code/code-server-4.8.3-linux-arm64/lib/vscode/extensions/bat', scheme: 'vscode-remote', authority: '8888.raspberrypi:28080' }, uuid: undefined, targetPlatform: 'undefined', name: 'bat', displayName: 'Windows Bat Language Basics', description: 'Provides snippets, syntax highlighting, bracket matching and folding in Windows batch files.', version: '1.0.0', publisher: 'vscode', license: 'MIT', engines: { vscode: '^1.52.0' }, scripts: { 'update-grammar': 'node ../node_modules/vscode-grammar-updater/bin mmims/language-batchfile grammars/batchfile.cson ./syntaxes/batchfile.tmLanguage.json' }, contributes: { languages: [Array], grammars: [Array], snippets: [Array] }, repository: { type: 'git', url: 'https://github.com/microsoft/vscode.git' } }, ```

Does the logger logs access ?

Because I accessed code-server via iptables fort forwarding , I also tried to access it directly from the server's internal network Got the same result :cry:

Screenshot ![image](https://user-images.githubusercontent.com/75509566/205091559-dd9b8fae-f98f-4328-82ab-7210c69a7fa6.png)

Also , I prefer using subpath here , then I don't have to do modify the hosts file if I'm on a new device.

UPD : I think that invalid certificate won't matter .(Not so sure) I used wireshark to check , it did send server_name

Screenshot ![image](https://user-images.githubusercontent.com/75509566/205099490-5ede0f7d-af82-4aa5-b910-81dafb6b2744.png)
jsjoeio commented 1 year ago

But when I try to access it , it shows the code-server , not the jupyter

Woah that's weird. Okay let me summarize to make sure I'm understanding.

You're running code-server on https://raspberry:28080. You can access code-server just fine there âś…

You pass VSCODE_PROXY_URI=https://{{port}}.raspberrypi:28080 and when you start a process, the Ports panel shows the correct address âś…

When you access said process i.e. Jupyter running at https://8888.raspberrypi:28080 it shows code-server 🤔

Also , I prefer using subpath here , then I don't have to do modify the hosts file if I'm on a new device.

Agreed! So let's try this:

  1. start code-server without VSCODE_PROXY_URI set
  2. in the Integrated Terminal, run python3 -m http.server
  3. try accessing it via the ports panel, which should use the built-in /proxy/<port> path

If that works, then try running Jupyter on 8888 and try again.

At least that's what I'd do. I don't know if Jupyter needs to have some environment variable or something to run behind a subpath but we can check that next. I just want to make sure the port forwarding works with your setup first.

a279437145 commented 1 year ago

@jsjoeio Yes , correct .

And , I found this https://jupyter-notebook.readthedocs.io/en/stable/public_server.html#running-the-notebook-with-a-customized-url-prefix, and I followed it. I added c.NotebookApp.base_url = '/proxy/8888/' to/home/ubuntu(my user name)/.jupyter/jupyter_notebook_config.py and launched it . This time when I access it , the jupyter icon loaded successfully but the site returned a 404

Screenshot ![image](https://user-images.githubusercontent.com/75509566/205294830-fad84b17-a296-4954-9a45-46554bc8f823.png)

And jupyter notebook output

Logs ``` ubuntu@UbuntuCode:~/workspace$ jupyter notebook [I 12:38:22.275 NotebookApp] Serving notebooks from local directory: /home/ubuntu/workspace [I 12:38:22.275 NotebookApp] Jupyter Notebook 6.5.2 is running at: [I 12:38:22.275 NotebookApp] http://localhost:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617 [I 12:38:22.275 NotebookApp] or http://127.0.0.1:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617 [I 12:38:22.275 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). [C 12:38:22.287 NotebookApp] To access the notebook, open this file in a browser: file:///home/ubuntu/.local/share/jupyter/runtime/nbserver-8769-open.html Or copy and paste one of these URLs: http://localhost:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617 or http://127.0.0.1:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617 [W 12:38:25.247 NotebookApp] 404 GET / (127.0.0.1) 64.800000ms referer=https://192.168.0.114:28080/?folder=/home/ubuntu/workspace [W 12:38:25.559 NotebookApp] 404 GET /static/style/style.min.css?v=e1ab1c38b672063a6541baf468c83345cd0f509729783ec9b7ccb64073004f5f056110c82c28aefbf3dbf32e0e040f05b8f0420bc411b669ed3d4f07511812ca (127.0.0.1) 8.280000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.600 NotebookApp] 404 GET /custom/custom.css (127.0.0.1) 6.840000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.610 NotebookApp] 404 GET /static/components/jquery-ui/dist/themes/smoothness/jquery-ui.min.css?v=32f9dcde0cd9843f2b66d34c1c9928b59a5d7ef007ba7a6a6a790b3e78f7857a698444d7a716dfaf8fa834c3b3175efd258bbc07cfc4aabb86769b07e5f358c3 (127.0.0.1) 4.900000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.615 NotebookApp] 404 GET /static/components/es6-promise/promise.min.js?v=bea335d74136a63ae1b5130f5ac9a50c6256a5f435e6e09fef599491a84d834a8b0f011ca3eaaca3b4ab6a2da2d3e1191567a2f171e60da1d10e5b9d52f84184 (127.0.0.1) 3.910000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.622 NotebookApp] 404 GET /static/components/jquery-typeahead/dist/jquery.typeahead.min.css?v=5edf53bf6bb9c3b1ddafd8594825a7e2ed621f19423e569c985162742f63911c09eba2c529f8fb47aebf27fafdfe287d563347f58c1126b278189a18871b6a9a (127.0.0.1) 4.220000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.628 NotebookApp] 404 GET /static/components/react/react.production.min.js?v=9a0aaf84a316c8bedd6c2ff7d5b5e0a13f8f84ec02442346cba0b842c6c81a6bf6176e64f3675c2ebf357cb5bb048e0b527bd39377c95681d22468da3d5de735 (127.0.0.1) 4.240000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.697 NotebookApp] 404 GET /static/components/react/react-dom.production.min.js?v=6fc58c1c4736868ff84f57bd8b85f2bdb985993a9392718f3b4af4bfa10fb4efba2b4ddd68644bd2a8daf0619a3844944c9c43f8528364a1aa6fc01ec1b8ae84 (127.0.0.1) 6.330000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.723 NotebookApp] 404 GET /static/components/requirejs/require.js?v=d37b48bb2137faa0ab98157e240c084dd5b1b5e74911723aa1d1f04c928c2a03dedf922d049e4815f7e5a369faa2e6b6a1000aae958b7953b5cc60411154f593 (127.0.0.1) 4.920000ms referer=https://192.168.0.114:28080/proxy/8888/ [W 12:38:25.727 NotebookApp] 404 GET /static/components/create-react-class/index.js?v=894ad57246e682b4cfbe7cd5e408dcd6b38d06af4de4f3425991e2676fdc2ef1732cbd19903104198878ae77de12a1996de3e7da3a467fb226bdda8f4618faec (127.0.0.1) 6.840000ms referer=https://192.168.0.114:28080/proxy/8888/ ```

I'll look into the docs later to see if there are something more I should specify.

And for python3 -m http.server , it does work

Screenshots ![image](https://user-images.githubusercontent.com/75509566/205295508-5d2149e7-26d8-4dca-82ed-bf8eb03c38fe.png) ![image](https://user-images.githubusercontent.com/75509566/205295563-7feb3252-036b-440f-ba61-1d03408ae80e.png) ![image](https://user-images.githubusercontent.com/75509566/205295436-3949047b-0133-4325-85f7-cb284292a9e8.png)

UPD: Figured out , seems jupyter messed the urls up.

Screenshots ![image](https://user-images.githubusercontent.com/75509566/205302755-fd98c99d-b9ee-4d3d-97aa-3986ebcf4629.png) accessing from the internal net : (not through code-server proxy) ![Screenshot from 2022-12-02 21-25-09](https://user-images.githubusercontent.com/75509566/205303026-b3c983ce-5bf0-490d-bdde-14221cc7a8fb.png)

The reverse proxy server used by code-server makes jupyter thinks that you are accessing path / while you are actually accessing /proxy/8888 , but jupyter only works when it thinks you are accessing /proxy/8888 , so in this case you have to double the /proxy/8888 . So accessing https://192.168.0.114:28080/proxy/8888/proxy/8888/login can show the login screen , but nothing else (like the logo or css files) loads because they were requested with https://192.168.0.114:28080/proxy/8888. Pretty confusing :sob:

jsjoeio commented 1 year ago

c.NotebookApp.base_url = '/proxy/8888/'

Does this need to include the full url? i.e c.NotebookApp.base_url = 'https://192.168.0.114:28080/proxy/8888/?

Figured out , seems jupyter messed the urls up.

Nice work!!!

The reverse proxy server used by code-server makes jupyter thinks that you are accessing path / while you are actually accessing /proxy/8888 , but jupyter only works when it thinks you are accessing /proxy/8888 , so in this case you have to double the /proxy/8888 .

Argh :( that is very confusing indeed. When @code-asher is back, maybe he'll have some ideas about this and know if there's anything we can do to make it better from the code-server side. Sorry for all the trouble!

a279437145 commented 1 year ago

Does this need to include the full url? i.e c.NotebookApp.base_url = 'https://192.168.0.114:28080/proxy/8888/?

It doens't need to include the full url , there are descriptions in jupyter's docs

For example, if you prefer that the notebook dashboard be located with a sub-directory that contains other ipython files, e.g. http://localhost:8888/ipython/, you can do so with configuration options like the following (see above for instructions about modifying jupyter_notebook_config.py): c.NotebookApp.base_url = '/ipython/'

code-asher commented 1 year ago

Yeah many applications use absolute URLs unfortunately and default to / so they will need to be made aware of the base /proxy/{port}/ path, including Jupyter.

The sub-domain proxy method is superior since it does not have the same issue but it needs to be enabled via the --proxy-domain flag. I think it would look like this:

VSCODE_PROXY_URI=https://{{port}}.raspberrypi:28080 code-server --proxy-domain *.raspberrypi

(Alternatively the proxy could be implemented via a separate reverse proxy.)

code-asher commented 1 year ago

I wonder if there is a way we can surface help around this, maybe via a tooltip or text underneath the forwarded port? Or something like a "help me" button that triggers a notification or opens documentation?

jsjoeio commented 1 year ago

Or something like a "help me" button that triggers a notification or opens documentation?

I like that! We could add a doc and then in the message that pops up about the forwarded port, add a link below and link to the docs.

a279437145 commented 1 year ago

Oh , thank you for explaining.

I wonder whether it's possible to add a field which lets us input 2 URLs A and B , the the reverse proxy will replace all 'A' in the resource location links from the page with 'B' . For example , if we input 'https://code.example/' as the URL to be replaced(A) and 'https://code.example/proxy/8888/' as the URL to replace with (B) . Then when the proxy is about to send the requested page(or js or something else) it will look for 'https://code.example/' and replace it with 'https://code.example/proxy/8888' if it is a link . (Also maybe adding a check box to disable the link check?) A bit like 'sub_filter' parameter in nginx. But does it work for dynamic sites and is the implementation complicated?

Or , is it possible to change how code server handles 404? Like when it handles 404 it checks the referer string in the http header and check whether it's linked there from a proxied page , if so , it will redirect it to the correct URL .(But maybe this method is complicated and buggy?)

jsjoeio commented 1 year ago

Hmm...that sounds a bit out of scope for now. We want to keep it as close to upstream (VS Code) as possible. Let me know if there's another way we can help you though.

code-asher commented 1 year ago

Yeah I think for advanced proxy configuration it might be better to set up NGINX or Caddy instead of using code-server's built-in.

Also if all you are looking for is to avoid stripping the base path then we have another proxy endpoint /absproxy for that so you could set VSCODE_PROXY_URI=https://my-domain.com/absproxy/{{port}}. It would be cool to have a way to choose between /proxy and /absproxy from the UI.

a279437145 commented 1 year ago

Thank you , the absproxy worked. image

But it can't solve extension's problems (For latex-workshop , I can't view pdf file with its built-in pdf viewer because some resources are requested with a bad URL) And it needs to configure the forwarded site .

a279437145 commented 1 year ago

I...tried nginx This is the configuration file I wrote

Note:This configuration doesn't fully work , use the one below

Configuration ```conf server { listen 28081 ssl; server_name 192.168.0.114; server_name raspberrypi; ssl_certificate "/path/to/mycert.crt"; ssl_certificate_key "/path/to/mycert.key"; client_max_body_size 50M; location ~* ^/proxy/(?[^\n\r\/]+)(?.*)$ { default_type text/html; if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) { add_header Content-Type 'text/html; charset=utf-8'; return 501 'Illegal port ${proxy_port}'; } if ($proxy_path = ''){ set $proxy_path '/'; #force to use relative path } #proxy_ssl_server_name on; proxy_pass http://127.0.0.1:$proxy_port$proxy_path; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; gzip off; proxy_buffering off; subs_filter_types text/css text/plain application/x-javascript application/javascript; subs_filter (.*)((|\>)?(.*) $1$2=$6/proxy/$proxy_port/$8$9 rg; } } ``` The subs_filter regex comes from https://regex101.com/r/zY1lJ7/1

And with the subs_filter option almost all the resources are requested correctly

Screenshot ![image](https://user-images.githubusercontent.com/75509566/206861982-b4236bf1-4f54-4d7b-9b9a-7c060b625f7d.png)

But from there you can see there's still main.min.js not loaded correctly so the page is not fully loaded.And I can't figure out where it's been requested . I checked the stack trace .

Screenshots ![image](https://user-images.githubusercontent.com/75509566/206862285-20195b95-60d7-4efe-aab9-5673161818ac.png) ![image](https://user-images.githubusercontent.com/75509566/206862287-03e9ddf7-3011-44a4-a56d-daea6b5011bb.png)
a279437145 commented 1 year ago

@code-asher @jsjoeio Well , I tried a new way today . And , it worked !

Here is the nginx configuration.

Configuration ``` server { listen 28081 ssl; server_name 192.168.0.114; server_name raspberrypi; ssl_certificate "/path/to/mycert.crt"; ssl_certificate_key "/path/to/mycert.key"; client_max_body_size 50M; location ~* ^/proxy/(?[^\n\r\/]+)(?.*)$ { default_type text/html; if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) { add_header Content-Type 'text/html; charset=utf-8'; return 501 'Illegal port ${proxy_port}'; } if ($proxy_path = ''){ set $proxy_path '/'; #force to use relative path } add_header Set-Cookie 'proxy_port=$proxy_port; Path=/'; proxy_http_version 1.1; #proxy_ssl_server_name on; proxy_pass http://127.0.0.1:$proxy_port$proxy_path; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; proxy_buffering off; #websocket set $to_upgrade ""; set $to_connection ""; if ($http_upgrade = "websocket"){ set $to_upgrade "websocket"; set $to_connection "Upgrade"; } proxy_set_header Upgrade $to_upgrade; proxy_set_header Connection $to_connection; proxy_read_timeout 86400; } location / { default_type text/html; if ($cookie_proxy_port = ''){ return 404 'Nothing here'; } proxy_http_version 1.1; proxy_pass http://127.0.0.1:$cookie_proxy_port; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; proxy_buffering off; #websocket set $to_upgrade ""; set $to_connection ""; if ($http_upgrade = "websocket"){ set $to_upgrade "websocket"; set $to_connection "Upgrade"; } proxy_set_header Upgrade $to_upgrade; proxy_set_header Connection $to_connection; proxy_read_timeout 86400; } } ```

When accessing https://server-host.com/proxy/{port} nginx will set proxy_port={port} to the client cookies. And if the client requests for https://server-host.com/path/to/the/resource with the cookie (proxy_port={port}), nginx will proxy_pass it to http://127.0.0.1:{port}/path/to/the/resource so the resource can be loaded correctly. Also nginx will check if the connection is a websocket , and if it is nginx will automatically configure it . (So it's able to connect to jupyter kernel through nginx)

What I have tested :

Screenshots ![image](https://user-images.githubusercontent.com/75509566/206895245-b809771a-316f-43b6-b5d2-afcd7e61e9d0.png) ![image](https://user-images.githubusercontent.com/75509566/206895401-5bbc75e0-c958-42c5-821f-9d18a89ff94b.png)
jsjoeio commented 1 year ago

Ahhh wow, nice debugging and thanks for posting your solution! I guess for now this is where we point people if they run into this.

qianchd commented 1 year ago

@a279437145, It is a nice solution. But what if I have already set the code-server through the Nginx server? e.g. https://www.example.com/code-server. It seems that now there is a two-layer forward issue...

a279437145 commented 1 year ago

As you are already using nginx and serving code-server behind subpath , I think you can merge the configuration files . I think..just make your 'location /code-server' block at top so the other configurations wont affect it .So I think merging them won't cause conflicts.

I have tons of homework to do now and I can't test , I'll test it this weekend and reply here. Sorry for keep you waiting ~

a279437145 commented 1 year ago

@qianchd Just tested. Here's my nginx configuration

Configuration ``` server { listen 28081 ssl; server_name 192.168.0.114; server_name raspberrypi; ssl_certificate "/path/to/certificate.crt"; ssl_certificate_key "/path/to/certificate.key"; client_max_body_size 50M; location /code-server/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Accept-Encoding gzip; } location ~* ^/proxy/(?[^\n\r\/]+)(?.*)$ { default_type text/html; if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) { add_header Content-Type 'text/html; charset=utf-8'; return 501 'Illegal port ${proxy_port}'; } if ($proxy_path = ''){ set $proxy_path '/'; #force to use relative path } add_header Set-Cookie 'proxy_port=$proxy_port; Path=/'; proxy_http_version 1.1; #proxy_ssl_server_name on; proxy_pass http://127.0.0.1:$proxy_port$proxy_path; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; proxy_buffering off; #websocket set $to_upgrade ""; set $to_connection ""; if ($http_upgrade = "websocket"){ set $to_upgrade "websocket"; set $to_connection "Upgrade"; } proxy_set_header Upgrade $to_upgrade; proxy_set_header Connection $to_connection; proxy_read_timeout 86400; } location / { default_type text/html; if ($cookie_proxy_port = ''){ return 404 'Nothing here'; } proxy_http_version 1.1; proxy_pass http://127.0.0.1:$cookie_proxy_port; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; proxy_buffering off; #websocket set $to_upgrade ""; set $to_connection ""; if ($http_upgrade = "websocket"){ set $to_upgrade "websocket"; set $to_connection "Upgrade"; } proxy_set_header Upgrade $to_upgrade; proxy_set_header Connection $to_connection; proxy_read_timeout 86400; } } ``` Remember to change the `listen 28081` to your port , `server name` to your domain (or ip) , the certificate path to your certificate file.
Screenshot ![image](https://user-images.githubusercontent.com/75509566/209468872-56cd45ce-2c09-4b56-9736-c64c9da1bfe8.png)

It works normally . And the reverse proxy still works , no conflicts.

Actually , since nginx is already forwarding ports at /proxy/{port} , you can directly access code-server with https://host.com/proxy/8080/ (while 8080 is the port code-server is using)

kamskanagi commented 1 year ago

I am having the same issue I am running a code-server on docker(image: lscr.io/linuxserver/code-server).

I am building a react application with vite dev server on the code-server.

when i npm run dev, the application will be redirect or forward to https://{ip_address}:8443/proxy/8081/. If base:'/proxy/8081/' or base:'/proxy/{port}/'' is not define in my vite.config.js, it will shows a blank page.

if base:'/proxy/8081' is define in vite.config.js, it will show the content of the url and can navigate through menus. However, if click on sublink, the /proxy/8081/ will disappear and shows "page not found".

I try to use ngnix but it ask for authorization. as shown in the attach.

ng

How can i solve this problems? need your help Thank you in advance

jsjoeio commented 1 year ago

Have you looked through Discussions? We've helped people with Vite in the past. Maybe this has your answer: https://github.com/coder/code-server/discussions/4541

kamskanagi commented 1 year ago

I have looked https://github.com/coder/code-server/discussions/4541 but does not solve the stripping of /absproxy/{port]/ when clicked on a sublink or suburl.

expectation when click on a suburl, i expect the url to be like this http://{ vm_ip}:8444/absproxy/8081/out-of-home-barcode/detail?id=2,

Reality But what i get is this http://{ vm_ip}:8444/out-of-home-barcode/detail?id=2, with page not found error. so i have to manually add the /absproxy/8081/ for it to work.

My second question is how to remove the authorization access on the code-server. because i edit the ~/.config/code-server/config.yaml file as shown below, but still ask for authorization access.

--bind-addr: 127.0.0.1:8080
--auth: none
password: 5*******f5
cert: false

the code-server is running on http://{vm_ip}:8444/?folder=/config/workspace/project

a279437145 commented 1 year ago

401 is from code-server , not from nginx or vite . If you want to use nginx to correct the url ,run nginx inside docker container with the configuration above (remember to remove the ssl as you are using http) and forward the nginx port and access http://your-ip:{nginx-port}/proxy/{react-app-port} nginx is a seperate process from code-server. The traffic doesn't go through code-server while using nginx to forward ports.

jsjoeio commented 1 year ago

Reality

Sounds like you need to set a base path or base URL for your application. That's where I'd look at least.

You'll need to pass --auth "none" to code-server or remove the password from your config.yaml and set auth: none I believe.

kamskanagi commented 1 year ago

@jsjoeio thanks for the reply.

I did set the base as base: "/absproxy/{port}/", in the vite.config.js. at the moment i manually add the /absproxy/{port}/ into the url for it to work.

For the authorization, i try your suggestion.

kamskanagi commented 1 year ago

@a279437145

Yes i know the 401 is from code-server not the nginx. Thats why i was thinking of removing or disabling the authorization access in code-server.

a279437145 commented 1 year ago

But to use nginx you don't have to disable code-server authorization.

a279437145 commented 1 year ago

@kamskanagi

Is your problem solved? And maybe later I'll write a tutorial here about how to use this . ^_^

a279437145 commented 1 year ago

I'm not an experienced nginx user , so this isn't a perfect solution . But anyways , it can work ! So I'm satisfied ^_^

1.Install nginx

It's the same as normal nginx installation , and nginx from the package manager should work , simply install with : (on Debian/Ubuntu)

sudo apt install nginx

Or you can install with any way you like.

2.Configure

Create a .conf file under /etc/nginx/sites-enabled , and copy the following configuration into it.

You can also download it via curl https://gist.githubusercontent.com/a279437145/d8c1565e49e5eda7577e68ec519660bc/raw/1086df501003368582fd59c23ef25f7e3ffc5481/proxy.conf -o proxy.conf

Configuration The same as https://gist.github.com/a279437145/d8c1565e49e5eda7577e68ec519660bc ``` server { listen {{PORT}} ssl; server_name {{IP_ADDRESS/DOMAIN}}; ssl_certificate "{{PATH_TO_SSL_CERTIFICATION_FILE}}"; ssl_certificate_key "{{PATH_TO_SSL_CERTIFICATION_KEY}}"; client_max_body_size 50M; location ~* ^/proxy/(?[^\n\r\/]+)(?.*)$ { default_type text/html; if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) { add_header Content-Type 'text/html; charset=utf-8'; return 501 'Illegal port ${proxy_port}'; } if ($proxy_path = ''){ set $proxy_path '/'; #force to use relative path } add_header Set-Cookie 'proxy_port=$proxy_port; Path=/'; proxy_http_version 1.1; #proxy_ssl_server_name on; proxy_pass http://127.0.0.1:$proxy_port$proxy_path; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; proxy_buffering off; #websocket set $to_upgrade ""; set $to_connection ""; if ($http_upgrade = "websocket"){ set $to_upgrade "websocket"; set $to_connection "Upgrade"; } proxy_set_header Upgrade $to_upgrade; proxy_set_header Connection $to_connection; proxy_read_timeout 86400; } location / { default_type text/html; if ($cookie_proxy_port = ''){ return 404 'Nothing here'; } proxy_http_version 1.1; proxy_pass http://127.0.0.1:$cookie_proxy_port; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real_IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header Accept-Encoding ""; proxy_buffering off; #websocket set $to_upgrade ""; set $to_connection ""; if ($http_upgrade = "websocket"){ set $to_upgrade "websocket"; set $to_connection "Upgrade"; } proxy_set_header Upgrade $to_upgrade; proxy_set_header Connection $to_connection; proxy_read_timeout 86400; } } ```

Then replace the double braces surrounded contents with yours. (Remember to use a different port than code-server)

Note :

  • 1 . If you don't use https to access the proxy , remove ssl on listen line , and remove ssl_certificate ssl_certificate_key .

  • 2 . If your backend is using https , change http on proxy_pass line to https.

3.Usage

Access https://{IP}:{NGINX_PORT}/proxy/{SERVICE_PORT}

Note : If the browser reported The site redirected you too many times , you can directly access https://{IP}/ after accessing https://{IP}:{NGINX_PORT}/proxy/{SERVICE_PORT}.

Notice

1.This uses / location , so better use a port without other services running .

2.This solution uses cookie , so don't use private mode in browser or disable cookie.

3.The default nginx.conf ( /etc/nginx/nginx.conf ) may contain a server config using port 80 , if you don't need it you can comment it to prevent conflicts with other services running on the same server.

jxfruit commented 1 year ago

I'm not an experienced nginx user , so this isn't a perfect solution . But anyways , it can work ! So I'm satisfied ^_^

1.Install nginx

It's the same as normal nginx installation , and nginx from the package manager should work , simply install with : (on Debian/Ubuntu)

sudo apt install nginx

Or you can install with any way you like.

2.Configure

Create a .conf file under /etc/nginx/sites-enabled , and copy the following configuration into it.

You can also download it via curl https://gist.githubusercontent.com/a279437145/d8c1565e49e5eda7577e68ec519660bc/raw/1086df501003368582fd59c23ef25f7e3ffc5481/proxy.conf -o proxy.conf

Configuration The same as https://gist.github.com/a279437145/d8c1565e49e5eda7577e68ec519660bc

 server {
    listen {{PORT}} ssl;
    server_name {{IP_ADDRESS/DOMAIN}};

    ssl_certificate "{{PATH_TO_SSL_CERTIFICATION_FILE}}";
    ssl_certificate_key "{{PATH_TO_SSL_CERTIFICATION_KEY}}";

    client_max_body_size 50M;
    location ~* ^/proxy/(?<proxy_port>[^\n\r\/]+)(?<proxy_path>.*)$ {
  default_type text/html;
      if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) {
      add_header Content-Type 'text/html; charset=utf-8';
      return 501 'Illegal port ${proxy_port}';
  }
  if ($proxy_path = ''){
      set $proxy_path '/'; #force to use relative path
  }
  add_header Set-Cookie 'proxy_port=$proxy_port; Path=/';
  proxy_http_version 1.1;
        #proxy_ssl_server_name on;
        proxy_pass http://127.0.0.1:$proxy_port$proxy_path;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding "";
        proxy_buffering off;
        #websocket
  set $to_upgrade "";
  set $to_connection "";
  if ($http_upgrade = "websocket"){
      set $to_upgrade "websocket";
      set $to_connection "Upgrade";
  }
  proxy_set_header Upgrade $to_upgrade;
  proxy_set_header Connection $to_connection;
  proxy_read_timeout 86400;
    }
    location / {
      default_type text/html;
  if ($cookie_proxy_port = ''){
      return 404 'Nothing here';
  }
  proxy_http_version 1.1;
  proxy_pass http://127.0.0.1:$cookie_proxy_port;
  proxy_set_header Host $http_host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Real_IP $remote_addr;
  proxy_set_header User-Agent $http_user_agent;
  proxy_set_header Accept-Encoding "";
  proxy_buffering off;
  #websocket
  set $to_upgrade "";
  set $to_connection "";
  if ($http_upgrade = "websocket"){
      set $to_upgrade "websocket";
      set $to_connection "Upgrade";
  }
  proxy_set_header Upgrade $to_upgrade;
  proxy_set_header Connection $to_connection;
  proxy_read_timeout 86400;
    }
}

Then replace the double braces surrounded contents with yours. (Remember to use a different port than code-server)

Note :

  • 1 . If you don't use https to access the proxy , remove ssl on listen line , and remove ssl_certificate ssl_certificate_key .
  • 2 . If your backend is using https , change http on proxy_pass line to https.

3.Usage

Access https://{IP}:{NGINX_PORT}/proxy/{SERVICE_PORT}

Note : If the browser reported The site redirected you too many times , you can directly access https://{IP}/ after accessing https://{IP}:{NGINX_PORT}/proxy/{SERVICE_PORT}.

Notice

1.This uses / location , so better use a port without other services running .

2.This solution uses cookie , so don't use private mode in browser or disable cookie.

3.The default nginx.conf ( /etc/nginx/nginx.conf ) may contain a server config using port 80 , if you don't need it you can comment it to prevent conflicts with other services running on the same server.

I am curious about the ip address. The address "https://{IP}:{NGINX_PORT}/proxy/{SERVICE_PORT}" only can be visited in container?

a279437145 commented 1 year ago

@jxfruit

No , it can be visited from anywhere that can access the machine running nginx. So you should be able to access it outside the container (with an address like {CONTAINER_IP}:{NGINX_PORT}) By the way , if you mean accessing it from a faraway host , you should forward the nginx port.(Just like forwarding the code-server port)

Then if you still can only access it inside the container , you may check the configuration file. Especially check if nginx is exposed to the host machine.(I think it always is,maybe)

thuzhf commented 5 months ago

@code-asher Is there a way for code-server's built-in port-forwarding to forward the full url path to backend server? For example, if my server is running at https://somehost/code-server-prefix/proxy/8080/ , and I visit https://somehost/code-server-prefix/proxy/8080/path1/path2, in the current version of code-server, my backend server will receive a request of /path1/path2, but I hope my backend server receive a request of /code-server-prefix/proxy/8080/path1/path2. That's to say, I do not want code-server remove the path prefix in the request url, but pass the full url to the backend server. Is there a way to achieve this effect?

code-asher commented 5 months ago

Yup, use absproxy instead of proxy: https://somehost/code-server-prefix/absproxy/8080/.

It would be nice if in the port forwarding panel we had an option to choose which one you want, since right now the only way to even know about absproxy is to read through the docs.

code-asher commented 5 months ago

One caveat though, code-server does not know about its own prefix, so this will result in /absproxy/8080/path1/path2 to your backend (minus the /code-server-prefix). There is not a way around this currently, so if you need this you might want to stand up a standalone proxy like Caddy or NGINX instead of using code-server's built-in (you can still tie it into the ports panel if necessary).

thuzhf commented 5 months ago

One caveat though, code-server does not know about its own prefix, so this will result in /absproxy/8080/path1/path2 to your backend (minus the /code-server-prefix). There is not a way around this currently, so if you need this you might want to stand up a standalone proxy like Caddy or NGINX instead of using code-server's built-in (you can still tie it into the ports panel if necessary).

Oh, that's a bit unexpected... You said "there is not a way around this currently". Does this mean code-server doesn't support any params like base_url such as in jupyter (e.g., you can start a jupyter server by running: jupyter lab --ServerApp.base_url=/jupyter-prefix/ so that in nginx config you can pass full request url to the backend jupyter server)?

thuzhf commented 5 months ago

@code-asher In addition, what I want to claim is that even though code-server itself doesn't need such a param like base_url because it probably uses relative URLs instead of absolute URLs, however, because users have a massive demand to set up others servers/apps inside the code-server (maybe that's a big reason why you develop the automatic port forwarding tools which I think is very useful) and many famous apps (including jupyter server, dash server, etc) do not use relative URLs (which itself is probably not a good implementation IMHO), thus these servers cannot be properly port-forwarded by the code-server's built-in port-forwarding functionality, which can do large damage to its ease of use and bothers me a lot. I hope code-server can also support base_url besides using relative URLs. In this way, code-server can both doesn't depend on the prefix path it is running on and also pass the base_url to the servers/apps running inside it for some of these servers/apps to be port-forwarded properly. That's my humble opinion, and if there is already a way to achieve this effect, please let know. Thanks!

code-asher commented 5 months ago

Does this mean code-server doesn't support any params like base_url [...] because it probably uses relative URLs instead of absolute URLs

Yup, exactly this.

Are you able to host at a subdomain instead? Something like code-server.domain.tld.

many famous apps (including jupyter server, dash server, etc) do not use relative URLs (which itself is probably not a good implementation IMHO)

100% agree.

I think it would be reasonable to add a --base-path flag that will be prepended to all absproxy requests.

code-asher commented 5 months ago

I opened an issue: https://github.com/coder/code-server/issues/6770

Not sure I can get to it any time soon, but happy to take pull requests.

thuzhf commented 5 months ago

@code-asher I see. Thank you for your clear explanation and understanding and support!