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 Istio Ingress 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.8.9 --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.1.0-beta6 --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
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 composition
The APIProduct comes with two versions of it:
    /v1 will expose endpoints for the /pets/* and /users/* endpoints, and it will route requests to the petstore-v1 application
    /v2 will expose all of the endpoints, including /pets/*, /users/* and also /store/*, and it will route requests to the petstore-v2 application
We'll start by deploying the well-known Petstore app, twice. 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:
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 is something like that:
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.
Finally, let's create the APIProduct, with its 2 versions:
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
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
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!
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 side, and Users on the other side
    v2 is build upon 1 APIDoc, containing all the operations
As you can see, we have configured 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:
Environment
In this workshop, and in order to leverage advanced API Gateway features, we will rely on Gloo Edge. The other option is to have Portal configure Gloo Mesh Gateway, built on top on the Istio Gateway.
We need to prepare an Environment CR, where we will set the domain(s) and, optionally, some security options like authentication or 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: "{%version%}" # this will dynamically prefix the API with the version names
27
gatewayConfig:
28
disableRoutes: false # we actually want to expose the APIs on a Gateway (optional)
29
EOF
30
31
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 over /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!
The combination of these CRs will generate the expected configuration for Envoy.

Step 2.3

Now let's consume the API!
1
# v1
2
# one of the /pet endpoints
3
curl -s $(glooctl proxy url)/ecommerce/v1/api/pet/1 -H "Host: api.mycompany.corp" | jq
4
# one of the /user endpoints
5
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"
6
curl -s $(glooctl proxy url)/ecommerce/v2/api/user/jdoe -H "Host: api.mycompany.corp" | jq
7
8
# v2
9
# one of the /store endpoints
10
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 configure a developer portal webapp, 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
21
22
publishedEnvironments:
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 domain name 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 explicetly set the APIs visiblity to public in the Portal config (see above: allApisPublicViewable: true)
Take a few minutes to browse the developer portal. Under the APIs menu, you will find the two versions of our APIProduct:
APIs
Click the line with the v1 to observe the list of aggregated endpoints for this version.
Based on the OpenAPI specifications, these endpoints require authentication. We will override this with Gloo Portal Custom Resources later in this workshop. Below, 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

On top of these developer portal web UIs, Gloo Portal comes with an additional Admin-centric web UI, so that you can see and configure all of the Gloo Portal resources:
    APIDocs and APIProducts
    Routes
    Environments
    Portals
    Users and Groups
You can access this admin 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

There are 2 options to secure the access to a developer Portal:
    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 portal web UI

In the "Access Control" section of the dashboard, click the "Create a Group" button...
group creation - step 1a
... and give it a name, here developers and display name, here 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 Portal 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:
user creation - step 2
Finally, add it as a member of the Group defined above:
user creation - step 3
Finally, you should see a configuration like this:
user group config overview

Option B - Using CRDs

Another way of working is by using the Gloo Portal Custom Resources:
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!

Connecting to the Portal, as a developer

Let's now 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 secure our developer Portal with OpenID Connect. We will rely on our Keycloak instance as the OpenID Provider and the few users and group we have created at the beginning.
Overview of the in-memory users & group in Keycloak:
    "user1" and "user2" belong to the (IdP) group named "users"
    "exec1" belongs to the (IdP) group "execs"
Here is a quick summary:
logged in
So, we will configure the Portal CR with some OIDC options. For that, we need to fetch the Client ID and Client secret that we have created earlier:
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 created above
35
groupClaimKey: group
36
issuer: ${KEYCLOAK_URL}/realms/master
37
portalUrlPrefix: http://portal.mycompany.corp/
38
# ---------------------------------------------
39
EOF
Copied!
We will now create a new Gloo Portal Group CR 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 you IdP (here Keycloak)
15
EOF
Copied!
And finally try to log onto the Portal, using our corporate "user1":
First, you may need to logout from the developer Portal:
dev2 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à!
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, both with basic auth and with OIDC.
The next step in this workshop is to secure the APIs themselves.
Depending on your organization and on your API governance, you can have different roles for managing an API lifecyle. Let's say we have these personas:
logged in
There are companies where the Product Owner dictates the 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.
So, Usage Plans are applicable on several Custom Resources:
    the APIProduct - this represents options an API Owner gives to consumers
    the Environment - the usage plans are actually defined 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 say you are the Product Owner of the Petstore APIProduct and you want to protect your API with two different methods:
    Method A or the basic plan: every client application can access your API, with an API key and also a limitation of 5 req/sec
    Method B or the trusted plan: every client application presenting a 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:
logged in

Deploying a JWT based RBAC

Let's create these plans at the Environment level, because you first want to try them out in your dev Environment and you don't want to block the access to APIs in production.
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: "{%version%}" # 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!
Now we update the Group for clients who will authenticate through 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 '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!
And, finally, we update the APIProduct to enforce the usage of 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 1 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 make 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!
Then, we can run 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

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: "{%version%}" # 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!
We 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!
You may see a warning message when applying this Group change. It is benign; you can safely ignore it.
We also update the petstore APIProduct so that is it accessible with both the basic plan and 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

Logout from [email protected] and log back in with the dev1 user credentials.
Click on dev1 on the top right corner and select API Keys.
Click on API Keys again and then click "Add an API Key".
User Developer Portal API Key
You can click on the key to copy the value to the clipboard.
Let's try it out in the Portal UI 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: