Skip to content
Merged
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
37 changes: 37 additions & 0 deletions shodan/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Git
.git
.git*
.gitignore

# Python cache
__pycache__/
*.py[cod]

# Virtual environments
.venv/
venv/
env/

# Build / packaging
build/
dist/
*.egg-info/

# Environment files
.env
config.yml

# Logs
logs/

# Tests & coverage
.pytest_cache/
.coverage
htmlcov/

# IDE
.idea/
.vscode/

# OS
.DS_Store
32 changes: 32 additions & 0 deletions shodan/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Python cache
__pycache__/
*.py[cod]

# Virtual environments
.venv/
venv/
env/

# Build / packaging
build/
dist/
*.egg-info/

# Environment files
.env
config.yml

# Logs
logs/

# Tests & coverage
.pytest_cache/
.coverage
htmlcov/

# IDE
.idea/
.vscode/

# OS
.DS_Store
1 change: 1 addition & 0 deletions shodan/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WIP
29 changes: 29 additions & 0 deletions shodan/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM python:3.13-alpine AS build

WORKDIR /opt/openaev-injector-shodan

# Copy dependency files (pyproject.toml / poetry.lock / README.md)
COPY pyproject.toml poetry.lock* README.md ./

# Build dependencies
RUN apk add --no-cache --virtual .build-dependencies \
build-base git libffi-dev libxml2-dev libxslt-dev \
&& pip3 install --no-cache-dir poetry==2.2.1 \
&& poetry config virtualenvs.create false \
&& poetry install --only main --extras prod --no-interaction --no-ansi --no-root \
&& apk del .build-dependencies

# Copy the injector
COPY shodan ./shodan

FROM python:3.13-alpine AS runner

WORKDIR /opt/openaev-injector-shodan

# Runtime libraries
RUN apk add --no-cache libmagic libffi libxml2 libxslt

COPY --from=build /opt/openaev-injector-shodan/shodan ./shodan
COPY --from=build /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages

CMD ["python3", "-m", "shodan"]
392 changes: 392 additions & 0 deletions shodan/README.md

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions shodan/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: '3.8'
services:
injector-shodan:
image: openaev/injector-shodan:2.0.14
environment:
- OPENAEV_URL=http://localhost
- OPENAEV_TOKEN=ChangeMe
# - INJECTOR_ID=shodan--a87488ad-2c72-4592-b429-69259d7bcef1
# - INJECTOR_NAME=Shodan
# - INJECTOR_LOG_LEVEL=error
- SHODAN_API_KEY=ChangeMe
# - SHODAN_BASE_URL=https://api.shodan.io
# - SHODAN_API_LEAKY_BUCKET_RATE=10
# - SHODAN_API_LEAKY_BUCKET_CAPACITY=10
# - SHODAN_API_RETRY=5
# - SHODAN_API_BACKOFF=PT30S
restart: always
depends_on:
openaev:
condition: service_healthy
144 changes: 144 additions & 0 deletions shodan/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project.urls]
Homepage = "https://filigran.io/"
Repository = "https://github.com/OpenAEV-Platform/injectors/tree/main/shodan"
Documentation = "https://github.com/OpenAEV-Platform/injectors/tree/main/shodan/README.md"
Issues = "https://github.com/OpenAEV-Platform/injectors/issues"

[project]
name = "openaev-shodan-injector"
version = "2.0.14"
description = "An injector for running with Shodan"
readme = "README.md"
authors = [{ name = "Filigran", email = "contact@filigran.io" }]
license = "Apache-2.0"
requires-python = ">=3.11, <3.14"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"pydantic~=2.11.7",
"pydantic-settings~=2.11.0",
"requests~=2.32.5",
"limiter==0.5.0",
"tenacity~=9.1.2",
"rich~=14.2.0",
]

[project.optional-dependencies]
prod = [
"pyoaev ~=2.0.14"
]
current = [
"pyoaev @ git+https://github.com/OpenAEV-Platform/client-python.git@release/current"
]
dev = [
"black~=25.1", # Code formatter
"isort~=7.0.0", # Import sorter
"ruff~=0.14.2", # linter
"mypy~=1.18.2", # Type validator
"pip_audit~=2.9.0", # Security checker
"pre-commit~=4.3.0", # Git hooks
"types-PyYAML~=6.0.12", # stubs for untyped module
]
test = [
"pytest~=8.4.1",
"polyfactory~=2.22.2",
]

[tool.poetry]
packages = [{ include = "shodan" }]

[project.scripts]
ShodanInjector = "shodan.__main__:main"

[tool.setuptools.packages.find]
where = ["."]

[tool.isort]
profile = "black"
src_paths = ["shodan"]

[tool.pytest.ini_options]
testpaths = ["./tests"]

[tool.ruff]
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]

target-version = "py312"

[tool.ruff.lint]
# Never enforce `I001` (unsorted import). Already handle with isort
# Never enforce `E501` (line length violations). Already handle with black
# Never enforce `F821` (Undefined name `null`). incorrect issue with notebook
# Never enforce `D213` (Multi-line docstring summary should start at the second line) conflict with our docstring convention
# Never enforce `D211` (NoBlankLinesBeforeClass)`
# Never enforce `G004` (logging-f-string) Logging statement uses f-string
# Never enforce `TRY003`() Avoid specifying long messages outside the exception class not useful
# Never enforce `D104` (Missing docstring in public package)
# Never enforce `D407` (Missing dashed underline after section)
# Never enforce `D408` (Section underline should be in the line following the section’s name)
# Never enforce `D409` (Section underline should match the length of its name)
ignore = [
"I001",
"D203",
"E501",
"F821",
"D205",
"D213",
"D211",
"G004",
"TRY003",
"D104",
"D407",
"D408",
"D409",
]
select = ["E", "F", "W", "D", "G", "T", "B", "C", "N", "I", "S"]

[tool.mypy]
strict = true
exclude = [
'^tests',
'^docs',
'^build',
'^dist',
'^venv',
'^site-packages',
'^__pypackages__',
'^.venv',
]
plugins = ["pydantic.mypy"]
5 changes: 5 additions & 0 deletions shodan/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Local dependency (client-python)
-e ../../client-python

# Local project with extras (dev + test)
-e .[dev,test]
20 changes: 20 additions & 0 deletions shodan/shodan/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# OPENAEV Environment Variables
# base URL to reach the OpenAEV server
# note this URL must be routable from inside the container
# so `localhost` will most likely not work
OPENAEV_URL=ChangeMe
# admin account API token from the OpenAEV server
OPENAEV_TOKEN=ChangeMe

# INJECTOR Environment Variables
#INJECTOR_ID=shodan--a87488ad-2c72-4592-b429-69259d7bcef1
#INJECTOR_NAME=Shodan
#INJECTOR_LOG_LEVEL=error

# SHODAN Environment Variables
SHODAN_API_KEY=ChangeMe
#SHODAN_BASE_URL=https://api.shodan.io
#SHODAN_API_LEAKY_BUCKET_RATE=10
#SHODAN_API_LEAKY_BUCKET_CAPACITY=10
#SHODAN_API_RETRY=5
#SHODAN_API_BACKOFF=PT30S
3 changes: 3 additions & 0 deletions shodan/shodan/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from shodan.models import ConfigLoader

__all__ = ["ConfigLoader"]
63 changes: 63 additions & 0 deletions shodan/shodan/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Main entry point for the injector."""

import logging
import os
import sys
from pathlib import Path

from pydantic import ValidationError
from pyoaev.helpers import OpenAEVInjectorHelper

from shodan.contracts.shodan_contracts import ShodanContracts
from shodan.injector.openaev_shodan import ShodanInjector
from shodan.models import ConfigLoader

# from shodan.injector.exception import InjectorConfigError

LOG_PREFIX = "[SHODAN_MAIN]"


def main() -> None:
"""Define the main function to run the injector."""
logger = logging.getLogger(__name__)

try:
# Loading injector configuration
config = ConfigLoader()

# Build Shodan contracts and adapt config for the helper
shodan_contracts = ShodanContracts(config).contracts()
config_helper_adapter = config.to_config_injector_helper_adapter(
contracts=shodan_contracts
)

# Load the injector icon for the helper
icon_path = Path(__file__).parent / "img" / "icon-shodan.png"
icon_bytes = icon_path.read_bytes()

# Instantiate the OpenAEV injector helper
helper = OpenAEVInjectorHelper(config=config_helper_adapter, icon=icon_bytes)

logger.info(
f"{LOG_PREFIX} - Shodan injector configuration initialized successfully."
)

# Start the Shodan injector
injector = ShodanInjector(config, helper)
injector.start()

except ValidationError as err:
logger.error(f"{LOG_PREFIX} Configuration error: {err}")
sys.exit(2)

except KeyboardInterrupt:
logger.info(f"{LOG_PREFIX} Injector stopped by user (Ctrl+C)")
os._exit(0)

except Exception as err:
logger.exception(f"{LOG_PREFIX} Fatal error starting injector: {err}")
sys.exit(1)


if __name__ == "__main__":
main()
16 changes: 16 additions & 0 deletions shodan/shodan/config.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
openaev:
url: 'ChangeMe'
token: 'ChangeMe'

#injector:
# id: 'shodan--a87488ad-2c72-4592-b429-69259d7bcef1'
# name: 'Shodan'
# log_level: 'error'

shodan:
api_key: 'ChangeMe'
# base_url: 'https://api.shodan.io'
# api_leaky_bucket_rate: 10
# api_leaky_bucket_capacity: 10
# api_retry: 5
# api_backoff: 'PT30S'
Loading