Skip to content
If you are interested in trying out Gloo Gateway with the Kubernetes Gateway API, check out Solo Enterprise for kgateway. This version adds enterprise functionality on top of the kgateway open source project.

CORS

Page as Markdown

Enforce client-site access controls with cross-origin resource sharing (CORS).

About CORS

Cross-Origin Resource Sharing (CORS) is a security feature that is implemented by web browsers and that controls how web pages in one domain can request and interact with resources that are hosted on a different domain. By default, web browsers only allow requests to resources that are hosted on the same domain as the web page that served the original request. Access to web pages or resources that are hosted on a different domain is restricted to prevent potential security vulnerabilities, such as cross-site request forgery (CSRF).

When CORS is enabled in a web browser and a request for a different domain comes in, the web browser checks whether this request is allowed or not. To do that, it typically sends a preflight request (HTTP OPTIONS method) to the server or service that serves the requested resource. The service returns the methods that are permitted to send the actual cross-origin request, such as GET, POST, etc. If the request to the different domain is allowed, the response includes CORS-specific headers that instruct the web browser how to make the cross-origin request. For example, the CORS headers typically include the origin that is allowed to access the resource, and the credentials or headers that must be included in the cross-origin request.

Note that the preflight request is optional. Web browsers can also be configured to send the cross-origin directly. However, access to the request resource is granted only if CORS headers were returned in the response. If no headers are returned during the preflight request, the web browser denies access to the resource in the other domain.

CORS policies are typically implemented to limit access to server resources for JavaScripts that are embedded in a web page, such as:

  • A JavaScript on a web page at example.com tries to access a different domain, such as api.com.
  • A JavaScript on a web page at example.com tries to access a different subdomain, such as api.example.com.
  • A JavaScript on a web page at example.com tries to access a different port, such as example.com:3001.
  • A JavaScript on a web page at https://example.com tries to access the resources by using a different protocol, such as http://example.com.

For more information, see the CORS API.

Configuration options

You can configure the CORS policy at two levels:

  • VirtualHostOption: Configure a CORS policy in the VirtualHostOption that is applied to a gateway proxy. The policy is automatically attached to all the listeners that are defined for one or multiple hosts on the gateway.
  • RouteOption: Configure a CORS policy in the RouteOption. Then, you can apply the policy to one or multiple routes by using the targetRefs field on the RouteOption or the ExtensionRef filter on the HTTPRoute. For more information, see Policy attachment.

By default, the configuration of the RouteOption takes precedence over the VirtualHostOption.

Before you begin

  1. Get the external address of the gateway and save it in an environment variable.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n gloo-system gloo-proxy-http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  
    kubectl port-forward deployment/gloo-proxy-http -n gloo-system 8080:8080

Set up CORS policies

Create a CORS policy for the httpbin app at either the virtual host level or the route level.

  1. Create the CORS policy. Policies at the virtual host level apply to all routes that are attached to the gateway that the virtual host targets. If you have a CORS policy at both virtual host and route levels, the route policy takes precedence by default.

    1. Create a VirtualHostOption resource to define your CORS rules. The following example allows requests from the https://example.com/ and https://*.example.com/ origins.

      kubectl apply -n gloo-system -f- <<EOF
      apiVersion: gateway.solo.io/v1
      kind: VirtualHostOption
      metadata:
        name: cors
        namespace: gloo-system
      spec:
        options:
          cors:
            allowCredentials: true
            allowHeaders:
            - origin
            allowMethods:
            - GET
            - POST
            - OPTIONS
            allowOrigin:
            - https://example.com/
            allowOriginRegex:
            - https://\[a-zA-Z0-9\]\*.example.com/
            exposeHeaders:
            - origin
            - vh-header
            maxAge: 1d
        targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
          namespace: gloo-system
      EOF
    2. If you do not already have one, create an HTTPRoute resource for the httpbin app that attaches to the gateway.

      kubectl apply -f- <<EOF
      apiVersion: gateway.networking.k8s.io/v1
      kind: HTTPRoute
      metadata:
        name: httpbin-cors
        namespace: httpbin
      spec:
        parentRefs:
        - name: http
          namespace: gloo-system
        hostnames:
          - cors.example
        rules:
          - backendRefs:
              - name: httpbin
                port: 8000
      EOF
    1. Create a RouteOption resource to define your CORS rules. The following example allows requests from the https://example.com/ and https://*.example.com/ origins.

      kubectl apply -f- <<EOF
      apiVersion: gateway.solo.io/v1
      kind: RouteOption
      metadata:
        name: cors
        namespace: httpbin
      spec:
        options:
          cors:
            allowCredentials: true
            allowHeaders:
            - origin
            allowMethods:
            - GET
            - POST
            - OPTIONS
            allowOrigin:
            - https://example.com/
            allowOriginRegex:
            - https://\[a-zA-Z0-9\]\*.example.com/
            exposeHeaders:
            - origin
            - route-header
            maxAge: 1d
      EOF
    2. Create an HTTPRoute resource for the httpbin app that applies the RouteOption resources that you created.

      kubectl apply -f- <<EOF
      apiVersion: gateway.networking.k8s.io/v1
      kind: HTTPRoute
      metadata:
        name: httpbin-cors
        namespace: httpbin
      spec:
        parentRefs:
        - name: http
          namespace: gloo-system
        hostnames:
          - cors.example
        rules:
          - filters:
              - type: ExtensionRef
                extensionRef:
                  group: gateway.solo.io
                  kind: RouteOption
                  name: cors
            backendRefs:
              - name: httpbin
                port: 8000
      EOF
  2. Send a request to the httpbin app on the cors.example domain and use https://example.com/ as the origin. Verify that your request succeeds and that you get back CORS headers, such as access-control-allow-origin, access-control-allow-credentials, and access-control-expose-headers.

    curl -v -X GET http://$INGRESS_GW_ADDRESS:8080/headers -H "host: cors.example:8080" \
     -H "Origin: https://example.com/" -H "Access-Control-Request-Method: GET"
    curl -v -X GET localhost:8080/headers -H "host: cors.example:8080" \
     -H "Origin: https://example.com/" -H "Access-Control-Request-Method: GET"

    Example output: Notice that the access-control-expose-headers value changes depending on the resources that you created.

    • If you created only a VirtualHostOption, you see the origin and vh-header headers.

    • If you created only a RouteOption or both a RouteOption and VirtualHostOption, you see the origin and route-header headers, because the RouteOption takes precedence.

      * Mark bundle as not supporting multiuse
      < HTTP/1.1 200 OK
      < content-type: text/xml
      < date: Mon, 03 Jun 2024 17:05:31 GMT
      < content-length: 86
      < x-envoy-upstream-service-time: 7
      < access-control-allow-origin: https://example.com/
      < access-control-allow-credentials: true
      < access-control-expose-headers: origin, vh-header
      < server: envoy
      < 

      {
        "headers": {
          "Accept": [
            "*/*"
          ],
          "Access-Control-Request-Method": [
            "GET"
          ],
          "Host": [
            "cors.example:8080"
          ],
          "Origin": [
            "https://example.com"
          ],
          "User-Agent": [
            "curl/8.7.1"
          ],
          "X-Envoy-Expected-Rq-Timeout-Ms": [
            "15000"
          ],
          "X-Envoy-External-Address": [
            "127.0.0.1"
          ],
          "X-Forwarded-For": [
            "10.xxx.xx.x"
          ],
          "X-Forwarded-Proto": [
            "http"
          ],
          "X-Request-Id": [
            "bce0d16b-e1fa-427d-a246-79acd36b4f3e"
          ]
        }
      }
  3. Send another request to the httpbin app. This time, you use notallowed.com as your origin. Although the request succeeds, you do not get back any CORS headers, because notallowed.com is not configured as a supported origin.

    curl -v -X GET http://$INGRESS_GW_ADDRESS:8080/api/pets -H "host: cors.example:8080" \
     -H "Origin: https://notallowed.com/" -H "Access-Control-Request-Method: GET"
    curl -v -X GET localhost:8080/api/pets -H "host: cors.example:8080" \
     -H "Origin: https://notallowed.com/" -H "Access-Control-Request-Method: GET"

    Example output:

    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    < content-type: text/xml
    < date: Mon, 03 Jun 2024 17:20:10 GMT
    < content-length: 86
    < x-envoy-upstream-service-time: 3
    < server: envoy
    < 
    {
      "headers": {
        "Accept": [
          "*/*"
        ],
        "Access-Control-Request-Method": [
          "GET"
        ],
        "Host": [
          "cors.example:8080"
        ],
        "Origin": [
          "https://example.com"
        ],
        "User-Agent": [
          "curl/8.7.1"
        ],
        "X-Envoy-Expected-Rq-Timeout-Ms": [
          "15000"
        ],
        "X-Envoy-External-Address": [
          "127.0.0.1"
        ],
        "X-Forwarded-For": [
          "10.xxx.xx.x"
        ],
        "X-Forwarded-Proto": [
          "http"
        ],
        "X-Request-Id": [
          "bce0d16b-e1fa-427d-a246-79acd36b4f3e"
        ]
      }
    }

Cleanup

Remove the resources that you created in this guide.

kubectl delete virtualhostoption cors -n gloo-system
kubectl delete routeoption cors -n httpbin
kubectl delete httproute httpbin-cors -n httpbin