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.
- Visit the website and read the docs
- Request a live demo or trial
- See video content on the solo.io YouTube channel
- Questions? Join the Solo.io Slack community and check out its #gloo-edge channel.