Handle CORS headers and OPTIONS method for HTTP API
routerino opened this issue · 18 comments
Bug description
When trying to use a browser to generate API requests (like, hypothetically, if you're building a web frontend for headscale), the browser expects to to use CORS to determine if it can talk to the external server. The browser does this by the following:
- Sending a pre-flight
OPTIONS
request, expecting back a 204 response with the CORS headers attached - Once the response is accepted, sending the real API request with all the data
For this to work, we need two things:
- The server (headscale) to generate CORS headers (and have it be configurable to set the domains appropriately)
- The server to accept
OPTIONS
requests without authorization.
To Reproduce
Generate a fetch request from a browser in a separate domain. Such as:
let apiKey = '<my api key>';
let url = 'https://<my headscale domain>/api/v1/machine';
window.fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`
}}).then((resp) => resp.json()).then(function (data) {console.log(data);}).catch(function (error) {console.log(error);});});
If no CORS headers are specified, you get this nice error in the browser console:
If you have the right headers (if you, for example, inject them with a reverse proxy) but the OPTIONS request is blocked by authorization, you get this nice error instead:
Because the OPTIONS request is returning a 401 unauthorized when it shouldn't.
Both are not ideal. You can fix both with a reverse proxy, but you certainly shouldn't have to. The web server (gin?) should return OPTIONS with a 204 and be setting the CORS headers on all requests (and the CORS headers should be configurable).
Context info
These problems were fixed externally by routing through a Caddy reverse proxy using these matching settings:
@hs-options {
host hs.<my-domain>
method OPTIONS
}
@hs-other {
host hs.<my-domain>
}
handle @hs-options {
header {
Access-Control-Allow-Origin https://<my-frontend-subdomain>
Access-Control-Allow-Headers *
}
respond 204
}
handle @hs-other {
reverse_proxy http://headscale:8080 {
header_down Access-Control-Allow-Origin https://<my-frontend-subdomain>
header_down Access-Control-Allow-Headers *
}
}
If you use caddy-docker-proxy, here's the same (mostly) config, done in labels:
labels:
caddy: "headscale.${BASE_DOMAIN}"
caddy.@hs-other.host: "headscale.${BASE_DOMAIN}"
caddy.@hs-options.host: "headscale.${BASE_DOMAIN}"
caddy.@hs-options.method: OPTIONS
caddy.0_import: tlsdns
caddy.1_handle: "@hs-options"
caddy.1_handle.header.Access-Control-Allow-Origin: "https://ui.headscale.${BASE_DOMAIN}"
caddy.1_handle.header.Access-Control-Allow-Headers: "*"
caddy.1_handle.header.Access-Control-Allow-Methods: '"POST, GET, OPTIONS, DELETE"'
caddy.1_handle.respond: "204"
caddy.8_handle: /metrics
caddy.8_handle.import: auth
caddy.8_handle.reverse_proxy: "{{upstreams 9090}}"
caddy.9_handle: "@hs-other"
caddy.9_handle.reverse_proxy: "{{upstreams 80}}"
caddy.9_handle.reverse_proxy.header_down_1: "Access-Control-Allow-Origin https://ui.headscale.${BASE_DOMAIN}"
caddy.9_handle.reverse_proxy.header_down_2: "Access-Control-Allow-Headers *"
caddy.9_handle.reverse_proxy.header_down_3: 'Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE"'
Someone know how to configure it for Traefik?
I tryed to add next labels:
traefik.http.routers.headscale-public-https.middlewares: headscale-cors
traefik.http.middlewares.headscale-cors.headers.customResponseHeaders.Access-Control-Allow-Origin: https://web.headscale.yourdomain.example
traefik.http.middlewares.headscale-cors.headers.customResponseHeaders.Access-Control-Allow-Methods: GET, POST, PUT, DELETE
traefik.http.middlewares.headscale-cors.headers.customResponseHeaders.Access-Control-Allow-Headers: Content-Type
Looks like the first part of the problem is solved, but I have a problem with 204 status code in the answer..
I think maybe this plugin should help but I can't configure it properly, it shows me error "status code is smallest than minimum value: 100"
(Issue with details opened here: Medzoner/traefik-plugin-cors-preflight#8)
@deimjons
Maybe try this plugin instead? https://plugins.traefik.io/plugins/628c9f0f108ecc83915d7771/replace-status-code
@Mikle-Bond Thank you for your attention. I tried this plugin but it didn't help me. I don't know: I doing something wrong or the plugin just not working.
I have added additional routes in labels:
traefik.http.routers.headscale-options.rule: Host(`headscale.yourdomain.example/api/v1/apikey`) && Method(`OPTIONS`)
traefik.http.routers.headscale-options.entrypoints: websecure
traefik.http.routers.headscale-options.tls: true
traefik.http.routers.headscale-options.tls.certresolver: prod
traefik.http.routers.headscale-options.middlewares: replace-response-code@file
also, I added a plugin and middleware (like they show in the documentation example) in the configuration file of traefik: traefik.yaml
experimental:
plugins:
traefik-replace-response-code:
moduleName: "github.com/pierre-verhaeghe/traefik-replace-response-code"
version: "v0.2.0"
http:
middlewares:
replace-response-code:
plugin:
traefik-replace-response-code:
inputCode: 401
outputCode: 200
removeBody: "true"
As a result, I have the same error:
Access to fetch at 'https://headscale.yourdomain.example/api/v1/apikey' from origin 'https://admin.headscale.yourdomain.example' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Someone know how to configure it for Traefik? I tryed to add next labels:
traefik.http.routers.headscale-public-https.middlewares: headscale-cors traefik.http.middlewares.headscale-cors.headers.customResponseHeaders.Access-Control-Allow-Origin: https://web.headscale.yourdomain.example traefik.http.middlewares.headscale-cors.headers.customResponseHeaders.Access-Control-Allow-Methods: GET, POST, PUT, DELETE traefik.http.middlewares.headscale-cors.headers.customResponseHeaders.Access-Control-Allow-Headers: Content-Type
Looks like the first part of the problem is solved, but I have a problem with 204 status code in the answer..
I think maybe this plugin should help but I can't configure it properly, it shows me error "status code is smallest than minimum value: 100" (Issue with details opened here: Medzoner/traefik-plugin-cors-preflight#8)
did you manage? I also have to do this
edit: https://doc.traefik.io/traefik/v2.4/middlewares/headers/
no, I use it via prefix /admin.. ((
How to add this to Nginx Proxy Manager ?
How to add this to Nginx Proxy Manager ?
Hi, did you ever figure this out? I am also unable to access api via NPM.
How to add this to Nginx Proxy Manager ?
Hi, did you ever figure this out? I am also unable to access api via NPM.
Yes, all working fine, if you using cloudflare disable the proxy (orange cloud)
Thank you very much. That sorted it.
I hope to support CORS, and I would like to use healscale directly instead of using Nginx and other programs for proxy, which is very inconvenient
If you use caddy-docker-proxy, here's the same (mostly) config, done in labels:
labels: caddy: "headscale.${BASE_DOMAIN}" caddy.@hs-other.host: "headscale.${BASE_DOMAIN}" caddy.@hs-options.host: "headscale.${BASE_DOMAIN}" caddy.@hs-options.method: OPTIONS caddy.0_import: tlsdns caddy.1_handle: "@hs-options" caddy.1_handle.header.Access-Control-Allow-Origin: "https://ui.headscale.${BASE_DOMAIN}" caddy.1_handle.header.Access-Control-Allow-Headers: "*" caddy.1_handle.header.Access-Control-Allow-Methods: '"POST, GET, OPTIONS, DELETE"' caddy.1_handle.respond: "204" caddy.8_handle: /metrics caddy.8_handle.import: auth caddy.8_handle.reverse_proxy: "{{upstreams 9090}}" caddy.9_handle: "@hs-other" caddy.9_handle.reverse_proxy: "{{upstreams 80}}" caddy.9_handle.reverse_proxy.header_down_1: "Access-Control-Allow-Origin https://ui.headscale.${BASE_DOMAIN}" caddy.9_handle.reverse_proxy.header_down_2: "Access-Control-Allow-Headers *" caddy.9_handle.reverse_proxy.header_down_3: 'Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE"'
Has anyone else made this work I can't figure it out.
I don't use Caddy, and I don't actually have any plans to use it. I just want to run Headscale directly.
Cross-Origin Request Warning: The Same Origin Policy will disallow reading the remote resource at https://headscale.domain.com/api/v1/node soon. (Reason: When the
Access-Control-Allow-Headers
is*
, theAuthorization
header is not covered. To include theAuthorization
header, it must be explicitly listed in CORS headerAccess-Control-Allow-Headers
).
I am getting this error with the above implementation using Headscale-admin
@B08Z This might be super old and you may have opened an issue on headscale-admin referencing this, but you should be able to use this value to allow CORS from anywhere:
"Access-Control-Allow-Headers Authorization, *"
or perhaps
'Access-Control-Allow-Headers "Authorization, *"'
is the right syntax?
It needs to be explicit for whatever reason.
This issue is stale because it has been open for 90 days with no activity.
This issue was closed because it has been inactive for 14 days since being marked as stale.
This issue is preventing and stopping developers from creating any serious web-ui for headscale and should not be closed, without this, all we're gonna have is the five minute UI's that currently exist.