How to update CORS policies without re-deploying your API

In a Kubernetes-native world, people building and deploying microservices need to consider many things. One important thing to consider is Cross-Origin Resource Sharing (CORS), which enables browsers to load resources from different servers or instances. CORS plays a vital role when a user interface (UI) talks to an application programming interface (API), as it controls which origins can support the request, what methods are allowed, and what headers can be read.

As the number of your microservices grow and you spread into hybrid- and even multi-cloud deployments, it often becomes harder for you maintain and update the CORS rules to cater different set of applications. Why? Because embedding a CORS rule in an API (your backend service) requires redeployment every time you want to update the rule.

The solution is to decouple CORS from your APIs.

In this blog, we will first deploy a Fruits API with no CORS, that is one does not allow cross origins. We will then use Gloo Edge as our API gateway and see how it helps by updating CORS without requiring you to redeploy the application.

Prerequisites

To deploy the demo application, you need:

  • A local Kubernetes Cluster – you can deploy one using Minikube or KinD
  • kubectl CLI to interact with the Kubernetes cluster
  • glooctl the CLI tool to interact with Gloo Edge resources on the Kubernetes Cluster
  • Optionally, httpie a user-friendly command-line HTTP client for APIs

As a first step, let’s clone the blog demo sources:

git clone https://github.com/kameshsampath/gloo-cors-demo

For easy reference, we will call the cloned repo $DEMO_HOME:

cd gloo-cors-demo
export DEMO_HOME="$(pwd)"

Next, let’s set up the KinD cluster $DEMO_HOME/cluster/kind.sh. Once your kind cluster is up and running, install Gloo Edge:

glooctl install gateway -n my-gloo --values $DEMO_HOME/cluster/install-override-ce.yaml

To check all is well, run the command:

glooctl check -n my-gloo

The command should show an output like:


Checking deployments... OK
Checking pods... OK
Checking upstreams... OK
Checking upstream groups... OK
Checking auth configs... OK
Checking rate limit configs... OK
Checking VirtualHostOptions... OK
Checking RouteOptions... OK
Checking secrets... OK
Checking virtual services... OK
Checking gateways... OK
Checking proxies... OK No problems detected.
I0713 20:18:03.427393 36026 request.go:645] Throttling request took 1.044172892s, request: GET:https://127.0.0.1:51113/apis/scheduling.k8s.io/v1?timeout=32s
Skipping Gloo Instance check -- Gloo Federation not detected

NOTE: It can take a couple of minutes for the Gloo Edge pods to come up. If you see errors with above command, try it again after a few minutes. Alternately, you can watch the pods on the my-gloo namespace:

kubectl get pods -n my-gloo

NAME                             READY   STATUS    RESTARTS   AGE
gateway-5f546f6547-b6rjr         1/1     Running   0          3m16s
gateway-proxy-7b4f98764d-8h587   1/1     Running   0          3m16s
gloo-64bf8c47b6-5l49g            1/1     Running   0          3m16s

NOTE: The demo setup allows you access the Gloo Edge Envoy Proxy via http://localhost:30080.

Deploying our application database and REST API

The Fruits API has both database and API code, let’s deploy them one by one.

Database

To deploy the database, run:

kubectl apply -k $DEMO_HOME/apps/manifests/fruits-api/db

Wait for the database to be up:

kubectl rollout status -n db deploy/postgresql --timeout=60s

REST API

Once database is up, let’s now deploy the Fruits REST API:

kubectl apply -k $DEMO_HOME/apps/manifests/fruits-api/app

Wait for the Fruits API to be up and running:

kubectl rollout status -n fruits-app deploy/fruits-api --timeout=60s

To access the application, we need to setup the Gloo Edge Upstream resources. These are resources that allow you to connect various backend destinations, typically Kubernetes services. Run:

kubectl apply -n my-gloo -f $DEMO_HOME/apps/manifests/fruits-api/gloo/upstream.yaml

Let us check the status of the Upstream:

glooctl get upstream -n my-gloo
-----------------------------------------------------------------------------+
|          UPSTREAM          |    TYPE    |  STATUS  |          DETAILS          |
-----------------------------------------------------------------------------+
| fruits-app-fruits-api-8080 | Kubernetes | Accepted | svc name:      fruits-api |
|                            |            |          | svc namespace: fruits-app |
|                            |            |          | port:          8080       |
|                            |            |          |                           |
-----------------------------------------------------------------------------+

If the upstream status is shown as *Pending*, wait for the status to be *Accepted* before proceeding further.

A Gateway(VirtualService) is a resource that allows you to connect the external world with your services. Let’s create one now:

kubectl apply -n my-gloo -f $DEMO_HOME/apps/manifests/fruits-api/gloo/gateway.yaml

Let’s check the status of the VirtualService:

glooctl get virtualservice -n my-gloo
----------------------------------------------------------------------------------------------
| VIRTUAL SERVICE | DISPLAY NAME | DOMAINS | SSL  |  STATUS  | LISTENERPLUGINS |       ROUTES        |
----------------------------------------------------------------------------------------------
| fruits-api      |              | *       | none | Accepted |                 | / -> 1 destinations |
----------------------------------------------------------------------------------------------

Let’s also make sure that our API works via command line,  you can use tool like httpie to do API calls:

http localhost:30080/v1/api/fruits/

The command should show a JSON output something like this (shortened for brevity):

[
  {
    "emoji": "U+1F34E",
    "id": 8,
    "name": "Apple",
    "season": "Fall"
  },
  {
    "emoji": "U+1F34C",
    "id": 6,
    "name": "Banana",
    "season": "Summer"
  },
  {
    "emoji": "U+1FAD0",
    "id": 5,
    "name": "Blueberry",
    "season": "Summer"
  }
]

Now we are all set with our API. It’s time to get the UI connected with it:

docker run --rm --name=fruits-app \
  -p 8085:8080 \
  quay.io/kameshsampath/fruits-app-ui

When you open the URL http://localhost:8085 on your browser, you will see application is still loading. If you view the console log (Developer Tools) in your browser, you will notice CORS errors, as shown below:

 

As we said earlier we can fix those CORS errors without need to redeploy the Fruits API.

Allowing different origins with CORS

The very first CORS error you see is because the backend resource is not allowing requests from http://locahost:8085  (your frontend application.) To fix that we need the backend to be configured with the CORS header Access-Control-Allow-Origin. Traditionally we do this when building the backend application, and again for updates we might need to rebuild and redeploy the backend application. But in today’s cloud-native world that’s not efficient.

Let us fix this with Gloo Edge. To allow Origins we need to configure the fruits-api virtual service as shown:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: fruits-api
  namespace: my-gloo
spec:
  displayName: FruitsAPI
  virtualHost:
    options:
      cors:
        allowOriginRegex:
          - '^http(s)?:\/\/localhost:[0-9]{4,5}$'
        maxAge: 1d
    domains:
      - "*"
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            destination:
              upstream:
                name: fruits-app-fruits-api-8080
                namespace: my-gloo

Let’s apply the updated gateway to allow cross origins:

kubectl apply -n my-gloo -f $DEMO_HOME/demos/manifests/cors/gateway-cors-1.yaml

Now refresh the browser http://localhost:8085/ which should show you a list of fruits like this:

 

Allowing different headers with CORS

Great! We have now successfully connected the frontend (UI) with our API. Let’s try adding fruit. Unfortunately, that shows another CORS error:

As you might have guessed, to fix the error requires us to add Access-Control-Allow-Headers as part of the CORS configuration.

Let’s take the same approach of updating the fruits-api VirtualService with the headers:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: fruits-api
  namespace: my-gloo
spec:
  displayName: FruitsAPI
  virtualHost:
    options:
      cors:
        allowOriginRegex:
          - '^http(s)?:\/\/localhost:[0-9]{4,5}$'
        allowHeaders:
          - origin
          - content-type
        maxAge: 1d
    domains:
      - "*"
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            destination:
              upstream:
                name: fruits-app-fruits-api-8080
                namespace: my-gloo

Now let’s apply the changes again:

kubectl apply -n my-gloo \
  -f $DEMO_HOME/demos/manifests/cors/gateway-cors-2.yaml

Refresh the browser URL https://localhost:8085. If you try to add a fruit again, you should succeed!

Other allow methods for CORS

Last, let’s try deleting a fruit. You should observe the following error:

 

As the error says, the Fruits API does not allow the origin to perform HTTP `DELETE. To fix this we need to add Access-Control-Allow-Methods to the CORS configuration.

Let’s take the same approach of updating the fruits-api VirtualService with these headers:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: fruits-api
  namespace: my-gloo
spec:
  displayName: FruitsAPI
  virtualHost:
    options:
      cors:
        allowOriginRegex:
          - '^http(s)?:\/\/localhost:[0-9]{4,5}$'
        allowHeaders:
          - origin
          - content-type
        allowMethods:  
          - DELETE
        maxAge: 1d
    domains:
      - '*'
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            destination:
              upstream:
                name: fruits-app-fruits-api-8080
                namespace: my-gloo

Now let’s apply the changes again:

kubectl apply -n my-gloo \
  -f $DEMO_HOME/demos/manifests/cors/gateway-cors-3.yaml

If you try to delete a fruit, you should succeed!

Summary

In this blog, you saw how you can quickly apply the CORS policies without redeploying your application(API.) All that was required is to change and reapply the Gloo Edge manifests, such as the VirtualService. Of course, Gloo Edge is not restricted to just CORS, you can do lot more. Please check out the following resources to learn more and get involved with the Gloo Edge team.