In a previous article, we explored how to connect to an SKS cluster with OIDC using GitLab as the OpenID provider. In this article we’ll introduce Dex, a CNCF Sandbox project, which adds an abstraction layer in front of the identity provider. We’ll demonstrate how to use Dex to connect to an SKS cluster with user and group management handled within GitLab.

About Dex

As defined in the official documentation: “Dex is an identity service that uses OpenID Connect to drive authentication for other apps. Dex acts as a portal to other identity providers through connectors.” This lets Dex defer authentication to LDAP servers, SAML providers, or Identity Providers (IdP) like GitHub and Google.”.


Dex is handy for abstracting the potential complexity or translating the specific protocol of the underlying IdP. For example, when using Google as an IdP, group information is not included in the OIDC token. This requires an additional API call to retrieve this information, which Dex handles in the background. Additionally, Dex allows users to be configured in its local database, enabling authentication even if the IdP is temporarily unavailable. Dex is a great fit for large organizations which integrate with multiple IdPs or require custom authentication logic. For smaller setups with simpler needs, a direct connection to the IdP may be sufficient.


The schema below illustrates Dex’s overall architecture. If an application uses OIDC with Dex, each authentication request initiated by a user is sent to Dex. Dex then redirects the request to the configured identity provider via a connector. Once the authentication is done, the identity provider sends the OIDC token back to Dex, which returns it to the application.


Architecture


When used in the context of Kubernetes, the client application is the kubectl binary. The following sequence diagram illustrates the interactions between the various actors involved. The oidc-login plugin, a widely used kubectl plugin in charge of communicating with OIDC providers (Dex in our example), will be discussed later.


sequence-diagram


Next, we’ll configure our GitLab environment.

Creating a GitLab application

To use GitLab as the identity provider, we need to create an application. In this article, we use Techwhale, a GitLab group created for demo purposes.

From the Settings/ Applications menu of this GitLab group, we create an application named K8S-DEX-OIDC. We set the redirect URL to https://auth.techwhale.io/callback and select the following scopes:

  • read_user
  • openid
  • profile
  • email


GitLab App Creation


Note

Scopes define which pieces of data (claims) are included in the token returned by GitLab, and restrict the actions the application can perform on behalf of the user.


Once the application is created, it provides the client ID and Secret which we save in the GITLAB_CLIENT_ID and GITLAB_CLIENT_SECRET environment variables. We’ll use these values in a later section.

Creating a cluster specifying OIDC parameters

When creating an SKS cluster, specific parameters can be used to configure the OIDC connectivity.

  • client-id: OpenID client ID
  • issuer-url: OpenID provider URL
  • username-claim: JWT claim to use as the user’s name
  • username-prefix: Prefix prepended to username claims
  • groups-claim: JWT claim to use as the user’s group
  • groups-prefix: Prefix prepended to group claims
  • required-claim: a map that describes a required claim in the ID Token


The cluster creation process was covered in the previous article. In this example, it’s important to set these parameters with the following values:

  • client-id: “kubectl”
  • issuer-url: “https://auth.techwhale.io”
  • groups-claim: “groups”
  • groups-prefix: “oidc:”
  • username-claim: “preferred_username”
  • username-prefix: “oidc:”


This configuration enables authentication via Dex, which will be available on https://auth.techwhale.io.

Note

issuer-url is set to a subdomain that does not exist yet, the DNS entry will be created in the next section.

Installing infrastructure components

In this section, we’ll install the following components in the cluster:

Traefik

Traefik is a widely used Ingress Controller in Kubernetes. We’re installing it using Helm:

helm install traefik oci://ghcr.io/traefik/helm/traefik -n traefik --create-namespace

Note

We’re using the Traefik Helm chart distributed through the GitHub OCI registry.

Next, we get the IP address of the Load balancer exposing Traefik:

$ kubectl get svc -n traefik
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                      AGE
my-traefik   LoadBalancer   10.102.52.194   XXX.XXX.XXX.XXX   80:30653/TCP,443:30783/TCP   75s

We use this IP address to create an A record, in our DNS provider, for the auth.techwhale.io subdomain.

Cert manager:

Cert-manager automates the certificate issuance and renewal process. We’re installing Cert-manager using Helm:

helm repo add cert-manager https://charts.jetstack.io
helm install cert-manager cert-manager/cert-manager --set crds.enabled=true --version 1.16.2 -n cert-manager --create-namespace

Next, we create a ClusterIssuer responsible for issuing certificates through Let’s Encrypt Certification Authority:

cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
 name: acme
spec:
 acme:
 email: devops@techwhale.io
 server: https://acme-v02.api.letsencrypt.org/directory
 privateKeySecretRef:
 name: acme-account-key
 solvers:
 - http01:
 ingress:
 class: traefik
EOF

Note

We need to create this ClusterIssuer so that cert-manager can obtain a certificate to expose Dex over TLS.

Installing DEX

Before installing Dex, we make sure the following environment variables are set:

  • GITLAB_CLIENT_ID: identifier of the GitLab application created previously
  • GITLAB_CLIENT_SECRET: secret associated with this same application
  • KUBECTL_CLIENT_ID: we can set the value to “kubectl” as this is Dex’s client application
  • KUBECTL_CLIENT_SECRET: we can use the value “StaticClientSuperSafeSecret” for this example

Since we’ll be installing Dex using Helm, we begin by creating the values.yaml configuration file as follows:

cat <<EOF > values.yaml
config:
 issuer: https://auth.techwhale.io

 storage:
 type: memory

 connectors:
 - type: gitlab
 id: gitlab
 name: GitLab
 config:
 baseURL: https://gitlab.com
 clientID: ${GITLAB_CLIENT_ID}
 clientSecret: ${GITLAB_CLIENT_SECRET}
 redirectURI: https://auth.techwhale.io/callback
 useLoginAsID: false
 scopes: 
 - openid
 - profile
 - email
 - groups

 staticClients:
 - id: ${KUBECTL_CLIENT_ID}
 redirectURIs:
 - "http://localhost:8000"
 name: 'Kubectl client'
 secret: ${KUBECTL_CLIENT_SECRET}

ingress:
 enabled: true
 className: traefik
 annotations:
 cert-manager.io/cluster-issuer: acme
 hosts:
 - host: auth.techwhale.io
 paths:
 - path: /
 pathType: ImplementationSpecific
 tls:
 - hosts:
 - auth.techwhale.io
 secretName: certs
EOF

This file defines the configuration of Dex’s connector, GitLab in this example. It also defines the kubectl client, within the staticClients property, which will interact with Dex.

We’re installing Dex using Helm:

helm repo add dex https://charts.dexidp.io
helm upgrade --install dex dex/dex --version 0.19.1 -f values.yaml -n dex --create-namespace

Updating the kubeconfig file

We’ll be using kubelogin (a.k.a kube-login), a kubectl plugin that allows kubectl to communicate with an OIDC identity provider. In the previous article, we used GitLab as the identity provider, in the current example we’re still using GitLab, but we’ll be abstracting this identity provider through Dex. When making a kubectl call, the user will be redirected to GitLab, via Dex, for authentication.

Note

The simplest installation method for a kubectl plugin is to install Krew - Kubernetes Plugins Manager, then use it to install the specific plugin: kubectl krew install oidc-login.

Once the plugin is installed, we can verify the content of the ID Token with the following command (which also helps in case we need to troubleshoot the configuration):

kubectl oidc-login setup --oidc-issuer-url=https://auth.techwhale.io --oidc-client-id=${KUBECTL_CLIENT_ID} --oidc-client-secret=${KUBECTL_CLIENT_SECRET} --oidc-extra-scope=groups --oidc-extra-scope=profile

This command returns the oidc-login configuration information, which we use to define a new user in the kubeconfig.

kubectl config set-credentials oidc \
 --exec-api-version=client.authentication.k8s.io/v1beta1 \
 --exec-command=kubectl \
 --exec-arg=oidc-login \
 --exec-arg=get-token \
 --exec-arg=--oidc-issuer-url=https://auth.techwhale.io \
 --exec-arg=--oidc-client-id=${KUBECTL_CLIENT_ID} \
 --exec-arg=--oidc-client-secret=${KUBECTL_CLIENT_SECRET} \
 --exec-arg=--oidc-extra-scope=groups \
 --exec-arg=--oidc-extra-scope=profile

Before using this new user, we’ll create the appropriate roles in the cluster.

GitLab groups

Several subgroups and users were created in TechWhale GitLab group:

  • user techwhale-dev is member of subgroup techwhale/viewer
  • user techwhale-admin is member of subgroup techwhale/admin

Next, we’ll use Kubernetes RBAC to restrict these subgroups’ access to a specific list of actions and resource types.

ClusterRole / ClusterRoleBinding based on GitLab’s groups

Our requirements are as follows:

  • Users in the techwhale/viewer group should only have read-only access to the main resources in the cluster (Deployments, Pods, ConfigMaps, …)
  • Users in the techwhale/admin group should have full access to these resources

To meet these requirements we create the necessary ClusterRoles and ClusterRoleBindings. These resources define the authorizations for both GitLab groups.

cat <<EOF | kubectl apply -f -
# Admin ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
 name: gitlab-oidc-admin
rules:
- apiGroups: [""]
 resources: ["pods", "services", "configmaps", "secrets"]
 verbs: ["get", "list", "watch", "create", "update", "delete"]
- apiGroups: ["apps"]
 resources: ["deployments", "daemonsets", "statefulsets"]
 verbs: ["get", "list", "watch", "create", "update", "delete"]
- apiGroups: ["batch"]
 resources: ["jobs", "cronjobs"]
 verbs: ["get", "list", "watch", "create", "update", "delete"]
---
# Admin ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: gitlab-oidc-admin
subjects:
- kind: Group
 name: "oidc:techwhale/admin"
 apiGroup: rbac.authorization.k8s.io
roleRef:
 kind: ClusterRole
 name: gitlab-oidc-admin
 apiGroup: rbac.authorization.k8s.io
---
# Viewer ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
 name: gitlab-oidc-viewer
rules:
- apiGroups: [""]
 resources: ["pods", "services", "configmaps"]
 verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
 resources: ["deployments", "daemonsets", "statefulsets"]
 verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
 resources: ["jobs", "cronjobs"]
 verbs: ["get", "list", "watch"]
---
# Viewer ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: gitlab-oidc-viewer
subjects:
- kind: Group
 name: "oidc:techwhale/viewer"
 apiGroup: rbac.authorization.k8s.io
roleRef:
 kind: ClusterRole
 name: gitlab-oidc-viewer
 apiGroup: rbac.authorization.k8s.io
EOF

Now that everything is set up, we’ll verify the authentication is working as expected.

Verification

First, we set the oidc user in the kubeconfig as the default. This ensures the kubectl command will be handled by Dex unless another user is specified:

kubectl config set-context --current --user=oidc

Next, we ensure we are signed out from gitlab.com in our browser. Then we create a simple Deployment:

kubectl create deploy www --image=nginx:1.24

This command opens a browser to GitLab and requests authentication. First, we log in as techwhale-admin


Admin login 1


Next, we accept the permissions requested by the GitLab application providing access to our account:


Admin login 2


Then the authentication is validated.


Admin login 3


In the terminal, we observe that the Deployment was correctly created.

deployment.apps/www created

We log out of GitLab. Next, we run the same command once more.

Note

We may also need to clear the cache of the oidc-login plugin (located in $HOME/.kube/cache/oidc-login).

kubectl create deploy www --image=nginx:1.24

We authenticate with user techwhale-dev.


Dev login 2


Then, we accept the requested permissions as we did previously.


In the terminal, we observe that the Deployment cannot be created because the user does not have the required permissions.

error: failed to create deployment: deployments.apps is forbidden: User "oidc:techwhale-dev" cannot create resource "deployments" in API group "apps" in the namespace "default"

Using GitLab groups we are thus able to limit the access to our cluster’s resources.

Key takeaways

Companies often rely on an SSO (Single Sign-On) solution to manage authentication and authorization across the entire organization. Configuring Kubernetes to use these solutions is straightforward, as a cluster has OIDC parameters designed for this purpose. Dex takes this integration a step further acting as an abstraction layer in front of identity providers. It provides a single interface for multiple OIDC providers, with a connector for each, including GitLab, Google, and LDAP, simplifying client configuration.