Skip to content

Commit

Permalink
new: first 3.x release
Browse files Browse the repository at this point in the history
Changes:
- add support to custom 'out-of-package' MISP modules
- new extra '[all]' installs dependencies required by all modules
- default package installs only workflow-required dependencies
- remove all dependencies located in git repositories
- enforce single approach to discover package and file system modules
- add pre-commit hook to run black, isort, and flake8
- logging captures deprecation warnings
- flake8 linter runs against all modules
- remove custom module discovery logic when building the documentation
- remove all exported symbols where not needed
  • Loading branch information
ostefano committed Mar 5, 2025
1 parent 882338f commit 1062a85
Show file tree
Hide file tree
Showing 214 changed files with 19,521 additions and 13,703 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
run: python -m pip install poetry

- name: Install dependencies
run: poetry install --with unstable
run: poetry install -E all

- name: Build package
run: poetry build
Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/test-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,16 @@ jobs:
run: python -m pip install poetry

- name: Install dependencies
run: poetry install --with test,unstable
run: poetry install --with test -E all

- name: Build package
run: poetry build

- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
run: poetry run flake8 --extend-exclude=misp_modules/lib/,tests/,website/

- name: Run server in background
run: poetry run misp-modules -l 127.0.0.1 -s 2>error.log &
run: poetry run misp-modules -l 127.0.0.1 2>error.log &

- name: Sleep for 10 seconds
run: sleep 10s
Expand Down
5 changes: 2 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[submodule "misp_modules/lib/misp-objects"]
path = misp_modules/lib/misp-objects
[submodule "misp-objects"]
path = misp-objects
url = https://github.com/MISP/misp-objects.git
branch = main
43 changes: 43 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-toml
- id: debug-statements

- repo: local
hooks:
- id: black
name: black
entry: poetry run black
language: system
exclude: |
(?x)^(
website/.*
)$
types_or: [python, pyi]
require_serial: true

- id: isort
name: isort
entry: poetry run isort
language: system
exclude: |
(?x)^(
website/.*
)$
types_or: [ python, pyi ]
require_serial: true

- id: flake8
name: flake8
entry: poetry run flake8
language: system
exclude: |
(?x)^(
misp_modules/lib/.*|
tests/.*|
website/.*
)$
types_or: [ python, pyi ]
require_serial: true
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ USE_DOCKER ?=

prepare_docs:
@echo "Preparing documentation."
poetry install --with docs,unstable
poetry run python $(DOCS_SRC_DIR)/generate_documentation.py
poetry install --with docs -E all
poetry run python $(DOCS_SRC_DIR)/generate.py
mkdir -p $(DOCS_DIST_DIR)/logos
mkdir -p $(DOCS_DIST_DIR)/img
mkdir -p $(DOCS_DIST_DIR)/expansion/logos
Expand All @@ -38,7 +38,7 @@ ifeq ($(USE_DOCKER), true)
@echo "Generating documentation using '$(MKDOCS_DOCKER_IMAGE)'."
docker run --rm -it -v $(PWD):/docs $(MKDOCS_DOCKER_IMAGE) build
else
@echo "Generating docunentation."
@echo "Generating documentation."
poetry run mkdocs build
endif

Expand All @@ -48,7 +48,7 @@ ifeq ($(USE_DOCKER), true)
@echo "Deploying documentation using '$(MKDOCS_DOCKER_IMAGE)'."
docker run --rm -it -v $(PWD):/docs -v /home/$(whoami)/.docker:/root/.docker:ro $(MKDOCS_DOCKER_IMAGE) gh-deploy
else
@echo "Deploying docunentation."
@echo "Deploying documentation."
poetry run mkdocs gh-deploy
endif

Expand All @@ -58,6 +58,6 @@ ifeq ($(USE_DOCKER), true)
@echo "Serving documentation using '$(MKDOCS_DOCKER_IMAGE)'."
docker run --rm -it -v $(PWD):/docs -p 8000:8000 $(MKDOCS_DOCKER_IMAGE)
else
@echo "Serving docunentation."
@echo "Serving documentation."
poetry run mkdocs serve
endif
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ For further Information see the [license file](https://misp.github.io/misp-modul
* [GeoIP City Lookup](https://misp.github.io/misp-modules/expansion/#geoip-city-lookup) - An expansion module to query a local copy of Maxmind's Geolite database with an IP address, in order to get information about the city where it is located.
* [GeoIP Country Lookup](https://misp.github.io/misp-modules/expansion/#geoip-country-lookup) - Query a local copy of Maxminds Geolite database, updated for MMDB format
* [Google Safe Browsing Lookup](https://misp.github.io/misp-modules/expansion/#google-safe-browsing-lookup) - Google safe browsing expansion module
* [Google Search](https://misp.github.io/misp-modules/expansion/#google-search) - An expansion hover module to expand google search information about an URL
* [Google Threat Intelligence Lookup](https://misp.github.io/misp-modules/expansion/#google-threat-intelligence-lookup) - An expansion module to have the observable's threat score assessed by Google Threat Intelligence.
* [GreyNoise Lookup](https://misp.github.io/misp-modules/expansion/#greynoise-lookup) - Module to query IP and CVE information from GreyNoise
* [Hashdd Lookup](https://misp.github.io/misp-modules/expansion/#hashdd-lookup) - A hover module to check hashes against hashdd.com including NSLR dataset.
Expand Down Expand Up @@ -159,6 +158,7 @@ For further Information see the [license file](https://misp.github.io/misp-modul
* [Nexthink NXQL Export](https://misp.github.io/misp-modules/export_mod/#nexthink-nxql-export) - Nexthink NXQL query export module
* [OSQuery Export](https://misp.github.io/misp-modules/export_mod/#osquery-export) - OSQuery export of a MISP event.
* [Event to PDF Export](https://misp.github.io/misp-modules/export_mod/#event-to-pdf-export) - Simple export of a MISP event to PDF.
* [Test Export](https://misp.github.io/misp-modules/export_mod/#test-export) - Skeleton export module.
* [ThreatStream Export](https://misp.github.io/misp-modules/export_mod/#threatstream-export) - Module to export a structured CSV file for uploading to threatStream.
* [ThreadConnect Export](https://misp.github.io/misp-modules/export_mod/#threadconnect-export) - Module to export a structured CSV file for uploading to ThreatConnect.
* [VirusTotal Collections Export](https://misp.github.io/misp-modules/export_mod/#virustotal-collections-export) - Creates a VT Collection from an event iocs.
Expand All @@ -178,6 +178,7 @@ For further Information see the [license file](https://misp.github.io/misp-modul
* [OCR Import](https://misp.github.io/misp-modules/import_mod/#ocr-import) - Optical Character Recognition (OCR) module for MISP.
* [OpenIOC Import](https://misp.github.io/misp-modules/import_mod/#openioc-import) - Module to import OpenIOC packages.
* [TAXII 2.1 Import](https://misp.github.io/misp-modules/import_mod/#taxii-2.1-import) - Import content from a TAXII 2.1 server
* [CSV Test Import](https://misp.github.io/misp-modules/import_mod/#csv-test-import) - Simple CSV import tool with mapable columns
* [ThreadAnalyzer Sandbox Import](https://misp.github.io/misp-modules/import_mod/#threadanalyzer-sandbox-import) - Module to import ThreatAnalyzer archive.zip / analysis.json files.
* [URL Import](https://misp.github.io/misp-modules/import_mod/#url-import) - Simple URL import tool with Faup
* [VMRay API Import](https://misp.github.io/misp-modules/import_mod/#vmray-api-import) - Module to import VMRay (VTI) results.
Expand Down
47 changes: 23 additions & 24 deletions documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -980,30 +980,6 @@ Google safe browsing expansion module
-----
#### [Google Search](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_search.py)
<img src=logos/google.png height=60>
An expansion hover module to expand google search information about an URL
[[source code](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_search.py)]
- **features**:
>The module takes an url as input to query the Google search API. The result of the query is then return as raw text.
- **input**:
>An url attribute.
- **output**:
>Text containing the result of a Google search on the input url.
- **references**:
>https://github.com/abenassi/Google-Search-API
- **requirements**:
>The python Google Search API library
-----
#### [Google Threat Intelligence Lookup](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_threat_intelligence.py)
<img src=logos/google_threat_intelligence.png height=60>
Expand Down Expand Up @@ -1272,6 +1248,9 @@ Module to query an IP ASN history service (https://github.com/D4-project/IPASN-H
- **features**:
>This module takes an IP address attribute as input and queries the CIRCL IPASN service. The result of the query is the latest asn related to the IP address, that is returned as a MISP object.

- **config**:
>custom_api

- **input**:
>An IP address MISP attribute.

Expand Down Expand Up @@ -3393,6 +3372,16 @@ Simple export of a MISP event to PDF.
-----
#### [Test Export](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/export_mod/testexport.py)
Skeleton export module.
[[source code](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/export_mod/testexport.py)]
- **features**:
>
-----
#### [ThreatStream Export](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/export_mod/threatStream_misp_export.py)
<img src=logos/threatstream.png height=60>
Expand Down Expand Up @@ -3794,6 +3783,16 @@ Import content from a TAXII 2.1 server

-----

#### [CSV Test Import](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/import_mod/testimport.py)

Simple CSV import tool with mapable columns
[[source code](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/import_mod/testimport.py)]

- **features**:
>

-----

#### [ThreadAnalyzer Sandbox Import](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/import_mod/threatanalyzer_import.py)

Module to import ThreatAnalyzer archive.zip / analysis.json files.
Expand Down
179 changes: 179 additions & 0 deletions documentation/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#!/usr/bin/env python
import collections
import copy
import importlib
import importlib.resources
import logging
import os
import pathlib
import sys

logging.captureWarnings(True)


import misp_modules

GH_LINK = "https://github.com/MISP/misp-modules/tree/main/misp_modules/modules"
GH_DOC_LINK = "https://misp.github.io/misp-modules"

MODULE_TYPE_TITLE = {
misp_modules.ModuleType.EXPANSION.value: "Expansion Modules",
misp_modules.ModuleType.EXPORT_MOD.value: "Export Modules",
misp_modules.ModuleType.IMPORT_MOD.value: "Import Modules",
misp_modules.ModuleType.ACTION_MOD.value: "Action Modules",
}
MODULE_INFO_TO_IGNORE = ["module-type", "author", "version"]

# ./
BASE = pathlib.Path(__file__).resolve().parent.parent
# ./documentation/
DOC_ROOT = pathlib.Path(__file__).resolve().parent
# ./misp_modules/
SRC_ROOT = pathlib.Path(misp_modules.__file__).resolve().parent

ALL_MODULE_INFO = collections.defaultdict(dict)


def _get_all_module_info() -> dict:
if not ALL_MODULE_INFO:
# Load libraries as root modules
misp_modules.promote_lib_to_root()
for module_type, module in misp_modules.iterate_modules(SRC_ROOT.joinpath(misp_modules.MODULES_DIR)):
module_name = os.path.splitext(module.name)[0]
module_package_name = (
f"{misp_modules.__package__}.{misp_modules.MODULES_DIR}.{module_type.name}.{module_name}"
)
try:
module = importlib.import_module(module_package_name)
module_info = copy.deepcopy(module.version())
except ImportError:
continue # skip if we have issues loading the module
ALL_MODULE_INFO[module_type.name][module_name] = dict(sorted(module_info.items()))

# sort for good measure
for module_type in list(ALL_MODULE_INFO.keys()):
ALL_MODULE_INFO[module_type] = dict(sorted(ALL_MODULE_INFO[module_type].items(), key=lambda item: item[0]))
return ALL_MODULE_INFO


def _generate_doc(module_type: str, logo_path: str = "logos") -> list[str]:
markdown = []
gh_path = f"{GH_LINK}/{module_type}"
for module_name, module_info in _get_all_module_info()[module_type].items():
gh_ref = f"{gh_path}/{module_name}.py"
module_info = copy.deepcopy(module_info)
for i in MODULE_INFO_TO_IGNORE:
module_info.pop(i)
try:
module_name_pretty = module_info.pop("name")
except KeyError:
exit(f"ERROR: Issue with module {module_name} - no field 'name' provided")
if module_name_pretty == "":
module_name_pretty = module_name
markdown.append(f"\n#### [{module_name_pretty}]({gh_ref})\n")
if module_info["logo"] != "":
logo = os.path.join(logo_path, module_info.pop("logo"))
markdown.append(f"\n<img src={logo} height=60>\n")
if "description" in module_info:
markdown.append(f"\n{module_info.pop('description')}\n")
markdown.append(f"[[source code]({gh_ref})]\n")
if "features" in module_info:
markdown.append(_get_single_value("features", str(module_info.pop("features")).replace("\n", "\n>")))
for field, value in sorted(module_info.items()):
if not value:
continue
if isinstance(value, list):
markdown.append(_handle_list(field, value))
continue
markdown.append(_get_single_value(field, str(value).replace("\n", "\n>")))
markdown.append("\n-----\n")
return markdown


def _generate_index_doc(module_type: str) -> list[str]:
markdown = []
for module_name, module_info in _get_all_module_info()[module_type].items():
module_name_pretty = module_info.get("name", module_name)
anchor_ref = f"{GH_DOC_LINK}/{module_type}/#{module_name_pretty.replace(' ', '-').lower()}"
description_without_newlines = module_info.get("description").replace("\n", " ")
markdown.append(f"* [{module_name_pretty}]({anchor_ref}) - {description_without_newlines}\n")
return markdown


def _get_single_value(field: str, value: str) -> str:
return f"\n- **{field}**:\n>{value}\n"


def _handle_list(field: str, values: list[str]) -> str:
if len(values) == 1:
return _get_single_value(field, values[0])
values = "\n> - ".join(values)
return f"\n- **{field}**:\n> - {values}\n"


def write_doc_for_readme():
markdown = ["# MISP modules documentation\n"]
for path, title in MODULE_TYPE_TITLE.items():
markdown.append(f"\n## {title}\n")
markdown.extend(_generate_doc(path))
with open(DOC_ROOT.joinpath("README.md"), "w") as w:
w.write("".join(markdown))


def write_docs_for_mkdocs():
for path, title in MODULE_TYPE_TITLE.items():
markdown = _generate_doc(path, logo_path="../logos")
with open(os.path.join(DOC_ROOT.joinpath("mkdocs", f"{path}.md")), "w") as w:
w.write("".join(markdown))


def update_docs_for_mkdocs_index():
with open(DOC_ROOT.joinpath("mkdocs", "index.md"), "r") as r:
old_doc = r.readlines()
new_doc = []
skip = False
for line in old_doc:
if skip and not line.startswith("## "): # find next title
continue # skip lines, as we're in the block that we're auto-generating
skip = False
new_doc.append(line)
if line.startswith("## Existing MISP modules"):
skip = True
for path, title in MODULE_TYPE_TITLE.items():
new_doc.append(f"\n### {title}\n")
new_doc.extend(_generate_index_doc(path))
new_doc.append("\n\n")
with open(DOC_ROOT.joinpath("mkdocs", "index.md"), "w") as w:
w.write("".join(new_doc))


def update_readme():
with open(BASE.joinpath("README.md"), "r") as r:
old_readme = r.readlines()
new_doc = []
skip = False
for line in old_readme:
if skip and not line.startswith("# List of MISP modules"): # find next title
continue # skip lines, as we're in the block that we're auto-generating
new_doc.append(line)
if line.startswith("# List of MISP modules"):
skip = True
for path, title in MODULE_TYPE_TITLE.items():
new_doc.append(f"\n## {title}\n")
new_doc.extend(_generate_index_doc(path))
new_doc.append("\n\n")
with open(BASE.joinpath("README.md"), "w") as w:
w.write("".join(new_doc))


def main():
"""Generate documentation."""
write_doc_for_readme()
write_docs_for_mkdocs()
update_docs_for_mkdocs_index()
update_readme()
return 0


if __name__ == "__main__":
sys.exit(main())
Loading

0 comments on commit 1062a85

Please sign in to comment.