Wazuh to OpenSearch Configuration Migration: Complete Guide
This comprehensive guide walks through the process of migrating Wazuh from Elasticsearch to OpenSearch, covering configuration changes, data migration, dashboard setup, and performance optimization. With the shift towards OpenSearch as the preferred indexer for Wazuh, this migration ensures continued support and enhanced features.
Overview
Wazuh 4.3+ officially supports OpenSearch as its primary indexer, moving away from the Open Distro for Elasticsearch. This migration involves:
- Updating Wazuh manager configuration
- Migrating existing indices and data
- Reconfiguring Wazuh dashboard
- Updating API and integration settings
- Performance tuning for OpenSearch
Prerequisites
System Requirements
-
Wazuh Components:
- Wazuh Manager 4.3+
- Wazuh Dashboard 4.3+
- Wazuh Indexer (OpenSearch) 2.x
-
Current Environment:
- Running Elasticsearch cluster (6.x, 7.x, or Open Distro)
- Sufficient storage for data migration
- Backup of all configurations
-
Target Environment:
- OpenSearch 2.x cluster
- Compatible hardware specifications
- Network connectivity between components
Migration Planning
Pre-Migration Checklist
#!/bin/bash# Pre-migration verification script
echo "=== Wazuh Migration Pre-Check ==="
# Check current versionsecho "Current Wazuh version:"/var/ossec/bin/wazuh-control info | grep "Version"
# Check Elasticsearch versionecho "Current Elasticsearch version:"curl -s localhost:9200 | jq '.version.number'
# Check index sizesecho "Current index sizes:"curl -s localhost:9200/_cat/indices/wazuh-* | awk '{print $3, $9}'
# Check disk spaceecho "Available disk space:"df -h /var/lib/elasticsearch
# Backup current configurationecho "Creating configuration backup..."mkdir -p /backup/wazuh-migration/$(date +%Y%m%d)cp -r /etc/elasticsearch /backup/wazuh-migration/$(date +%Y%m%d)/cp -r /etc/wazuh-indexer /backup/wazuh-migration/$(date +%Y%m%d)/ 2>/dev/null || truecp -r /usr/share/wazuh-dashboard/data/wazuh/config /backup/wazuh-migration/$(date +%Y%m%d)/
Step 1: Install OpenSearch
OpenSearch Installation
# Add OpenSearch repositorycurl -o- https://artifacts.opensearch.org/publickeys/opensearch.pgp | sudo gpg --dearmor --batch --yes -o /usr/share/keyrings/opensearch-keyringecho "deb [signed-by=/usr/share/keyrings/opensearch-keyring] https://artifacts.opensearch.org/releases/bundle/opensearch/2.x/apt stable main" | sudo tee /etc/apt/sources.list.d/opensearch-2.x.list
# Install OpenSearchsudo apt-get updatesudo apt-get install opensearch=2.11.1
# Initial configurationsudo nano /etc/opensearch/opensearch.yml
OpenSearch Configuration
Create /etc/opensearch/opensearch.yml
:
# OpenSearch Configuration for Wazuhcluster.name: wazuh-clusternode.name: opensearch-node1network.host: 0.0.0.0http.port: 9200discovery.seed_hosts: ["127.0.0.1"]cluster.initial_master_nodes: ["opensearch-node1"]
# Security settingsplugins.security.disabled: falseplugins.security.ssl.transport.pemcert_filepath: /etc/opensearch/certs/node.pemplugins.security.ssl.transport.pemkey_filepath: /etc/opensearch/certs/node-key.pemplugins.security.ssl.transport.pemtrustedcas_filepath: /etc/opensearch/certs/ca.pemplugins.security.ssl.transport.enforce_hostname_verification: falseplugins.security.ssl.http.enabled: trueplugins.security.ssl.http.pemcert_filepath: /etc/opensearch/certs/node.pemplugins.security.ssl.http.pemkey_filepath: /etc/opensearch/certs/node-key.pemplugins.security.ssl.http.pemtrustedcas_filepath: /etc/opensearch/certs/ca.pem
# Wazuh-specific settingscompatibility.override_main_response_version: truepath.data: /var/lib/opensearchpath.logs: /var/log/opensearch
# Performance settingsbootstrap.memory_lock: trueindices.query.bool.max_clause_count: 2000
# Cluster settingscluster.routing.allocation.disk.threshold_enabled: truecluster.routing.allocation.disk.watermark.low: 85%cluster.routing.allocation.disk.watermark.high: 90%cluster.routing.allocation.disk.watermark.flood_stage: 95%
JVM Configuration
Update /etc/opensearch/jvm.options
:
# JVM heap size (50% of available RAM, max 32GB)-Xms16g-Xmx16g
# GC configuration-XX:+UseG1GC-XX:MaxGCPauseMillis=200-XX:InitiatingHeapOccupancyPercent=70
# GC logging-XX:+PrintGCDetails-XX:+PrintGCDateStamps-XX:+PrintTenuringDistribution-XX:+PrintGCApplicationStoppedTime-Xloggc:/var/log/opensearch/gc.log-XX:+UseGCLogFileRotation-XX:NumberOfGCLogFiles=32-XX:GCLogFileSize=64m
Step 2: Data Migration
Export Data from Elasticsearch
#!/bin/bash# Export Wazuh indices from Elasticsearch
# Install elasticsearch-dump if not presentnpm install -g elasticsearch-dump
# Create export directorymkdir -p /backup/wazuh-indicescd /backup/wazuh-indices
# Export all Wazuh indicesfor index in $(curl -s localhost:9200/_cat/indices | grep wazuh | awk '{print $3}'); do echo "Exporting $index..."
# Export mappings elasticdump \ --input=http://localhost:9200/$index \ --output=$index-mapping.json \ --type=mapping
# Export data elasticdump \ --input=http://localhost:9200/$index \ --output=$index-data.json \ --type=data \ --limit=1000done
# Export templateselasticdump \ --input=http://localhost:9200 \ --output=templates.json \ --type=template
# Export aliaseselasticdump \ --input=http://localhost:9200 \ --output=aliases.json \ --type=alias
Import Data to OpenSearch
#!/bin/bash# Import Wazuh indices to OpenSearch
cd /backup/wazuh-indices
# Update templates for OpenSearch compatibilityecho "Updating templates for OpenSearch..."sed -i 's/"type": "keyword"/"type": "keyword"/g' templates.jsonsed -i 's/"type": "text"/"type": "text"/g' templates.json
# Import templates firstelasticdump \ --input=templates.json \ --output=https://admin:admin@localhost:9200 \ --type=template \ --headers='Content-Type: application/json'
# Import indicesfor mapping_file in *-mapping.json; do index_name=${mapping_file%-mapping.json} echo "Importing $index_name..."
# Import mapping elasticdump \ --input=$mapping_file \ --output=https://admin:admin@localhost:9200/$index_name \ --type=mapping \ --headers='Content-Type: application/json'
# Import data elasticdump \ --input=${index_name}-data.json \ --output=https://admin:admin@localhost:9200/$index_name \ --type=data \ --limit=1000 \ --headers='Content-Type: application/json'done
# Import aliaseselasticdump \ --input=aliases.json \ --output=https://admin:admin@localhost:9200 \ --type=alias \ --headers='Content-Type: application/json'
Step 3: Update Wazuh Manager Configuration
Filebeat Configuration
Update /etc/filebeat/filebeat.yml
:
# Wazuh - Filebeat configuration for OpenSearchfilebeat.inputs: - type: log paths: - "/var/ossec/logs/alerts/alerts.json" json.keys_under_root: true json.add_error_key: true json.message_key: "message"
processors: - decode_json_fields: fields: ["data"] target: "" overwrite_keys: true - drop_fields: fields: ["beat", "input_type", "offset", "log", "host.name"] ignore_missing: true
output.elasticsearch: hosts: ["localhost:9200"] protocol: "https" username: "admin" password: "admin" ssl.verification_mode: "none" index: "wazuh-alerts-4.x-%{+yyyy.MM.dd}" template.name: "wazuh" template.pattern: "wazuh-*" template.settings: index.number_of_shards: 1 index.number_of_replicas: 0 index.refresh_interval: "5s" index.query.default_field: "*"
logging.level: infologging.to_files: truelogging.files: path: /var/log/filebeat name: filebeat keepfiles: 7 permissions: 0640
Wazuh API Configuration
Update /var/ossec/api/configuration/api.yaml
:
# Wazuh API configuration for OpenSearchindexer: host: "https://localhost:9200" username: "wazuh_api" password: "${WAZUH_API_PASSWORD}" verify_ssl: false
logging: level: "info" path: "/var/ossec/logs/api.log"
auth: max_login_attempts: 5 block_time: 300 max_request_per_minute: 300
cache: enabled: true time: 0.75
upload: limits: eps: allow: true size: 1048576
Step 4: Wazuh Dashboard Migration
Install Wazuh Dashboard for OpenSearch
# Remove old Kibana-based dashboardsudo systemctl stop wazuh-dashboardsudo apt-get remove wazuh-dashboard
# Install new OpenSearch Dashboards-based Wazuh Dashboardsudo apt-get install wazuh-dashboard
# Configure dashboardsudo nano /etc/wazuh-dashboard/opensearch_dashboards.yml
Dashboard Configuration
server.port: 5601server.host: "0.0.0.0"server.name: "wazuh-dashboard"
opensearch.hosts: ["https://localhost:9200"]opensearch.username: "kibanaserver"opensearch.password: "${DASHBOARD_PASSWORD}"opensearch.ssl.verificationMode: "none"
# Wazuh plugin configurationwazuh.api.url: "https://localhost:55000"wazuh.api.port: 55000wazuh.api.username: "wazuh"wazuh.api.password: "${WAZUH_API_PASSWORD}"
# Security settingsopensearch_security.multitenancy.enabled: falseopensearch_security.readonly_mode.roles: ["kibana_read_only"]server.ssl.enabled: trueserver.ssl.certificate: /etc/wazuh-dashboard/certs/dashboard.crtserver.ssl.key: /etc/wazuh-dashboard/certs/dashboard.key
# Logginglogging.dest: /var/log/wazuh-dashboard/dashboard.loglogging.silent: falselogging.quiet: falselogging.verbose: false
Import Saved Objects
#!/bin/bash# Import Wazuh dashboards and visualizations
# Export from old Kibana (if needed)curl -X POST "localhost:5601/api/saved_objects/_export" \ -H "Content-Type: application/json" \ -H "kbn-xsrf: true" \ -d '{ "type": ["dashboard", "visualization", "search", "index-pattern"], "includeReferencesDeep": true }' > wazuh-saved-objects.ndjson
# Import to new dashboardcurl -X POST "localhost:5601/api/saved_objects/_import" \ -H "Content-Type: multipart/form-data" \ -H "kbn-xsrf: true" \ -F file=@wazuh-saved-objects.ndjson
Step 5: Index Management
Index Lifecycle Management
Create /etc/opensearch/index-lifecycle-policy.json
:
{ "policy": { "description": "Wazuh index lifecycle policy", "default_state": "hot", "states": [ { "name": "hot", "actions": [ { "rollover": { "min_size": "50gb", "min_index_age": "1d" } } ], "transitions": [ { "state_name": "warm", "conditions": { "min_index_age": "7d" } } ] }, { "name": "warm", "actions": [ { "replica_count": { "number_of_replicas": 0 } }, { "shrink": { "number_of_shards": 1 } }, { "force_merge": { "max_num_segments": 1 } } ], "transitions": [ { "state_name": "cold", "conditions": { "min_index_age": "30d" } } ] }, { "name": "cold", "actions": [ { "read_only": {} }, { "allocation": { "include": { "node_type": "cold" } } } ], "transitions": [ { "state_name": "delete", "conditions": { "min_index_age": "90d" } } ] }, { "name": "delete", "actions": [ { "delete": {} } ] } ] }}
Apply the policy:
# Create ISM policycurl -X PUT "https://localhost:9200/_plugins/_ism/policies/wazuh-policy" \ -H "Content-Type: application/json" \ -u admin:admin \ -d @/etc/opensearch/index-lifecycle-policy.json
# Apply policy to indicescurl -X PUT "https://localhost:9200/wazuh-alerts-*/_settings" \ -H "Content-Type: application/json" \ -u admin:admin \ -d '{ "index": { "plugins.index_state_management.policy_id": "wazuh-policy" } }'
Index Templates
Create /etc/opensearch/wazuh-template.json
:
{ "index_patterns": ["wazuh-alerts-4.x-*", "wazuh-archives-4.x-*"], "priority": 1, "template": { "settings": { "index": { "number_of_shards": "1", "number_of_replicas": "0", "refresh_interval": "5s", "query.default_field": [ "rule.description", "agent.name", "agent.ip", "data.aws.source_ip_address", "data.url" ], "plugins.index_state_management.policy_id": "wazuh-policy", "plugins.index_state_management.rollover_alias": "wazuh-alerts" } }, "mappings": { "dynamic": true, "dynamic_templates": [ { "string_as_keyword": { "match_mapping_type": "string", "mapping": { "type": "keyword", "ignore_above": 1024 } } } ], "properties": { "@timestamp": { "type": "date" }, "agent": { "properties": { "id": { "type": "keyword" }, "name": { "type": "keyword" }, "ip": { "type": "ip" } } }, "rule": { "properties": { "level": { "type": "integer" }, "id": { "type": "keyword" }, "description": { "type": "text" } } } } } }}
Step 6: Security Configuration
Configure OpenSearch Security
#!/bin/bash# Security configuration script
# Generate certificatescd /etc/opensearch./tools/generate-certificates.sh
# Configure internal userscat > /etc/opensearch/internal_users.yml << EOF_meta: type: "internalusers" config_version: 2
admin: hash: "$2y$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" reserved: true backend_roles: - "admin" description: "Admin user"
kibanaserver: hash: "$2y$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" reserved: true description: "OpenSearch Dashboards user"
wazuh_api: hash: "$2y$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" reserved: false backend_roles: - "wazuh_api" description: "Wazuh API user"EOF
# Configure rolescat > /etc/opensearch/roles.yml << EOF_meta: type: "roles" config_version: 2
wazuh_api: cluster_permissions: - "cluster:monitor/main" - "indices:admin/template/get" - "indices:admin/template/put" - "cluster:admin/ingest/pipeline/get" - "cluster:admin/ingest/pipeline/put" index_permissions: - index_patterns: - "wazuh-*" allowed_actions: - "crud" - "create_index" - "manage"EOF
# Configure role mappingscat > /etc/opensearch/roles_mapping.yml << EOF_meta: type: "rolesmapping" config_version: 2
all_access: reserved: false backend_roles: - "admin" description: "Maps admin to all_access"
wazuh_api: reserved: false users: - "wazuh_api" description: "Maps wazuh_api user to wazuh_api role"EOF
# Apply security configurationcd /usr/share/opensearch/plugins/opensearch-security/tools./securityadmin.sh -cd /etc/opensearch -icl -nhnv \ -cacert /etc/opensearch/certs/ca.pem \ -cert /etc/opensearch/certs/admin.pem \ -key /etc/opensearch/certs/admin-key.pem
Step 7: Performance Optimization
OpenSearch Performance Tuning
# System settings for OpenSearchcat > /etc/sysctl.d/opensearch.conf << EOF# Virtual memoryvm.max_map_count=262144vm.swappiness=1
# Networknet.core.somaxconn=65535net.ipv4.tcp_max_syn_backlog=65535
# File descriptorsfs.file-max=131072EOF
# Apply settingssysctl -p /etc/sysctl.d/opensearch.conf
# Update limitscat > /etc/security/limits.d/opensearch.conf << EOFopensearch soft nofile 65535opensearch hard nofile 65535opensearch soft memlock unlimitedopensearch hard memlock unlimitedEOF
Query Optimization
// Optimize Wazuh queries{ "persistent": { "search.max_buckets": 100000, "indices.query.bool.max_clause_count": 2048, "cluster.routing.allocation.total_shards_per_node": 1000 }, "transient": { "indices.recovery.max_bytes_per_sec": "100mb", "cluster.routing.allocation.node_concurrent_recoveries": 2, "indices.memory.index_buffer_size": "20%" }}
Step 8: Monitoring and Validation
Health Check Script
#!/bin/bashecho "=== Post-Migration Health Check ==="
# Check OpenSearch cluster healthecho "OpenSearch Cluster Health:"curl -s -u admin:admin https://localhost:9200/_cluster/health?pretty
# Check Wazuh indicesecho -e "\nWazuh Indices:"curl -s -u admin:admin https://localhost:9200/_cat/indices/wazuh-*?v
# Check Filebeat statusecho -e "\nFilebeat Status:"systemctl status filebeat --no-pager | grep Active
# Check Wazuh API connectivityecho -e "\nWazuh API Status:"curl -s -u wazuh:wazuh https://localhost:55000/security/user/authenticate?raw=true
# Check Dashboard statusecho -e "\nWazuh Dashboard Status:"systemctl status wazuh-dashboard --no-pager | grep Active
# Verify data ingestionecho -e "\nRecent Alerts Count:"curl -s -u admin:admin -X POST https://localhost:9200/wazuh-alerts-*/_count \ -H "Content-Type: application/json" \ -d '{ "query": { "range": { "@timestamp": { "gte": "now-1h" } } } }' | jq '.count'
Monitoring Dashboard
Create a monitoring dashboard in OpenSearch Dashboards:
{ "version": "2.11.0", "objects": [ { "id": "wazuh-migration-dashboard", "type": "dashboard", "attributes": { "title": "Wazuh Migration Monitoring", "panels": [ { "gridData": { "x": 0, "y": 0, "w": 24, "h": 15 }, "type": "visualization", "id": "cluster-health-gauge" }, { "gridData": { "x": 24, "y": 0, "w": 24, "h": 15 }, "type": "visualization", "id": "index-size-timeline" }, { "gridData": { "x": 0, "y": 15, "w": 48, "h": 20 }, "type": "visualization", "id": "alerts-per-hour" } ] } } ]}
Troubleshooting
Common Issues and Solutions
1. Authentication Failures
# Reset admin password/usr/share/opensearch/plugins/opensearch-security/tools/hash.sh -p newpassword
# Update internal usersvim /etc/opensearch/internal_users.yml# Replace admin hash with new one
# Apply changes./securityadmin.sh -cd /etc/opensearch -icl -nhnv \ -cacert /etc/opensearch/certs/ca.pem \ -cert /etc/opensearch/certs/admin.pem \ -key /etc/opensearch/certs/admin-key.pem
2. Index Compatibility Issues
# Fix mapping conflictscurl -X PUT "https://localhost:9200/wazuh-alerts-*/_mapping" \ -u admin:admin \ -H "Content-Type: application/json" \ -d '{ "properties": { "agent.id": { "type": "keyword" } } }'
# Reindex if neededcurl -X POST "https://localhost:9200/_reindex" \ -u admin:admin \ -H "Content-Type: application/json" \ -d '{ "source": {"index": "wazuh-alerts-old"}, "dest": {"index": "wazuh-alerts-new"} }'
3. Performance Issues
# Check slow queriescurl -X GET "https://localhost:9200/wazuh-*/_search" \ -u admin:admin \ -H "Content-Type: application/json" \ -d '{ "profile": true, "query": { "match_all": {} } }'
# Optimize indicescurl -X POST "https://localhost:9200/wazuh-*/_forcemerge?max_num_segments=1" \ -u admin:admin
Rollback Plan
Emergency Rollback Procedure
#!/bin/bashecho "Starting rollback to Elasticsearch..."
# Stop OpenSearch servicessystemctl stop opensearchsystemctl stop wazuh-dashboardsystemctl stop filebeat
# Restore Elasticsearch configurationcp -r /backup/wazuh-migration/$(date +%Y%m%d)/elasticsearch/* /etc/elasticsearch/
# Start Elasticsearchsystemctl start elasticsearch
# Restore Filebeat configurationcp /backup/wazuh-migration/$(date +%Y%m%d)/filebeat.yml /etc/filebeat/
# Update output to Elasticsearchsed -i 's/output.elasticsearch/output.elasticsearch/g' /etc/filebeat/filebeat.ymlsed -i 's/https:\/\/localhost:9200/http:\/\/localhost:9200/g' /etc/filebeat/filebeat.yml
# Start servicessystemctl start filebeatsystemctl start kibana
echo "Rollback completed. Verify services are running correctly."
Best Practices
1. Phased Migration
- Migrate in stages: Test → Development → Production
- Validate each component before proceeding
- Maintain parallel environments during transition
2. Data Retention
- Keep Elasticsearch backup for at least 30 days
- Document all configuration changes
- Test restore procedures regularly
3. Security Hardening
# Disable unnecessary featurescurl -X PUT "https://localhost:9200/_cluster/settings" \ -u admin:admin \ -H "Content-Type: application/json" \ -d '{ "persistent": { "plugins.security.audit.type": "internal_opensearch", "plugins.security.audit.config.disabled_rest_categories": ["AUTHENTICATED"], "plugins.security.audit.config.disabled_transport_categories": ["AUTHENTICATED"] } }'
4. Monitoring Setup
# Metricbeat configuration for monitoringmetricbeat.modules: - module: elasticsearch metricsets: - node - node_stats - cluster_stats - index period: 10s hosts: ["https://localhost:9200"] username: "monitoring" password: "${MONITORING_PASSWORD}" ssl.verification_mode: "none"
Conclusion
Migrating from Elasticsearch to OpenSearch for Wazuh provides improved security features, better performance, and long-term support. Key benefits include:
- Native security plugin with fine-grained access control
- Improved index lifecycle management
- Better resource utilization
- Active development and community support
The migration process, while complex, ensures your security monitoring infrastructure remains modern and maintainable. Regular monitoring and optimization will maximize the benefits of your new OpenSearch-based Wazuh deployment.