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

CORS Preflight OPTIONS request with private port is blocked with 401 Unauthorized #14576

Closed
omarkohl opened this issue Nov 9, 2022 · 14 comments
Labels
editor: code (browser) feature: ports meta: stale This issue/PR is stale and will be closed soon team: workspace Issue belongs to the Workspace team type: bug Something isn't working

Comments

@omarkohl
Copy link

omarkohl commented Nov 9, 2022

Bug description

My app is separated into an API and a GUI, started on different ports. If the API port is public, everything works fine. If the port is made private, then the OPTIONS request never reaches the API but seems to be blocked by Gitpod.

OPTIONS /query HTTP/2
Host: 8080-cleodoraforeca-cleodora-cnqioetp8sl.ws-eu74.gitpod.io
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Referer: https://3000-cleodoraforeca-cleodora-cnqioetp8sl.ws-eu74.gitpod.io/
Origin: https://3000-cleodoraforeca-cleodora-cnqioetp8sl.ws-eu74.gitpod.io
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
TE: trailers

This request never reaches my API. Instead I see the following response in the browser console:

HTTP/2 401 Unauthorized
content-length: 0
date: Wed, 09 Nov 2022 20:05:01 GMT
X-Firefox-Spdy: h2

This suggests to me that Gitpod is blocking it. Possibly because Gitpod expects the request to already contain the authentication cookie. But note that this would be against the spec and the browser does NOT send authentication in OPTIONS requests.

Preflight requests and credentials

CORS-preflight requests must never include credentials. The response to a preflight request must specify Access-Control-Allow-Credentials: true to indicate that the actual request can be made with credentials.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials

If the Gitpod port is public then everything works fine and both the preflight OPTIONS request and the POST request reach my API!

I can tell that the OPTIONS request is not reaching my API thanks to logging output in my app.

More details: cleodora-forecasting/cleodora#27

Steps to reproduce

You can check the workspace below for a concrete example, but this must be a general issue. Check the README of my example repository for step-by-step instructions: https://github.com/omarkohl/gitpod-cors

Workspace affected

cleodoraforeca-cleodora-1raaym6t355

Expected behavior

OPTIONS requests are let through by Gitpod and only other HTTP requests are checked for credentials.

Example repository

https://github.com/omarkohl/gitpod-cors

Anything else?

No response

@omarkohl omarkohl added the type: bug Something isn't working label Nov 9, 2022
@omarkohl omarkohl changed the title Preflight OPTIONS request with private port is blocked with 401 Unauthorized CORS Preflight OPTIONS request with private port is blocked with 401 Unauthorized Nov 9, 2022
@omarkohl
Copy link
Author

I created a small example repository that reproduces the issue. I updated the issue description. This is the repository. The README contains step by step instructions to reproduce the problem https://github.com/omarkohl/gitpod-cors

@omarkohl
Copy link
Author

For what it's worth, GitHub Codespaces has exactly the same problem (same code base?) and yesterday there was confirmation that the issue is real and that my hypothesis concerning the OPTIONS request and authentication is correct.

https://github.com/orgs/community/discussions/15351

So now it's a race, who will fix it first, Codespaces or Gitpod 😉

@axonasif
Copy link
Member

Hi @omarkohl, this issue is now forwarded to an engineering team.

Meanwhile you may take a look at the following resources:

@stale
Copy link

stale bot commented Feb 19, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@omarkohl
Copy link
Author

@axonasif any update from the engineering team?

@GCHQDeveloper926
Copy link

I am also suffering this exact issue, are there any updates?

@GCHQDeveloper926
Copy link

@omarkohl I've got round this for now with a small nginx proxy running in gitpod, happy to share the config and tasks with you?

@stale
Copy link

stale bot commented Sep 17, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the meta: stale This issue/PR is stale and will be closed soon label Sep 17, 2023
@billy1kaplan
Copy link

@GCHQDeveloper926 do you have more info on the workaround for this?

@stale stale bot removed the meta: stale This issue/PR is stale and will be closed soon label Feb 6, 2024
@GCHQDeveloper926
Copy link

Hi @billy1kaplan I've spun up NGINX inside my gitpod to forward requests to the app. I can send you a config if you would like?

@dsschneidermann
Copy link

dsschneidermann commented Mar 1, 2024

The workaround that is possible here is to prevent the OPTIONS pre-flights all together by hosting all endpoints on the same domain name (eg. same port for Gitpod).

It means we don't get the added benefit of always checking that our pre-flights requests are fine, but that might neeed to be validated at test/staging anyway to have production-env parity. This issue seems like a real blocker for private ports working out of the box, and I don't suppose it can get fixed at anytime by Gitpod/Github due to the resulting security questions, so I'm taking the extra liberty here of documenting a full copy-pastable solution - hopefully it will save someone a few hours, without needing to use exposed ports.

Below is a sample nginx setup (2 files), usable with pre-installed Gitpod nginx, current version nginx/1.25.4 that uses mostly default settings but runs nginx as a standalone program. To run nginx, you need to make sure there is a tmp/nginx directory preexisting and use the command line: nginx -p $(pwd) -c .dev-api-proxy/nginx.conf (where '-p' is the way to set current working dir).

I am using the folder ".dev-api-proxy" to hold the nginx config. The config in question targets a backend on port 4000, a frontend-serve at port 3001 and a resulting reverse-proxy that accepts requests to port 3000. The API is surfaced with the /api endpoint prefix, and (you can remove this if not needed) socket.io requests to /socket.io. Below the nginx config, I've also included some sample code for a nuxt-vue3 frontend that redirects to the reverse-proxy port when opened.

File: .dev-api-proxy/nginx.conf

daemon off;
worker_processes auto;
pid tmp/nginx/nginx.pid;
error_log tmp/nginx/error.log;

events {
	worker_connections 768;
}

http {
	sendfile on;
	tcp_nopush on;
	types_hash_max_size 2048;

	include ./proxy.conf;
}

File: .dev-api-proxy/proxy.conf

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
upstream http3001 {
    server 127.0.0.1:3001;
}
upstream http4000 {
    server 127.0.0.1:4000;
}
server {
    listen 3000;
    proxy_request_buffering off;
    proxy_buffers 16 128k;
    proxy_buffer_size 256k;
    proxy_busy_buffers_size 256k;
    location /api {
        proxy_pass http://http4000;
        proxy_pass_request_headers on;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
    }
    location /socket.io {
        proxy_pass http://http4000;
        proxy_pass_request_headers on;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
    # Forward all other requests to the frontend build running on 3001.
    location / {
        proxy_pass http://http3001;
        proxy_pass_request_headers on;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

Additional sample code for nuxt-vue3 to ensure the right port is always used.

File: utils/gitpod.ts

export const useGitpodPortRedirect = () => {
    // Ports in use are currently hardcoded here and should match the dev-api-proxy config.
    // The necessity of running dev-api-proxy is only for gitpod, where private ports will
    // block OPTIONS pre-flight requests due to not having any credentials (which is defined
    // as correct in HTTP spec). Gitpod probably cannot fix this situation as it would need
    // to allow inbound traffic on private ports, potentially opening security issues.
    // The workaround is to use dev-api-proxy that exposes both the nuxt build and API
    // endpoints, thus avoiding all pre-flights, and here we just redirect the browser.
    let isRedirected = false
    const nuxtPort = "3001"
    const proxyPort = "3000"
    if (
        window.location.host.endsWith(".gitpod.io") &&
        window.location.host.startsWith(`${nuxtPort}-`)
    ) {
        window.location.host = window.location.host.replace(nuxtPort, proxyPort)
        isRedirected = true
    }
    return { isRedirected }
}

File: app.vue

<script setup>
import { useGitpodPortRedirect } from "~/utils/gitpod"

const { isRedirected } = useGitpodPortRedirect()
</script>

<template>
    <div v-if="isRedirected">&nbsp;Redirecting...</div>
    <NuxtLayout v-else>
        <NuxtPage />
    </NuxtLayout>
</template>

Copy link
Contributor

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions github-actions bot added the meta: stale This issue/PR is stale and will be closed soon label May 30, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 10, 2024
@MattWorthington95
Copy link

Can this issue be reopened? Im running into the exact same situation

@axonasif
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
editor: code (browser) feature: ports meta: stale This issue/PR is stale and will be closed soon team: workspace Issue belongs to the Workspace team type: bug Something isn't working
Projects
No open projects
Status: No status
Development

No branches or pull requests

6 participants