Keycloak with opensearch is not working

Versions (relevant - OpenSearch/Dashboard/Server OS/Browser):

2.8.0

Describe the issue:
Keycloak with opensearch is not working.

Configuration:
Hi All,
We have deployed the below configuration file

apiVersion: opensearch.opster.io/v1
kind: OpenSearchCluster
metadata:
  name: my-cluster1
  namespace: opensearch
spec:
  initHelper:
    image: "public.ecr.aws/opsterio/busybox"
  security:
    config:
      adminCredentialsSecret:
        name: a-admin-credentials-secret
      securityConfigSecret:
        name: a-securityconfig-secret
    tls:
      transport:
        generate: true
      http:
        generate: true
  general:
    serviceName: my-cluster1
    version: "2.8.0"
    pluginsList: ["repository-s3"]
    drainDataNodes: true
    setVMMaxMapCount: true
    imagePullPolicy: IfNotPresent
    additionalVolumes:
    - name: openid-certs
      path: /usr/share/opensearch/config/certs/
      configMap:
        name: openid-certs
      restartPods: true
      #additionalConfig:
      #plugins.security.allow_default_init_securityindex: "true"
      #plugins.security.ssl.transport.pemtrustedcas_filepath: /usr/share/opensearch/config/certs/openid-certs
  dashboards:
    additionalConfig:
      logging.verbose: "true"
      opensearch_security.auth.type: '["basicauth","openid"]'
      opensearch_security.auth.multiple_auth_enabled: "True"
      opensearch_security.openid.connect_url: https://efktest.com/auth/realms/os/.well-known/openid-configuration
      opensearch_security.openid.base_redirect_url: https://osdashs.dev26.tatacommunications.com/
      opensearch_security.openid.client_id: grafana
      opensearch_security.openid.client_secret: 4zQdkx7ZSvHxpuiw4SCNTLibmGPElHhr
      opensearch_security.openid.scope: openid profile email
      opensearch_security.openid.header: Authorization
      opensearch_security.openid.trust_dynamic_headers: "true"
      opensearch.optimizedHealthcheckId: "my-cluster1"
      opensearch_security.openid.verify_hostnames: "false"
      opensearch.ssl.verificationMode: none
      opensearch_security.cookie.secure: "false"
      opensearch_security.auth.type: "openid"
      opensearch.requestHeadersWhitelist:  |
        ["securitytenant","Authorization","security_tenant"]
      opensearch_security.readonly_mode.roles: '[ "kibana_user", "readall" ]'
    imagePullPolicy: IfNotPresent
    opensearchCredentialsSecret:
      name: a-admin-credentials-secret
    enable: true
    tls:
      enable: true
      generate: true
    version: "2.8.0"
    replicas: 1
    resources:
      requests:
         memory: "512Mi"
         cpu: "200m"
      limits:
         memory: "512Mi"
         cpu: "200m"
  nodePools:
    - component: masters
      replicas: 3
      diskSize: "5Gi"
      jvm: "-Dopensearch.allow_insecure_settings=true"
      resources:
         requests:
            memory: "2Gi"
            cpu: "500m"
         limits:
            memory: "3Gi"
            cpu: "1000m"
      roles:
        - "data"
        - "master"
        - "ingest"
      persistence:
        pvc:
          storageClass: efk
          accessModes: # You can change the accessMode
          - ReadWriteOnce

Following is the security config.

apiVersion: v1
kind: Secret
metadata:
  name: a-securityconfig-secret
  namespace: opensearch
type: Opaque
stringData:
      internal_users.yml: |-
        _meta:
          type: "internalusers"
          config_version: 2
        admin:
          hash: "$2a$12$JyfMv0Rsd9W0wjZWQGFi5udp7MPoNiacQ0b3Zzoh7rq219QU4fCLu"
          reserved: true
          backend_roles:
          - "admin"
          description: "Demo admin user"

        anomalyadmin:
          hash: "$2y$12$TRwAAJgnNo67w3rVUz4FIeLx9Dy/llB79zf9I15CKJ9vkM4ZzAd3."
          reserved: false
          opendistro_security_roles:
          - "anomaly_full_access"
          description: "Demo anomaly admin user, using internal role"

        kibanaserver:
          hash: "$2a$12$4AcgAt3xwOWadA5s5blL6ev39OXDNhmOesEoo33eZtrq2N0YrU3H."
          reserved: true
          description: "Demo OpenSearch Dashboards user"

        kibanaro:
          hash: "$2a$12$JJSXNfTowz7Uu5ttXfeYpeYE0arACvcwlPBStB1F.MI7f0U9Z4DGC"
          reserved: false
          backend_roles:
          - "kibanauser"
          - "readall"
          attributes:
            attribute1: "value1"
            attribute2: "value2"
            attribute3: "value3"
          description: "Demo OpenSearch Dashboards read only user, using external role mapping"

        logstash:
          hash: "$2a$12$u1ShR4l4uBS3Uv59Pa2y5.1uQuZBrZtmNfqB3iM/.jL0XoV9sghS2"
          reserved: false
          backend_roles:
          - "logstash"
          description: "Demo logstash user, using external role mapping"

        readall:
          hash: "$2a$12$ae4ycwzwvLtZxwZ82RmiEunBbIPiAmGZduBAjKN0TXdwQFtCwARz2"
          reserved: false
          backend_roles:
          - "readall"
          description: "Demo readall user, using external role mapping"

        snapshotrestore:
          hash: "$2y$12$DpwmetHKwgYnorbgdvORCenv4NAK8cPUg8AI6pxLCuWf/ALc0.v7W"
          reserved: false
          backend_roles:
          - "snapshotrestore"
          description: "Demo snapshotrestore user, using external role mapping"
      config.yml: |-
        _meta:
          type: "config"
          config_version: 2
        config:
          dynamic:
            authz: {}
            authc:
              basic_internal_auth_domain:
                http_enabled: true
                transport_enabled: true
                order: 0
                http_authenticator:
                  type: basic
                  challenge: false
                authentication_backend:
                  type: internal

              openid_auth_domain:
                http_enabled: true
                transport_enabled: true
                order: 1
                http_authenticator:
                  type: openid
                  challenge: false
                  config:
                    openid_connect_idp:
                      enable_ssl: true
                      verify_hostnames: false
                      pemtrustedcas_filepath: /usr/share/opensearch/config/certs/openid-certs
                    subject_key: preferred_username
                    roles_key: roles
                    openid_connect_url: "https://efktest.com/auth/realms/os/.well-known/openid-configuration"
                authentication_backend:
                  type: noop
      roles_mapping.yml: |-
        _meta:
          type: "rolesmapping"
          config_version: 2

        # Define your roles mapping here

        ## Demo roles mapping

        all_access:
          reserved: false
          backend_roles:
          - "admin"
          - "roles"
          description: "Maps admin to all_access"

        own_index:
          reserved: false
          users:
          - "*"
          description: "Allow full access to an index named like the username"

        logstash:
          reserved: false
          backend_roles:
          - "logstash"

        kibana_user:
          reserved: false
          backend_roles:
          - "kibanauser"
          description: "Maps kibanauser to kibana_user"

        readall:
          reserved: false
          backend_roles:
          - "readall"

        manage_snapshots:
          reserved: false
          backend_roles:
          - "snapshotrestore"

        kibana_server:
          reserved: true
          users:
          - "kibanaserver"

Relevant Logs or Screenshots:
We have configured the keycloak configuration correctly. Also, the session id also provided to opensearch from keycloak. But we are getting the authorization error.

Hi @kannan,

Could you please share the error message?

Thanks,
mj

This should be an “Absolute path to the PEM file containing the root CAs of your IdP.”

Is the file extension (.pem) missing?

best,
mj

Hi @Mantas ,

kubectl  get cm -n opensearch openid-certs -o yaml
apiVersion: v1
data:
  openid-certs: '{"keys":[

Our file name is without extensions. Even if we keep *.crt as extension, it is not working

Thanks,
Kannan V

HI @Mantas ,
Following is the error messages in opensearch dashboard

{“type”:“ops”,“@timestamp”:“2024-01-08T11:25:26Z”,“tags”:,“pid”:1,“os”:{“load”:[1.18,1.49,1.64],“mem”:{“total”:16690663424,“free”:7230930944},“uptime”:688592.29},“proc”:{“uptime”:511335.865961632,“mem”:{“rss”:137912320,“heapTotal”:106000384,“heapUsed”:94563832,“external”:20752615,“arrayBuffers”:1649894},“delay”:0.12295293807983398},“load”:{“requests”:{“5601”:{“total”:1,“disconnects”:0,“statusCodes”:{“200”:1}}},“responseTimes”:{“5601”:{“avg”:2,“max”:2}},“sockets”:{“http”:{“total”:0},“https”:{“total”:0}}},“message”:“memory: 90.2MB uptime: 142:02:16 load: [1.18 1.49 1.64] delay: 0.123”}
{“type”:“log”,“@timestamp”:“2024-01-08T11:25:26Z”,“tags”:[“debug”,“metrics”],“pid”:1,“message”:“Refreshing metrics”}
{“type”:“ops”,“@timestamp”:“2024-01-08T11:25:31Z”,“tags”:,“pid”:1,“os”:{“load”:[1.24,1.5,1.64],“mem”:{“total”:16690663424,“free”:7252758528},“uptime”:688597.29},“proc”:{“uptime”:511340.867510952,“mem”:{“rss”:137269248,“heapTotal”:106000384,“heapUsed”:93918536,“external”:21280151,“arrayBuffers”:594874},“delay”:0.2468470335006714},“load”:{“requests”:{“5601”:{“total”:0,“disconnects”:0,“statusCodes”:{}}},“responseTimes”:{“5601”:{“avg”:null,“max”:0}},“sockets”:{“http”:{“total”:0},“https”:{“total”:0}}},“message”:“memory: 89.6MB uptime: 142:02:21 load: [1.24 1.50 1.64] delay: 0.247”}
{“type”:“log”,“@timestamp”:“2024-01-08T11:25:31Z”,“tags”:[“debug”,“metrics”],“pid”:1,“message”:“Refreshing metrics”}
{“type”:“ops”,“@timestamp”:“2024-01-08T11:25:36Z”,“tags”:,“pid”:1,“os”:{“load”:[1.22,1.49,1.64],“mem”:{“total”:16690663424,“free”:7216771072},“uptime”:688602.29},“proc”:{“uptime”:511345.867303809,“mem”:{“rss”:137539584,“heapTotal”:106000384,“heapUsed”:94182088,“external”:21815879,“arrayBuffers”:1130602},“delay”:0.13360393047332764},“load”:{“requests”:{“5601”:{“total”:0,“disconnects”:0,“statusCodes”:{}}},“responseTimes”:{“5601”:{“avg”:null,“max”:0}},“sockets”:{“http”:{“total”:0},“https”:{“total”:0}}},“message”:“memory: 89.8MB uptime: 142:02:26 load: [1.22 1.49 1.64] delay: 0.134”}
{“type”:“log”,“@timestamp”:“2024-01-08T11:25:36Z”,“tags”:[“debug”,“metrics”],“pid”:1,“message”:“Refreshing metrics”}
{“type”:“error”,“@timestamp”:“2024-01-08T11:25:41Z”,“tags”:[“connection”,“client”,“error”],“pid”:1,“level”:“error”,“error”:{“message”:“140479110416320:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:…/deps/openssl/openssl/ssl/record/rec_layer_s3.c:1565:SSL alert number 46\n”,“name”:“Error”,“stack”:“Error: 140479110416320:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:…/deps/openssl/openssl/ssl/record/rec_layer_s3.c:1565:SSL alert number 46\n”,“code”:“ERR_SSL_SSLV3_ALERT_CERTIFICATE_UNKNOWN”},“message”:“140479110416320:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:…/deps/openssl/openssl/ssl/record/rec_layer_s3.c:1565:SSL alert number 46\n”}
{“type”:“error”,“@timestamp”:“2024-01-08T11:25:41Z”,“tags”:[“connection”,“client”,“error”],“pid”:1,“level”:“error”,“error”:{“message”:“140479110416320:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:…/deps/openssl/openssl/ssl/record/rec_layer_s3.c:1565:SSL alert number 46\n”,“name”:“Error”,“stack”:“Error: 140479110416320:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:…/deps/openssl/openssl/ssl/record/rec_layer_s3.c:1565:SSL alert number 46\n”,“code”:“ERR_SSL_SSLV3_ALERT_CERTIFICATE_UNKNOWN”},“message”:“140479110416320:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:…/deps/openssl/openssl/ssl/record/rec_layer_s3.c:1565:SSL alert number 46\n”}
{“type”:“log”,“@timestamp”:“2024-01-08T11:25:41Z”,“tags”:[“debug”,“http”,“server”,“OpenSearchDashboards”,“cookie-session-storage”],“pid”:1,“message”:“Error: Unauthorized”}

Could you please share the output of the below command:

ls -l /usr/share/opensearch/config/certs/

Best,
mj

[opensearch@my-cluster1-masters-0 ~]$ ls -l /usr/share/opensearch/config/certs/
total 0
lrwxrwxrwx 1 root root 19 Jan 8 12:49 openid-certs → …data/openid-certs
[opensearch@my-cluster1-masters-0 ~]$

Above is the output from my-cluster1-masters-0

Above data is present only on master node. Not on dashboard pod

Is the directory empty?

best,
mj

Hi @Mantas,

Since the file is linked it is showing 0. Kindly find the below.

[opensearch@my-cluster1-masters-0 certs]$ ls -l /usr/share/opensearch/config/certs/
total 0
lrwxrwxrwx 1 root root 19 Jan  8 12:49 openid-certs -> ..data/openid-certs

[opensearch@my-cluster1-masters-0 certs]$ ls -l /usr/share/opensearch/config/certs/..data/
total 4
-rw-r--r-- 1 root root 2901 Jan  8 12:49 openid-certs

[opensearch@my-cluster1-masters-0 certs]$ cat /usr/share/opensearch/config/certs/..data/openid-certs
{"keys":[{"kid":"1qGlXsK7PC-H6-EFQ8fP69uJMpfXh_EP1TTP4xtuIrw","kty":"RSA","alg":"RSA-OAEP","use":"enc","n":"rykU_GBbcZ7PJM5hHviq3Modu-7iuRImn2aubVp_l2ShMBLwC6E91DVvLSTD73V37FCHvxPEn98OcUmHME-CK80JxrYHK4dPQ1Ho0e9WFgQL5GUKTfIqZabP1rnFvncYHvnHJGh6Jyts98

Hi @Mantas ,
What is the possible cause for this issue ?
Thanks,
Kannan V

Hi @kannan,

Could you share your keycloak Token Claim Name from Client scopes/roles/Meppers(tab)/realm roles should look similar to belov:

96a3f34bd971e2dae4a096e7008274b3082a01db_2_689x392

Thanks,
mj

Hi @Mantas ,
Following is my token Claim keycloak details

Below one is the mapping with client

Let us know your suggestion to us.

Thanks,
Kannan V

What is the Token Claim Name?

step #1
image

step #2

step #3
image

step #4
image

Can you share the full (all values) screenshot of the step #4 ?

best,
mj

Hi @Mantas,

Refer the below token claim name in client scope - mapper - realm roles.

Hi @kannan,

I see, missed the first screenshot somehow.

Could you share the JWT token that Keycloak generates?
I can see that you are trying to map roles: roles to all_access (quotes below), is that correct (meaning you have a realm role called roles with a user you are logging in?)?

Best,
mj

Hi @Mantas,

Keycloak generated JWT Token - Are you referring this /usr/share/opensearch/config/certs/openid-certs pem file which is mounted in the pods?

Yes… we are providing all_access to the roles.

Could you please share the list of your Realm roles:
image

JWT is JSON Web Token Keycloak will send JWT to OpenSearch, this token is used for authentication.
You can get the token by using something like (some adjustments might be required:

curl -k --insecure --noproxy '*' -d 'client_id=grafana' -d 'username=[**username**]' -d 'password=[**password**]' -d 'grant_type=password' -d 'client_secret=4zQdkx7ZSvHxpuiw4SCNTLibmGPElHhr' -d 'scope=openid' -XPOST 'https://efktest.com:8080/realms/os/protocol/openid-connect/token'

best,
mj

Hi @Mantas ,

Refer the list of realm roles.

Hi @Mantas,

We got the JWT access token by above curl command.

Refer the below access_token after decode:

{
  "exp": 1705037246,
  "iat": 1705036946,
  "jti": "dbc8ffbe-65d5-4f62-9f1b-6065fcba8249",
  "iss": "https://efktest.com/auth/realms/os",
  "aud": [
    "grafana",
    "kubernetes"
  ],
  "sub": "582aa0ee-08a6-4eea-96a5-6f024dbf4b1b",
  "typ": "Bearer",
  "azp": "grafana",
  "session_state": "fbaff75d-c1a5-484f-9980-36b7b14bb194",
  "acr": "1",
  "allowed-origins": [
    "*"
  ],
  "resource_access": {
    "grafana": {
      "roles": [
        "roles"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid email profile my-good-service",
  "sid": "fbaff75d-c1a5-484f-9980-36b7b14bb194",
  "email_verified": true,
  "roles": [
    "default-roles-nha",
    "offline_access",
    "opensearch-secure",
    "uma_authorization",
    "specialuser",
    "default-roles-nha",
    "offline_access",
    "opensearch-secure",
    "uma_authorization",
    "specialuser"
  ],
  "name": "Kannan V",
  "preferred_username": "kannan",
  "given_name": "Kannan",
  "family_name": "V",
  "email": "kannan@test.com"
}

Hi @rmssath,

it does not look like you have a realm role called roles, can you Add Role called roles and add your user ( kannan) to it and test it again, please?

You are mapping a role called roles to all_access in your config but you are not sending it in your JWT.

thanks,
Mantas