Wazuh using Keycloak as an IdP (OIDC)

164 views
Skip to first unread message

Jacob Molland

unread,
Feb 23, 2026, 11:09:38 PMFeb 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 PMFeb 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,
Feb 24, 2026, 2:55:12 AMFeb 24
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.

Bony V John

unread,
Feb 24, 2026, 11:54:03 PMFeb 24
to Wazuh | Mailing List

Hi,

If you are encountering any errors while following the official Wazuh documentation, please share a screenshot of the error. Also, share the details below so we can analyze the issue further.

Run this on the Wazuh indexer server and share the output:
cat /var/log/wazuh-indexer/wazuh-cluster.log | grep -iE "error|warn"

Also share the indexer configuration file: /etc/wazuh-indexer/opensearch-security/config.yml

Run this on the Wazuh dashboard server and share the generated file:  
journalctl -u wazuh-dashboard -n 200 --no-pager | grep -inE "error|warn" >> dashboard-log.txt

The dashboard-log.txt file will be created in the directory where you run the command. Please share that file with us. These details will help us troubleshoot the issue faster.


It looks like you are running Keycloak on port 8443. I replicated the same in my lab and used the steps below (tested on Amazon Linux—adjust as needed for your OS).  

Set environment variablesOn the server, set the following variables:
KEYCLOAK_IP="<keycloak-server-IP>" KEYCLOAK_DNS="keycloak.example.com" ADMIN_USER="admin" ADMIN_PASS="ChangeMeStrong!" KC_VERSION="26.4.7" BASE="/root/keycloak" CERTDIR="$BASE/certs"

Create the certificate directory and generate the certificates:
mkdir -p "$CERTDIR" cd "$CERTDIR" cat > san.cnf <<EOF [req] distinguished_name=req_distinguished_name x509_extensions=v3_req prompt=no [req_distinguished_name] CN=$KEYCLOAK_DNS [v3_req] keyUsage = keyEncipherment, dataEncipherment, digitalSignature extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = $KEYCLOAK_DNS IP.1 = $KEYCLOAK_IP EOF openssl req -x509 -nodes -newkey rsa:2048 \ -keyout keycloak.key -out keycloak.crt \ -days 365 -config san.cnf chmod 644 "$CERTDIR/keycloak.key" "$CERTDIR/keycloak.crt"


Make sure port 8443 is not already in use:
ss -lntp | grep ':8443' && echo "8443 already in use!" || echo "8443 free"


Pull the image and start the Keycloak container on port 8443:
docker rm -f keycloak 2>/dev/null || true docker run -d --name keycloak \ -p 8443:8443 \ -e KC_BOOTSTRAP_ADMIN_USERNAME="$ADMIN_USER" \ -e KC_BOOTSTRAP_ADMIN_PASSWORD="$ADMIN_PASS" \ -e KC_HTTPS_CERTIFICATE_FILE=/opt/keycloak/conf/tls.crt \ -e KC_HTTPS_CERTIFICATE_KEY_FILE=/opt/keycloak/conf/tls.key \ -e KC_HOSTNAME_STRICT=false \ -v "$CERTDIR/keycloak.crt":/opt/keycloak/conf/tls.crt:ro \ -v "$CERTDIR/keycloak.key":/opt/keycloak/conf/tls.key:ro \ quay.io/keycloak/keycloak:"$KC_VERSION" start-dev

This will use the admin credentials configured in the variables above.

Confirm that Keycloak is listening on port 8443:
ss -tulpen | grep 8443 docker logs -f keycloak

Open a browser and access: https://<keycloak-server-IP>:8443

Log in using:
  • Username: admin
  • Password: ChangeMeStrong!
You can also refer to the Keycloak documentation for additional configuration and production-ready deployment guidance.

image (1).png

Reply all
Reply to author
Forward
0 new messages