Skip to main content

Configure Dex to Use Keycloak as an OIDC Connector

PaletteAI authenticates users through Dex, which can broker authentication to an external OpenID Connect (OIDC) provider. This page shows how to configure Dex to use Keycloak as an OIDC connector so that PaletteAI users sign in with their Keycloak credentials.

The authentication flow is: user -> PaletteAI -> Dex -> Keycloak -> Dex -> PaletteAI.

info

Dex can connect to many OIDC providers — Keycloak is one example. For the full list and per-provider configuration, refer to the upstream Dex connectors reference.

Prerequisites

This page only configures user sign-in through Keycloak. To control what each authenticated user can do in the cluster, follow either Configure Kubernetes API Server to Trust OIDC Provider or Configure User Impersonation to enforce per-user Role-Based Access Control (RBAC).

warning

The two enforcement paths are mutually exclusive. If you configure the Kubernetes API server to trust Dex, do not also enable user impersonation. Refer to Configure User Impersonation, which documents this restriction in detail.

Enablement

Configure the Keycloak Realm and OIDC Client

  1. Sign in to the Keycloak admin console and create a new realm named paletteai. Do not configure the integration in the default Keycloak administration realm.

  2. In the realm, create the groups that you want to map to Kubernetes groups. This page uses admins, editors, and viewers as a worked example.

  3. Create users and assign each user to the appropriate group. For each user, set a non-temporary password under the Credentials tab and turn on Email verified.

  4. Create an OIDC client for Dex:

    • Set Client type to OpenID Connect and Client ID to dex.

    • Turn on Client authentication.

    • Enable only the Standard flow capability.

    • Set Valid redirect URIs to https://<your-paletteai-domain>/dex/callback.

    • Set Valid post logout redirect URIs and Web origins to + (Keycloak shorthand for "use the same values as Valid Redirect URIs").

  5. Copy the value from the Credentials tab of the dex client. This value becomes the clientSecret in the Dex connector configuration.

  6. Create a client scope named groups:

    • Set Protocol to OpenID Connect and Type to Default.

    • Leave Display on consent screen turned off.

  7. Assign the groups scope to the dex client as a default scope.

  8. Open the dex-dedicated client scope (Keycloak auto-creates this scope for every client) and add a Group Membership mapper with the following values:

    • Name: groups

    • Token Claim Name: member_groups

    • Full group path: off

    • Add to ID token: on

    • Add to access token: on

    • Add to userinfo: on

    info

    Dex does not pick up a claim named groups from upstream OIDC providers reliably. Emit the claim as member_groups in Keycloak and map it back to groups in the Dex connector configuration (Configure the Dex Connector).

  9. Verify the Keycloak configuration by requesting a token and inspecting the userinfo endpoint.

    TOKEN=$(curl --silent --request POST \
    "https://<your-keycloak-domain>/realms/paletteai/protocol/openid-connect/token" \
    --data "client_id=dex" \
    --data "client_secret=<client-secret>" \
    --data "grant_type=password" \
    --data "username=<user-email>" \
    --data "password=<user-password>" \
    --data "scope=openid profile email groups" | jq --raw-output '.access_token')

    curl --silent --header "Authorization: Bearer $TOKEN" \
    "https://<your-keycloak-domain>/realms/paletteai/protocol/openid-connect/userinfo" | jq .

    The response includes a member_groups array.

    Example Output
    {
    "sub": "...",
    "email_verified": true,
    "name": "Jane Doe",
    "member_groups": ["admins"],
    "preferred_username": "jane@example.com",
    "email": "jane@example.com"
    }

Configure the Dex Connector

Add a Keycloak connector to your PaletteAI Helm values.yaml under dex.config.connectors.

dex:
config:
connectors:
- type: oidc
id: keycloak
name: Keycloak
config:
issuer: https://<your-keycloak-domain>/realms/paletteai
clientID: dex
clientSecret: <client-secret-from-keycloak>
redirectURI: https://<your-paletteai-domain>/dex/callback
getUserInfo: true
insecureEnableGroups: true
insecureSkipEmailVerified: true
scopes:
- profile
- email
- groups
claimMapping:
groups: member_groups

Each of the following fields is required for the Keycloak integration to work:

  • getUserInfo: true forces Dex to fetch claims from the userinfo endpoint. Without this, Keycloak group information may not reach Dex.

  • insecureEnableGroups: true is required for Dex to read and forward group claims from upstream OIDC providers. The insecure prefix is historical — it dates from when the feature was experimental, not from any security concern.

  • insecureSkipEmailVerified: true prevents Dex from rejecting users whose email verification status it cannot confirm from the upstream provider.

  • claimMapping.groups: member_groups maps the member_groups claim from Keycloak to Dex's internal groups field. This works around the Dex behavior noted in the Group Membership mapper step.

  • redirectURI must use the public PaletteAI URL: Keycloak redirects the user's browser to this URL after authentication.

info

Your existing Dex static clients and static passwords remain unchanged. The Keycloak connector coexists with local Dex users — at the sign-in page, users can either enter credentials for a local Dex user or select the Keycloak connector.

Configure PaletteAI for OIDC

In the same values.yaml, configure the PaletteAI OIDC settings.

canvas:
oidc:
sessionSecret: '' # leave empty; populated via global.auth.sessionSecret
sessionDir: '/app/sessions'
issuerK8sService: '' # leave empty; automatically configured in the Helm chart templates
skipSSLCertificateVerification: false
redirectURL: 'https://<your-paletteai-domain>/ai/callback'

Set skipSSLCertificateVerification to true only when Dex presents a self-signed certificate that the PaletteAI UI cannot otherwise trust.

Configure User Impersonation

Skip this subsection if you are configuring the Kubernetes API server to trust Dex directly. If you are using user impersonation instead, add a canvas.impersonationProxy block that maps the Keycloak groups created in Configure the Keycloak Realm and OIDC Client to Kubernetes groups. The minimal configuration below maps admins, editors, and viewers to the built-in Kubernetes admin, edit, and view ClusterRole resources.

canvas:
impersonationProxy:
enabled: true
userMode: passthrough
groupsMode: map
userMap: {}
groupMap:
admins: ['admin']
editors: ['edit']
viewers: ['view']
dexGroupMap: {}
warning

groupsMode: map is required when you also need to assign Kubernetes groups to local Dex users through dexGroupMap. groupsMode: passthrough cannot be combined with dexGroupMap.

Refer to Configure User Impersonation for the full parameter reference and additional mapping scenarios.

Apply the Configuration

Apply the changes with Helm.

helm upgrade mural oci://public.ecr.aws/mural/mural \
--namespace <namespace> \
--version <version> \
--values <values-file> \
--atomic --timeout 5m

After the upgrade completes, restart Dex and the PaletteAI UI so they pick up the new configuration.

kubectl rollout restart deployment dex --namespace <namespace>
kubectl rollout restart deployment canvas --namespace <namespace>

Validate

  1. Sign in to PaletteAI. Your browser is redirected to Dex, then to Keycloak, and finally back to PaletteAI, where you are signed in.

  2. Confirm that Dex received the groups from Keycloak.

    kubectl logs --namespace <namespace> --selector app.kubernetes.io/name=dex --tail=20

    Look for a line that contains groups=[admins] (or your own group names). An entry of groups=[] indicates that the connector did not receive the claim. Refer to Common Issues.

  3. Verify that Kubernetes RBAC grants the expected permissions for each group. Substitute the appropriate user email and Kubernetes group name for each probe. The trailing comment on each line shows the expected result.

    kubectl auth can-i create pods --as=admin@example.com --as-group=admin                          # yes
    kubectl auth can-i create pods --as=editor@example.com --as-group=edit # yes
    kubectl auth can-i create clusterrolebindings --as=editor@example.com --as-group=edit # no
    kubectl auth can-i get pods --as=viewer@example.com --as-group=view # yes
    kubectl auth can-i create pods --as=viewer@example.com --as-group=view # no

    The two no results confirm that the bound ClusterRole resources scope down to the expected permissions (edit cannot create cluster-scoped bindings, view is read-only).

Common Issues

Invalid Redirect URI from Keycloak

The redirectURI in the Dex connector configuration must exactly match a Valid Redirect URIs entry on the dex client in Keycloak. Differences as small as a trailing slash cause this error. Inspect the redirect_uri query parameter in the browser address bar to confirm the exact value Dex is sending.

Invalid Scopes Error

The groups scope must exist as a client scope in Keycloak and be assigned as a default scope to the dex client. Refer to the client-scope steps in Configure the Keycloak Realm and OIDC Client.

Empty Groups in Dex Logs

When groups=[] appears in the Dex logs after a successful sign-in, the cause is typically one of the following:

  • insecureEnableGroups: true is missing from the Dex connector configuration.

  • The Group Membership mapper is not configured on the dex-dedicated client scope in Keycloak.

  • The user is not assigned to any group in Keycloak.

No Impersonation Groups Found

Groups are reaching Dex but not PaletteAI. Confirm that canvas.impersonationProxy.groupsMode is set to map and that every external group is listed under groupMap. For local Dex users, confirm that each user is listed under dexGroupMap.

Next Steps

Dex now authenticates users through Keycloak. To finish, choose how Kubernetes enforces per-user RBAC:

warning

Until the Kubernetes API server is configured to use Dex as its OIDC provider, Kubernetes RBAC enforcement is UI-only. Any user with a valid kubeconfig bypasses OIDC group enforcement and is authorized solely by the credentials in that kubeconfig.

To use a different identity provider, replace the keycloak connector in dex.config.connectors. Refer to the upstream Dex connectors reference for all supported connectors and their configuration options.