headscale
headscale copied to clipboard
Handle CORS headers and OPTIONS method for HTTP API
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}"
[email protected]: "headscale.${BASE_DOMAIN}"
[email protected]: "headscale.${BASE_DOMAIN}"
[email protected]: 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: https://github.com/Medzoner/traefik-plugin-cors-preflight/issues/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}" [email protected]: "headscale.${BASE_DOMAIN}" [email protected]: "headscale.${BASE_DOMAIN}" [email protected]: 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