This write-up explains how to implement Logstash with Wazuh to create lookup files and enrich incoming data. The implementation's topology is shown below.

resim.png

First, install Logstash and follow this documentation: Wazuh Elastic Stack integration · Wazuh documentation

Download and install the Public Signing Key:

wget -qO - <https://artifacts.elastic.co/GPG-KEY-elasticsearch> | sudo gpg --dearmor -o /usr/share/keyrings/elastic-keyring.gpg

You may need to install the apt-transport-https package on Debian before proceeding:

sudo apt-get install apt-transport-https

Save the repository definition to /etc/apt/sources.list.d/elastic-8.x.list:

echo "deb [signed-by=/usr/share/keyrings/elastic-keyring.gpg] <https://artifacts.elastic.co/packages/8.x/apt> stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-8.x.list

Run sudo apt-get update and the repository is ready for use. You can install it with:

sudo apt-get update && sudo apt-get install logstash

After installing Logstash install the opensearch-output plugin

sudo /usr/share/logstash/bin/logstash-plugin install logstash-output-opensearch

You only need Wazuh-indexer certificates for Logstash if you install Logstash on a different server. You can add to this path /etc/logstash/wazuh-indexer-certs and give Logstash user the necessary permissions sudo chmod -R 755 </PATH/TO/LOCAL/WAZUH_INDEXER/CERTIFICATE>/root-ca.pem .

The next step is to edit the filebeat.yml file to send logs to Logstash instead of directly to the Wazuh-indexer. The filebeat.yml file is located here etc/filebeat/filebeat.yml, and edit the file like this:


# Uncomment and configure either Elasticsearch or Logstash output. Only one should be active.

# Elasticsearch output (commented out in this case)
# output.elasticsearch:
#   hosts: ["127.0.0.1:9200"]
#   protocol: https
#   username: ${username}
#   password: ${password}
#   ssl.certificate_authorities:
#     - /etc/filebeat/certs/root-ca.pem
#   ssl.certificate: "/etc/filebeat/certs/wazuh-server.pem"
#   ssl.key: "/etc/filebeat/certs/wazuh-server-key.pem"
#   pipeline: "extract_fields_pipeline"

# Logstash output
output.logstash:
  hosts: ["127.0.0.1:5044"]

# Setup for Wazuh template
setup.template.json.enabled: true
setup.template.json.path: '/etc/filebeat/wazuh-template.json'
setup.template.json.name: 'wazuh'
setup.ilm.overwrite: true
setup.ilm.enabled: false

# Enable Wazuh Filebeat module
filebeat.modules:
  - module: wazuh
    alerts:
      enabled: true
    archives:
      enabled: false

# Logging configuration
logging.level: info
logging.to_files: true
logging.files:
  path: /var/log/filebeat
  name: filebeat
  keepfiles: 7
  permissions: 0644

logging.metrics.enabled: false

# Seccomp configuration
seccomp:
  default_action: allow
  syscalls:
    - action: allow
      names:
        - rseq

Now edit logstash.conf file under this directory /etc/logstash/conf.d to read logs coming from Filebeat, enrich the data with CSV files, and send the enriched logs to Wazuh-indexer. Here is the example configuration:

input {
  beats {
    port => 5044
  }
}

filter {
  json {
    source => "message"
    target => "parsed_message"
  }

  # Load the CSV file into a dictionary
  ruby {
    path => "/home/berkan/Documents/enrich.rb"
  }

  # Use the enriched data
  mutate {
    add_field => {
      "department" => "%{[parsed_message][data][department]}"
      "vlan_addr" => "%{[parsed_message][data][vlan_address]}"
      "role" => "%{[parsed_message][data][Role]}"
    }
  }

  mutate {
    remove_field => ["parsed_message"]
  }
}

output {
  # Send enriched logs to OpenSearch
  opensearch {
    hosts => ["<https://127.0.0.1:9200>"]
    index => "si_tcodes-%{+YYYY.MM.dd}"
    user => "replace it with your wazuh username"
    password => "replace it with your wazuh password"
    ssl => true
    cacert => "/etc/logstash/wazuh-indexer-certs/root-ca.pem"
    pipeline => "filebeat-7.10.2-wazuh-alerts-pipeline"
  }

Now I will explain the enrichment process. First You should use the JSON filter plugin to parse incoming logs I do this because we do lookups based on a field that already exists on the logs and if we try to parse a specific field we can’t do it on raw format. The output of this is, parsed_message, we can access the si_user field in this example. Then I wrote a ruby script to do the matching field between incoming logs and the CSV file, after the matching it will get the new values from the CSV file and format for us to use in the mutate plugin for generating new fields. And the other reason we use this script is we can read multiple columns in a CSV file. The ruby script and CSV file are below:

require 'csv'

# Load the CSV file into a hash
csv_file = '/home/berkan/Documents/test.csv'
$enrichment_data = {}
CSV.foreach(csv_file, headers: true) do |row|
  $enrichment_data[row['si_user']] = {
    'department' => row['Department'],
    'vlan_address' => row['vlan_address'],
    'Role' => row['Role']
  }
end

# Enrich the event
def register(params)
end

def filter(event)
    if event.get('[parsed_message][data][si_user]')
       si_user = event.get('[parsed_message][data][si_user]')
       if $enrichment_data.key?(si_user)
          enrichment = $enrichment_data[si_user]
          event.set('[parsed_message][data][department]', enrichment['department'])
          event.set('[parsed_message][data][vlan_address]', enrichment['vlan_address'])
          event.set('[parsed_message][data][Role]', enrichment['Role'])
       end
    end
  return [event]
end