Configure OpenSearch SSO with Keycloak
ClusterNest Managed OpenSearch can delegate Dashboards login to Keycloak over either OIDC or SAML. Pick one - auth_type accepts only a single mode per cluster.
You'll need your cluster's OpenSearch Dashboards hostname before registering the client. Set opensearch_dashboards_custom_hostname so you know it upfront, or create the cluster first with auth_type = "internal" and note the generated opensearch_dashboards_url output before switching it over.
- OIDC
- SAML
OIDC
Keycloak's ID token doesn't include realm/client roles by default - roles_key only works once a matching mapper is attached to the client.
- Console
- Terraform
- In the Keycloak admin console, go to your realm's Clients > Create client.
- Client type: OpenID Connect, Client ID: e.g.
opensearch-dashboards - Client authentication: On (confidential client), Standard flow: enabled
- Valid redirect URIs:
https://<dashboards-hostname>/auth/openid/login
- Client type: OpenID Connect, Client ID: e.g.
- On the Credentials tab, note the Client secret.
- To get a claim for
roles_key, go to the client's Client scopes tab, open its dedicated scope, and add a Group Membership mapper with token claim namegroups.
connect_url is https://<keycloak-host>/realms/<realm>/.well-known/openid-configuration.
In the ClusterNest console, open the cluster's create/edit form and set Authentication Type to OIDC, then fill in:
- Connect URL:
https://auth.example.com/realms/myrealm/.well-known/openid-configuration - Client ID / Client Secret: from the client's Credentials tab
- Roles Key:
groups(or whatever your mapper's claim name is; leave blank if you skipped step 3)
Keycloak's official provider (keycloak/keycloak) manages both the client and the role-mapping claim in the same config as the ClusterNest cluster:
terraform {
required_providers {
keycloak = {
source = "keycloak/keycloak"
version = "~> 5.0"
}
clusternest = {
source = "tf.clusternest.com/clusternest/clusternest"
version = ">=1.1.0"
}
}
}
provider "keycloak" {
url = "https://auth.example.com"
client_id = "admin-cli"
}
provider "clusternest" {
email = "[email protected]"
password = var.clusternest_app_password
}
locals {
dashboards_hostname = "opensearch-dashboards.example.com"
}
data "keycloak_realm" "myrealm" {
realm = "myrealm"
}
resource "keycloak_openid_client" "opensearch" {
realm_id = data.keycloak_realm.myrealm.id
client_id = "opensearch-dashboards"
name = "OpenSearch Dashboards"
enabled = true
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
valid_redirect_uris = ["https://${local.dashboards_hostname}/auth/openid/login"]
}
resource "keycloak_openid_group_membership_protocol_mapper" "groups" {
realm_id = data.keycloak_realm.myrealm.id
client_id = keycloak_openid_client.opensearch.id
name = "groups"
claim_name = "groups"
}
resource "clusternest_opensearch" "sso" {
name = "logs"
tier = "standard"
organization_id = 123
auth_type = "oidc"
oidc_config = {
connect_url = "https://auth.example.com/realms/myrealm/.well-known/openid-configuration"
client_id = keycloak_openid_client.opensearch.client_id
client_secret = keycloak_openid_client.opensearch.client_secret
roles_key = "groups"
}
opensearch_dashboards_custom_hostname = local.dashboards_hostname
}
client_secret is left unset on the client so Keycloak generates one, exposed as a sensitive computed attribute. Drop the keycloak_openid_group_membership_protocol_mapper resource and roles_key if you don't need role mapping.
SAML
roles_key defaults to an ADFS-style claim URI Keycloak never emits - it only works once a matching Role List mapper is attached to the client (Client scopes > dedicated scope > Add mapper in the console).
- Console
- Terraform
- In the Keycloak admin console, go to your realm's Clients > Create client.
- Client type: SAML, Client ID: the value you'll use as
sp_entity_idbelow - Valid redirect URIs / Master SAML Processing URL:
https://<dashboards-hostname>/_opendistro/_security/saml/acs - Name ID format: email (or whatever your subject_key mapping expects)
- Client type: SAML, Client ID: the value you'll use as
- To map roles into the assertion, go to the client's Client scopes, edit the dedicated scope, and add a Role list mapper - note the SAML attribute name it's configured to emit.
idp_metadata_url is Keycloak's realm SAML descriptor endpoint - it needs no authentication. idp_entity_id is the realm issuer URL, https://<keycloak-host>/realms/<realm>.
In the ClusterNest console, open the cluster's create/edit form and set Authentication Type to SAML, then fill in:
- IDP Metadata URL:
https://auth.example.com/realms/myrealm/protocol/saml/descriptor - IDP Entity ID:
https://auth.example.com/realms/myrealm - SP Entity ID:
opensearch-dashboards - Roles Key:
Role(or whatever your mapper's attribute name is)
terraform {
required_providers {
keycloak = {
source = "keycloak/keycloak"
version = "~> 5.0"
}
clusternest = {
source = "tf.clusternest.com/clusternest/clusternest"
version = ">=1.1.0"
}
}
}
provider "keycloak" {
url = "https://auth.example.com"
client_id = "admin-cli"
}
provider "clusternest" {
email = "[email protected]"
password = var.clusternest_app_password
}
locals {
dashboards_hostname = "opensearch-dashboards.example.com"
}
data "keycloak_realm" "myrealm" {
realm = "myrealm"
}
resource "keycloak_saml_client" "opensearch" {
realm_id = data.keycloak_realm.myrealm.id
client_id = "opensearch-dashboards"
name = "OpenSearch Dashboards"
valid_redirect_uris = ["https://${local.dashboards_hostname}/_opendistro/_security/saml/acs"]
name_id_format = "email"
}
resource "clusternest_opensearch" "sso" {
name = "logs"
tier = "standard"
organization_id = 123
auth_type = "saml"
saml_config = {
idp_metadata_url = "https://auth.example.com/realms/myrealm/protocol/saml/descriptor"
idp_entity_id = "https://auth.example.com/realms/myrealm"
sp_entity_id = keycloak_saml_client.opensearch.client_id
}
opensearch_dashboards_custom_hostname = local.dashboards_hostname
}
For roles_key, add a Role List mapper resource to the client and set roles_key to the SAML attribute name it's configured to emit.
See the Terraform resource reference for the full field list.