Versions (relevant - OpenSearch/Dashboard/Server OS/Browser):
Opensearch: opensearchproject/opensearch:2.16.0. (opensearch-k8s-operator)
Opensearch Dashboard: opensearchproject/opensearch-dashboards:2.16.0
K8s: 1.26
Chrome Version 127.0.6533.100 (Official Build) (arm64)
Describe the issue:
Hi everyone!
I’ve been facing an issue while integrating OpenSearch Dashboards with Keycloak. The specific problem occurs during the authorization process through Keycloak. After entering my login credentials, I receive a 502 error in the browser.
Through multiple configuration tests, I’ve found that this issue arises when both an access_token
and a refresh_token
are returned in the authentication response. However, as soon as I disable the “Use refresh tokens” option in Keycloak settings or set refresh_tokens: false
in the Dashboard settings, the authorization works fine, and I’m able to access the Dashboard.
In our setup, Keycloak is configured with federation to Active Directory (AD). Users and groups are synchronized with the domain, and we have the following setup: there is a group named js-devops-team
that is pulled from AD, and a role mapping is configured for this group, which is mapped to the opensearch_admin
role created earlier.
In real-world use cases, this solution is inconvenient because the session duration would then be equal to the token’s lifespan. Once the token expires, the user would have to log in again, and with a token lifetime of only 5 minutes, this isn’t practical. I would prefer not to disable refresh token usage or extend the access token’s lifetime.
Additionally, it’s worth mentioning that the OpenSearch Dashboard is installed separately from the operator using a Helm chart.
Has anyone else faced this issue, and if so, how did you resolve it? Any insights or suggestions would be greatly appreciated!
Thanks in advance!
Configuration:
opensearch dashboards: values.yaml
# Copyright OpenSearch Contributors
# SPDX-License-Identifier: Apache-2.0
# Default values for opensearch-dashboards.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
opensearchHosts: "https://opensearch-cluster.observability.svc.cluster.local:9200"
replicaCount: 1
image:
repository: "opensearchproject/opensearch-dashboards"
tag: "2.16.0"
pullPolicy: "IfNotPresent"
startupProbe:
tcpSocket:
port: 5601
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 20
successThreshold: 1
initialDelaySeconds: 10
livenessProbe:
tcpSocket:
port: 5601
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 10
successThreshold: 1
initialDelaySeconds: 10
readinessProbe:
tcpSocket:
port: 5601
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 10
successThreshold: 1
initialDelaySeconds: 10
imagePullSecrets: []
nameOverride: ""
fullnameOverride: "os-dashboard"
serviceAccount:
create: true
annotations: {}
name: ""
rbac:
create: true
podAnnotations: {}
dashboardAnnotations: {}
extraEnvs:
- name: OPENID_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: opensearch-oidc-secret-4
key: client-secret
envFrom: []
secretMounts:
- name: certs
secretName: opensearch-cluster-http-cert
path: /usr/share/opensearch-dashboards/certs/
extraVolumes:
- name: sbis-crts
secret:
secretName: sbis-crts
extraVolumeMounts:
- name: sbis-crts
mountPath: /usr/share/opensearch-dashboards/certs/sbis-crts/
readOnly: true
extraInitContainers: ""
extraContainers: ""
podSecurityContext: {}
securityContext:
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
config:
opensearch_dashboards.yml:
# opensearch.ssl.verificationMode: none
logging.verbose: true
logging.root.level: debug
server:
name: "os-dashboard"
host: "os-dashboard"
port: "5601"
opensearch:
username: kibanaserver
password: kibanaserver
ssl:
certificateAuthorities:
- "/usr/share/opensearch-dashboards/certs/ca.crt"
certificate: "/usr/share/opensearch-dashboards/certs/tls.crt"
key: "/usr/share/opensearch-dashboards/certs/tls.key"
verificationMode: none #"certificate"
alwaysPresentCertificate: false
requestHeadersAllowlist:
- securitytenant
- Authorization
- security_tenant
opensearch_security:
session:
keepalive: true
ttl: 86400000
cookie:
secure: true
ttl: 86400000
password: "redacted"
auth:
type: ["basicauth","openid"]
multiple_auth_enabled: true
openid:
extra_storage:
additional_cookies: 3
cookie_prefix: security_authentication_oidc
base_redirect_url: "https://opensearch-ng.prod.domain.local"
connect_url: "https://claims-keycloak.dev.domain.local/sso/realms/opensearch/.well-known/openid-configuration"
client_id: "opensearch-dashboard"
client_secret: "${OPENID_CLIENT_SECRET}"
scope: "openid profile email"
verify_hostnames: true
root_ca: "/usr/share/opensearch-dashboards/certs/sbis-crts/CA.crt"
trust_dynamic_headers: "true"
header: Authorization
refresh_tokens: true
readonly_mode:
roles:
- kibana_read_only
multitenancy:
enabled: false
ui:
openid:
login:
buttonname: "Login with KeyCloak"
showbrandimage: true
home:
disableWelcomeScreen: true
opensearchDashboardsYml:
defaultMode:
priorityClassName: ""
opensearchAccount:
secret: "os-admin-dashboard-secret"
keyPassphrase:
enabled: false
labels:
sidecar.istio.io/inject: true
hostAliases: []
serverHost: "0.0.0.0"
service:
type: ClusterIP
port: 5601
labels: {}
annotations: {}
loadBalancerSourceRanges: []
httpPortName: http
ingress:
enabled: false
annotations: {}
labels: {}
hosts:
- host: chart-example.local
paths:
- path: /
backend:
serviceName: ""
servicePort: ""
tls: []
resources:
requests:
cpu: "1000m"
memory: "4096M"
limits:
cpu: "1000m"
memory: "4096M"
autoscaling:
enabled: true
minReplicas: 1
maxReplicas: 5
targetCPU: "80"
targetMemory: "80"
updateStrategy:
type: "Recreate"
nodeSelector:
node-role.kubernetes.io/observability: ""
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/infra
- effect: NoSchedule
key: node-role.kubernetes.io/observability
affinity: {}
topologySpreadConstraints: []
extraObjects: []
lifecycle: {}
plugins:
enabled: false
installList: []
opensearch: securityconfig-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: os-securityconfig-secret
namespace: observability
type: Opaque
stringData:
action_groups.yml: |-
_meta:
type: "actiongroups"
config_version: 2
my-action-group:
reserved: false
hidden: false
allowed_actions:
- "indices:data/write/index*"
- "indices:data/write/update*"
- "indices:admin/mapping/put"
- "indices:data/write/bulk*"
- "read"
- "write"
static: false
internal_users.yml: |-
_meta:
type: "internalusers"
config_version: 2
admin:
hash: "redacted"
reserved: true
backend_roles:
- "admin"
description: "admin user"
dashboarduser:
hash: "redacted"
reserved: true
description: "OpenSearch Dashboards user"
backend_roles:
- "admin"
nodes_dn.yml: |-
_meta:
type: "nodesdn"
config_version: 2
whitelist.yml: |-
_meta:
type: "whitelist"
config_version: 2
tenants.yml: |-
_meta:
type: "tenants"
config_version: 2
roles_mapping.yml: |-
_meta:
type: "rolesmapping"
config_version: 2
all_access:
reserved: false
backend_roles:
- "admin"
description: "Maps admin to all_access"
admin_kc_role:
reserved: false
backend_roles:
- "opensearch_admin"
description: "Maps Keycloak admin role to OpenSearch admin role"
dashboard_server:
reserved: true
users:
- "dashboarduser"
roles.yml: |-
_meta:
type: "roles"
config_version: 2
admin_kc_role:
cluster_permissions:
- 'cluster_all'
index_permissions:
- index_patterns:
- '*'
allowed_actions:
- 'indices_all'
static: false
reserved: false
hidden: false
description: "Administrator role with full access"
config.yml: |-
_meta:
type: "config"
config_version: "2"
config:
dynamic:
http:
anonymous_auth_enabled: false
authc:
basic_internal_auth_domain:
description: "Internal User Database"
http_enabled: true
transport_enabled: true
order: "1"
http_authenticator:
type: basic
challenge: true
authentication_backend:
type: internal
openid_auth_domain:
description: "Keycloak OpenID Connect"
http_enabled: true
transport_enabled: true
order: 0
http_authenticator:
type: openid
challenge: false
config:
openid_connect_idp:
enable_ssl: true
verify_hostnames: false
trust_all: true
subject_key: preferred_username
roles_key: roles
openid_connect_url: "https://claims-keycloak.dev.domain.local/sso/realms/opensearch/.well-known/openid-configuration"
client_id: "opensearch-dashboard"
client_secret: "${OPENID_CLIENT_SECRET}"
jwt_clock_skew_tolerance_seconds: 20
authentication_backend:
type: noop
access_token
{
"exp": 1724440608,
"iat": 1724440308,
"jti": "2b4f7417-b327-4dfc-aae7-edf4f3e48fc6",
"iss": "https://claims-keycloak.dev.domain.local/sso/realms/opensearch",
"sub": "0c7da72f-0852-497b-bc2c-0d7a93567ad1",
"typ": "Bearer",
"azp": "opensearch-dashboard",
"session_state": "35e426fa-0325-4532-8b3d-1790bb8f1161",
"acr": "1",
"allowed-origins": [
"*"
],
"scope": "openid email profile",
"sid": "35e426fa-0325-4532-8b3d-1790bb8f1161",
"email_verified": false,
"roles": [
"default-roles-opensearch",
"offline_access",
"uma_authorization",
"opensearch_admin"
],
"name": "Ageev Anton Ageev",
"preferred_username": "aaageev",
"given_name": "Anton Ageev",
"family_name": "Anton",
"email": "aaageev@domain.com"
}
refresh_token
{
"exp": 1724442108,
"iat": 1724440308,
"jti": "4720ed26-04c8-44c5-8c4c-7da97d53973d",
"iss": "https://claims-keycloak.dev.domain.local/sso/realms/opensearch",
"aud": "https://claims-keycloak.dev.domain.local/sso/realms/opensearch",
"sub": "0c7da72f-0852-497b-bc2c-0d7a93567ad1",
"typ": "Refresh",
"azp": "opensearch-dashboard",
"session_state": "35e426fa-0325-4532-8b3d-1790bb8f1161",
"scope": "openid email profile",
"sid": "35e426fa-0325-4532-8b3d-1790bb8f1161"
}
Relevant Logs or Screenshots: