Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ setup/master/.env

# Data and storage directories
setup/splunk/data/
setup/elastic/data/
postgres_data/
share/smb_test_file
share/cases/*
Expand All @@ -51,3 +52,6 @@ share/log/task_traces.jsonl
test.py
share/log/celery.log
OSIR/src/osir_web/osir_web/pages/dynamic/index.html

# IA agent
.opencode
Binary file added OSIR/bin/json2elastic-rs
Binary file not shown.
34 changes: 34 additions & 0 deletions OSIR/configs/modules/elastic/indexer_elastic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
metadata:
version: "1.0"
author: Grand-Duc
description: ElasticSearch ingestion (DFIR ORC and UAC) using the same module-specific
datamodel and VRL normalization as the Splunk indexer.
os: generic

configuration:
module: indexer_elastic
type: post_parsing
disk_only: false
no_multithread: false
processor_type:
- internal
processor_os: unix

tool:
path: json2elastic-rs
cmd: >-
--input {input_dir_replaced_by_internal_module}
--index {case_name}
--config /OSIR/setup/conf/agent.yml
--indexer_patterns {indexer_path}
--vrl_dir /OSIR/OSIR/configs/dependencies/
source: https://github.com/Grand-Duc/json2elastic-rs/releases
version: 1.0

input:
type: dir
path: "{case_path}"

output:
type: single_file
format: jsonl
48 changes: 47 additions & 1 deletion OSIR/src/osir_lib/osir_lib/core/OsirAgentConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,38 @@ class SplunkConfig(BaseModel):
ssl: bool # booléen pour True/False


class ElasticConfig(BaseModel):
"""
Contains the authentication and connection parameters for ElasticSearch integration.

Args:
host (str): The address of the ElasticSearch server (default 127.0.0.1).
user (str): Username for ElasticSearch (default elastic).
password (str): Password for ElasticSearch.
port (int): The destination port for the REST API (default 9200).
ssl (bool): Whether to use encrypted HTTPS for the connection.
kibana_port (int): The Kibana web interface port (default 5601). Display-only,
used to build the "Open Kibana" link in the web sidebar.
"""
host: str
user: str
password: str
port: int
ssl: bool
kibana_port: int = 5601


class FullAgentConfig(BaseModel):
"""
The root validation model for the 'agent.yml' configuration file.

It acts as a single point of truth, validating that the Master, Windows Box,
and Splunk sections are correctly formatted and present before the agent starts.
Splunk and ElasticSearch sections are correctly formatted and present before the agent starts.
"""
master: MasterConfig
windows_box: WindowsBoxConfig
splunk: SplunkConfig
elasticsearch: ElasticConfig

Comment on lines +96 to 115

@singleton
Expand Down Expand Up @@ -204,6 +226,30 @@ def splunk_mport(self) -> int:
def splunk_ssl(self) -> bool:
return self.config_data.splunk.ssl

@property
def elastic_host(self) -> str:
return self.config_data.elasticsearch.host

@property
def elastic_user(self) -> str:
return self.config_data.elasticsearch.user

@property
def elastic_password(self) -> str:
return self.config_data.elasticsearch.password

@property
def elastic_port(self) -> int:
return self.config_data.elasticsearch.port

@property
def elastic_ssl(self) -> bool:
return self.config_data.elasticsearch.ssl

@property
def elastic_kibana_port(self) -> int:
return self.config_data.elasticsearch.kibana_port

def _is_standalone(self) -> bool:
"""
Determines if the Agent is running on the same physical or virtual host as the Master.
Expand Down
83 changes: 83 additions & 0 deletions OSIR/src/osir_lib/osir_lib/modules/indexer/indexer_elastic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os
from osir_lib.core.OsirDecorator import osir_internal_module
from osir_lib.core.model.OsirModuleModel import OsirModuleModel
from osir_lib.core.OsirModule import OsirModule
from osir_lib.logger import AppLogger, CustomLogger

logger: CustomLogger = AppLogger().get_logger()


@osir_internal_module
class InjectionModule():
"""
PyModule to inject parsed logs into ElasticSearch.

Mirrors the Splunk ``indexer_ng`` internal module: it iterates every child
module output directory and, for each child that carries an indexing
datamodel (``splunk:`` block, reused as-is for ElasticSearch since it is
SIEM-agnostic), runs the ``json2elastic-rs`` tool via the framework tool
runner.
"""

def __init__(self, module: OsirModule):
"""
Initializes the Module.

Args:
module (OsirModule): Instance of OsirModule containing configuration details for the extraction process.
"""
self.module = module
self.case_path = module.input.match

def __call__(self) -> bool:
"""
Execute the internal processor of the module.

Returns:
bool: True if the processing completes successfully, False otherwise.
"""
try:
logger.debug(f"Processing case {self.case_path}")
for module_dir_name in os.listdir(self.case_path):
if os.path.isdir(os.path.join(self.case_path, module_dir_name)):
try:
child_module_model = OsirModuleModel.from_name(module_dir_name)
except FileNotFoundError:
logger.warning(f"Skipping directory '{module_dir_name}' — no matching module found.")
continue # Skip to next directory
if child_module_model:
logger.debug(f"Module found {module_dir_name}")

# Dump model to add mandatory fields before validation : case_path and match
child_module_data = child_module_model.model_dump()
child_module_data["case_path"] = self.module.case_path
child_module_data["input"]["match"] = os.path.join(self.case_path, module_dir_name)

# Transform model to instance. Mandatory to get _module_filepath
child_module_instance = OsirModule.model_validate(child_module_data)

replacements = {
"indexer_path": child_module_instance._module_filepath,
"input_dir_replaced_by_internal_module": os.path.join(self.case_path, module_dir_name)
}
tool_with_place_holders = self.module.tool.cmd

# Backup the original command for next modules to ingest
self.module.tool.cmd = self.module.tool.safe_format(self.module.tool.cmd, **replacements)

# Run json2elastic-rs if an indexing datamodel exists. The
# ``splunk:`` block is reused as-is since it is SIEM-agnostic
# (file-matching patterns, timestamp extraction, VRL scripts);
# a dedicated ``elastic:`` block is also supported if present.
indexer_data = child_module_instance.splunk or getattr(child_module_instance, "elastic", None)
if indexer_data:
self.module.tool.run()
# Restore cmd with place holders
self.module.tool.cmd = tool_with_place_holders
else:
logger.warning(f"Module not found {module_dir_name}")

logger.debug(f"{self.module.module_name} done")

except Exception as exc:
logger.error_handler(exc)
15 changes: 11 additions & 4 deletions OSIR/src/osir_web/osir_web/utils/OsirWebSidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,26 @@ def sidebar():
# time.sleep(1)
# host = location_details["hostname"]

# Get splunk host
# Get splunk and elastic hosts
try:
agent_config = OsirAgentConfig()
host = agent_config.master_host
splunk_host = agent_config.splunk_host if agent_config.splunk_host not in ["host.docker.internal", "127.0.0.1"] else host
elastic_host = agent_config.elastic_host if agent_config.elastic_host not in ["host.docker.internal", "127.0.0.1"] else host
splunk_port = agent_config.splunk_port
kibana_port = agent_config.elastic_kibana_port
except FileNotFoundError:
# agent.yml missing -> happens if master launched before agent is installed
host = "localhost"
splunk_host = host

elastic_host = host
splunk_port = 8000
kibana_port = 5601
Comment on lines 67 to +80

# Set the URL for the iframe
# Set the URLs
url = f"http://{host}:80/"
splunk_url = f"http://{splunk_host}:8000/"
splunk_url = f"http://{splunk_host}:{splunk_port}/"
kibana_url = f"http://{elastic_host}:{kibana_port}/"
colored_header(
label="Useful links",
description="",
Expand All @@ -85,6 +91,7 @@ def sidebar():
with st.expander(":round_pushpin: External"):
st.page_link(url, label="Open Database", help="open a new tab to pgadmin", width='stretch', icon="💾")
st.page_link(splunk_url, label="Splunk", help="open a new tab to local Splunk server", width='stretch', icon="💹")
st.page_link(kibana_url, label="Kibana", help="open a new tab to local Kibana server", width='stretch', icon="📊")

# Master specs
colored_header(
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ OSIR is a dockerized and distributed parsing framework that works on Linux and W


# Table of Contents
- [OSIR](#osir)
- [Table of Contents](#table-of-contents)
- [Architecture](#architecture)
- [How does it work ?](#how-does-it-work-)
- [Quick Start](#quick-start)
- [Contributing](#contributing)
- [Quick start](#quick-start)
- [Clone the project including dependencies](#clone-the-project-including-dependencies)
- [Example of usage: parsing DFIR ORC triage on Ubuntu host](#example-of-usage-parsing-dfir-orc-triage-on-ubuntu-host)
- [Main features](#main-features)
- [Documentation](#documentation)
- [Currently supported modules](#currently-supported-modules)
- [Supported Modules](#supported-modules)
- [Contributing](#contributing)
- [Creators](#creators)
- [License](#license)
- [Other references](#other-references)

# Architecture

Expand Down Expand Up @@ -73,6 +78,7 @@ git clone --recurse-submodules https://github.com/maxspl/OSIR
- Dockerized installation
- Modular: processing tasks are defined by easily modifiable configuration files
- Splunk integration for output analysis
- ElasticSearch (ELK) integration for output analysis (choose Splunk, ElasticSearch or both at master setup)


# Documentation
Expand All @@ -84,7 +90,8 @@ Project documentation: https://osir.readthedocs.io
| OS | Filename | Description | Author | Version | Processor Type | Tool Path |
|:--------|:-------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|:---------------|----------:|:-------------------|:------------------------------------|
| generic | age_decrypt.yml | Used to decrypt age files. Don't forget to put the key in /OSIR/OSIR/configs/dependencies/encryption/key.age. | maxspl | 1 | external | age |
| generic | indexer-ng.yml | Splunk logs ingestion (DFIR ORC and UAC) using module-specific json2splunk-rs configuration. | maxspl | 1 | internal | json2splunk-rs |
| generic | indexer-ng.yml | Splunk logs ingestion (DFIR ORC and UAC) using module-specific json2splunk-rs configuration. | maxspl | 1 | internal | json2splunk-rs |
| generic | indexer_elastic.yml | ElasticSearch logs ingestion (DFIR ORC and UAC) using module-specific json2elastic-rs configuration. | Grand-Duc | 1 | internal | json2elastic-rs |
| generic | mongodb.yml | Splunk logs ingestion of Mongodb logs. | Typ | 1 | external | json2splunk-rs |
| generic | thor_lite.yml | Scan of collected file using Thor Lite. | typ | 1 | external | thor-lite/thor-lite-linux-64 |
| generic | thor_orc.yml | Scan of collected DFIR ORC (output of restore_fs module) file using Thor (requires Forensic license). | maxspl | 1 | external | thor/thor-linux-64 |
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ python-dotenv
rich
requests
tabulate
elasticsearch
Loading