Building a Production-Ready OCSF Security Data Pipeline: Wazuh to Tenzir Integration Guide
Table of Contents
In the modern security landscape, organizations struggle with disparate data formats from various security tools, making correlation and analysis challenging. The Open Cybersecurity Schema Framework (OCSF) emerges as the solution, providing a vendor-agnostic standard for security telemetry. This comprehensive guide demonstrates how to build a production-ready pipeline that transforms Wazuh logs into OCSF-compliant format using Tenzir, creating a unified security data architecture.
Executive Summary
Organizations today face three critical challenges in security operations:
- Data Silos: Security tools produce incompatible data formats
- Integration Complexity: Custom parsers and mappings for each tool
- Scalability Issues: Growing data volumes strain traditional SIEM architectures
This guide presents a modern solution using:
- Wazuh for comprehensive log collection
- Tenzir for intelligent data transformation
- OCSF for standardized security schemas
Understanding OCSF: The Foundation
What is OCSF?
The Open Cybersecurity Schema Framework (OCSF) is an open-source project delivering an extensible framework for developing schemas, along with a vendor-agnostic core security schema. Founded through collaboration between AWS, Splunk, IBM, and 15+ other industry leaders, OCSF is now governed by the Linux Foundation.
Key Benefits
- Vendor Independence: Break free from proprietary data formats
- Cost Reduction: Store all security telemetry in a unified format
- Enhanced Analytics: Correlate data across all security tools
- Future-Proof Architecture: Adapt to new threats and tools easily
OCSF Schema Structure
{ "metadata": { "version": "1.0.0", "product": { "name": "Wazuh", "vendor_name": "Wazuh Inc.", "version": "4.8.0" }, "profiles": ["host", "security_control"], "event_code": "authentication" }, "authentication": { "activity_id": 1, "activity_name": "Logon", "actor": { "user": { "name": "john.doe", "uid": "S-1-5-21-123456", "type": "User", "type_id": 1 } }, "device": { "hostname": "WORKSTATION-01", "ip": "192.168.1.100", "os": { "name": "Windows", "version": "10.0.19044" } }, "time": 1704538800000, "severity_id": 1, "status": "Success", "status_id": 1 }}Architecture Overview
flowchart TB
subgraph "Data Sources"
A1[Windows Endpoints]
A2[Linux Servers]
A3[Network Devices]
A4[Cloud Services]
A5[Applications]
end
subgraph "Wazuh Manager"
B1[Log Collection]
B2[Rules Engine]
B3[JSON Output]
B4[Alert Generation]
end
subgraph "Tenzir Pipeline"
C1[Ingest Module]
C2[OCSF Operators]
C3[Enrichment]
C4[Validation]
C5[Output Router]
end
subgraph "Storage & Analytics"
D1[Security Data Lake]
D2[Real-time SIEM]
D3[Threat Hunting Platform]
D4[Compliance Reporting]
end
A1 --> B1
A2 --> B1
A3 --> B1
A4 --> B1
A5 --> B1
B1 --> B2
B2 --> B3
B3 --> B4
B4 --> C1
C1 --> C2
C2 --> C3
C3 --> C4
C4 --> C5
C5 --> D1
C5 --> D2
C5 --> D3
C5 --> D4
style A1 fill:#e1f5fe
style A2 fill:#e1f5fe
style A3 fill:#e1f5fe
style A4 fill:#e1f5fe
style A5 fill:#e1f5fe
style B1 fill:#fff3e0
style B2 fill:#fff3e0
style B3 fill:#fff3e0
style B4 fill:#fff3e0
style C1 fill:#f3e5f5
style C2 fill:#f3e5f5
style C3 fill:#f3e5f5
style C4 fill:#f3e5f5
style C5 fill:#f3e5f5
style D1 fill:#e8f5e9
style D2 fill:#e8f5e9
style D3 fill:#e8f5e9
style D4 fill:#e8f5e9
Step 1: Configuring Wazuh for JSON Output
Enable JSON Logging
Edit /var/ossec/etc/ossec.conf to enable JSON output:
<ossec_config> <logging> <log_format>json</log_format> <jsonout_output>yes</jsonout_output> </logging>
<global> <jsonout_output>yes</jsonout_output> <alerts_log>yes</alerts_log> <logall>yes</logall> <logall_json>yes</logall_json> </global></ossec_config>Configure Alert Forwarding
Set up syslog forwarding to Tenzir:
<ossec_config> <syslog_output> <server>tenzir-host</server> <port>514</port> <format>json</format> <level>1</level> </syslog_output></ossec_config>Optimize Wazuh Rules for OCSF
Create custom rules that include OCSF-relevant metadata:
<group name="authentication,ocsf"> <rule id="100001" level="5"> <if_sid>5715</if_sid> <field name="win.eventdata.logonType">^3$</field> <description>Windows Network Logon - OCSF Class 3002</description> <options>no_full_log</options> <group>authentication_success,ocsf_3002</group> </rule>
<rule id="100002" level="10"> <if_sid>5716</if_sid> <field name="win.eventdata.status">^0xC000006D$</field> <description>Windows Logon Failed - Bad Username - OCSF Class 3002</description> <group>authentication_failed,ocsf_3002</group> </rule></group>Verify JSON Output
Check that Wazuh is producing JSON logs:
# View real-time alerts in JSON formattail -f /var/ossec/logs/alerts/alerts.json | jq '.'
# Example output{ "timestamp": "2025-01-06T10:00:00.000+0000", "rule": { "level": 5, "description": "Windows Network Logon - OCSF Class 3002", "id": "100001", "firedtimes": 1, "groups": ["authentication", "authentication_success", "ocsf_3002"] }, "agent": { "id": "001", "name": "WORKSTATION-01", "ip": "192.168.1.100" }, "data": { "win": { "eventdata": { "logonType": "3", "targetUserName": "john.doe", "targetDomainName": "CORP", "ipAddress": "192.168.1.200" } } }}Step 2: Setting Up Tenzir
Installation
# Install Tenzir on Ubuntu/Debiancurl -L https://get.tenzir.app | sh
# Or using Dockerdocker run -d \ --name tenzir \ -p 5158:5158 \ -v tenzir-data:/var/lib/tenzir \ tenzir/tenzir:latestBasic Configuration
Create /etc/tenzir/tenzir.yaml:
tenzir: # Data directory state-directory: /var/lib/tenzir
# Network settings endpoint: 0.0.0.0:5158
# Performance tuning max-partition-size: 1048576 max-resident-partitions: 10 max-taste-partitions: 5
# Plugins plugins: - ocsf - wazuh - parquetStart Tenzir Service
# Start Tenzirsystemctl start tenzirsystemctl enable tenzir
# Verify it's runningtenzir statusStep 3: Building the OCSF Transformation Pipeline
Understanding Tenzir’s OCSF Operators
Tenzir provides three main OCSF operators:
ocsf::derive- Enriches events with OCSF metadataocsf::apply- Validates and enforces OCSF schemaocsf::trim- Removes optional fields to optimize storage
Creating the Base Pipeline
Create /etc/tenzir/pipelines/wazuh-ocsf.tql:
// Wazuh to OCSF Pipeline// This pipeline transforms Wazuh alerts to OCSF format
// Stage 1: Ingest Wazuh JSONread_json file=/var/ossec/logs/alerts/alerts.json| where rule.groups has "ocsf_3002" // Filter authentication events
// Stage 2: Map Wazuh fields to OCSF| put ocsf = { metadata: { version: "1.0.0", product: { name: "Wazuh", vendor_name: "Wazuh Inc.", version: "4.8.0" }, profiles: ["host"], logged_time: timestamp, original_time: timestamp }, class_uid: 3002, class_name: "Authentication", category_uid: 3, category_name: "Identity & Access Management", severity_id: if (rule.level <= 3) {1} else if (rule.level <= 6) {2} else if (rule.level <= 9) {3} else {4}, activity_id: if (rule.groups has "authentication_success") {1} else {2}, activity_name: if (rule.groups has "authentication_success") {"Logon"} else {"Logon Failed"}, time: timestamp, actor: { user: { name: data.win.eventdata.targetUserName, domain: data.win.eventdata.targetDomainName, type: "User", type_id: 1 }, session: { uid: data.win.eventdata.logonGuid } }, device: { hostname: agent.name, ip: agent.ip, os: { name: "Windows", type: "Windows", type_id: 100 } }, logon_type: data.win.eventdata.logonType, status: if (rule.groups has "authentication_success") {"Success"} else {"Failure"}, status_id: if (rule.groups has "authentication_success") {1} else {2} }
// Stage 3: Apply OCSF validation| ocsf::derive| ocsf::apply| ocsf::trim profile=standard
// Stage 4: Output to multiple destinations| publish "ocsf-events"Advanced Pipeline with Enrichment
Create /etc/tenzir/pipelines/wazuh-ocsf-enriched.tql:
// Advanced Wazuh to OCSF Pipeline with Enrichment
// Define lookup tableslet threat_intel = read_csv file=/etc/tenzir/threat_intel.csvlet asset_inventory = read_parquet file=/etc/tenzir/assets.parquet
// Main pipelinesubscribe "wazuh-raw"| read_json
// Enrich with threat intelligence| enrich threat_intel on ip = src_ip| put threat_score = if (threat_intel.reputation == "malicious") {100} else if (threat_intel.reputation == "suspicious") {50} else {0}
// Enrich with asset information| enrich asset_inventory on hostname = agent.name| put asset_criticality = asset_inventory.criticality ?? "medium"
// Transform to OCSF with enrichments| put ocsf = { // ... base OCSF fields ... enrichments: [ { name: "threat_intelligence", type: "reputation", value: threat_intel.reputation, provider: "internal_ti" }, { name: "asset_context", type: "criticality", value: asset_criticality, provider: "cmdb" } ], risk_score: threat_score, observables: [ { name: "source_ip", type: "ip_address", type_id: 2, value: src_ip, reputation: { score: threat_score, provider: "internal_ti" } } ] }
// Validate and optimize| ocsf::derive| ocsf::apply| ocsf::trim profile=detection
// Route based on severity| where ocsf.severity_id >= 3| publish "ocsf-high-priority"
// Archive all events| write_parquet file=/data/ocsf/archive/authentication.parquetStep 4: Implementing Event Class Mappings
Authentication Events (Class 3002)
// OCSF Class 3002: Authentication Activityexport auth_mapper = function(event) { return { class_uid: 3002, class_name: "Authentication", activity_id: switch { event.action == "login_success" => 1, // Logon event.action == "logout" => 2, // Logoff event.action == "login_failed" => 3, // Authentication Failed default => 0 // Unknown }, actor: { user: { name: event.username, uid: event.user_id, type: "User", type_id: 1, credential_uid: event.session_id }, process: if (event.process_name != null) {{ name: event.process_name, pid: event.process_id }} else {null} }, auth_protocol: event.auth_method, auth_protocol_id: switch { event.auth_method == "NTLM" => 1, event.auth_method == "Kerberos" => 2, event.auth_method == "LDAP" => 3, event.auth_method == "OAuth2" => 4, default => 99 }, dst_endpoint: { hostname: event.target_host, ip: event.target_ip, port: event.target_port }, logon_type: event.logon_type, logon_type_id: event.logon_type_id, response_time: event.duration_ms, session: { uid: event.session_id, created_time: event.session_start, is_remote: event.is_remote }, src_endpoint: { hostname: event.source_host, ip: event.source_ip, location: { city: event.geo_city, country: event.geo_country, coordinates: [event.geo_lon, event.geo_lat] } }, status: event.status, status_code: event.status_code, status_detail: event.error_message, status_id: if (event.success) {1} else {2} }}Network Activity Events (Class 4001)
// OCSF Class 4001: Network Activityexport network_mapper = function(event) { return { class_uid: 4001, class_name: "Network Activity", activity_id: switch { event.action == "allowed" => 1, // Allowed event.action == "denied" => 2, // Denied event.action == "dropped" => 3, // Dropped default => 0 // Unknown }, connection_info: { direction: event.direction, direction_id: switch { event.direction == "inbound" => 1, event.direction == "outbound" => 2, event.direction == "lateral" => 3, default => 0 }, protocol_num: event.protocol_number, protocol_name: event.protocol, tcp_flags: event.tcp_flags }, dst_endpoint: { hostname: event.dst_hostname, ip: event.dst_ip, port: event.dst_port, mac: event.dst_mac, interface_name: event.dst_interface }, src_endpoint: { hostname: event.src_hostname, ip: event.src_ip, port: event.src_port, mac: event.src_mac, interface_name: event.src_interface }, traffic: { bytes: event.bytes_total, bytes_in: event.bytes_received, bytes_out: event.bytes_sent, packets: event.packets_total, packets_in: event.packets_received, packets_out: event.packets_sent }, duration: event.duration_ms, start_time: event.flow_start, end_time: event.flow_end }}File Activity Events (Class 1001)
// OCSF Class 1001: File Activityexport file_mapper = function(event) { return { class_uid: 1001, class_name: "File Activity", activity_id: switch { event.action == "create" => 1, event.action == "read" => 2, event.action == "update" => 3, event.action == "delete" => 4, event.action == "rename" => 5, event.action == "attributes_modified" => 6, event.action == "permissions_modified" => 7, default => 0 }, actor: { process: { name: event.process_name, pid: event.process_id, file: { path: event.process_path, hash: { algorithm: "SHA256", value: event.process_hash } } }, user: { name: event.username, uid: event.user_id } }, file: { path: event.file_path, name: event.file_name, parent_folder: event.file_directory, type: event.file_type, type_id: map_file_type(event.file_type), size: event.file_size, hash: if (event.file_hash != null) {{ algorithm: "SHA256", value: event.file_hash }} else {null}, modified_time: event.file_mtime, accessed_time: event.file_atime, created_time: event.file_ctime, is_system: event.is_system_file, security_descriptor: event.file_acl }, device: { hostname: event.hostname, os: { name: event.os_name, version: event.os_version } } }}Step 5: Production Deployment
High Availability Setup
# docker-compose.yml for HA Tenzir deploymentversion: '3.8'
services: tenzir-node1: image: tenzir/tenzir:latest container_name: tenzir-node1 environment: - TENZIR_ENDPOINT=0.0.0.0:5158 - TENZIR_NODE_ID=node1 - TENZIR_CLUSTER_ENDPOINTS=tenzir-node2:5158,tenzir-node3:5158 volumes: - tenzir-data1:/var/lib/tenzir - ./pipelines:/etc/tenzir/pipelines ports: - "5158:5158" networks: - tenzir-cluster deploy: resources: limits: cpus: '4' memory: 8G reservations: cpus: '2' memory: 4G
tenzir-node2: image: tenzir/tenzir:latest container_name: tenzir-node2 environment: - TENZIR_ENDPOINT=0.0.0.0:5158 - TENZIR_NODE_ID=node2 - TENZIR_CLUSTER_ENDPOINTS=tenzir-node1:5158,tenzir-node3:5158 volumes: - tenzir-data2:/var/lib/tenzir - ./pipelines:/etc/tenzir/pipelines networks: - tenzir-cluster deploy: resources: limits: cpus: '4' memory: 8G
tenzir-node3: image: tenzir/tenzir:latest container_name: tenzir-node3 environment: - TENZIR_ENDPOINT=0.0.0.0:5158 - TENZIR_NODE_ID=node3 - TENZIR_CLUSTER_ENDPOINTS=tenzir-node1:5158,tenzir-node2:5158 volumes: - tenzir-data3:/var/lib/tenzir - ./pipelines:/etc/tenzir/pipelines networks: - tenzir-cluster deploy: resources: limits: cpus: '4' memory: 8G
haproxy: image: haproxy:2.9-alpine container_name: tenzir-lb volumes: - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro ports: - "80:80" - "443:443" - "5158:5158" networks: - tenzir-cluster depends_on: - tenzir-node1 - tenzir-node2 - tenzir-node3
volumes: tenzir-data1: tenzir-data2: tenzir-data3:
networks: tenzir-cluster: driver: bridgePerformance Optimization
// Optimized pipeline with parallel processingexport optimized_pipeline = ( // Use parallel processing for high-volume streams read_json file=/var/ossec/logs/alerts/alerts.json | batch 1000 | parallel apply=transform_to_ocsf
// Implement smart caching | cache key=hash(rule.id, agent.id) ttl=300s
// Compress before storage | compress algorithm=zstd level=3
// Partition by time for efficient queries | partition by=floor(time, 1h) | write_parquet file="/data/ocsf/{partition}/events.parquet")
// Resource-aware processingexport adaptive_pipeline = ( read_json | measure cpu_usage | if (cpu_usage > 0.8) { // Reduce processing when under load sample rate=0.5 | ocsf::trim profile=minimal } else { // Full processing when resources available ocsf::derive | ocsf::apply | enrich_all } | write)Monitoring and Alerting
# prometheus.yml configurationglobal: scrape_interval: 15s
scrape_configs: - job_name: 'tenzir' static_configs: - targets: - 'tenzir-node1:9090' - 'tenzir-node2:9090' - 'tenzir-node3:9090'
- job_name: 'wazuh' static_configs: - targets: ['wazuh-manager:55000']
# Alert rulesgroups: - name: pipeline_health rules: - alert: PipelineBacklog expr: tenzir_pipeline_backlog_events > 10000 for: 5m annotations: summary: "Pipeline backlog growing"
- alert: TransformationErrors expr: rate(tenzir_ocsf_transform_errors[5m]) > 0.01 annotations: summary: "OCSF transformation errors detected"
- alert: HighMemoryUsage expr: tenzir_memory_usage_bytes / tenzir_memory_limit_bytes > 0.9 for: 10m annotations: summary: "Tenzir memory usage critical"Step 6: Integration with Security Tools
Amazon Security Lake Integration
// Direct integration with Amazon Security Lakeexport asl_publisher = ( subscribe "ocsf-events" | where ocsf.severity_id >= 2 // Only send medium+ severity
// Add Security Lake required fields | put ocsf.metadata.product.feature = { name: "Wazuh SIEM", version: "4.8.0" } | put ocsf.metadata.labels = ["wazuh", "production", "regulated"]
// Write to S3 in Parquet format | to_s3 bucket="my-security-lake-bucket" prefix="ext/wazuh-ocsf/{year}/{month}/{day}/" format="parquet" compression="snappy" partition_by=["year", "month", "day"])Splunk Integration
// Send OCSF events to Splunkexport splunk_forwarder = ( subscribe "ocsf-events" | to_splunk url="https://splunk-hec.company.com:8088" token=env("SPLUNK_HEC_TOKEN") index="security_ocsf" sourcetype="ocsf:json")OpenSearch Integration
// Index OCSF events in OpenSearchexport opensearch_indexer = ( subscribe "ocsf-events"
// Create time-based indices | put _index = strftime("ocsf-wazuh-%Y.%m.%d", ocsf.time)
// Send to OpenSearch | to_opensearch nodes=["https://opensearch-node1:9200", "https://opensearch-node2:9200"] username=env("OPENSEARCH_USER") password=env("OPENSEARCH_PASS") ssl_verify=true bulk_size=1000 flush_interval=5s)Best Practices and Lessons Learned
1. Schema Evolution Management
# Version management for OCSF schemasclass OCSFSchemaManager: def __init__(self): self.schema_versions = { "1.0.0": self.load_schema_v1_0_0(), "1.1.0": self.load_schema_v1_1_0() }
def migrate_event(self, event, from_version, to_version): """Migrate events between OCSF versions""" if from_version == "1.0.0" and to_version == "1.1.0": # Add new required fields event['type_uid'] = self.calculate_type_uid(event) event['metadata']['processed_time'] = int(time.time() * 1000)
return event
def validate_event(self, event, version="1.1.0"): """Validate event against OCSF schema""" schema = self.schema_versions[version] return jsonschema.validate(event, schema)2. Performance Tuning Guidelines
# Tenzir performance configurationperformance_settings: # Buffer sizes for different event volumes low_volume: # < 1K events/sec batch_size: 100 buffer_size: 10000 parallelism: 2
medium_volume: # 1K-10K events/sec batch_size: 1000 buffer_size: 100000 parallelism: 8
high_volume: # > 10K events/sec batch_size: 5000 buffer_size: 500000 parallelism: 16 compression: true
# Memory management memory_limits: heap_size: "8g" off_heap_size: "4g" direct_memory: "2g"3. Error Handling and Recovery
// Robust pipeline with error handlingexport resilient_pipeline = ( read_json file=/var/ossec/logs/alerts/alerts.json
// Validate input | where timestamp != null && rule != null
// Transform with error catching | try { transform_to_ocsf | ocsf::validate } catch { // Send failed events to error queue put error = { original_event: this, error_message: error.message, error_time: now() } | publish "ocsf-errors" }
// Continue with valid events | where ocsf != null | publish "ocsf-valid")
// Error recovery pipelineexport error_recovery = ( subscribe "ocsf-errors" | limit 1000 // Process in batches
// Attempt to fix common issues | put fixed_event = fix_common_errors(error.original_event)
// Retry transformation | try { select fixed_event | transform_to_ocsf | publish "ocsf-recovered" } catch { // Log permanently failed events write_json file="/var/log/tenzir/permanent_errors.json" })4. Data Quality Monitoring
-- SQL queries for OCSF data quality monitoring-- Run these against your data lake
-- Check event distribution by classSELECT class_name, class_uid, COUNT(*) as event_count, COUNT(*) * 100.0 / SUM(COUNT(*)) OVER() as percentageFROM ocsf_eventsWHERE date = CURRENT_DATEGROUP BY class_name, class_uidORDER BY event_count DESC;
-- Monitor schema complianceSELECT DATE_TRUNC('hour', time) as hour, COUNT(*) as total_events, SUM(CASE WHEN severity_id IS NULL THEN 1 ELSE 0 END) as missing_severity, SUM(CASE WHEN activity_id = 0 THEN 1 ELSE 0 END) as unknown_activity, SUM(CASE WHEN metadata.version != '1.1.0' THEN 1 ELSE 0 END) as wrong_versionFROM ocsf_eventsWHERE date >= CURRENT_DATE - INTERVAL '7 days'GROUP BY hourORDER BY hour DESC;
-- Identify enrichment gapsSELECT class_name, COUNT(*) as total_events, SUM(CASE WHEN enrichments IS NULL THEN 1 ELSE 0 END) as not_enriched, SUM(CASE WHEN risk_score = 0 THEN 1 ELSE 0 END) as no_risk_scoreFROM ocsf_eventsWHERE date = CURRENT_DATEGROUP BY class_name;Troubleshooting Guide
Common Issues and Solutions
1. Pipeline Backlog Growing
Symptoms:
- Increasing lag between Wazuh alerts and OCSF output
- Memory usage climbing
- Events taking longer to process
Solutions:
// Diagnose bottlenecksshow pipeline metrics| where name == "wazuh-ocsf"| select throughput, backlog, errors
// Increase parallelismalter pipeline "wazuh-ocsf" set parallelism = 16
// Add sampling for high-volume periodsread_json| sample adaptive // Automatically adjust sampling rate| continue_normal_processing2. OCSF Validation Failures
Symptoms:
- Events rejected by ocsf::apply
- Errors in transformation logs
Solutions:
// Debug validation issuesread_json| transform_to_ocsf| ocsf::validate verbose=true| where validation.valid == false| select original_event, validation.errors| write_json file="/tmp/validation_failures.json"
// Common fixesfix_validation_errors = function(event) { // Ensure required fields event.time = event.time ?? now() event.severity_id = event.severity_id ?? 1
// Fix data types event.class_uid = int(event.class_uid)
// Add missing metadata if (event.metadata == null) { event.metadata = { version: "1.1.0", product: {name: "Unknown", vendor_name: "Unknown"} } }
return event}3. Memory Issues
Symptoms:
- OutOfMemoryError in logs
- Process crashes
- Slow performance
Solutions:
# Tune JVM settingsexport TENZIR_HEAP_SIZE=16gexport TENZIR_DIRECT_SIZE=8g
# Enable memory profilingtenzir start --memory-profiler
# Monitor memory usagewatch -n 1 'tenzir show system | grep memory'Security Considerations
1. Data Privacy and Compliance
// Implement data masking for PIIexport privacy_pipeline = ( read_json | transform_to_ocsf
// Mask sensitive fields | put ocsf.actor.user.email = mask_email(ocsf.actor.user.email) | put ocsf.src_endpoint.ip = mask_ip(ocsf.src_endpoint.ip, preserve_subnet=true)
// Remove unnecessary PII | remove ocsf.actor.user.phone | remove ocsf.actor.user.address
// Add compliance tags | put ocsf.metadata.labels = append(ocsf.metadata.labels, "gdpr_compliant")
| write)2. Access Control
# Tenzir RBAC configurationrbac: roles: - name: ocsf_reader permissions: - read:ocsf-events - read:ocsf-archive
- name: ocsf_admin permissions: - "*:ocsf-*" - manage:pipelines
users: - name: wazuh_service roles: [ocsf_writer]
- name: analyst_team roles: [ocsf_reader]
- name: security_admin roles: [ocsf_admin]3. Encryption
// Enable encryption for sensitive pipelinesexport encrypted_pipeline = ( read_json | transform_to_ocsf
// Encrypt sensitive fields | put ocsf.actor.user.credential = encrypt( ocsf.actor.user.credential, algorithm="AES-256-GCM", key=env("ENCRYPTION_KEY") )
// Sign events for integrity | put ocsf.metadata.signature = sign( serialize(ocsf), algorithm="HMAC-SHA256", key=env("SIGNING_KEY") )
| write_encrypted file="/secure/ocsf/events.enc" key=env("STORAGE_KEY"))Cost Optimization Strategies
1. Intelligent Sampling
// Adaptive sampling based on event valueexport cost_optimized_pipeline = ( read_json
// Full fidelity for high-value events | if (rule.level >= 10 || rule.groups has "critical") { // Process everything transform_to_ocsf | enrich_full } else if (rule.level >= 5) { // Sample medium priority sample rate=0.5 | transform_to_ocsf | enrich_basic } else { // Aggressive sampling for low priority sample rate=0.1 | transform_to_ocsf | ocsf::trim profile=minimal }
| write)2. Storage Tiering
// Implement storage tieringexport tiered_storage = ( subscribe "ocsf-events"
// Hot tier: Last 7 days | if (age(ocsf.time) < 7d) { write_parquet file="/hot/ocsf/{date}/events.parquet" compression="lz4" // Fast compression }
// Warm tier: 7-30 days else if (age(ocsf.time) < 30d) { write_parquet file="/warm/ocsf/{date}/events.parquet" compression="zstd" // Balanced compression }
// Cold tier: Archive else { // Aggressive compression and aggregation | aggregate by=[class_uid, severity_id, actor.user.name] window=1h count=count() | write_parquet file="/cold/ocsf/{year}/{month}/summary.parquet" compression="zstd:9" // Maximum compression })Future Enhancements
1. Machine Learning Integration
# ML-powered event enrichmentclass OCSFMLEnricher: def __init__(self): self.anomaly_detector = IsolationForest() self.risk_predictor = RandomForestClassifier()
def enrich_with_ml(self, event): # Anomaly detection features = self.extract_features(event) anomaly_score = self.anomaly_detector.predict_proba([features])[0][1]
# Risk prediction risk_score = self.risk_predictor.predict_proba([features])[0][1]
# Add ML insights to OCSF event event['enrichments'].append({ 'name': 'ml_analysis', 'type': 'anomaly_detection', 'value': { 'anomaly_score': float(anomaly_score), 'risk_score': float(risk_score), 'confidence': 0.85 }, 'provider': 'internal_ml' })
return event2. Advanced Correlation
// Cross-event correlation pipelineexport correlation_pipeline = ( subscribe "ocsf-events"
// Maintain sliding window of events | window size=1000 slide=100
// Detect attack patterns | detect_pattern name="brute_force" condition=( count(class_uid == 3002 && status_id == 2) > 5 && unique(src_endpoint.ip) == 1 && time_span() < 60s )
| detect_pattern name="lateral_movement" condition=( has(class_uid == 3002 && status_id == 1) && has(class_uid == 4001 && dst_endpoint.port in [445, 3389]) && same(actor.user.name) )
// Generate meta-events for patterns | on_pattern_match create_alert | publish "ocsf-correlations")3. Automated Response
// SOAR integration pipelineexport soar_pipeline = ( subscribe "ocsf-correlations" | where risk_score > 80
// Trigger automated responses | call_webhook url="https://soar.company.com/api/v1/playbooks/execute" headers={"Authorization": "Bearer " + env("SOAR_TOKEN")} body={ "playbook": determine_playbook(this), "event": this, "priority": map_severity_to_priority(severity_id) }
// Track response actions | put response_metadata = { action_taken: response.playbook_name, ticket_id: response.ticket_id, status: response.status }
| write_json file="/var/log/tenzir/soar_actions.json")Conclusion
Building a production-ready OCSF pipeline with Wazuh and Tenzir provides organizations with a powerful, standardized approach to security data management. This implementation offers:
- Vendor Independence: No lock-in to proprietary formats
- Scalability: Handle millions of events per second
- Cost Efficiency: Optimize storage and processing costs
- Enhanced Security: Standardized correlation and detection
- Future-Proof Architecture: Ready for new tools and threats
The combination of Wazuh’s comprehensive collection capabilities, Tenzir’s powerful transformation engine, and OCSF’s standardized schema creates a modern security data platform that can adapt to evolving threats and requirements.
Additional Resources
Official Documentation
Community Resources
Sample Code Repository
All code examples from this guide are available at: https://github.com/yourusername/wazuh-ocsf-tenzir-pipeline
Have questions or improvements? Feel free to reach out or contribute to the project!