Step-by-Step Centralized Authentication for Kubernetes with Keycloak and the Ambassador Edge Stack

When you are building Kubernetes applications, it’s easy to end up with “authentication sprawl” where all of your services have different authentication mechanisms. This tutorial walks through how to centralize your authentication mechanisms using an IdP and an API gateway.

Alex Gervais
Ambassador Labs

--

Keycloak is a widely adopted Identity and Access Management (IAM for short) open-source solution. 2014 was a big year for groundbreaking technologies; both the Keycloak and Kubernetes projects were first released a few weeks apart. Unsurprisingly, many Kubernetes end-users are turning to Keycloak as the preferred way to manage access to the secure APIs and services of their platform.

Strictly running Keycloak in Kubernetes won’t make your platform secure. A lot of concerns are left to the user to configure and implement: from exposing the Keycloak API endpoints using TLS and an ingress-controller, to enforcing security policies on specific business endpoints. When solving these problems, we have a bias towards using an API gateway solution to handle encrypted connections and centralize API management policies instead of re-implementing authentication strategies in every language and application of your microservice architecture.

Our goal today will be to install Keycloak as our IAM solution and secure it behind the Ambassador Edge Stack (our API gateway acting as a Kubernetes ingress controller). As a final step, we will deploy a sample application and demonstrate how to use Keycloak as an Identity Provider (IdP for short) to restrict access to this application with OAuth2 using request Filters.

Getting Started

In this tutorial, we will use a non-production-ready Keycloak installation. To simplify our dependency graph, we’ll use the in-memory datastore, which is suitable for a demo but wouldn’t guarantee high-availability in a production environment. If you are looking for production-grade persistence, Keycloak offers a variety of storage solutions.

The ingress controller is the missing building block in most Kubernetes offerings. Although Kubernetes defines an ingress resource, it is not actually backed by any implementation that will turn the resource into a public service which means that the choice and installation of an ingress solution are left to the operator. Here, we will use the community version of the Ambassador Edge Stack, because of its direct integration with Keycloak for authentication, to expose and secure public traffic coming in from internet requests to downstream private services running in our Kubernetes cluster.

At the end of the tutorial, we’ll be up and running with the Ambassador Edge Stack doing TLS termination at the edge of the Kubernetes network, exposing our Keycloak installation under /auth/ and securing our Quote application under /backend/.

Scaffolding with the K8s Initializer

Developers often poke fun at Kubernetes because of the copious amounts of YAML required. Instead of going into a scavenger hunt for YAML samples and assembling all puzzle pieces together from stale sources, we’ll be using the K8s Initializer to generate all of the Kubernetes resources for us. The K8s Initializer is a project generator tool similar to those that exist for application developers: think Spring Initializr or Yeoman.

The wizard-like interface of the K8s Initializer will guide us through a few questions to understand and configure specific settings that vary from one cloud provider to the other. These implementation details are often where promises are broken and portability falls short, making it hard to configure ingress controllers and expose services to public traffic. Hopefully, we can do away with the little gotchas by using a comprehensive tool like the K8s Initializer who will provide us with an optimal configuration.

Specifically for this tutorial, we picked our target Kubernetes cluster: “Google Kubernetes Engine” with a “Google External Load Balancer (L4)” load balancer. We also chose a public Hostname for our installation. Using a public hostname will require an extra step to configure a DNS entry to point to our installation, but given we want to demonstrate how to build a public secure application stack, it’s worth the extra effort. As for the K8s Initializer’s Auth configuration, we undoubtedly selected Keycloak, with a temporary password.

Once satisfied with our K8s Initializer options, we hit the “download” button. We’ll be given a set of ready-to-go YAML files and instructions.

Give it a try: https://app.getambassador.io/initializer/

Installing the Ambassador Edge Stack

We’ll start by installing the Ambassador Edge Stack because it contains a bunch of Kubernetes Custom Resource Definitions dependencies. Given that you have access to your desired Kubernetes cluster, the installation will be as simple as running kubectl apply commands and configuring a DNS entry to point to the external IP of the provisioned service.

Installing Keycloak

Installing Keycloak from the generated YAML is again straightforward: a single kubectl apply command. One curious cat might peek at the Keycloak YAML file (don’t worry it won’t kill you!). You’ll actually be able to appreciate how the Ambassador Mapping resource will instruct traffic hitting the public /auth/ prefix endpoint to be forwarded to our private Keycloak pod running in our Kubernetes cluster.

Giving Keycloak a few minutes to start, we’ll then be able to access its UI at https://domain-name/auth/. Since we chose to let “Ambassador terminate TLS using a Let’s Encrypt certificate” in the K8s Initializer options, we can appreciate how automatic TLS termination is happening with a secure certificate for our Keycloak installation.

We can then log in to Keycloak’s Administration Console UI using the default admin username and the temporary password we’ve selected previously to configure Keycloak to our needs. Don’t forget to change the temporary password!

Securing your APIs with Keycloak

UPDATE: The K8s Initializer now supports Filter and FilterPolicy resources, which automates this part of the tutorial. To check it out, follow part 2 of this tutorial.

Configuring a Keycloak Realm, Client and User

To secure our APIs, we will be using our shiny new Keycloak installation as our IdP. We first need to create a client to handle authentication requests from Ambassador Edge Stack. All of these configuration steps can be achieved using the Keycloak UI.

  1. We first start by creating a new “Realm”. Hovering on top of the “Master” label in the right-hand navigation, we can click on “Add realm”. We picked ambassador as the “Name” of our new realm. This will be needed later on to configure the authorizationURL field in the auth Filter.
  2. We’ll create a new client by navigating to “Clients” and clicking “Create”. We chose the following settings:
    - Client ID: ambassador — This value will be used in the clientID field of the auth Filter.
    - Client Protocol: openid-connect
    - Root URL: None, left blank
  3. On the following screen, we configured the Client with:
    - Access Type: confidential
    - Valid Redirect URIs: *
  4. Navigating to the “Mappers” tab in our Client, we clicked “Create” and used the following settings:
    - Protocol: openid-connect
    - Name: Ambassador Mapper
    - Mapper Type: Audience
    - Included Client Audience: Select the name of the Client from the dropdown. Remember, we named our Client ambassador.
  5. Going back to our ambassador Client, we navigated to the “Client Scopes” section and configured our Client for offline_access.
  6. Back again to our ambassador Client, we then navigated to the “Credentials” section. We took note of the “Secret” value as it will be used later when configuring our auth Filter.

Along with our Client configuration, let’s configure a Keycloak “User”:

  1. Navigating to the “Users” section of the Keycloak Administration Console, we will click on “Add user”. We gave our user a simple username: my-keycloak-user, then clicked “Save”.
  2. On the following screen, we switched to the User’s “Credentials” tab to set a temporary password.

Now that we have a user with which we are able to authenticate ourselves with, let’s deploy an application.

Deploying a Quote service

Deploying custom applications on Kubernetes is achieved by creating some Kubernetes resources defined as… You guessed it, more YAML! This time, since we are deploying a sample backend application, the sample is somewhat lightweight. You can save the following definitions to a “quote-service.yaml” file and deploy it using kubectl apply -f quote-service.yaml.

---
apiVersion: v1
kind: Service
metadata:
name: quote
namespace: default
spec:
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: quote
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: quote
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: quote
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: quote
spec:
containers:
- name: backend
image: docker.io/datawire/quote:0.4.1
ports:
- name: http
containerPort: 8080
---
apiVersion: getambassador.io/v2
kind: Mapping
metadata:
name: quote-backend
namespace: default
spec:
prefix: /backend/
service: quote

This will create a Kubernetes Deployment, Service, and Mapping to publicly expose our running Quote application under the /backend/ path. Give it a try, it’s currently unprotected: https://domain-name/backend/.

Securing access

Building on the configurations we applied to our Keycloak installation in the “Configuring a Keycloak Realm, Client and User” section earlier, we’ll be creating an OAuth2 Filter and FilterPolicy resources in Kubernetes. Don’t forget to replace the placeholders in this YAML sample with the values from your installation! Once again, save the following definitions to a “keycloak-filter.yaml” file and deploy it using kubectl apply -f keycloak-filter.yaml.

---
apiVersion: getambassador.io/v2
kind: Filter
metadata:
name: keycloak-filter
namespace: ambassador
spec:
OAuth2:
authorizationURL: https://{domain-name}/auth/realms/ambassador
audience: ambassador
clientID: ambassador
secret: {client_secret}
protectedOrigins:
- origin: https://{domain-name}
---
apiVersion: getambassador.io/v2
kind: FilterPolicy
metadata:
name: quote-policy
namespace: default
spec:
rules:
- host: "*"
path: /backend/
filters:
- name: keycloak-filter
namespace: ambassador
arguments:
scopes:
- "offline_access"

Since the FilterPolicy is acting on the /backend/ path, when navigating to our Quote service under https://domain-name/backend/ we are now prompted for authentication by Keycloak! Try logging in with the my-keycloak-user username we created earlier!

We are just one step away from extending this FilterPolicy configuration to protect multiple paths, endpoints and services using the same authentication strategy. Talk about an efficient way to roll out single sign-on and centralize your authentication mechanism! Now, just follow these instructions to configure fine-grained settings of Filter and FilterPolicy resources with Keycloak.

Learn More

In this tutorial, we’ve shown how to centralize your authentication in Kubernetes by deploying Keycloak as your IdP and the Ambassador Edge Stack as your Kubernetes-native API Gateway. With the help of the K8s Initializer, you are able to get these tools up and running in just a few clicks.

To learn more about these tools and centralized authentication strategies, check out the following resources:

Finally, you can now check out part 2 of this series, which showcases the new K8s Initializer functionality where you can automate Filter and FilterPolicy resources.

--

--

Outdoorsy, data-driven, eternal student, not so geeky creative mind and traveler. Distributed Systems Architect & Tech Lead. ex-Ambassador Labs, ex-AppDirect