ingress-nginx: nginx.ingress.kubernetes.io/cors-allow-origin doesn't seem to work?

When I enable the cors header using this annotation:

nginx.ingress.kubernetes.io/enable-cors: “true” nginx.ingress.kubernetes.io/cors-allow-methods: “GET”

I will get the * domain annotation and all the methods allowed. I am able to use this annotation to only allow for GETs, which also works. But when I try to add this:

nginx.ingress.kubernetes.io/cors-allow-origin: http://localhost:3000

(Or any other domain for that matter), I get no cors headers at all. When that is removed, the cors headers show up again.


NGINX Ingress controller Release: v1.1.3 Build: 9d3a285f19a704524439c75b947e2189406565ab Repository: https://github.com/kubernetes/ingress-nginx nginx version: nginx/1.19.10


Kubernetes version (use kubectl version):

Client Version: version.Info{Major:“1”, Minor:“23”, GitVersion:“v1.23.5”, GitCommit:“c285e781331a3785a7f436042c65c5641ce8a9e9”, GitTreeState:“clean”, BuildDate:“2022-03-16T15:58:47Z”, GoVersion:“go1.17.8”, Compiler:“gc”, Platform:“linux/amd64”} Server Version: version.Info{Major:“1”, Minor:“23”, GitVersion:“v1.23.3”, GitCommit:“816c97ab8cff8a1c72eccca1026f7820e93e0d25”, GitTreeState:“clean”, BuildDate:“2022-01-25T21:19:12Z”, GoVersion:“go1.17.6”, Compiler:“gc”, Platform:“linux/amd64”}

Environment:

  • Cloud provider or hardware configuration: minikube on local hardware, virtualbox provider

  • OS (e.g. from /etc/os-release): Ubuntu 21.10

  • Kernel (e.g. uname -a): victory 5.13.0-39-lowlatency

  • Install tools:

    • Please mention how/where was the cluster created like kubeadm/kops/minikube/kind etc.
  • Basic cluster related info:

    • kubectl version
    • kubectl get nodes -o wide
  • How was the ingress-nginx-controller installed: ArgoCD / Helm

Helm values: ingress-nginx: controller: config: compute-full-forwarded-for: “true” large-client-header-buffers: “4 64k” proxy-body-size: “100m” proxy-buffer-size: “64k” ssl-redirect: “true” use-forwarded-headers: “true” service: type: ClusterIP hostNetwork: true dnsPolicy: ClusterFirstWithHostNet admissionWebhooks: enabled: false extraArgs: default-ssl-certificate: ingress-nginx/ingress-certificate podLabels: gafaelfawr.lsst.io/ingress: “true” hub.jupyter.org/network-access-proxy-http: “true” metrics: enabled: true service: annotations: prometheus.io/port: “10254” prometheus.io/scrape: “true”

  • Current State of the controller:
    • kubectl describe ingressclasses Name: nginx Labels: app.kubernetes.io/component=controller app.kubernetes.io/instance=ingress-nginx app.kubernetes.io/managed-by=Helm app.kubernetes.io/name=ingress-nginx app.kubernetes.io/part-of=ingress-nginx app.kubernetes.io/version=1.1.3 argocd.argoproj.io/instance=ingress-nginx helm.sh/chart=ingress-nginx-4.0.19 Annotations: <none> Controller: k8s.io/ingress-nginx Events: <none>

Here’s my ingress yaml:

apiVersion: v1
items:
- apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      kubernetes.io/ingress.class: nginx
      meta.helm.sh/release-name: sherlock-dev
      meta.helm.sh/release-namespace: sherlock-dev
      nginx.ingress.kubernetes.io/auth-method: GET
      nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Token
      nginx.ingress.kubernetes.io/auth-signin: https://minikube.lsst.codes/login
      nginx.ingress.kubernetes.io/auth-url: https://minikube.lsst.codes/auth?scope=exec:admin
      nginx.ingress.kubernetes.io/cors-allow-methods: GET
      nginx.ingress.kubernetes.io/enable-cors: "true"
    creationTimestamp: "2022-04-13T00:49:04Z"
    generation: 1
    labels:
      app.kubernetes.io/instance: sherlock-dev
      app.kubernetes.io/managed-by: Helm
      app.kubernetes.io/name: sherlock
      app.kubernetes.io/version: 0.1.6
      helm.sh/chart: sherlock-0.1.11
    name: sherlock-dev
    namespace: sherlock-dev
    resourceVersion: "82836"
    uid: afd005c3-ba99-4477-bc0a-cdc166e17398
  spec:
    rules:
    - host: minikube.lsst.codes
      http:
        paths:
        - backend:
            service:
              name: sherlock-dev
              port:
                number: 8080
          path: /sherlock
          pathType: ImplementationSpecific
  status:
    loadBalancer:
      ingress:
      - ip: 10.98.0.156
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 8
  • Comments: 22 (10 by maintainers)

Most upvoted comments

In my case was that the backend was returning wrong CORS headers (Allow *) and nginx is not overwritting them. If backend sends an *, nginx just append it to the nginx.ingress.kubernetes.io/cors-allow-origin response header. Is there a way to overwrite this behaviour without modifying the backend?

Is there any movement on this? It looks like there’s a PR… I really need this working.

I had a similar issue (getting CORS issues in Burp Suite Vulnerability test) and, it finally worked with this:

If I Remove any cors related annotation, and add:

    nginx.ingress.kubernetes.io/server-snippet: |
      add_header Access-Control-Allow-Origin "https://*.example.com";

it returns the right header (Access-Control-Allow-Origin: https://*.example.com) but fails in Burp Suite Vulnerability test.

If I add also nginx.ingress.kubernetes.io/enable-cors: 'true'

Returns also the ‘Access-Control-Allow-Origin: *’ header but passes the Vulnerability test.

I hope that this maybe helps someone

@rudolfbyker case3 & case4 are a cause of concern.

  • There has been at least 1 change to harden the use of CORS in the past year
  • Could you kindly write a step-by-step doc that someone can copy/paste from to reproduce on a minikube or a kind cluster. I think its important that we clarify the status in clear unambiguous data
  • The project is in a stabilization phase but close to completing the stabilization work. So if this is a bug, then it will be taken up after ending the stabilization work that is in progress now
  • In case this is an issue and in case a PR comes in from community, it will be appreciated and reviewed sooner

I will try to take a look at this when I have time Until then feel free to comment 😃

The expected behavior for the CORS preflight request (the OPTIONS request) is that the server responds to all requests, regardless of origin, to indicate what type of requests are allowed on the resource. Browsers use the results of preflight requests to determine if the actual requests should be sent. The Mozilla CORS documentation describes this requirement. Without responding to the preflight request for non-allowed origins, the browser cannot decide that it is unsafe to send the original request, which is the purpose of setting up CORS in nginx.

To handle multiple allowed origins, the server also needs to set the Vary response header to Origin to indicate to browsers that the response can change based on the Origin request header. This way different allowed-origins can be returned and the browser can handle caching the information appropriately.

What is not clear is how origins with wildcarded subdomains should be handled because it doesn’t appear that https://*.example.com is a valid value for Access-Control-Allow-Origin. I think it’s unclear how a browser might choose to handle such a response. Maybe a nginx.ingress.kubernetes.io/cors-default-allow-origin value is required when wildcarded subdomains are used? For a strict list of valid allowed origins, I think it’s acceptable just to return one of them as long as the Vary header is set.

Thanks for the pointer to the e2e tests. I think what’s missing are the tests for the preflight requests specifically, which is the trueoptions branch in the current implementation. I don’t see coverage for OPTIONS requests from either an allowed origin or a non-allowed origin. I think what we need here is essentially a falseoptions branch that can handle multiple allowed origins and wildcarded subdomains and then all the e2e tests.

I opened https://github.com/kubernetes/ingress-nginx/pull/10490 that may fix the issue with multiple allowed origins. Please have a look. Thank you @michaelliau for the heads-up!

I’m having the same, or similar issue. Let me demonstrate what I get in the /etc/nginx/nginx.conf in the controller pod in response to various annotations:

Case 1: All origins, no credentials.

This works.

nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-credentials: "false"
set $http_origin *;
set $cors 'true';

if ($request_method = 'OPTIONS') {
    set $cors ${cors}options;
}

if ($cors = "true") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
}

if ($cors = "trueoptions") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
    more_set_headers 'Content-Type: text/plain charset=UTF-8';
    more_set_headers 'Content-Length: 0';
    return 204;
}

Case 2: All origins, with credentials

This does not work, because of line set $http_origin *;. This discards the Origin value set in the browser and uses the literal “*”, which is only supported when Access-Control-Allow-Credentials is false. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#directives .

nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
set $http_origin *;
set $cors 'true';

if ($request_method = 'OPTIONS') {
    set $cors ${cors}options;
}

if ($cors = "true") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true'; 
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
}

if ($cors = "trueoptions") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true'; 
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
    more_set_headers 'Content-Type: text/plain charset=UTF-8';
    more_set_headers 'Content-Length: 0';
    return 204;
}

Case 3: Specific origin, with credentials

This does not work, because the $cors variable is never set, only read. This must surely be a bug!

nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: {{ .Values.global.domain }}
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
if ($request_method = 'OPTIONS') {
    set $cors ${cors}options;
}

if ($cors = "true") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true'; 
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
}

if ($cors = "trueoptions") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true'; 
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
    more_set_headers 'Content-Type: text/plain charset=UTF-8';
    more_set_headers 'Content-Length: 0';
    return 204;
}

Case 4: Specific origin, without credentials

Same issue as case 3 above.

nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: {{ .Values.global.domain }}
nginx.ingress.kubernetes.io/cors-allow-credentials: "false"
if ($request_method = 'OPTIONS') {
    set $cors ${cors}options;
}

if ($cors = "true") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
}

if ($cors = "trueoptions") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    
    more_set_headers 'Access-Control-Allow-Methods: PUT, GET, POST, PATCH, OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    
    more_set_headers 'Access-Control-Max-Age: 1728000';
    more_set_headers 'Content-Type: text/plain charset=UTF-8';
    more_set_headers 'Content-Length: 0';
    return 204;
}

I came across this issue while I was battling with CORS deployed in ingress-nginx because a web server I deployed did not ship it in its configuration, so I wanted to add it in nginx instead.

I was confused when I did not get the expected CORS behaviour when enabling cors and setting the expected allowed-origin(s) when testing with cURL:

❯ curl -i -XOPTIONS https://qa.example.org/api
HTTP/2 405
date: Tue, 09 Aug 2022 11:39:25 GMT
content-length: 0
allow: GET
x-api-version: foo-service
x-response-time-ms: 0
strict-transport-security: max-age=15724800; includeSubDomains

Setting the allowed origins to * made it work (as reported here as well). However, when I read the generated config posted here https://github.com/kubernetes/ingress-nginx/issues/8469#issuecomment-1194784059, I see that the first line requires the request to contain the Origin header to match the query and therefore respond appropriately. I added this in my cURL request and voilá, the CORS headers I expected:

❯ curl -i -XOPTIONS -H "Origin: https://qa.example.org" https://qa.example.org/api
HTTP/2 204
date: Tue, 09 Aug 2022 11:39:35 GMT
strict-transport-security: max-age=15724800; includeSubDomains
access-control-allow-origin: https://qa.example.org
access-control-allow-credentials: true
access-control-allow-methods: GET, PUT, POST, DELETE, PATCH, OPTIONS
access-control-allow-headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization
access-control-expose-headers: x-api-version,x-response-time-ms
access-control-max-age: 1728000

Not sure if this is the case for anyone here, but in case it is, I thought I’d share. 😃

Annotations for this ingress:

{
  "kubernetes.io/ingress.class": "nginx",
  "nginx.ingress.kubernetes.io/cors-allow-credentials": "true",
  "nginx.ingress.kubernetes.io/cors-allow-origin": "https://qa.example.org,https://subdomain.qa.example.org",
  "nginx.ingress.kubernetes.io/cors-expose-headers": "x-api-version,x-response-time-ms",
  "nginx.ingress.kubernetes.io/enable-cors": "true",
  "nginx.ingress.kubernetes.io/service-upstream": "true"
}

I’m experiencing the same behavior. For OPTIONS requests coming from origins which do not match the allowed-origins configured through nginx.ingress.kubernetes.io/cors-allow-origin, there are no returned CORS headers. This is resulting in browsers thinking CORS is disabled.

I believe the issue is with how nginx.conf is being generated. Here’s what I get generated for cors-allow-origin=https://*.mydomain.com:

if ($http_origin ~* ((https://[A-Za-z0-9\-]+\.mydomain\.com))$ ) { set $cors 'true'; }

if ($request_method = 'OPTIONS') {
    set $cors ${cors}options;
}

if ($cors = "true") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true';
    more_set_headers 'Access-Control-Allow-Methods: GET,PUT,POST,DELETE,PATCH,OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: accept,allow,x-my-custom-header';

    more_set_headers 'Access-Control-Max-Age: 1728000';
}

if ($cors = "trueoptions") {
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true';
    more_set_headers 'Access-Control-Allow-Methods: GET,PUT,POST,DELETE,PATCH,OPTIONS';
    more_set_headers 'Access-Control-Allow-Headers: accept,allow,x-my-custom-header';

    more_set_headers 'Access-Control-Max-Age: 1728000';
    more_set_headers 'Content-Type: text/plain charset=UTF-8';
    more_set_headers 'Content-Length: 0';
    return 204;
}

The issue is that there’s no handling for non-allowed-origin matching OPTIONS requests. I see the OPTIONS requests from non-allowed origins falling through to my API servers, resulting in 404s because OPTIONS is not supported at that layer. The generated configuration needs to handle these requests and return a valid value for Access-Control-Allow-Origin. I believe the issue was introduced when support for multiple allowed origins was added. Maybe @larivierec has some insight here.