From 2fd5259940bf2f5cfaeca2b6cedd9dd272e166b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Thu, 5 Oct 2023 12:03:44 +0200 Subject: [PATCH] Add event generator script --- ecs/.gitignore | 2 + ecs/README.md | 43 +++- .../event-generator/event_generator.py | 235 ++++++++++++++++++ 3 files changed, 274 insertions(+), 6 deletions(-) create mode 100755 ecs/vulnerability-detector/event-generator/event_generator.py diff --git a/ecs/.gitignore b/ecs/.gitignore index c143b7143beef..a8047fcd2d67d 100644 --- a/ecs/.gitignore +++ b/ecs/.gitignore @@ -1 +1,3 @@ **/mappings +*.log +generatedData.json \ No newline at end of file diff --git a/ecs/README.md b/ecs/README.md index b10ed5b9a01af..d16301fa9bdff 100644 --- a/ecs/README.md +++ b/ecs/README.md @@ -1,19 +1,19 @@ -### ECS mappings generator +## ECS mappings generator This script generates the ECS mappings for the Wazuh indices. -#### Requirements +### Requirements - ECS repository clone. The script is meant to be launched from the root level of that repository. - Python 3.6 or higher - jq -#### Folder structrue +### Folder structrue There is a folder for each module. Inside each folder, there is a `fields` folder with the required files to generate the mappings. These are the inputs for the ECS generator. -#### Usage +### Usage **Copy the `generate.sh` script to the root level of the ECS repository.** @@ -36,7 +36,7 @@ ECS version `v8.10.0` and the Wazuh indexer in path `~/wazuh/wazuh-indexer`: ./generate.sh v8.10.0 ~/wazuh/wazuh-indexer vulnerability-detector ``` -#### Output +### Output A new `mappings` folder will be created inside the module folder, containing all the generated files. The files are versioned using the ECS version, so different versions of the same module can be generated. @@ -53,7 +53,20 @@ to make this template compatible with OpenSearch, the following changes are made The script takes care of these changes automatically, generating the `opensearch-template.json` file as a result. -#### Adding new mappings +### Upload + +You can either upload the index template using cURL or the UI (dev tools). + +```bash +curl -u admin:admin -k -X PUT "https://indexer:9200/_index_template/wazuh-vulnerability-detector" -H "Content-Type: application/json" -d @opensearch-template.json +``` + +Notes: +- PUT and POST are interchangable. +- The name of the index template does not matter. Any name can be used. +- Adjust credentials and URL accordingly. + +### Adding new mappings The easiest way to create mappings for a new module is to take a previous one as a base. Copy a folder and rename it to the new module name. Then, edit the `fields` files to @@ -66,6 +79,24 @@ are required. - `fields/template-settings-legacy.json`: This file contains the legacy template settings for the module. - `fields/template-settings.json`: This file contains the composable template settings for the module. +### Event generator + +For testing purposes, the script `generate_events.py` can be used to generate events for a given module. +Currently, it is only able to generate events for the `vulnerability-detector` module. To support other +modules, please extend of refactor the script. + +The script prompts for the required parameters, so it can be launched without arguments: + +```bash +./event_generator.py +``` + +The script will generate a JSON file with the events, and will also ask whether to upload them to the +indexer. If the upload option is selected, the script will ask for the indexer URL and port, credentials, +and index name. + +The script uses log file. Check it out for debugging or additonal information. + #### References - [ECS repository](https://github.com/elastic/ecs) diff --git a/ecs/vulnerability-detector/event-generator/event_generator.py b/ecs/vulnerability-detector/event-generator/event_generator.py new file mode 100755 index 0000000000000..9cbc0efc44f92 --- /dev/null +++ b/ecs/vulnerability-detector/event-generator/event_generator.py @@ -0,0 +1,235 @@ +#!/bin/python3 + +# This script generates sample events and injects them into the Wazuh Indexer. +# The events follow the Elastic Common Schema (ECS) format, and contains the following fields: +# - ecs +# - base +# - event +# - agent +# - package +# - host +# - vulnerability +# +# This is an ad-hoc script for the vulnearbility module. Extend to support other modules. + +import datetime +import random +import json +import requests +import warnings +import logging + +# Constants and Configuration +LOG_FILE = 'generate_data.log' +GENERATED_DATA_FILE = 'generatedData.json' +DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" + +# Configure logging +logging.basicConfig(filename=LOG_FILE, level=logging.INFO) + +# Suppress warnings +warnings.filterwarnings("ignore") + + +def generate_random_date(): + start_date = datetime.datetime.now() + end_date = start_date - datetime.timedelta(days=10) + random_date = start_date + (end_date - start_date) * random.random() + return random_date.strftime(DATE_FORMAT) + + +def generate_random_agent(): + agent = { + 'build': {'original': f'build{random.randint(0, 9999)}'}, + 'id': f'agent{random.randint(0, 99)}', + 'name': f'Agent{random.randint(0, 99)}', + 'version': f'v{random.randint(0, 9)}-stable', + 'ephemeral_id': f'{random.randint(0, 99999)}', + 'type': random.choice(['filebeat', 'windows', 'linux', 'macos']) + } + return agent + + +def generate_random_event(): + event = { + 'action': random.choice(['login', 'logout', 'create', 'delete', 'modify', 'read', 'write', 'upload', 'download', + 'copy', 'paste', 'cut', 'move', 'rename', 'open', 'close', 'execute', 'run', 'install', + 'uninstall', 'start', 'stop', 'kill', 'suspend', 'resume', 'sleep', 'wake', 'lock', + 'unlock', 'encrypt', 'decrypt', 'compress', 'decompress', 'archive', 'unarchive', + 'mount', 'unmount', 'eject', 'connect', 'disconnect', 'send', 'receive']), + 'agent_id_status': random.choice(['verified', 'mismatch', 'missing', 'auth_metadata_missing']), + 'category': random.choice(['authentication', 'authorization', 'configuration', 'communication', 'file', + 'network', 'process', 'registry', 'storage', 'system', 'web']), + 'code': f'{random.randint(0, 99999)}', + 'created': generate_random_date(), + 'dataset': random.choice(['process', 'file', 'registry', 'socket', 'dns', 'http', 'tls', 'alert', + 'authentication', 'authorization', 'configuration', 'communication', 'file', + 'network', 'process', 'registry', 'storage', 'system', 'web']), + 'duration': random.randint(0, 99999), + 'end': generate_random_date(), + 'hash': str(hash(f'hash{random.randint(0, 99999)}')), + 'id': f'{random.randint(0, 99999)}', + 'ingested': generate_random_date(), + 'kind': random.choice(['alert', 'asset', 'enrichment', 'event', 'metric', + 'state', 'pipeline_error', 'signal']), + 'module': random.choice(['process', 'file', 'registry', 'socket', 'dns', 'http', 'tls', 'alert', + 'authentication', 'authorization', 'configuration', 'communication', 'file', + 'network', 'process', 'registry', 'storage', 'system', 'web']), + 'original': f'original{random.randint(0, 99999)}', + 'outcome': random.choice(['success', 'failure', 'unknown']), + 'provider': random.choice(['process', 'file', 'registry', 'socket', 'dns', 'http', 'tls', 'alert', + 'authentication', 'authorization', 'configuration', 'communication', 'file', + 'network', 'process', 'registry', 'storage', 'system', 'web']), + 'reason': f'This event happened due to reason{random.randint(0, 99999)}', + 'reference': f'https://system.example.com/event/#{random.randint(0, 99999)}', + 'risk_score': round(random.uniform(0, 10), 1), + 'risk_score_norm': round(random.uniform(0, 10), 1), + 'sequence': random.randint(0, 10), + 'severity': random.randint(0, 10), + 'start': generate_random_date(), + 'timezone': random.choice(['UTC', 'GMT', 'PST', 'EST', 'CST', 'MST', 'PDT', 'EDT', 'CDT', 'MDT']), + 'type': random.choice(['access', 'admin', 'allowed', 'change', 'connection', 'creation', 'deletion', + 'denied', 'end', 'error', 'group', 'indicator', 'info', 'installation', 'protocol', + 'start', 'user']), + 'url': f'http://mysystem.example.com/alert/{random.randint(0, 99999)}' + } + return event + + +def generate_random_host(): + family = random.choice(['debian', 'ubuntu', 'macos', 'ios', 'android', 'RHEL']) + version = f'{random.randint(0, 99)}.{random.randint(0, 99)}' + host = { + 'os': { + 'family': family, + 'full': f'{family} {version}', + 'kernel': f'{version}kernel{random.randint(0, 99)}', + 'name': f'{family} {version}', + 'platform': family, + 'type': random.choice(['windows', 'linux', 'macos', 'ios', 'android', 'unix']), + 'version': version + } + } + return host + + +def generate_random_labels(): + labels = {'label1': f'label{random.randint(0, 99)}', 'label2': f'label{random.randint(0, 99)}'} + return labels + + +def generate_random_package(): + package = { + 'architecture': random.choice(['x86', 'x64', 'arm', 'arm64']), + 'build_version': f'build{random.randint(0, 9999)}', + 'checksum': f'checksum{random.randint(0, 9999)}', + 'description': f'description{random.randint(0, 9999)}', + 'install_scope': random.choice(['user', 'system']), + 'installed': generate_random_date(), + 'license': f'license{random.randint(0, 9)}', + 'name': f'name{random.randint(0, 99)}', + 'path': f'/path/to/package{random.randint(0, 99)}', + 'reference': f'package-reference-{random.randint(0, 99)}', + 'size': random.randint(0, 99999), + 'type': random.choice(['deb', 'rpm', 'msi', 'pkg', 'app', 'apk', 'exe', 'zip', 'tar', 'gz', '7z', + 'rar', 'cab', 'iso', 'dmg', 'tar.gz', 'tar.bz2', 'tar.xz', 'tar.Z', 'tar.lz4', + 'tar.sz', 'tar.zst']), + 'version': f'v{random.randint(0, 9)}-stable' + } + return package + + +def generate_random_tags(): + tags = [f'tag{random.randint(0, 99)}' for _ in range(random.randint(0, 9))] + return tags + + +def generate_random_vulnerability(): + id = random.randint(0, 9999) + vulnerability = { + 'category': random.choice(['security', 'config', 'os', 'package', 'custom']), + 'classification': [f'classification{random.randint(0, 9999)}'], + 'description': f'description{random.randint(0, 9999)}', + 'enumeration': 'CVE', + 'id': f'CVE-{id}', + 'reference': f'https://mycve.test.org/cgi-bin/cvename.cgi?name={id}', + 'report_id': f'report-{random.randint(0, 9999)}', + 'scanner': {'vendor': f'vendor-{random.randint(0, 9)}'}, + 'score': { + 'base': round(random.uniform(0, 10), 1), + 'environmental': round(random.uniform(0, 10), 1), + 'temporal': round(random.uniform(0, 10), 1), + 'version': round(random.uniform(0, 10), 1) + }, + 'severity': random.choice(['low', 'medium', 'high', 'critical']) + } + return vulnerability + + +def generate_random_data(number): + data = [] + for _ in range(number): + event_data = { + '@timestamp': generate_random_date(), + 'agent': generate_random_agent(), + 'ecs': {'version': '1.7.0'}, + 'event': generate_random_event(), + 'host': generate_random_host(), + 'labels': generate_random_labels(), + 'message': f'message{random.randint(0, 99999)}', + 'package': generate_random_package(), + 'tags': generate_random_tags(), + 'vulnerability': generate_random_vulnerability() + } + data.append(event_data) + return data + + +def inject_events(ip, port, index, username, password, data): + url = f'https://{ip}:{port}/{index}/_doc' + session = requests.Session() + session.auth = (username, password) + session.verify = False + headers = {'Content-Type': 'application/json'} + + try: + for event_data in data: + response = session.post(url, json=event_data, headers=headers) + if response.status_code != 201: + logging.error(f'Error: {response.status_code}') + logging.error(response.text) + break + logging.info('Data injection completed successfully.') + except Exception as e: + logging.error(f'Error: {str(e)}') + + +def main(): + try: + number = int(input("How many events do you want to generate? ")) + except ValueError: + logging.error("Invalid input. Please enter a valid number.") + return + + logging.info(f"Generating {number} events...") + data = generate_random_data(number) + + with open(GENERATED_DATA_FILE, 'a') as outfile: + for event_data in data: + json.dump(event_data, outfile) + outfile.write('\n') + + logging.info('Data generation completed.') + + inject = input("Do you want to inject the generated data into your indexer? (y/n) ").strip().lower() + if inject == 'y': + ip = input("Enter the IP of your Indexer: ") + port = input("Enter the port of your Indexer: ") + index = input("Enter the index name: ") + username = input("Username: ") + password = input("Password: ") + inject_events(ip, port, index, username, password, data) + + +if __name__ == "__main__": + main()