Traefik headers middleware is not handling CORS preflight request

I have the following middleware defined:

[http.middlewares.cors-allow.headers]
accessControlAllowHeaders = [ "Authorization" ]
accessControlAllowMethods = [ "GET", "HEAD", "OPTIONS" ]
accessControlAllowOrigin = "*"
accessControlAllowCredentials = true

When performing an OPTIONS request on the URL (tried using curl), traefik does not handle the response, and instead passes it through to the service, which does not handle OPTIONS requests. This breaks CORS preflight requests.

What am I doing wrong? How can I troubleshoot this?

Can you share your test curl ?

curl -i -X OPTIONS -H 'Origin: https://example.com' -H 'access-control-request-method: GET' https://api.example.com/info
HTTP/2 200 
access-control-allow-credentials: true
access-control-allow-headers: Authorization,Origin,Content-Type,Accept
access-control-allow-methods: GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS
access-control-allow-origin: https://example.com
access-control-max-age: 0
content-length: 0
date: Mon, 11 Jan 2021 00:31:25 GMT

@cakiwi

$ curl -i -X OPTIONS -H 'Origin https://example.com' -H 'access-control-request-method: GET' http://robotben-test-project.ide.localdomain/
HTTP/1.1 400 Bad Request: invalid header name
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request: invalid header name

[edit] Here are both configs in full (these are two separate files in the traefik config dir):

base.toml:

[http.routers.ide-controller]
rule = "Host(`controller.ide.localdomain`)"
service = "ide-controller"

[http.routers.dashboard]
rule = "Host(`dashboard.ide.localdomain`)"
service = "api@internal"
middlewares = [ "dashboard-auth" ]

[[http.services.ide-controller.loadBalancer.servers]]
url = "http://app:3000"

[http.middlewares.cors-allow.headers]
accessControlAllowHeaders = "*"
accessControlAllowMethods = "*"
accessControlAllowOrigin = "*"
accessControlAllowCredentials = true

[http.middlewares.auth-handler.forwardAuth]
address = "http://app:3000/auth"
trustForwardHeader = true

[http.middlewares.dashboard-auth.basicAuth]
users = [ "admin:XXXXXXXXXXXXXXXXXXXXXXXX" ]

theia-deployment--robotben--test-project.toml:

[http.routers.theia-deployment--robotben--test-project]
rule = "Host(`robotben-test-project.ide.localdomain`) || HostRegexp(`{webview:[^.]}.webview.robotben-test-project.ide.localdomain`)"
service = "theia-deployment--robotben--test-project"
middlewares = [ "cors-allow" ]

[http.routers.theia-deployment--robotben--test-project--preview]
rule = "Host(`preview.robotben-test-project.ide.localdomain`)"
service = "theia-deployment--robotben--test-project--preview"

[[http.services.theia-deployment--robotben--test-project.loadBalancer.servers]]
url = "http://172.24.0.5:3000"

[[http.services.theia-deployment--robotben--test-project--preview.loadBalancer.servers]]
url = "http://172.24.0.5:8080"

You missed the : after Origin

I really don't know if * is allowed here, maybe someone else knows. I might have time for a test later.

Ok, I've found the issue seems to be sporadic. I'm seeing this message in the logs:

middleware \"cors-allow@file\" does not exist

Which is odd, b/c the middleware is definitely there in the base config.I'm not changing or updating the base config at any time. But sometimes when I touch-update the individual deployment config, the middleware loads successfully.

Is there some race condition that can occur when updating configs that causes the middleware to not be found?

Should I instead be defining middleware with each individual deployment config and not the base config?

Check the log at startup. Maybe with --loglevel=DEBUG. I'm guessing an error on loading that middleware.

As per Cors Headers
This is the way I do it, lists of methods and origins, but using docker labels.
I'm not too savvy with the TOML I use yaml when I have to do a file provider/config, so I cannot parse a

[http.middlewares]
  [http.middlewares.testHeader.headers]
    accessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
    accessControlAllowOriginList = ["https://foo.bar.org","https://example.org"]
    accessControlMaxAge = 100
    addVaryHeader = true

I define it on the traefik service using labels. A file provider defining the middleware should work just as well.