Skip to content
Draft
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
10 changes: 10 additions & 0 deletions prowler/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# OpenAEV connection
OPENAEV_URL=ChangeMe
OPENAEV_TOKEN=ChangeMe
# Optional for multi-tenancy (Enterprise Edition)
OPENAEV_TENANT_ID=ChangeMe

# Injector identity and logging
INJECTOR_ID=ChangeMe
INJECTOR_NAME=Prowler
INJECTOR_LOG_LEVEL=error
10 changes: 10 additions & 0 deletions prowler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
config.yml
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
.mypy_cache/
.ruff_cache/
build/
dist/
*.egg-info/
28 changes: 28 additions & 0 deletions prowler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# OpenAEV Prowler Injector

The Prowler injector foundation registers Prowler with OpenAEV. Assessment
providers, credentials, execution, mapping, routes, and contracts are outside
CHK.001 and are intentionally not configured here.

## Configuration

| Environment variable | Configuration key | Purpose |
|---|---|---|
| `OPENAEV_URL` | `openaev.url` | OpenAEV server URL |
| `OPENAEV_TOKEN` | `openaev.token` | OpenAEV API token |
| `OPENAEV_TENANT_ID` | `openaev.tenant_id` | Optional tenant identifier |
| `INJECTOR_ID` | `injector.id` | Unique injector identifier |
| `INJECTOR_NAME` | `injector.name` | Injector display name |
| `INJECTOR_LOG_LEVEL` | `injector.log_level` | Runtime log level |

Copy `config.yml.sample` to the ignored `config.yml` for local use, or supply
the equivalent environment variables. Never commit real tokens.

## Run

```shell
python -m prowler
```

The CHK.001 foundation starts with zero assessment contracts. Contract catalog
registration is deferred to CHK.006.
10 changes: 10 additions & 0 deletions prowler/config.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
openaev:
url: 'ChangeMe'
token: 'ChangeMe'
# Optional for multi-tenancy (Enterprise Edition)
tenant_id: 'ChangeMe'

injector:
id: 'ChangeMe'
name: 'Prowler'
log_level: 'error'
18 changes: 18 additions & 0 deletions prowler/manifest-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"title": "Prowler",
"slug": "openaev_prowler",
"description": "Run Prowler cloud security assessments from OpenAEV.",
"short_description": "Run cloud security assessments with Prowler",
"use_cases": ["Technical"],
"verified": false,
"last_verified_date": "",
"playbook_supported": false,
"max_confidence_level": 80,
"support_version": "",
"subscription_link": "",
"source_code": "",
"manager_supported": true,
"container_version": "rolling",
"container_image": "openaev/injector-prowler",
"container_type": "INJECTOR"
}
1 change: 1 addition & 0 deletions prowler/prowler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Prowler injector package."""
38 changes: 38 additions & 0 deletions prowler/prowler/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Main entry point for the Prowler injector."""

import logging
import sys

from pydantic import ValidationError
from pyoaev.helpers import OpenAEVConfigHelper, OpenAEVInjectorHelper

from prowler.injector import ProwlerInjector
from prowler.models import ConfigLoader

LOG_PREFIX = "[PROWLER_MAIN]"


def main() -> None:
"""Load configuration and start the injector."""
logger = logging.getLogger(__name__)
try:
config = ConfigLoader()
helper = OpenAEVInjectorHelper(
config=OpenAEVConfigHelper.from_configuration_object(
config.to_daemon_config()
),
icon=None,
)
ProwlerInjector(config=config, helper=helper).start()
except ValidationError:
logger.error("[PROWLER_MAIN] Configuration error")
sys.exit(2)
except KeyboardInterrupt:
logger.info("%s Injector stopped by user", LOG_PREFIX)
except Exception:
logger.error("[PROWLER_MAIN] Fatal startup error")
sys.exit(1)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions prowler/prowler/injector/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Prowler injector runtime."""

from prowler.injector.openaev_prowler import ProwlerInjector

__all__ = ["ProwlerInjector"]
23 changes: 23 additions & 0 deletions prowler/prowler/injector/openaev_prowler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""OpenAEV runtime boundary for the Prowler injector."""

from pyoaev.helpers import OpenAEVInjectorHelper

from prowler.models import ConfigLoader


class ProwlerInjector:
"""Register the foundation injector without assessment contracts."""

def __init__(self, config: ConfigLoader, helper: OpenAEVInjectorHelper) -> None:
"""Initialize the injector with its configuration and helper."""
self.config = config
self.helper = helper

def start(self) -> None:
"""Start the injector listener after zero-contract registration."""
self.config.to_daemon_config()
self.helper.listen(message_callback=self.process_message)

def process_message(self, _data: dict[str, object]) -> None:
"""Reject execution because CHK.001 registers no contracts."""
raise RuntimeError("Prowler assessment contracts are not registered")
5 changes: 5 additions & 0 deletions prowler/prowler/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Prowler injector models."""

from prowler.models.configs import ConfigLoader

__all__ = ["ConfigLoader"]
5 changes: 5 additions & 0 deletions prowler/prowler/models/configs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Prowler injector configuration models."""

from prowler.models.configs.config_loader import ConfigLoader

__all__ = ["ConfigLoader"]
42 changes: 42 additions & 0 deletions prowler/prowler/models/configs/config_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Configuration foundation for the Prowler injector."""

from pydantic import Field
from pyoaev.configuration import (
ConfigLoaderCollector,
ConfigLoaderOAEV,
Configuration,
SettingsLoader,
)


class InjectorConfig(ConfigLoaderCollector):
"""Standard injector identity and logging settings."""

name: str = Field(default="Prowler", description="Name of the injector.")
icon_filepath: str | None = Field(
default=None, description="Optional path to an injector icon."
)


class ConfigLoader(SettingsLoader):
"""Load only the standard OpenAEV and injector settings."""

openaev: ConfigLoaderOAEV = Field(default_factory=ConfigLoaderOAEV)
injector: InjectorConfig = Field(default_factory=InjectorConfig)

def to_daemon_config(self) -> Configuration:
"""Translate settings into the OpenAEV daemon configuration."""
return Configuration(
config_hints={
"openaev_url": {"data": str(self.openaev.url)},
"openaev_token": {"data": self.openaev.token},
"openaev_tenant_id": {"data": getattr(self.openaev, "tenant_id", None)},
"injector_id": {"data": self.injector.id},
"injector_name": {"data": self.injector.name},
"injector_type": {"data": "openaev_prowler"},
"injector_contracts": {"data": []},
"injector_log_level": {"data": self.injector.log_level},
"injector_icon_filepath": {"data": self.injector.icon_filepath},
},
config_base_model=self,
)
64 changes: 64 additions & 0 deletions prowler/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[project]
name = "openaev-prowler-injector"
version = "0.1.0"
description = "OpenAEV injector for Prowler cloud security assessments"
readme = "README.md"
authors = [{ name = "Filigran", email = "contact@filigran.io" }]
license = "Apache-2.0"
requires-python = ">=3.11,<4.0"
dependencies = [
"pyoaev==2.3.5",
"pydantic>=2.11.3,<2.14.0",
"pydantic-settings>=2.11.0,<2.15.0",
]

[project.scripts]
ProwlerInjector = "prowler.__main__:main"

[project.optional-dependencies]
dev = [
"black>=26.3.1,<26.4.0",
"build>=1.4.0,<1.5.0",
"isort>=8.0.1,<8.1.0",
"mypy~=1.18.2",
"pytest>=9.0.0,<9.1.0",
"ruff~=0.14.2",
]

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

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

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

[tool.ruff]
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "W", "D", "B", "I", "S"]
ignore = ["D104", "D203", "D211", "D213"]

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "S106"]

[tool.mypy]
strict = true
plugins = ["pydantic.mypy"]

[[tool.mypy.overrides]]
module = ["prowler.__main__", "prowler.injector.*"]
# pyoaev 2.3.5 does not publish a py.typed marker or external stubs.
disable_error_code = ["import-untyped"]

[[tool.mypy.overrides]]
module = ["prowler.models.configs.config_loader"]
# These two pyoaev base classes are consequently exposed to mypy as Any.
disable_error_code = ["import-untyped", "misc"]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file added prowler/tests/__init__.py
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Feature: Prowler catalog registration and project scaffold
Scenario: Discoverable Prowler catalog registration
Given a Prowler injector project is available
When its catalog registration and configuration are completed
Then Prowler is discoverable in the OpenAEV catalog
And its available configuration is clearly identified

# ---- Constraints identified ----
Scenario: Foundation configuration excludes future provider settings
Given CHK.001 owns only the injector configuration foundation
When the available configuration is inspected
Then only the standard OpenAEV and injector settings are present

Scenario: Foundation startup registers no assessment contracts
Given assessment contracts are deferred to CHK.006
When the Prowler injector starts
Then it registers with an empty contract catalog

Scenario Outline: Startup failures are logged without sensitive exception details
Given startup fails with a <failure type> containing a sensitive canary
When the Prowler injector handles the startup failure
Then it emits a safe ERROR log without the exception or a traceback
And it exits with status <exit status>

Examples:
| failure type | exit status |
| configuration error | 2 |
| unexpected exception | 1 |
14 changes: 14 additions & 0 deletions prowler/tests/behaviour/chk001_catalog_scaffold/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Fixtures local to the CHK.001 catalog scaffold behaviour tests."""

import pytest


@pytest.fixture
def standard_injector_environment(monkeypatch: pytest.MonkeyPatch) -> None:
"""Configure the standard injector environment owned by CHK.001."""
monkeypatch.setenv("OPENAEV_URL", "http://localhost:8080")
monkeypatch.setenv("OPENAEV_TOKEN", "test-token")
monkeypatch.setenv("OPENAEV_TENANT_ID", "test-tenant")
monkeypatch.setenv("INJECTOR_ID", "test-injector")
monkeypatch.setenv("INJECTOR_NAME", "Prowler")
monkeypatch.setenv("INJECTOR_LOG_LEVEL", "debug")
Loading
Loading