Securing Wazuh- A Guide to Keycloak OAuth2 & MFA Integration

5 minute read

If you’re running Wazuh in an environment with compliance requirements like PCI DSS, SOC 2, or HIPAA, you know that multi-factor authentication (MFA) and strict access control are non-negotiable. The need to prove who has access to sensitive security logs—and who can modify configurations—means that shared accounts and simple passwords are no longer enough.

It’s time to connect Wazuh to a modern authentication provider. This guide will walk you through integrating Wazuh with Keycloak, a powerful open-source Identity and Access Management (IAM) solution.

Connecting Keycloak to Wazuh accomplishes two key goals:

  • Elevates Security & Compliance: It immediately brings Wazuh’s authentication up to modern standards with support for SSO, MFA, and centralized user management.
  • Improves Usability: It allows you to grant role-based access to more people on your team, like developers and analysts, without managing separate credentials for each tool. By centralizing identity, everyone can log in and view the alerts relevant to them securely.

Github guide with code

https://github.com/samma-io/wazuh-help/blob/main/1-before-prod/setupUsers.md


The End Result: A Seamless Login Flow

Once this integration is complete, the default Wazuh login screen is completely bypassed. When a user navigates to the Wazuh dashboard, they are automatically redirected to Keycloak to authenticate, providing a single, secure entry point for your entire team.

Login Flow: User navigates to Wazuh Dashboard → Automatic redirect to Keycloak login page → User authenticates (with MFA) → Redirect back to Wazuh Dashboard with a valid session.


Step 1: Deploying Keycloak with Docker Compose

There are many ways to set up Keycloak, including Helm charts for Kubernetes. For this guide, we’ll use a simple docker-compose file to get a server running quickly. This setup includes a PostgreSQL database for persistence.

Create a file named docker-compose.yml:

version: '3.8'

services:
  postgres:
    image: postgres:16.2
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    networks:
      - keycloak_network

  keycloak:
    image: quay.io/keycloak/keycloak:23.0.6
    command: start
    environment:
      KC_HOSTNAME: localhost
      KC_HOSTNAME_PORT: 8080
      KC_HOSTNAME_STRICT_BACKCHANNEL: 'false'
      KC_HTTP_ENABLED: 'true'
      KC_HOSTNAME_STRICT_HTTPS: 'false'
      KC_HEALTH_ENABLED: 'true'
      KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}
      KC_DB_USERNAME: ${POSTGRES_USER}
      KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - "8080:8080"
    restart: always
    depends_on:
      - postgres
    networks:
      - keycloak_network

volumes:
  postgres_data:
    driver: local

networks:
  keycloak_network:
    driver: bridge

This docker-compose.yml file references environment variables for your secrets. Create a .env file in the same directory to store them:

# .env file
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=your_strong_postgres_password

KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=your_strong_admin_password

Now, launch Keycloak by running:

docker-compose up -d

You should be able to access the Keycloak admin console at http://localhost:8080.


Step 2: Configuring the Keycloak Realm for Wazuh

Setting up Keycloak is the easy part. Now we need to configure it to act as an authentication provider for Wazuh. This involves creating a client and mapping users to the correct groups.

  • Create a Client: In the Keycloak admin console, create a new client for Wazuh. Let’s call the Client ID wazuh. Ensure the client has Standard flow enabled and add your Wazuh dashboard URL to the Valid Redirect URIs (e.g., https://your-wazuh-host.com/*).
  • Configure Groups: Using groups is the best way to manage user permissions. Keycloak can pass group membership to Wazuh, which then maps them to roles. Ensure your users are members of the following groups:
    • security_analytics_full_access
    • security_analytics_read_access
    • admin: Add your administrative users to this group in Keycloak. When they log in, they will be granted admin privileges in Wazuh.

Shortcut: To make this easier, I have exported my Keycloak realm setup. You can download the wazuh-keycloak.json file and import it into your Keycloak instance.


Step 3: Configuring Wazuh to Use Keycloak

To make Wazuh work with Keycloak, we need to perform three key steps:

  1. Configure the Wazuh Dashboard to use OpenID and redirect users to Keycloak.
  2. Configure the Wazuh Indexer to validate tokens from Keycloak and map user groups to roles.
  3. Run the securityadmin tool to apply the new security configuration to the indexer cluster.

1. Configure the Dashboard

SSH into your Wazuh server and edit /etc/wazuh-dashboard/opensearch_dashboards.yml.

Important: Before editing, make a backup of this file!
cp /etc/wazuh-dashboard/opensearch_dashboards.yml /etc/wazuh-dashboard/opensearch_dashboards.yml.bak

# /etc/wazuh-dashboard/opensearch_dashboards.yml
opensearch_security.auth.type: "openid"
opensearch_security.openid:
  connect_url: "http://YOUR_KEYCLOAK_IP:8080/realms/master/.well-known/openid-configuration"
  client_id: "wazuh"
  client_secret: "YOUR_CLIENT_SECRET_FROM_KEYCLOAK" # Find this in your 'wazuh' client credentials tab
  scope: "openid profile email"
  • Update the connect_url to point to your Keycloak server’s IP or hostname.
  • Retrieve your client_secret from the Credentials tab of your wazuh client in Keycloak.

After saving the file, restart the Wazuh dashboard to apply the changes:

systemctl restart wazuh-dashboard

2. Configure the Indexer

Open the file config.yml within the OpenSearch security plugin directory (e.g., /usr/share/wazuh-indexer/plugins/opensearch-security/securityconfig/config.yml) and add the settings for the OpenID authentication domain.

config:
  dynamic:
    http:
      anonymous_auth_enabled: false
      xff:
        enabled: false
        internalProxies: '192\.168\.0\.10|192\.168\.0\.11' # regex pattern
    authc:
      basic_internal_auth_domain:
        description: "Authenticate via HTTP Basic against internal users database"
        http_enabled: true
        transport_enabled: true
        order: 0
        http_authenticator:
          type: basic
          challenge: false
        authentication_backend:
          type: intern
      openid_auth_domain:
        http_enabled: true
        transport_enabled: true
        order: 1
        http_authenticator:
          type: openid
          challenge: true
          config:
            subject_key: preferred_username
            roles_key: groups
            openid_connect_url: http://YOUR_KEYCLOAK_IP:8080/realms/master/.well-known/openid-configuration
            required_audience: wazuh
        authentication_backend:
          type: noop

3. Apply the Security Configuration

Run the securityadmin.sh script on the indexer node to load the new configuration into the cluster. Note that this tool may be deprecated in future versions.

(I’m running this in Docker, so please modify the paths to your certificates and config files to match your environment).

export JAVA_HOME=/usr/share/wazuh-indexer/jdk/ && \
bash /usr/share/wazuh-indexer/plugins/opensearch-security/tools/securityadmin.sh \
-f /path/to/your/config.yml \
-icl \
-key /usr/share/wazuh-indexer/certs/admin-key.pem \
-cert /usr/share/wazuh-indexer/certs/admin.pem \
-cacert /usr/share/wazuh-indexer/certs/root-ca.pem \
-h 127.0.0.1 \
-nhnv

Troubleshooting Tips

The most common issue is a misconfiguration in the JWT token. If the user’s groups aren’t being passed correctly from Keycloak, the role mapping in Wazuh will fail.

You can verify the token directly in Keycloak:

  • Go to Clients > wazuh > Client Scopes.
  • Click on the Evaluate tab.
  • Select a user who is a member of your Wazuh groups.
  • Click Evaluate and inspect the Generated Access Token. In the decoded token, you should see a groups array containing the expected group names. If it’s not there, check your Client Scope and Mapper configurations in Keycloak.

Here is an example of a valid user token:

{
  "sub": "0a11ca94-142f-4bb5-b056-8b0e65696126",
  "email_verified": true,
  "groups": [
    "wazuh_admin",
    "default-roles-master",
    "offline_access",
    "uma_authorization",
    "security_analytics_full_access",
    "admin"
  ],
  "preferred_username": "matte45",
  "email": "matte45@gmail.com"
}

Kubernetes Deployment

I run my Wazuh instance in Kubernetes. In my public repository, you will find the ConfigMaps I’m using to manage these settings declaratively, as well as a Helm deployment for Wazuh.

A Quick Note on Production Security

The configurations in this guide use HTTP for simplicity. In a production environment, you must configure proper TLS/HTTPS for both Keycloak and Wazuh. Exposing your authentication system over an unencrypted channel is a major security risk.

References