Gloo Portal Workshop
Gloo Portal is a Kubernetes native solution aiming to facilitate API publication and API consumption for developers.
More technically, Gloo Portal adheres to the Operator pattern and transforms Custom Resources into customized and ready-to-use developer portals. These portals are fully brandable and secured web applications.
Gloo Portal provides a framework for managing API definitions, API client identity, and API policies on top of Gloo Edge or Gloo Mesh Gateway. Vendors of API products can leverage Gloo Portal to secure, manage, and publish their APIs independently of the operations used to manage networking infrastructure.
This workshop aims to expose some key features of the Gloo Portal like API lifecycle, authentication, and branding.

Your workshop environment

The Lab environment consists of a Virtual Machine where you will deploy a Kubernetes cluster using kind. You will then deploy Gloo Edge and Gloo Portal on this Kubernetes cluster.

Init step 1: Kubernetes

Navigate to the work directory and create local Kubernetes cluster with KinD:
1
cd /home/solo/workshops/gloo-portal
2
../scripts/deploy.sh 1 gloo-portal
Copied!
Then verify that your Kubernetes cluster is ready:
1
../scripts/check.sh gloo-portal
Copied!
The check.sh script will return immediately with no output if the cluster is ready. Otherwise, it will output a series of periodic "waiting" messages until the cluster is up.

Init step 2: Gloo Edge

Let's deploy Gloo Edge:
1
helm repo add glooe https://storage.googleapis.com/gloo-ee-helm
2
helm repo update
3
4
helm upgrade -i gloo glooe/gloo-ee --namespace gloo-system --version 1.9.2 --create-namespace --set-string license_key="$LICENSE_KEY"
5
6
sleep 1
7
8
kubectl -n gloo-system wait po --for condition=Ready --timeout -1s --all
Copied!
NOTE: Gloo Portal requires a subscription to Gloo Edge Enterprise or to Gloo Mesh Enterprise.

Init step 3: Gloo Portal

Finally, let's deploy Gloo Portal:
1
cat << EOF > portal-values.yaml
2
glooEdge:
3
enabled: true
4
licenseKey:
5
secretRef:
6
name: license
7
namespace: gloo-system
8
key: license-key
9
EOF
10
11
helm repo add gloo-portal https://storage.googleapis.com/dev-portal-helm
12
helm repo update
13
helm install gloo-portal gloo-portal/gloo-portal -n gloo-portal --values portal-values.yaml --version=1.2.0-beta4 --create-namespace
14
15
kubectl -n gloo-portal wait pod --all --for condition=Ready --timeout -1s
Copied!

Init step 4: Keycloak

Keycloak is an open-source identity management platform that we will use to secure access to your APIs and to the developer Portal.
Deploy a Keycloak instance to our Kubernetes cluster:
1
kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/12.0.4/kubernetes-examples/keycloak.yaml
2
kubectl rollout status deploy/keycloak
Copied!
Then, we create a Client application and a few users:
1
# Get Keycloak URL and token
2
KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth
3
KEYCLOAK_TOKEN=$(curl -s -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token)
4
GLOO_GW_IP=$(glooctl proxy address | cut -d':' -f1)
5
6
# Create initial token to register the client
7
read -r client token <<<$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"expiration": 0, "count": 1}' $KEYCLOAK_URL/admin/realms/master/clients-initial-access | jq -r '[.id, .token] | @tsv')
8
9
# Register the client
10
read -r id secret <<<$(curl -X POST -d "{ \"clientId\": \"${client}\" }" -H "Content-Type:application/json" -H "Authorization: bearer ${token}" ${KEYCLOAK_URL}/realms/master/clients-registrations/default| jq -r '[.id, .secret] | @tsv')
11
12
# Add allowed redirect URIs
13
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X PUT -H "Content-Type: application/json" -d '{"serviceAccountsEnabled": true, "authorizationServicesEnabled": true, "redirectUris": ["https://portal.mycompany.corp/callback", "http://portal.mycompany.corp/callback", "http://'${GLOO_GW_IP}'/callback"]}' $KEYCLOAK_URL/admin/realms/master/clients/${id}
14
15
# Add the group attribute in the JWT token returned by Keycloak
16
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"name": "group", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": {"claim.name": "group", "jsonType.label": "String", "user.attribute": "group", "id.token.claim": "true", "access.token.claim": "true"}}' $KEYCLOAK_URL/admin/realms/master/clients/${id}/protocol-mappers/models
17
18
# create groups "users" and "execs"
19
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"name": "users"}' $KEYCLOAK_URL/admin/realms/master/groups
20
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"name": "execs"}' $KEYCLOAK_URL/admin/realms/master/groups
21
22
# Create first user "user1", group: users, mail address: [email protected]
23
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "user1", "email": "[email protected]", "enabled": true, "groups": ["users"], "attributes": {"group": "users"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users
24
25
# Create second user "user2", group: users, mail address: [email protected]
26
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "user2", "email": "[email protected]", "enabled": true, "groups": ["users"], "attributes": {"group": "users"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users
27
28
# Create third user "exec1", group: execs, mail address: [email protected]
29
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "exec1", "email": "[email protected]", "enabled": true, "groups": ["execs"], "attributes": {"group": "execs"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users
Copied!
For curious ones, the Keycloak admin web UI is available at $KEYCLOAK_URL/admin

Init step 5: HTTPBIN

Finally, at some point in the workshop, we will need a backend service mirroring the headers.
So, let's just deploy the httpbin app:
1
kubectl apply -f -<<EOF
2
apiVersion: v1
3
kind: ServiceAccount
4
metadata:
5
name: httpbin
6
namespace: default
7
---
8
apiVersion: v1
9
kind: Service
10
metadata:
11
name: httpbin
12
namespace: default
13
labels:
14
app: httpbin
15
spec:
16
ports:
17
- name: http
18
port: 8000
19
targetPort: 80
20
selector:
21
app: httpbin
22
---
23
apiVersion: apps/v1
24
kind: Deployment
25
metadata:
26
name: httpbin
27
namespace: default
28
spec:
29
replicas: 1
30
selector:
31
matchLabels:
32
app: httpbin
33
version: v1
34
template:
35
metadata:
36
labels:
37
app: httpbin
38
version: v1
39
spec:
40
serviceAccountName: httpbin
41
containers:
42
- image: docker.io/kennethreitz/httpbin
43
imagePullPolicy: IfNotPresent
44
name: httpbin
45
ports:
46
- containerPort: 80
47
env:
48
- name: GUNICORN_CMD_ARGS
49
value: "--capture-output --error-logfile - --access-logfile - --access-logformat '%(h)s %(t)s %(r)s %(s)s Host: %({Host}i)s}'"
50
EOF
Copied!

Lab 1: Crafting your first API Product

First, some conceptual elements to better understand how the Gloo Portal CRDs work together.
You will define APIDocs Kubernetes Custom Resources, standing for "references" to OpenAPI (or gRPC) specifications.
APIDocs
Then, you will combine these APIDocs into a single APIProduct.
APIProducts
API Products are Kubernetes Custom Resources which bundle the APIs defined in API Docs into a product that can be exposed to ingress traffic as well as published on a Portal UI. An API Product defines what API operations are being exposed, and the routing information to reach the services.
In this workshop, we will combine 2 small APIDocs into the v1 of our Petstore APIProduct And one larger APIDoc as the v2 of our Petstore APIProduct. See:
APIProduct with two versions
The APIProduct comes with two versions of it:
  • /v1 will expose endpoints for the /pet/* and /user/* endpoints, and it will route requests to the petstore-v1 application
  • /v2 will expose a few more endpoints, including /pet/*, /user/* and also /store/*, and it will route requests to the petstore-v2 application
We'll start by deploying the well-known Petstore app, twice (as Deployments). This will simulate the two versions of it, accessible behind two different Kubernetes Services.

Step 1.1

Create two deployments of the Petstore app
1
for i in {1..2}; do
2
kubectl apply -f - <<EOF
3
apiVersion: apps/v1
4
kind: Deployment
5
metadata:
6
name: petstore-v$i
7
spec:
8
replicas: 1
9
selector:
10
matchLabels:
11
app: petstore
12
version: v$i
13
template:
14
metadata:
15
labels:
16
app: petstore
17
version: v$i
18
spec:
19
containers:
20
- name: petstore
21
image: swaggerapi/petstore
22
# env:
23
# - name: SWAGGER_BASE_PATH
24
# value: /
25
imagePullPolicy: Always
26
ports:
27
- name: http
28
containerPort: 8080
29
---
30
apiVersion: v1
31
kind: Service
32
metadata:
33
name: petstore-v$i
34
spec:
35
ports:
36
- name: http
37
port: 8080
38
targetPort: http
39
protocol: TCP
40
selector:
41
app: petstore
42
version: v$i
43
EOF
44
done
Copied!
Now, let's check if Gloo Edge has automatically created 2 Upstream CRs for these 2 services, thanks to the Discovery feature:
1
kubectl -n gloo-system get upstreams
Copied!
The output should be like:
1
...
2
default-petstore-v1-8080 9s
3
default-petstore-v2-8080 8s
4
...
Copied!
Great!

Step 1.2

Create the APIDocs from our 3 OpenApi specs:
Pets only:
Pets only
Users only:
Users only
Pets, users and stores:
All combined
1
for i in petstore-openapi-v1-pets petstore-openapi-v1-users petstore-openapi-v2-full; do
2
cat <<EOF | kubectl apply -f -
3
apiVersion: portal.gloo.solo.io/v1beta1
4
kind: APIDoc
5
metadata:
6
name: $i
7
namespace: default
8
spec:
9
openApi:
10
content:
11
fetchUrl: https://raw.githubusercontent.com/solo-io/workshops/master/gloo-portal/openapi-specs/$i.json
12
EOF
13
done
Copied!
Let's be curious and take a look at the status of one these APIDocs:
1
kubectl get apidoc
2
kubectl get apidoc petstore-openapi-v1-pets -o yaml
Copied!
The output looks like the following:
1
...
2
status:
3
description: 'This is a sample server Petstore server. You can find out more about
4
Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For
5
this sample, you can use the api key `special-key` to test the authorization filters.'
6
displayName: Swagger Petstore
7
observedGeneration: 1
8
openApi:
9
operations:
10
- operationId: addPet
11
path: /api/pet
12
summary: Add a new pet to the store
13
verb: POST
14
- operationId: deletePet
15
path: /api/pet/{petId}
16
summary: Deletes a pet
17
verb: DELETE
18
- operationId: findPetsByStatus
19
path: /api/pet/findByStatus
20
summary: Finds Pets by status
21
verb: GET
22
- operationId: findPetsByTags
23
path: /api/pet/findByTags
24
summary: Finds Pets by tags
25
verb: GET
26
- operationId: getPetById
27
path: /api/pet/{petId}
28
summary: Find pet by ID
29
verb: GET
30
- operationId: updatePet
31
path: /api/pet
32
summary: Update an existing pet
33
verb: PUT
34
- operationId: updatePetWithForm
35
path: /api/pet/{petId}
36
summary: Updates a pet in the store with form data
37
verb: POST
38
- operationId: uploadFile
39
path: /api/pet/{petId}/uploadImage
40
summary: uploads an image
41
verb: POST
42
state: Succeeded
43
version: 1.0.5
Copied!
As you can see, the different endpoints of the OpenAPI spec have been parsed by the Gloo Portal controller.
APIProduct
Let's create the APIProduct, with the two versions of it:
1
cat << EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: APIProduct
4
metadata:
5
name: petstore-product
6
namespace: default
7
labels:
8
app: petstore
9
spec:
10
displayInfo:
11
title: Petstore Product
12
description: Fabulous API product for the Petstore
13
versions:
14
- name: v1 # ------------ VERSION 1 -------------
15
apis:
16
- apiDoc:
17
name: petstore-openapi-v1-pets
18
namespace: default
19
- apiDoc:
20
name: petstore-openapi-v1-users
21
namespace: default
22
gatewayConfig:
23
route:
24
inlineRoute:
25
backends:
26
- upstream:
27
name: default-petstore-v1-8080
28
namespace: gloo-system
29
- name: v2 # ------------ VERSION 2 -------------
30
apis:
31
- apiDoc:
32
name: petstore-openapi-v2-full
33
namespace: default
34
gatewayConfig:
35
route:
36
inlineRoute:
37
backends:
38
- upstream:
39
name: default-petstore-v2-8080
40
namespace: gloo-system
41
EOF
Copied!
Quick reminder: the APIProduct is named petstore-product. It is available in 2 different versions:
  • v1 is built upon 2 APIDocs, containing operations for Pets on one hand, and Users on the other hand
  • v2 is build upon 1 APIDoc, containing all the operations (/pet, /user and /store)
Also, we have configured two different routes for the two versions, so that the v1 will target our Upstream called default-petstore-v1-8080 and the v2 will target our Upstream called default-petstore-v2-8080.

Lab 2: Deploying the API

Step 2.1

Let's publish our API on a Gateway! First we need to create an Environment CR, that will select one or more APIProduct(s).
Environment
Once the Environment is created, Gloo Portal will configure an API Gateway:
Automatic API Gateway configuration
In this workshop, and in order to leverage advanced API Gateway features, we will rely on Gloo Edge. The other option is to have Gloo Portal to configure Gloo Mesh Gateway, which is built on top of the Istio Ingress Gateway.
We need to prepare an Environment CR, where we will set the domain(s) and, optionally, some security options like authentication and rate-limiting rules:
1
cat << EOF > env.yaml
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: Environment
4
metadata:
5
name: dev
6
namespace: default
7
spec:
8
domains:
9
- api.mycompany.corp # the domain name where the API will be exposed
10
displayInfo:
11
description: This environment is meant for developers to deploy and test their APIs.
12
displayName: Development
13
basePath: /ecommerce # a global basepath for our APIs
14
apiProducts: # we will select our APIProduct using a selector and the 2 version of it
15
- namespaces:
16
- "*"
17
labels:
18
- key: app
19
operator: In
20
values:
21
- petstore
22
versions:
23
names:
24
- v1
25
- v2
26
basePath: "<div data-gb-custom-block data-tag="version"></div>" # this will dynamically prefix the API path with the version name
27
EOF
28
29
kubectl apply -f env.yaml
Copied!
You can then check the status of the Environment using the following command:
1
kubectl get environments.portal.gloo.solo.io dev -o yaml
Copied!
The output is pretty big but it should end with:
1
state: Succeeded
Copied!

Step 2.2

As explained above, Gloo Portal will configure Gloo Edge to expose our APIs. Using the command below, you'll see the Gloo Edge VirtualService created by Gloo Portal:
1
kubectl get virtualservice dev -o yaml
Copied!
You should see something like this:
1
...
2
spec:
3
displayName: Development
4
virtualHost:
5
domains:
6
- api.mycompany.corp
7
routes:
8
- delegateAction:
9
selector:
10
labels:
11
apiproducts.portal.gloo.solo.io: petstore-product.default
12
apiproducts.portal.gloo.solo.io/version: v2
13
environments.portal.gloo.solo.io: dev.default
14
matchers:
15
- prefix: /
16
name: petstore-product.v2
17
options:
18
regexRewrite:
19
pattern:
20
regex: ^/ecommerce/v2/(.*)$
21
substitution: /\1
22
- delegateAction:
23
selector:
24
labels:
25
apiproducts.portal.gloo.solo.io: petstore-product.default
26
apiproducts.portal.gloo.solo.io/version: v1
27
environments.portal.gloo.solo.io: dev.default
28
matchers:
29
- prefix: /
30
name: petstore-product.v1
31
options:
32
regexRewrite:
33
pattern:
34
regex: ^/ecommerce/v1/(.*)$
35
substitution: /\1
36
status:
37
reportedBy: gateway
38
state: 1
39
subresourceStatuses:
40
'*v1.Proxy.gloo-system.gateway-proxy':
41
reportedBy: gloo
42
state: 1
Copied!
There are two things to note here:
  • Gloo Portal used the version names of your APIProduct as prefixes for your endpoints. Meaning the endpoints of the version called 'v1' are now accessible behind /ecommerce/v1/..., etc. This represents automatic version-based routing.
  • The Environment CR has been used to generate a VirtualService CR and also some RouteTables CRs. Let's have a closer look at the RouteTables:
1
kubectl get routetable
Copied!
1
NAME AGE
2
dev.petstore-product.v1 10m
3
dev.petstore-product.v2 10m
Copied!
1
kubectl get routetable dev.petstore-product.v1 -o yaml
Copied!
Extract:
1
...
2
- matchers:
3
- methods:
4
- GET
5
- OPTIONS
6
regex: /ecommerce/v1/api/pet/[^/]+?
7
name: petstore-product.default.petstore-openapi-v1-pets.default.getPetById
8
options:
9
stagedTransformations:
10
early:
11
requestTransforms:
12
- matcher:
13
prefix: /
14
requestTransformation:
15
transformationTemplate:
16
dynamicMetadataValues:
17
- key: environment
18
value:
19
text: dev.default
20
- key: api_product
21
value:
22
text: petstore-product.default
23
passthrough: {}
24
routeAction:
25
multi:
26
destinations:
27
- destination:
28
upstream:
29
name: default-petstore-v1-8080
30
namespace: gloo-system
31
weight: 1
32
...
Copied!
There is one route per OperationId selected by the APIProduct. We didn't experienced it yet but you absolutely can cherry-pick API endpoints from your different APIDoc when building the APIProduct. It's useful when you want to hide some sensible endpoints to your end-users.
The combination of these CRs will generate the expected configuration for Envoy.

Step 2.3

Finally, let's consume the API!
1
# v1
2
# GET one of the /pet endpoints, on the version 1
3
curl -s $(glooctl proxy url)/ecommerce/v1/api/pet/1 -H "Host: api.mycompany.corp" | jq
Copied!
1
# POST then GET some /user endpoints, on the version 2
2
curl -s -X POST $(glooctl proxy url)/ecommerce/v2/api/user/createWithList -H "Host: api.mycompany.corp" -d '[{"id":0,"username":"jdoe","firstName":"John","lastName":"Doe","email":"[email protected]","password":"string","phone":"string","userStatus":0}]' -H "Content-type: application/json"
3
curl -s $(glooctl proxy url)/ecommerce/v2/api/user/jdoe -H "Host: api.mycompany.corp" | jq
Copied!
1
# v2
2
# GET one of the /store endpoints, on the version 2
3
curl -s $(glooctl proxy url)/ecommerce/v2/api/store/order/1 -H "Host: api.mycompany.corp" | jq
Copied!

Lab 3 - Publishing the APIs on a Developer Portal

You need a Portal Custom Resource to expose your APIs to developers. That will generate a Developer Portal web UI, which is fully brandable.
Portal controller
1
cat <<EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: Portal
4
metadata:
5
name: ecommerce-portal
6
namespace: default
7
spec:
8
displayName: E-commerce Portal
9
description: The Gloo Portal for the Petstore API and much more!
10
banner:
11
fetchUrl: https://i.imgur.com/EXbBN1a.jpg
12
favicon:
13
fetchUrl: https://i.imgur.com/QQwlQG3.png
14
primaryLogo:
15
fetchUrl: https://i.imgur.com/hjgPMNP.png
16
customStyling: {}
17
staticPages: []
18
19
domains:
20
- portal.mycompany.corp # ------ THE DOMAIN NAME ---------
21
22
publishedEnvironments: # ---- APIs we will publish -----
23
- name: dev
24
namespace: default
25
26
allApisPublicViewable: true # this will make APIs visible by unauthenticated users
27
EOF
Copied!
To access it, you need to override the Hosts file on your machine:
1
cat <<EOF | sudo tee -a /etc/hosts
2
$(kubectl -n gloo-system get service gateway-proxy -o jsonpath='{.status.loadBalancer.ingress[0].ip}') portal.mycompany.corp
3
$(kubectl -n gloo-system get service gateway-proxy -o jsonpath='{.status.loadBalancer.ingress[0].ip}') api.mycompany.corp
4
EOF
Copied!
The developer Portal we have created is now available at http://portal.mycompany.corp/
1
/opt/google/chrome/chrome http://portal.mycompany.corp/
Copied!
Developer Portal
Note that we explicitly set the APIs visiblity to public in the Portal config (see above: allApisPublicViewable: true)
Take a few minutes to browse the Developer Portal web UI Under the APIs menu, you will find the two versions of our APIProduct:
APIs and their available versions
Click the line with the v1 to observe the list of aggregated endpoints for this version.
You can download the OpenAPI schema that has been generated from the selected APIDoc / endpoints:
Download the OpenAPI spec
Based on the raw OpenAPI specifications, these endpoints require authentication. We will override this with Gloo Portal Custom Resources later in this workshop. Later in this tutorial, there is a section where you will secure the access to the Developer Portal and also the access to the APIs.

Lab 4: Explore the Admin UI

In addition to these Developer Portal web UIs, Gloo Portal comes with an admin-centric web UI. It can help to see and configure all of the Gloo Portal resources:
  • APIDocs and APIProducts -- for building up APIs
  • Routes -- for fine-grained routing rules
  • Environments -- to expose your APIs on API Gateways
  • Portals -- to publish your APIs on a Developer Portal
  • Users and Groups -- for access control
You can access this Admin web UI using a port-forward:
1
kubectl -n gloo-portal port-forward svc/gloo-portal-admin-server 8080 &
Copied!
Then, open http://localhost:8080 and you should find this webapp:
Admin UI Homepage
Explore the menus and find your APIProduct, Environment and Portal resources.
We will use the Admin UI to secure the access to the Developer Portal. And, later on, we will use CRDs to secure the access to the APIs. You can achieve the same results either way, using Custom Resources or the Admin web UI.

Lab 5: Securing the access to the Developer Portal with Basic Auth

Back to the Developer Portal, there are 2 options to secure its access:
  • Basic Auth, using the User and Group CRDs
  • OpenID Connect (Portal docs)
In this lab #5, we will secure the access to a Developer Portal with Basic Auth.

Option A - Using the Admin Web UI

In the menu bar, click the "Access Control" link, then click "Create a Group"...
group creation - step 1a
... and give it a Name: developers and also a Display Name: ecommerce developers:
group creation - step 1
Click "Next step" and then click "Create Group".
Now, let's configure access control so that the "ecommerce developers" Group can access the "developer" Portal. Click the Manage link under "Portal Access", next to the group name:
edit group
Then add the "E-commerce Portal" Portal to the list of allowed Portals for this Group:
add portal
Now, let's create a User with the same method:
user creation - step 1
Then, give it a name, here dev1 and a password, for example Password1!:
user creation - step 2
Then, add it as a member of the Group defined right before and click "Create User".
user creation - step 3
Finally, you should see a configuration like the following:
user group config overview

Option B - Using CRDs

Another way of working is by using the Gloo Portal Custom Resources.
The code snippet below will create another User called "dev2". This new user will be made part of the "developers" Group.
1
pass=$(htpasswd -bnBC 10 "" super-password2 | tr -d ':\n')
2
kubectl create secret generic dev2-password \
3
-n gloo-portal --type=opaque \
4
--from-literal=password=$pass
5
6
kubectl apply -f -<<EOF
7
apiVersion: portal.gloo.solo.io/v1beta1
8
kind: User
9
metadata:
10
name: dev2
11
namespace: gloo-portal
12
labels:
13
groups.portal.gloo.solo.io/gloo-portal.developers: "true"
14
spec:
15
basicAuth:
16
passwordSecretKey: password
17
passwordSecretName: dev2-password
18
passwordSecretNamespace: gloo-portal
19
username: dev2
20
EOF
Copied!
The "Access Control" page will automatically be updated with our new User:
user dev2 from CRD
As always with Solo.io products, everything is GitOps friendly!

Signing in the Developer Portal

Let's give it a try, with one of our Users.
Navigate to your Developer Portal: http://portal.mycompany.corp/ and click the "Log In" button in the upper right corner.
login basic auth
Since it's the first connection with your User, you may be requested to change the default password for a new one. If you have any issue while logging in, please double check the password and the permission on the Group to access the Portal.
Once logged in, you should be able to browse the API catalog and see your Petstore (API) product.

Lab 6: Securing the access to the developer Portal with OIDC

Let's now secure our Developer Portal with OpenID Connect. We will rely on the Keycloak instance as the OpenID Provider and the few users and group that were created by the init script.
Overview of the in-memory users & groups in Keycloak:
  • "user1" and "user2" belong to the (IdP) group named "users"
  • "exec1" belongs to the (IdP) group "execs"
Here is a quick summary:
RBAC
We need to configure the Portal CR with OIDC options. For that, we need to fetch the Client ID and Client Secret that were genereted earlier. Then, we store the Client Secret into a Kubernetes Secret resource:
1
KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth
2
KEYCLOAK_TOKEN=$(curl -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token)
3
4
KEYCLOAK_ID=$(curl -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -H "Content-Type: application/json" $KEYCLOAK_URL/admin/realms/master/clients | jq -r '.[] | select(.redirectUris[0] == "https://portal.mycompany.corp/callback") | .id')
5
KEYCLOAK_CLIENT=$(curl -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -H "Content-Type: application/json" $KEYCLOAK_URL/admin/realms/master/clients | jq -r '.[] | select(.redirectUris[0] == "https://portal.mycompany.corp/callback") | .clientId')
6
KEYCLOAK_SECRET=$(curl -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -H "Content-Type: application/json" $KEYCLOAK_URL/admin/realms/master/clients/$KEYCLOAK_ID/client-secret | jq -r .value)
7
8
cat <<EOF | kubectl apply -f -
9
apiVersion: v1
10
kind: Secret
11
metadata:
12
name: petstore-portal-oidc-secret
13
namespace: default
14
data:
15
client_secret: $(echo $KEYCLOAK_SECRET | base64)
16
EOF
Copied!
And now, we add the OIDC configuration to our Portal CR:
1
cat <<EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: Portal
4
metadata:
5
name: ecommerce-portal
6
namespace: default
7
spec:
8
displayName: E-commerce Portal
9
description: The Gloo Portal for the Petstore API and much more!
10
banner:
11
fetchUrl: https://i.imgur.com/EXbBN1a.jpg
12
favicon:
13
fetchUrl: https://i.imgur.com/QQwlQG3.png
14
primaryLogo:
15
fetchUrl: https://i.imgur.com/hjgPMNP.png
16
customStyling: {}
17
staticPages: []
18
19
domains:
20
- portal.mycompany.corp
21
22
publishedEnvironments:
23
- name: dev
24
namespace: default
25
26
allApisPublicViewable: true
27
28
# ------------------- NEW ---------------------
29
oidcAuth:
30
clientId: ${KEYCLOAK_CLIENT}
31
clientSecret:
32
name: petstore-portal-oidc-secret
33
namespace: default
34
key: client_secret # this is the k8s secret we have created above
35
groupClaimKey: group # we will use the 'group' claim in the 'id_token' to associate the user with a group
36
issuer: ${KEYCLOAK_URL}/realms/master
37
# ---------------------------------------------
38
portalUrlPrefix: http://portal.mycompany.corp/
39
EOF
Copied!
We will now create a new Gloo Portal Group CR, called "users", representing these corporate users logged in through Keycloak:
1
cat << EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: Group
4
metadata:
5
name: users
6
namespace: default
7
spec:
8
displayName: corporate users
9
accessLevel:
10
portals:
11
- name: ecommerce-portal
12
namespace: default
13
oidcGroup:
14
groupName: users # this represents the group name in the IdP (Keycloak)
15
EOF
Copied!
And finally, let's log onto the Developer Portal, using our corporate user "user1". Navigate to http://portal.mycompany.corp/ and logout if already logged in with user "dev1" (that was from the Basic Auth lab):
Logout
Then, click again the "Log in" button in the upper right corner, and click the "Log in with OpenID Connect" link:
login mire
Then sign in using user1 and password on the Keycloak login form:
login keycloak
And voilà!
logged in
If you are interested in the integration with SaaS based OIDC service, check out this blog post.

Lab 7 - Securing your APIs

We have secured the access to the Developer Portal, with Basic Auth at first (Lab 5), and then with OIDC (Lab 6).
The next step in this workshop is to secure the access to APIs themselves.
Depending on your organization and on your API governance, you might have different roles in terms of API lifecycle management. Let's say we have these personas:
API management personas
There are companies where the Product Owner dictates the usage plans that must be applied to APIs, and other places where it's someone else, like the Portal Admin or a person from the Security team.
In this regard, Usage Plans are applicable on different Custom Resources:
  • the APIProduct - this represents options an API Owner gives to consumers
  • the Environment - the usage plans are actually enforced here
  • the Group - this enforces a security policy on a group of users
You can mix them together or, for instance, stick with Usage Plans only on the Environment CR.
In this lab, let's imagine you are the Product Owner of the Petstore APIProduct and you want to protect your API with two different methods:
  • the Usage Plan called "basic": Clients (end-user or application) can access your API with an API-key and also they are subject to a limitation of 5 req/sec
  • the Usage Plan called "truted": Clients (end-user or application) presenting a valid JWT (could optionally be an id_token) can access your API with a higher consumption rate, set to 10 req/sec
To better understand the RBAC system we are deploying here to control the access to the APIs, here is a summary:
RBAC overview
Let's configure these Usage Plans on the Environment CR.

Deploying a JWT based RBAC

First, we create the "trusted" Usage Plan, which will verify the signature of a JWT with a remote JWKS:
1
KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth
2
3
cat << EOF > env.yaml
4
apiVersion: portal.gloo.solo.io/v1beta1
5
kind: Environment
6
metadata:
7
name: dev
8
namespace: default
9
spec:
10
domains:
11
- api.mycompany.corp # the domain name where the API will be exposed
12
displayInfo:
13
description: This environment is meant for developers to deploy and test their APIs.
14
displayName: Development
15
basePath: /ecommerce # a global basepath for our APIs
16
apiProducts: # we will select our APIProduct using a selector and the 2 version of it
17
- namespaces:
18
- "*"
19
labels:
20
- key: app
21
operator: In
22
values:
23
- petstore
24
versions:
25
names:
26
- v1
27
- v2
28
basePath: "<div data-gb-custom-block data-tag="version"></div>" # this will dynamically prefix the API with the version names
29
# ------------------------ NEW -----------------------------
30
usagePlans:
31
- trusted
32
# ----------------------------------------------------------
33
gatewayConfig:
34
disableRoutes: false # we actually want to expose the APIs on a Gateway (optional)
35
# ------------------------- NEW --------------------------------
36
parameters:
37
usagePlans:
38
trusted:
39
displayName: trusted plan
40
rateLimit:
41
unit: MINUTE
42
requestsPerUnit: 10
43
authPolicy:
44
oauth:
45
authorizationUrl: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/auth
46
tokenUrl: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token
47
jwtValidation:
48
issuer: ${KEYCLOAK_URL}/realms/master
49
remoteJwks:
50
refreshInterval: 60s
51
url: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/certs
52
# ----------------------------------------------------------------
53
EOF
54
55
kubectl apply -f env.yaml
Copied!
Then we update the users Group for Clients who will authenticate with a JWT:
1
cat << EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: Group
4
metadata:
5
name: users
6
namespace: default
7
spec:
8
accessLevel:
9
apis:
10
- environments:
11
names:
12
- dev
13
namespaces:
14
- '*'
15
# -------------- Enforce the 'trusted' usage plan (JWT) ---------------
16
usagePlans:
17
- trusted
18
# -----------------------------------------------------------------
19
products: {}
20
portals:
21
- name: ecommerce-portal
22
namespace: default
23
oidcGroup:
24
groupName: users
25
displayName: corporate users
26
EOF
Copied!
Finally, update the APIProduct to allow for this Usage Plan:
1
cat << EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: APIProduct
4
metadata:
5
name: petstore-product
6
namespace: default
7
labels:
8
app: petstore
9
spec:
10
displayInfo:
11
title: Petstore Product
12
description: Fabulous API product for the Petstore
13
# ---------------- This API offers one Usage Plan ---------------------
14
usagePlans:
15
- trusted
16
# --------------------------------------------------------------------
17
versions:
18
- name: v1
19
apis:
20
- apiDoc:
21
name: petstore-openapi-v1-pets
22
namespace: default
23
- apiDoc:
24
name: petstore-openapi-v1-users
25
namespace: default
26
gatewayConfig:
27
route:
28
inlineRoute:
29
backends:
30
- upstream:
31
name: default-petstore-v1-8080
32
namespace: gloo-system
33
- name: v2
34
apis:
35
- apiDoc:
36
name: petstore-openapi-v2-full
37
namespace: default
38
gatewayConfig:
39
route:
40
inlineRoute:
41
backends:
42
- upstream:
43
name: default-petstore-v2-8080
44
namespace: gloo-system
45
EOF
Copied!
Let's do some tests!

Testing the JWT based plan

Let's fetch an access_token JWT from the IdP:
1
token=$(curl -s -d "client_id=admin-cli" -d "username=user1" -d "password=password" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token)
Copied!
With that token, we can query the PetStore API with the following command:
1
curl -H "Authorization: Bearer ${token}" -s $(glooctl proxy url)/ecommerce/v1/api/pet/1 -H "Host: api.mycompany.corp" | jq
Copied!
You should see a successful response with some yaml content:
1
{
2
"id": 1,
3
"category": {
4
"id": 2,
5
"name": "Cats"
6
},
7
"name": "Cat 1",
8
"photoUrls": [
9
"url1",
10
"url2"
11
],
12
"tags": [
13
{
14
"id": 1,
15
"name": "tag1"
16
},
17
{
18
"id": 2,
19
"name": "tag2"
20
}
21
],
22
"status": "available"
23
}
Copied!
Congratulations! you just secured you API with JWT verification!

Deploying an API-key based RBAC

Another mean of securing the access to your APIs is API keys.
Let's start by updating the Environment CR with a new Usage Plan:
1
KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth
2
3
cat << EOF > env.yaml
4
apiVersion: portal.gloo.solo.io/v1beta1
5
kind: Environment
6
metadata:
7
name: dev
8
namespace: default
9
spec:
10
domains:
11
- api.mycompany.corp # the domain name where the API will be exposed
12
displayInfo:
13
description: This environment is meant for developers to deploy and test their APIs.
14
displayName: Development
15
basePath: /ecommerce # a global basepath for our APIs
16
apiProducts: # we will select our APIProduct using a selector and the 2 version of it
17
- namespaces:
18
- "*"
19
labels:
20
- key: app
21
operator: In
22
values:
23
- petstore
24
versions:
25
names:
26
- v1
27
- v2
28
basePath: "<div data-gb-custom-block data-tag="version"></div>" # this will dynamically prefix the API with the version names
29
# ------------------------ UPDATE -----------------------------
30
usagePlans:
31
- basic2
32
- trusted
33
# -------------------------------------------------------------
34
gatewayConfig:
35
disableRoutes: false # we actually want to expose the APIs on a Gateway (optional)
36
37
parameters:
38
usagePlans:
39
# ------------------------- NEW --------------------------------
40
basic2:
41
authPolicy:
42
apiKey: {}
43
displayName: api-keys based plan
44
rateLimit:
45
requestsPerUnit: 5
46
unit: MINUTE
47
# --------------------------------------------------------------
48
trusted:
49
displayName: trusted plan
50
rateLimit:
51
unit: MINUTE
52
requestsPerUnit: 10
53
authPolicy:
54
oauth:
55
authorizationUrl: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/auth
56
tokenUrl: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token
57
jwtValidation:
58
issuer: ${KEYCLOAK_URL}/realms/master
59
remoteJwks:
60
refreshInterval: 60s
61
url: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/certs
62
EOF
63
64
kubectl apply -f env.yaml
Copied!
Update the Group for the developers, so that they must authenticate with Basic Auth in order to consume the APIs in the dev Environment:
1
cat << EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: Group
4
metadata:
5
name: developers
6
namespace: gloo-portal
7
spec:
8
accessLevel:
9
apis:
10
- environments:
11
names:
12
- dev
13
namespaces:
14
- '*'
15
# ------------------ Enforce basic auth usage plan ----------------
16
usagePlans:
17
- basic2
18
# -----------------------------------------------------------------
19
products:
20
namespaces:
21
- '*'
22
portals:
23
- name: ecommerce-portal
24
namespace: default
25
displayName: ecommerce developers
26
userSelector:
27
matchLabels:
28
groups.portal.gloo.solo.io/gloo-portal.developers: "true"
29
namespaces:
30
- '*'
31
EOF
Copied!
Ignore the warning message.
We also update the Petstore APIProduct so that is it accessible with both the basic plan and also the trusted plan.
1
cat << EOF | kubectl apply -f -
2
apiVersion: portal.gloo.solo.io/v1beta1
3
kind: APIProduct
4
metadata:
5
name: petstore-product
6
namespace: default
7
labels:
8
app: petstore
9
spec:
10
displayInfo:
11
title: Petstore Product
12
description: Fabulous API product for the Petstore
13
# ---------------- This API offers 2 usage plans ---------------------
14
usagePlans:
15
- basic2
16
- trusted
17
# --------------------------------------------------------------------
18
versions:
19
- name: v1
20
apis:
21
- apiDoc:
22
name: petstore-openapi-v1-pets
23
namespace: default
24
- apiDoc:
25
name: petstore-openapi-v1-users
26
namespace: default
27
gatewayConfig:
28
route:
29
inlineRoute:
30
backends:
31
- upstream:
32
name: default-petstore-v1-8080
33
namespace: gloo-system
34
- name: v2
35
apis:
36
- apiDoc:
37
name: petstore-openapi-v2-full
38
namespace: default
39
gatewayConfig:
40
route:
41
inlineRoute:
42
backends:
43
- upstream:
44
name: default-petstore-v2-8080
45
namespace: gloo-system
46
EOF
Copied!
Let's do some more tests!

Testing the basic auth plan

Navigate to http://portal.mycompany.corp/ , logout from [email protected] and log back in with the dev1 user credentials. Remember the password you set for this user in Lab 5.
Click on dev1 on the top right corner and select API Keys.
Click on API Keys again and then click "Add an API Key".
Generate an API-key on the Developer Portal
You can click on the key to copy the value to the clipboard.
Let's try it out in the Developer Portal at first.
Navigate back to your API and click the 2nd line with 'v2', and you are now able to use the try-it-out feature.
First, click the Authorize button:
try-it-out