Skip to content

Azure Entra SSO for TAP GUI via Keycloak

ChatGPT Generated title image

In recent years, I've worked with many customers who want to add SSO to a developer tool but run into blocking issues.

These are the two most common issues:

  1. there is no update-compatible way to configure Proxy configuration
  2. they want to use Technical Accounts, but they are not supported or allowed by their corporate SSO solution

Working with customers on Tanzu Application Platform(TAP)1, I often run into the first.

Tanzu Developer Portal2, the main GUI of TAP, is based on Backstage3 and the authentication is in Backstage is provided by adding plugins.

Many of these authentication plugins do not support configuring an outgoing Proxy other than changing the plugin's code.

This is not an acceptable solution for our customers.

This guide explores how we can leverage Keycloak4 to function as an authentication proxy.

Keycloak is an application for authentication and authorization federation (and more) that supports proxy configurations.

Keycloak is also built to run in Kubernetes, so it is an excellent solution to this problem.

As an identity provider, we use Azure Entra ID5, as this is a combination (TAP, Backstage, Azure Entra ID) that I've seen several times.

Note

You might be wondering what Azure Entra ID is.

Azure Active Directory, or AzureAD, has been rebranded as Entra ID.

Prerequisites

  1. TAP installation of either Full or View profile
  2. A Cert-manager ClusterIssuer
  3. Azure administrator account
    • not sure if a Free account is valid, but Entra ID is free with regards to how this guide uses it
  4. Helm CLI

Steps

In this learning path, you will:

  1. Install Keycloak
  2. Configure TAP GUI Client in Keycloak
  3. Create App Registration in Entra (Azure AD)
  4. Configure Entra (Azure AD) in Keycloak
  5. Configure Keycloak OIDC in TAP GUI

Deploy Keycloak

We are going to deploy Keycloak via Helm charts.

As the customer might not be able to load Helm charts directly from the internet, we need a way to bring them in.

We will use the helm pull mechanism to download the OCI bundles and distribute them to the desired location.

Warning

The Bitnami Helm charts support overriding the repository from which the images come.

In this guide, we skip over the relocation of these images, but know you can do that:

global:
  imageRegistry: some-internal-registry.example.com
  imagePullSecrets: internal-registry-secret

Prepare

There isn't much to prepare here, so we stick to creating the keycloak Namespace.

kubectl create namespace keycloak

Postgres Helm Chart

You might encounter the challenge of not being able to directly map the Helm Chart's Image repository to the internal one.

In that case, you must install the Postgres database separately from the Keycloak instance.

It also makes it easier to manage the Postgres instance, so we start with installing the Postgres Helm Chart9 from Bitnami.

First, we pull the Helm chart OCI bundle:

helm pull \
  oci://registry-1.docker.io/bitnamicharts/postgresql \
  --version 14.0.5

Then we create the install values (feel free to use a better password):

keycloak-postgres-values.yaml
auth:
  database: "keycloak"
  username: "keycloak"
  password: "keycloak"

And then we install Postgres via the helm upgrade --install command:

helm upgrade --install postgres \
    postgresql-14.0.5.tgz \
    --values keycloak-postgres-values.yaml \
    --namespace keycloak

Let's see if everything is there:

kubectl get secret,cm,svc,deploy,sts,po -n keycloak

If all is well, it should look like this:

NAME                                    TYPE                 DATA   AGE
secret/postgres-postgresql              Opaque               2      75s
secret/sh.helm.release.v1.postgres.v1   helm.sh/release.v1   1      76s

NAME                         DATA   AGE
configmap/kube-root-ca.crt   1      8m7s

NAME                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/postgres-postgresql      ClusterIP   10.96.177.191   <none>        5432/TCP   75s
service/postgres-postgresql-hl   ClusterIP   None            <none>        5432/TCP   75s

NAME                                   READY   AGE
statefulset.apps/postgres-postgresql   1/1     76s

NAME                        READY   STATUS    RESTARTS   AGE
pod/postgres-postgresql-0   1/1     Running   0          76s

Keycloak Proxy & Certificate

Keycloak requires TLS, so we generate a valid certificate (self-signed or not) by leveraging a ClusterIssuer named my-issuer.

Keycloak Helm Chart

Now install Keycloak.

We configure the Ingress, relying on the ClusterIssuer to generate a Certificate and populate the TLS secret.

We set the 'proxy: edge` values to get the front end to collaborate.

keycloak-values.yaml
ingress:
  enabled: true
  ingressClassName: "contour"
  pathType: ImplementationSpecific
  hostname: keycloak.tap1.example.com
  tls: true
  annotations: 
    cert-manager.io/cluster-issuer: my-issuer

proxy: edge

networkPolicy:
  enabled: true

auth:
  adminUser: keycloakadmin
  existingSecret: keycloak-credentials
  passwordSecretKey: password
service:
  type: ClusterIP
postgresql:
  enabled: false
externalDatabase:
  host: postgres-postgresql
  database: keycloak
  user: keycloak
  password: keycloak

Important

If you want Keycloak to use a proxy server, you can set these extraEnvVars as described in Keycloak documentation8.

extraEnvVars:
- name: HTTP_PROXY
    value: "https://myproxy.com:8443"
- name: HTTPS_PROXY
    value: "https://myproxy.com:8443"
- name: NO_PROXY
    value: "*.svc.local,..."

As Keycloak is the external facing system and it can be pretty sensitive, I recommend using a separate secret for the admin credentials:

kubectl create secret generic keycloak-credentials \
  --from-literal=username="keycloakadmin" \
  --from-literal=password='InsertRandomGeneratedPassword' \
  --namespace keycloak

As with the Postgres database, we pull the Keycloak Helm Chart's OCI bundle.

helm pull \
  oci://registry-1.docker.io/bitnamicharts/keycloak \
  --version 18.3.0

With the bundle and the values file, we can install Keycloak:

helm upgrade --install keycloak \
    keycloak-18.3.0.tgz \
    --values keycloak-values.yaml \
    --namespace keycloak

The Ingres and its related resources should be valid:

kubectl get cert,secret,ing -n keycloak

And indeed they are:

NAME                                                                       READY   SECRET                                         AGE
certificate.cert-manager.io/keycloak.tap1.example.com-tls   True    keycloak.tap1.example.com-tls   1d

NAME                                                  TYPE                 DATA   AGE
secret/keycloak-admin-tls                             kubernetes.io/tls    3       1d
secret/keycloak-credentials                           Opaque               2       1d
secret/keycloak-externaldb                            Opaque               1       1d
secret/keycloak-tls                                   kubernetes.io/tls    3       1d
secret/keycloak.tap1.example.com-tls                  kubernetes.io/tls    3       1d
secret/postgres-postgresql                            Opaque               2       1d
secret/sh.helm.release.v1.keycloak.v1                 helm.sh/release.v1   1       1d
secret/sh.helm.release.v1.postgres.v1                 helm.sh/release.v1   1       1d

NAME                                 CLASS     HOSTS                                      ADDRESS          PORTS     AGE
ingress.networking.k8s.io/keycloak   contour   keycloak.tap1.example.com                  10.214.162.198   80, 443    1d

Let's look at the runtime resources:

kubectl get cm,svc,deploy,sts,po -n keycloak
NAME                          DATA   AGE
configmap/keycloak-env-vars   15      1d
configmap/kube-root-ca.crt    1       1d

NAME                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/keycloak                 ClusterIP   10.96.218.211   <none>        80/TCP      1d
service/keycloak-headless        ClusterIP   None            <none>        80/TCP      1d
service/postgres-postgresql      ClusterIP   10.96.177.191   <none>        5432/TCP    1d
service/postgres-postgresql-hl   ClusterIP   None            <none>        5432/TCP    1d

NAME                                   READY   AGE
statefulset.apps/keycloak              1/1      1d
statefulset.apps/postgres-postgresql   1/1      1d

NAME                        READY   STATUS    RESTARTS   AGE
pod/keycloak-0              1/1     Running   0           1d
pod/postgres-postgresql-0   1/1     Running   0           1d

All is well, and we can login to Keycloak now.

Configure Entra ID & Keycloak

Assuming Keycloak is working as intended, we can now configure the Keycloak < > Entra ID interaction.

Create App Registration in Entra

We start by logging into the Azure Portal and navigating to Entra ID.

Azure Portal Homepage

Open the App registrations menu, and select + New Registration:

Entra ID App Registrations

Then, in the creation process:

  • Name: Give it a descriptive name, e.g., keycloak
  • Supported account types: Accounts in this organizational directory only (Default Directory only - Single tenant)
  • Redirect URL: leave this blank for now

Entra ID App Registration - create page

We then create a Client Secret by clicking on the Add a certificate or secret link after the Client credentials heading.

Entra ID App Registration - client credentials

Click on New client secret, give a useful description, such as keycloak, set an expiration date, and hit Add.

Entra ID App Registration - create secret

Keep this page open, as we need the information in a moment.

Entra ID App Registration - secrets page

Create Realm in Keycloak

Log in to Keycloak in a new tab so we can copy and paste values between Keycloak and Azure.

First, we create a Realm in Keycloak.

In the top left corner, there is a dropdown menu with the default (master); click on this and select Create realm.

Keycloak - create new realm-1

We name the realm tap.

Keycloak - create new realm-2

Add Azure Entra as an Identity Provider

Next, we add Entra as an identity provider; in the left menu, go to Identity providers.

Keycloak - configure identity provider

Select Microsoft.

Keycloak - configure identity provider Microsoft

Fill in the App registrion details in our Microsoft provider, which we can copy from our Azure Entra tab (that should still be open):

  • Client Secret: copy the secret's Value

Next, go back to the overview page:

  • Client ID: copy the Application (client) ID value
  • Tenant ID: copy the Directory (tenant) ID value

Note

If you hover over the text, you'll see a "copy" button that allows you to copy the value to your clipboard.

Entra ID App Registration - overview

Verify the details, hit the Add button, and copy the Redirect URI.

Keycloak - configure identity provider Microsoft

In Azure, we go to the Overview page (of the App registration) and click the Add redirect URI link behind the Redirect URIs header.

Entra ID App Registration - redirect-1

We add a web platform URI and paste it into the redirect URI from Keycloak.

Entra ID App Registration - redirect-2

Which should look something like this: https://keycloak.example.com/realms/tap/broker/microsoft/endpoint

Entra ID App Registration - redirect-3 Entra ID App Registration - redirect-4

Create TAP Client In Keycloak

Keycloak - create-client

Go to Clients and hit Create client:

Keycloak - create-client-page1

  • Client type: OpenID Connect
  • Client ID: tap
  • Name: Tanzu Application Platform

Next page (2):

Keycloak - create-client-page2

  • Client authentication: togle to On

Next page (3):

Keycloak - create-client-page3

  • Root URL: set this to TAP GUI root, e.g., https://tap-gui.example.com
  • Home URL: same
  • Valid redirect URLs: https://tap-gui.example.com/api/auth/oidc/handler/frame
  • Valid post logout redirect URIs: "
  • Web origins: +

Go to Credentials tab, and copy the Client Secret to use in the TAP GUI config.

Keycloak - client-create-secret

Then go to the Advanced tab and scroll to the Advanced settings section. Here we update all the timeouts to 30 Minutes. Select the Expires in in the dropdown, and then set the timer.

Keycloak - client-set-timeouts

There are some reports of the defaults here causing problems with some of the views in TAP GUI. It is best to set this to 15 minutes or longer to combat this.

Keycloak - client-set-timeouts

Configure TAP GUI

Now we can configure TAP GUI to use our OIDC Client10, by setting the auth values in the tap_gui.app_config property:

tap_gui:
  app_config:
    auth:
      allowGuestAccess: true
      environment: development
      loginPage:
        oidc:
          message: Sign in with Keycloak
          title: Keycloak Login
      providers:
        oidc:
          development:
            clientId: tap
            clientSecret: ???
            metadataUrl: ???
            prompt: auto
            scope: openid profile email offline_access
      session:
        secret: ???

Replace the metadataUrl with your Keycloak realm's metadata URL, which should be something like https://keycloak.example.com/realms/tap/.well-known/openid-configuration.

You can collect it from the menu item Realm settings, the tab General, and then the link called OpenID Endpoint Configuration after the header Endpoints.

Keycloak - well-known-config

Replace the clientSecret with the Keycloak Client credential secret we copied earlier.

And replace the session.secret value with a randomly generated value, such as Y2MAACjPlZHPK8NhHMdf24g2mnBwcFgap01Mx.

Once you've updated the TAP install and the TAP GUI application is reloaded, you should see Keycloak as an option to log in:

TAP GUI - Login with Keycloak

References