Wazuh using Keycloak as an IdP (OIDC)

9 views
Skip to first unread message

Jacob Molland

unread,
Feb 23, 2026, 11:09:38 PM (5 hours ago) Feb 23
to Wazuh | Mailing List
Hello all,

I have been working on configuring Wazuh to use Keycloak as an IdP via OIDC authentication. I followed this Medium article to get me where I'm at: https://medium.com/@jaishiaditya2000/integrating-wazuh-with-keycloak-for-secure-sso-authentication-0b2eebd00632

Currently, when I navigate to Wazuh's dashboard URL (https://10.95.1.24:30100), my browser (firefox-vnc) routes me to sign-in via the WazuhOpenID Keycloak realm's portal. After correctly entering the 'test' user's credentials, I am prompted to change the password. After changing the password, I believe I should be authenticated into the Wazuh Dashboard. However, I instead receive the following error:

statusCode: 401
error: "Unauthorized"
message: "Unauthorized"

Both Wazuh and Keycloak are configured with TLS, and I have created a cert (ca-bundle.pem) that is the result of cat'ing Wazuh's root-ca.pem + Keycloak's tls.pem, in that order. This secret is stored on the wazuh-dashboard pod at '/usr/share/wazuh-dashboard/certs/ca-bundle.pem', and the wazuh-indexer pod at '/usr/share/wazuh-indexer/certs/ca-bundle.pem'.

Below are some of my Wazuh files:

/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:
    server.host: 0.0.0.0
    server.port: 5601
    opensearch.hosts: https://indexer:9200
    opensearch.ssl.verificationMode: none
    opensearch.requestHeadersWhitelist: [ authorization,securitytenant ]
    opensearch_security.multitenancy.enabled: false
    opensearch_security.readonly_mode.roles: ["kibana_read_only"]
    server.ssl.enabled: true
    server.ssl.key: "/usr/share/wazuh-dashboard/certs/key.pem"
    server.ssl.certificate: "/usr/share/wazuh-dashboard/certs/cert.pem"
    opensearch.ssl.certificateAuthorities: ["/usr/share/wazuh-dashboard/certs/ca-bundle.pem"]
    uiSettings.overrides.defaultRoute: /app/wz-home
    # Session expiration settings
    opensearch_security.cookie.ttl: 900000
    opensearch_security.session.ttl: 900000
    opensearch_security.session.keepalive: true
    # OIDC stuff
    opensearch_security.auth.multiple_auth_enabled: true
    opensearch_security.auth.type: ["basicauth","openid"]
    opensearch_security.openid.connect_url: https://keycloak.keycloak.svc.cluster.local:8443/realms/WazuhOpenID/.well-known/openid-configuration
    opensearch_security.openid.base_redirect_url: https://10.95.1.24:30100
    opensearch_security.openid.client_id: wazuh-OpenID

'authc' block inside /usr/share/wazuh-indexer/config/opensearch-security/config.yml:
        authc:
          openid_auth_domain:
            http_enabled: true
            transport_enabled: true
            order: 0
            http_authenticator:
              type: openid
              challenge: false
              config:
                openid_connect_idp:
                  enable_ssl: true
                  verify_hostnames: true
                subject_key: preferred_username
                roles_key: roles
                openid_connect_url: https://keycloak.keycloak.svc.cluster.local:8443/realms/WazuhOpenID/.well-known/openid-configuration
            authentication_backend:
              type: noop
          basic_internal_auth_domain:
            description: "Authenticate via HTTP Basic against internal users database"
            http_enabled: true
            transport_enabled: true
            order: 1
            http_authenticator:
              type: basic
              challenge: true
            authentication_backend:
              type: intern
          kerberos_auth_domain:
            http_enabled: false
            transport_enabled: false
            order: 6
            http_authenticator:
              type: kerberos
              challenge: true
              config:
                # If true a lot of kerberos/security related debugging output will be logged to standard out
                krb_debug: false
                # If true then the realm will be stripped from the user name
                strip_realm_from_principal: true
            authentication_backend:
              type: noop
          proxy_auth_domain:
            description: "Authenticate via proxy"
            http_enabled: false
            transport_enabled: false
            order: 3
            http_authenticator:
              type: proxy
              challenge: false
              config:
                user_header: "x-proxy-user"
                roles_header: "x-proxy-roles"
            authentication_backend:
              type: noop
          jwt_auth_domain:
            description: "Authenticate via Json Web Token"
            http_enabled: false
            transport_enabled: false
            order: 1
            http_authenticator:
              type: jwt
              challenge: false
              config:
                signing_key: "base64 encoded HMAC key or public RSA/ECDSA pem key"
                jwt_header: "Authorization"
                jwt_url_parameter: null
                jwt_clock_skew_tolerance_seconds: 30
                roles_key: null
                subject_key: null
            authentication_backend:
              type: noop
          clientcert_auth_domain:
            description: "Authenticate via SSL client certificates"
            http_enabled: false
            transport_enabled: false
            order: 2
            http_authenticator:
              type: clientcert
              config:
                username_attribute: cn #optional, if omitted DN becomes username
              challenge: false
            authentication_backend:
              type: noop
          ldap:
            description: "Authenticate via LDAP or Active Directory"
            http_enabled: false
            transport_enabled: false
            order: 5
            http_authenticator:
              type: basic
              challenge: false
            authentication_backend:
              # LDAP authentication backend (authenticate users against a LDAP or Active Directory)
              type: ldap
              config:
                # enable ldaps
                enable_ssl: false
                # enable start tls, enable_ssl should be false
                enable_start_tls: false
                # send client certificate
                enable_ssl_client_auth: false
                # verify ldap hostname
                verify_hostnames: true
                hosts:
                - localhost:8389
                bind_dn: null
                password: null
                userbase: 'ou=people,dc=example,dc=com'
                # Filter to search for users (currently in the whole subtree beneath userbase)
                # {0} is substituted with the username
                usersearch: '(sAMAccountName={0})'
                # Use this attribute from the user as username (if not set then DN is used)
                username_attribute: null


I also set the 'NODE_EXTRA_CA_CERTS' environment variable inside of the wazuh-dashboard container to equal '/usr/share/wazuh-dashboard/certs/ca-bundle.pem'.


wazuh-dashboard pod logs after I attempt to sign-in, resulting in that 401 'Unauthorized' error:

{"type":"log","@timestamp":"2026-02-24T00:10:01Z","tags":["error","plugins","securityDashboards"],"pid":54,"message":"OpenId authentication failed: Error: Authentication Exception"}

{"type":"response","@timestamp":"2026-02-24T00:10:01Z","tags":[],"pid":54,"method":"get","statusCode":401,"req":{"url":"/auth/openid/login?state=_Qppr3iTK6vuohYKl6L9KO&session_state=050c68e6-600a-3a67-d56a-e97262622907&iss=https%3A%2F%2Fkeycloak.keycloak.svc.cluster.local%3A8443%2Frealms%2FWazuhOpenID&code=61d1bc0d-8d37-2d28-43de-1e4efc72c6b3.050c68e6-600a-3a67-d56a-e97262622907.8ffe91e2-c2a9-425a-9c82-9e0be8f0d0f6","method":"get","headers":{"host":"10.95.1.24:30100","user-agent":"Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","accept-language":"en-US,en;q=0.5","accept-encoding":"gzip, deflate, br, zstd","connection":"keep-alive","upgrade-insecure-requests":"1","sec-fetch-dest":"document","sec-fetch-mode":"navigate","sec-fetch-site":"none","sec-fetch-user":"?1","priority":"u=0, i"},"remoteAddress":"10.150.1.0","userAgent":"Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"},"res":{"statusCode":401,"responseTime":92,"contentLength":9},"message":"GET /auth/openid/login?state=_Qppr3iTK6vuohYKl6L9KO&session_state=050c68e6-600a-3a67-d56a-e97262622907&iss=https%3A%2F%2Fkeycloak.keycloak.svc.cluster.local%3A8443%2Frealms%2FWazuhOpenID&code=61d1bc0d-8d37-2d28-43de-1e4efc72c6b3.050c68e6-600a-3a67-d56a-e97262622907.8ffe91e2-c2a9-425a-9c82-9e0be8f0d0f6 401 92ms - 9.0B"}

{"type":"response","@timestamp":"2026-02-24T00:10:01Z","tags":[],"pid":54,"method":"get","statusCode":401,"req":{"url":"/favicon.ico","method":"get","headers":{"host":"10.95.1.24:30100","user-agent":"Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0","accept":"image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5","accept-language":"en-US,en;q=0.5","accept-encoding":"gzip, deflate, br, zstd","connection":"keep-alive","referer":"https://10.95.1.24:30100/auth/openid/login?state=_Qppr3iTK6vuohYKl6L9KO&session_state=050c68e6-600a-3a67-d56a-e97262622907&iss=https%3A%2F%2Fkeycloak.keycloak.svc.cluster.local%3A8443%2Frealms%2FWazuhOpenID&code=61d1bc0d-8d37-2d28-43de-1e4efc72c6b3.050c68e6-600a-3a67-d56a-e97262622907.8ffe91e2-c2a9-425a-9c82-9e0be8f0d0f6","sec-fetch-dest":"image","sec-fetch-mode":"no-cors","sec-fetch-site":"cross-site","priority":"u=6"},"remoteAddress":"10.150.1.0","userAgent":"Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0","referer":"https://10.95.1.24:30100/auth/openid/login?state=_Qppr3iTK6vuohYKl6L9KO&session_state=050c68e6-600a-3a67-d56a-e97262622907&iss=https%3A%2F%2Fkeycloak.keycloak.svc.cluster.local%3A8443%2Frealms%2FWazuhOpenID&code=61d1bc0d-8d37-2d28-43de-1e4efc72c6b3.050c68e6-600a-3a67-d56a-e97262622907.8ffe91e2-c2a9-425a-9c82-9e0be8f0d0f6"},"res":{"statusCode":401,"responseTime":2,"contentLength":9},"message":"GET /favicon.ico 401 2ms - 9.0B"}


I have been troubleshooting this for a painfully long time. If anybody can offer me some kind of advice here, it would be appreciated beyond words.

Please feel free to ask me for any additional information and I will be more than happy to provide it.

Thank you,
Jacob Molland

Bony V John

unread,
Feb 23, 2026, 11:23:35 PM (5 hours ago) Feb 23
to Wazuh | Mailing List
Hi,

Please allow me some time, I'm working on this and will get back to you with an update as soon as possible.

Bony V John

unread,
2:55 AM (1 hour ago) 2:55 AM
to Wazuh | Mailing List

Hi,

I tried to replicate this on my end, and it looks like the issue is related to communication between the Wazuh indexer and Keycloak when using the OIDC method. However, when I tested the SAML authentication method, it worked fine.

I reviewed the documentation you shared and also checked a few non-official guides for configuring Keycloak with OIDC, but I was not able to get it working. At the moment, there is no official Wazuh documentation for integrating Keycloak using the OIDC method.

As a workaround, you can try the SAML integration method. Please refer to the Wazuh documentation for step-by-step guidance.

In parallel, I will check with my internal team to confirm whether there is a supported or recommended way to achieve this integration using OIDC.

Reply all
Reply to author
Forward
0 new messages