chore(merge): master

This commit is contained in:
pedrooot committed 2026-06-09 17:59:00 +02:00
commit 510da0154a
1307 files changed
+91297 -67361

No files matched your search

+75 -36
View File
@@ -8,7 +8,77 @@ This guide explains the AI Skills system that provides on-demand context and pat
**What are AI Skills?** Skills are structured instructions that help AI agents (Claude Code, Cursor, Copilot, etc.) understand Prowler's conventions, patterns, and best practices.
</Info>
## Architecture Overview
Skills live in the [`skills/`](https://github.com/prowler-cloud/prowler/tree/master/skills) directory of the Prowler OSS repository. Each skill is a folder containing a `SKILL.md` file with its patterns and metadata.
## Installation
To enable skills for the supported AI coding assistants, run the setup script from the repository root:
```bash
./skills/setup.sh
```
The script creates symlinks so each tool finds the skills in its expected location:
| Tool | Created by setup |
|------|------------------|
| Claude Code | `.claude/skills/` symlink and `CLAUDE.md` |
| Gemini CLI | `.gemini/skills/` symlink and `GEMINI.md` |
| Codex (OpenAI) | `.codex/skills/` symlink (uses `AGENTS.md` natively) |
| GitHub Copilot | `.github/copilot-instructions.md` symlink to `AGENTS.md` |
After running the setup, restart the AI coding assistant to load the skills.
## Using Skills
AI agents discover skills automatically and load them when a request matches a skill trigger. To load a skill manually during a session, point the agent to the skill's `SKILL.md` file:
```text
Read skills/{skill-name}/SKILL.md
```
For the full list of available skills, their triggers, and the Auto-invoke mappings, see the [`skills/README.md`](https://github.com/prowler-cloud/prowler/blob/master/skills/README.md) and [`AGENTS.md`](https://github.com/prowler-cloud/prowler/blob/master/AGENTS.md) in the repository.
## Available Skills
| Type | Skills |
|------|--------|
| **Generic** | typescript, react-19, nextjs-16, tailwind-4, pytest, playwright, django-drf, zod-4, zustand-5, ai-sdk-5, vitest, tdd |
| **Prowler** | prowler, prowler-sdk-check, prowler-api, prowler-ui, prowler-mcp, prowler-provider, prowler-compliance, prowler-compliance-review, prowler-docs, prowler-pr, prowler-ci, prowler-attack-paths-query |
| **Testing** | prowler-test-sdk, prowler-test-api, prowler-test-ui |
| **Meta** | skill-creator, skill-sync |
<Note>
This table is a snapshot. The repository is the source of truth: see [`skills/README.md`](https://github.com/prowler-cloud/prowler/blob/master/skills/README.md) for the current, complete list.
</Note>
## Skill Structure
Each skill follows the [Agent Skills spec](https://agentskills.io):
```text
skills/{skill-name}/
├── SKILL.md # Patterns, rules, decision trees
├── assets/ # Code templates, schemas
└── references/ # Links to local docs (single source of truth)
```
## Key Design Decisions
1. **Self-contained skills** - Critical patterns inline for fast loading
2. **Local doc references** - No web URLs, points to `docs/developer-guide/*.mdx`
3. **Single source of truth** - Skills reference docs, no duplication
4. **On-demand loading** - AI loads only what's needed for the task
## Creating New Skills
Use the `skill-creator` meta-skill to create new skills that follow the Agent Skills spec. See [`AGENTS.md`](https://github.com/prowler-cloud/prowler/blob/master/AGENTS.md) for the full list of available skills and their triggers.
## How Skills Work
The diagrams below explain the internals of the skill system. They are useful for understanding the design, but are not required to install or use skills.
### Architecture Overview
```mermaid
graph LR
@@ -28,7 +98,7 @@ graph LR
style F fill:#1a4d2e,stroke:#66bb6a,color:#fff
```
## How It Works
### Request Lifecycle
```mermaid
sequenceDiagram
@@ -68,7 +138,7 @@ sequenceDiagram
A->>U: Creates check with correct patterns
```
## Before vs After
### With and Without Skills
```mermaid
graph TD
@@ -96,7 +166,7 @@ graph TD
style AFTER fill:#1a4d1a,stroke:#66bb6a,color:#fff
```
## Complete Architecture
### Full Component Map
```mermaid
flowchart TB
@@ -110,7 +180,7 @@ flowchart TB
subgraph GENERIC["Generic Skills"]
G1["typescript"]
G2["react-19"]
G3["nextjs-15"]
G3["nextjs-16"]
G4["tailwind-4"]
G5["pytest"]
G6["playwright"]
@@ -186,34 +256,3 @@ flowchart TB
style STRUCTURE fill:#5c3d1a,stroke:#ffb74d,color:#fff
style DOCS fill:#1a3d4d,stroke:#4dd0e1,color:#fff
```
## Skills Included
| Type | Skills |
|------|--------|
| **Generic** | typescript, react-19, nextjs-15, tailwind-4, pytest, playwright, django-drf, zod-4, zustand-5, ai-sdk-5 |
| **Prowler** | prowler, prowler-sdk-check, prowler-api, prowler-ui, prowler-mcp, prowler-provider, prowler-compliance, prowler-compliance-review, prowler-docs, prowler-pr, prowler-ci |
| **Testing** | prowler-test-sdk, prowler-test-api, prowler-test-ui |
| **Meta** | skill-creator, skill-sync |
## Skill Structure
Each skill follows the [Agent Skills spec](https://agentskills.io):
```
skills/{skill-name}/
├── SKILL.md # Patterns, rules, decision trees
├── assets/ # Code templates, schemas
└── references/ # Links to local docs (single source of truth)
```
## Key Design Decisions
1. **Self-contained skills** - Critical patterns inline for fast loading
2. **Local doc references** - No web URLs, points to `docs/developer-guide/*.mdx`
3. **Single source of truth** - Skills reference docs, no duplication
4. **On-demand loading** - AI loads only what's needed for the task
## Creating New Skills
Use the `skill-creator` meta-skill to create new skills that follow the Agent Skills spec. See `AGENTS.md` for the full list of available skills and their triggers.
+2 -2
View File
@@ -20,8 +20,8 @@ The most common high level steps to create a new check are:
3. Create a check-specific folder. The path should follow this pattern: `prowler/providers/<provider>/services/<service>/<check_name_want_to_implement>`. Adhere to the [Naming Format for Checks](#naming-format-for-checks).
4. Populate the folder with files as specified in [File Creation](#file-creation).
5. Run the check locally to ensure it works as expected. For checking you can use the CLI in the next way:
- To ensure the check has been detected by Prowler: `poetry run python prowler-cli.py <provider> --list-checks | grep <check_name>`.
- To run the check, to find possible issues: `poetry run python prowler-cli.py <provider> --log-level ERROR --verbose --check <check_name>`.
- To ensure the check has been detected by Prowler: `uv run python prowler-cli.py <provider> --list-checks | grep <check_name>`.
- To run the check, to find possible issues: `uv run python prowler-cli.py <provider> --log-level ERROR --verbose --check <check_name>`.
6. Create comprehensive tests for the check that cover multiple scenarios including both PASS (compliant) and FAIL (non-compliant) cases. For detailed information about test structure and implementation guidelines, refer to the [Testing](/developer-guide/unit-testing) documentation.
7. If the check and its corresponding tests are working as expected, you can submit a PR to Prowler.
+1 -1
View File
@@ -28,7 +28,7 @@ This includes the [AGENTS.md](https://github.com/prowler-cloud/prowler/blob/mast
<Steps>
<Step title="Install Mintlify CLI">
```bash
npm i -g mint
npm install --global mint@4.2.560
```
For detailed instructions, check the [Mintlify documentation](https://www.mintlify.com/docs/installation).
</Step>
+10 -17
View File
@@ -80,7 +80,7 @@ Before proceeding, ensure the following:
- Git is installed.
- Python 3.10 or higher is installed.
- `poetry` is installed to manage dependencies.
- `uv` is installed to manage dependencies.
### Forking the Prowler Repository
@@ -97,28 +97,21 @@ cd prowler
### Dependency Management and Environment Isolation
To prevent conflicts between environments, we recommend using `poetry`, a Python dependency management solution. Install it by following the [instructions](https://python-poetry.org/docs/#installation).
To prevent conflicts between environments, we recommend using [`uv`](https://docs.astral.sh/uv/), a fast Python package and project manager. Install it by following the [official instructions](https://docs.astral.sh/uv/getting-started/installation/).
### Installing Dependencies
To install all required dependencies, including those needed for development, run:
```
poetry install --with dev
eval $(poetry env activate)
uv sync
source .venv/bin/activate
```
<Warning>
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
In case you have any doubts, consult the [Poetry environment activation guide](https://python-poetry.org/docs/managing-environments/#activating-the-environment).
</Warning>
### Pre-Commit Hooks
This repository uses Git pre-commit hooks managed by the [prek](https://prek.j178.dev/) tool, it is installed with `poetry install --with dev`. Next, run the following command in the root of this repository:
This repository uses Git pre-commit hooks managed by the [prek](https://prek.j178.dev/) tool, it is installed with `uv sync`. Next, run the following command in the root of this repository:
```shell
prek install
@@ -155,11 +148,11 @@ Once installed, TruffleHog runs before each push and blocks the operation when v
Before merging pull requests, several automated checks and utilities ensure code security and updated dependencies:
<Note>
These should have been already installed if `poetry install --with dev` was already run.
These should have been already installed if `uv sync` was already run.
</Note>
- [`bandit`](https://pypi.org/project/bandit/) for code security review.
- [`safety`](https://pypi.org/project/safety/) and [`dependabot`](https://github.com/features/security) for dependencies.
- [`osv-scanner`](https://github.com/google/osv-scanner) and [`dependabot`](https://github.com/features/security) for dependencies.
- [`hadolint`](https://github.com/hadolint/hadolint) and [`dockle`](https://github.com/goodwithtech/dockle) for container security.
- [`Snyk`](https://docs.snyk.io/integrations/snyk-container-integrations/container-security-with-docker-hub-integration) for container security in Docker Hub.
- [`clair`](https://github.com/quay/clair) for container security in Amazon ECR.
@@ -183,7 +176,7 @@ These resources help ensure that AI-assisted contributions maintain consistency
All dependencies are listed in the `pyproject.toml` file.
The SDK keeps direct dependencies pinned to exact versions, while `poetry.lock` records the full resolved dependency tree and the artifact hashes for every package. Use `poetry install` from the lock file instead of ad-hoc `pip` installs when you need a reproducible environment.
The SDK keeps direct dependencies pinned to exact versions, while `uv.lock` records the full resolved dependency tree and the artifact hashes for every package. Use `uv sync` from the lock file instead of ad-hoc `pip` installs when you need a reproducible environment.
For proper code documentation, refer to the following and follow the code documentation practices presented there: [Google Python Style Guide - Comments and Docstrings](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings).
@@ -209,8 +202,8 @@ prowler/
├── contrib/ # Community-contributed scripts or modules
├── kubernetes/ # Kubernetes deployment files
├── .github/ # GitHub-related files (workflows, issue templates, etc.)
├── pyproject.toml # Python project configuration (Poetry)
├── poetry.lock # Poetry lock file
├── pyproject.toml # Python project configuration (uv)
├── uv.lock # uv lock file
├── README.md # Project overview and getting started
├── Makefile # Common development commands
├── Dockerfile # SDK Docker container
+6 -4
View File
@@ -1277,10 +1277,12 @@ Dependencies ensure that your provider's required libraries are available when P
**File:** `pyproject.toml`
```toml
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
# ... other dependencies
your-sdk-library = "^1.0.0" # Add your SDK dependency
[project]
requires-python = ">=3.10,<3.13"
dependencies = [
# ... other dependencies
"your-sdk-library>=1.0.0,<2.0.0", # Add your SDK dependency
]
```
#### Step 18: Create Tests
@@ -2,40 +2,228 @@
title: 'Creating a New Security Compliance Framework in Prowler'
---
This guide explains how to add a new security compliance framework to Prowler, end to end. It covers directory layout, the JSON schema, check mapping conventions, the Pydantic models that validate each framework, the CSV output formatter, local validation, testing, and the pull request process.
This guide explains how to add a new security compliance framework to Prowler, end to end. It covers directory layout, the two supported JSON schemas (universal and legacy), the Pydantic models that validate each framework, check mapping conventions, output formatting, local validation, testing, and the pull request process.
## Introduction
A compliance framework in Prowler maps a public or custom control catalog (for example CIS, NIST 800-53, PCI DSS, HIPAA, ENS, CCC) to the security checks that Prowler already runs. Each requirement links to zero, one or more Prowler checks. When a scan executes, findings are aggregated per requirement to produce the compliance report rendered by Prowler CLI and Prowler Cloud.
A compliance framework in Prowler maps a public or custom control catalog (for example CIS, NIST 800-53, PCI DSS, HIPAA, ENS, CCC, DORA) to the security checks that Prowler already runs. Each requirement links to zero, one or more Prowler checks. When a scan executes, findings are aggregated per requirement to produce the compliance report rendered by Prowler CLI and Prowler Cloud.
Prowler ships with 85+ compliance frameworks across All Providers. The catalog lives under `prowler/compliance/<provider>/` (or `prowler/compliance/` for universal compliance frameworks)
Prowler ships 85+ compliance frameworks across all providers. The catalog lives under `prowler/compliance/<provider>/` (legacy, per-provider) or `prowler/compliance/` (universal, multi-provider).
<Warning>
A compliance framework must represent the **complete state** of the source catalog. Every requirement defined by the framework has to be present in the JSON file, even when none of the existing Prowler checks can automate it. In that case, leave `Checks` as an empty array, but do not omit the requirement.
A compliance framework must represent the **complete state** of the source catalog. Every requirement defined by the framework has to be present in the JSON file, even when no Prowler check can automate it. In that case, leave the requirement's check list empty, but do not omit the requirement.
Requirement coverage feeds the compliance percentage calculations and the metadata surfaces (dashboards, widgets, exports). Missing requirements skew those metrics and break the report as a faithful snapshot of the framework.
</Warning>
### Two supported schemas
| Schema | When to use | File location | Discovered as |
| --- | --- | --- | --- |
| **Universal (recommended for new frameworks)** | Multi-provider frameworks, or single-provider frameworks that benefit from declarative table/PDF rendering | `prowler/compliance/<framework>.json` (top-level) | Available for **every** provider whose key appears in any `requirement.checks` dict |
| **Legacy provider-specific** | Single-provider frameworks with framework-specific attribute classes already declared in the codebase (CIS, ENS, ISO 27001, etc.) | `prowler/compliance/<provider>/<framework>_<version>_<provider>.json` | Available only under that provider |
Auto-discovery happens in `get_bulk_compliance_frameworks_universal(provider)` (`prowler/lib/check/compliance_models.py:915`), which scans **both** the top-level `prowler/compliance/` directory and every per-provider sub-directory. Legacy frameworks are transparently converted to the universal `ComplianceFramework` model via `adapt_legacy_to_universal()` before being returned, so the rest of Prowler — CLI table rendering, CSV/OCSF outputs, PDF generation — works the same regardless of the source schema.
> The legacy entry-point `Compliance.get_bulk(provider)` (used by older code paths) only scans per-provider sub-directories. Universal top-level files are picked up exclusively via the universal loader; this matters if you are wiring a new code path against the legacy API.
For **new** frameworks, prefer the universal schema: it requires no Python code changes, supports multiple providers in a single file, and table/PDF rendering is driven entirely from declarative configuration inside the JSON.
> All Pydantic models in `compliance_models.py` are imported from `pydantic.v1`. Subclasses you add for the legacy schema must use `from pydantic.v1 import BaseModel`.
### Prerequisites
Before adding a new framework, complete the following checks:
- **Verify the framework is not already supported.** Inspect `prowler/compliance/<provider>/` for an existing JSON file matching the name and version.
- **Verify the framework is not already supported.** Inspect `prowler/compliance/` and every `prowler/compliance/<provider>/` for an existing JSON file matching the name and version.
- **Confirm the required checks exist.** Every requirement that can be automated must point to one or more existing Prowler checks. For each missing check, implement it first by following the [Prowler Checks](/developer-guide/checks) guide.
- **Review a reference framework.** Use an existing framework with a similar structure as your template. `cis_2.0_aws.json` is the canonical reference for CIS-style frameworks. `ccc_aws.json`, `ens_rd2022_aws.json`, and `nist_800_53_revision_5_aws.json` illustrate other attribute shapes.
- **Review a reference framework.** Use an existing framework with a similar structure as your template:
- Universal: `prowler/compliance/dora.json`, `prowler/compliance/csa_ccm_4.0.json`.
- Legacy: `prowler/compliance/aws/cis_2.0_aws.json` (canonical CIS shape), `prowler/compliance/aws/ccc_aws.json`, `prowler/compliance/aws/ens_rd2022_aws.json`, `prowler/compliance/aws/nist_800_53_revision_5_aws.json`.
## Four-Layer Architecture
## Universal Compliance Framework
A compliance framework spans four layers. A complete contribution must touch each layer that applies.
### Where the file lives
- **Layer 1 – Schema validation:** The Pydantic models in `prowler/lib/check/compliance_models.py` define the canonical schema for each attribute shape (CIS, ENS, Mitre, CCC, C5, CSA CCM, ISO 27001, KISA ISMS-P, AWS Well-Architected, Prowler ThreatScore, and a generic fallback).
- **Layer 2 – JSON catalog:** The framework JSON file in `prowler/compliance/<provider>/` lists every requirement and maps it to checks.
- **Layer 3 – Output formatter:** The Python module in `prowler/lib/outputs/compliance/<framework>/` builds the CSV row model, the per-provider transformer, and the CLI summary table.
- **Layer 4 – Output dispatchers:** The dispatchers in `prowler/lib/outputs/compliance/compliance.py` and `prowler/lib/outputs/compliance/compliance_output.py` route findings to the right formatter based on the framework identifier.
Place the file at the top level of the compliance directory:
The rest of this guide walks each layer in order.
```
prowler/compliance/<framework_name>.json
```
## Directory Structure and File Naming
Examples in the repository: `prowler/compliance/csa_ccm_4.0.json`, `prowler/compliance/dora.json`.
The file is auto-discovered — there is **no** need to register it in any `__init__.py`, modify `prowler/lib/outputs/`, or update any other Python module. The framework key Prowler CLI accepts via `--compliance` is the basename of the JSON file without `.json` (`dora.json` → `dora`).
### Top-level structure
```json
{
"framework": "<short identifier, e.g. \"DORA\" or \"CSA-CCM\">",
"name": "<human-readable full name>",
"version": "<framework version>",
"description": "<one-paragraph description shown in --list-compliance and PDF reports>",
"icon": "<short icon slug, optional>",
"attributes_metadata": [ /* see below */ ],
"outputs": { /* see below — optional */ },
"requirements": [ /* see below */ ]
}
```
A `provider` field at the top level is **optional**. The framework's effective provider list is derived by `ComplianceFramework.get_providers()` (`compliance_models.py:739`) from the union of all keys appearing in `requirement.checks` across all requirements; the explicit `provider` field is used **only as a fallback** when no requirement carries any `checks` key. This is what enables a single file (e.g. `dora.json`) to cover AWS today and add Azure / GCP / etc. tomorrow without restructuring.
Provider keys inside `requirement.checks` must match the directory names under `prowler/providers/`. The valid keys at present are: `aws`, `azure`, `gcp`, `m365`, `kubernetes`, `iac`, `github`, `googleworkspace`, `alibabacloud`, `cloudflare`, `mongodbatlas`, `nhn`, `openstack`, `oraclecloud`, `llm`. Comparison in `supports_provider()` is case-insensitive, but lowercase is the convention used everywhere in the repository.
### `attributes_metadata`
Declares the shape of the per-requirement `attributes` dict. When this field is present, the root validator `validate_attributes_against_metadata` (`compliance_models.py:669`) enforces the schema at load time and rejects:
- Missing keys marked `required: true`.
- Keys present in `attributes` but not declared in `attributes_metadata` (typo / drift guard).
- Values that violate a declared `enum`.
- Values whose Python type does not match a declared `int`, `float` or `bool`.
The runtime type check **only** covers `int`, `float` and `bool`. For `str`, `list_str` and `list_dict` the type is documentation-only — non-conforming values won't fail validation. If `attributes_metadata` is omitted, **no per-requirement validation runs at all**.
```json
"attributes_metadata": [
{
"key": "Pillar",
"label": "Pillar",
"type": "str",
"required": true,
"enum": [
"ICT Risk Management",
"ICT-Related Incident Reporting",
"Digital Operational Resilience Testing",
"ICT Third-Party Risk Management",
"Information Sharing"
],
"output_formats": { "csv": true, "ocsf": true }
},
{
"key": "Article",
"label": "Article",
"type": "str",
"required": true,
"output_formats": { "csv": true, "ocsf": true }
}
]
```
Per attribute:
- `key` (required): attribute name as it will appear in `requirement.attributes`.
- `label`: human-readable label used in CSV headers and PDF.
- `type`: one of `str`, `int`, `float`, `bool`, `list_str`, `list_dict`. Defaults to `str`.
- `enum`: optional list of allowed values; non-conforming values are rejected at load time.
- `required`: if `true`, every requirement must include this key with a non-null value.
- `enum_display` / `enum_order`: optional per-enum-value visual metadata (label, abbreviation, color, icon) and explicit ordering for PDF rendering.
- `output_formats`: `{ "csv": <bool>, "ocsf": <bool> }` — toggles inclusion in each output format. Both default to `true`.
### `outputs`
Optional. Controls how the framework is rendered in the console table and in the generated PDF report. Skipping it falls back to sensible defaults.
```json
"outputs": {
"table_config": {
"group_by": "Pillar"
},
"pdf_config": {
"language": "en",
"primary_color": "#003399",
"secondary_color": "#0055A5",
"bg_color": "#F0F4FA",
"group_by_field": "Pillar",
"sections": [ "ICT Risk Management", "ICT-Related Incident Reporting", "..." ],
"section_short_names": { "ICT Risk Management": "ICT Risk Mgmt" },
"charts": [
{
"id": "pillar_compliance",
"type": "horizontal_bar",
"group_by": "Pillar",
"title": "Compliance Score by Pillar",
"y_label": "Pillar",
"x_label": "Compliance %",
"value_source": "compliance_percent",
"color_mode": "by_value"
}
],
"filter": { "only_failed": true, "include_manual": false }
}
}
```
`table_config.group_by` must reference an attribute key declared in `attributes_metadata`. The same applies to `pdf_config.group_by_field` and to every `charts[].group_by`.
For frameworks with weighted scoring (e.g. ThreatScore) declare `pdf_config.scoring` with `risk_field` / `weight_field` / `risk_boost_factor`. For column splitting (e.g. CIS Level 1 vs Level 2) use `table_config.split_by`.
### `requirements`
```json
"requirements": [
{
"id": "DORA-Art5",
"name": "Governance and organisation",
"description": "Financial entities shall have a sound, comprehensive and well-documented ICT internal governance and control framework. ...",
"attributes": {
"Pillar": "ICT Risk Management",
"Article": "Article 5",
"ArticleTitle": "Governance and organisation"
},
"checks": {
"aws": [
"iam_avoid_root_usage",
"iam_no_root_access_key",
"iam_root_mfa_enabled"
],
"azure": [],
"gcp": []
}
}
]
```
Per requirement:
- `id` (required): unique identifier within the framework.
- `description` (required): the requirement text as authored by the framework.
- `name`: short title shown alongside the id.
- `attributes`: flat dict; keys must conform to `attributes_metadata`.
- `checks`: dict keyed by provider name (the same lowercase keys listed in the previous section). Each value is a list of Prowler check names that evidence this requirement for that provider. The list **may be empty** and the dict itself defaults to `{}` if omitted; either way the requirement is still loaded and listed by `--list-compliance-requirements`, it just has zero checks to execute. Note: there is **no automatic check-existence validation** at load time — referencing a non-existent check name will silently produce a requirement with no findings. Validate this yourself (see "Validating Your Framework" below).
For MITRE-style frameworks, additional optional fields are available on the requirement: `tactics`, `sub_techniques`, `platforms`, `technique_url` (these are populated automatically when adapting a legacy MITRE JSON to the universal model).
### Multi-provider frameworks
A single universal file can cover any number of providers. The framework appears under each provider's `--list-compliance` output as long as **at least one** requirement has that provider key in its `checks` dict.
When extending an existing universal framework with a new provider, the only change required is editing `requirement.checks`:
```diff
"checks": {
"aws": ["iam_avoid_root_usage", "iam_no_root_access_key"],
+ "azure": ["entra_policy_ensure_mfa_for_admin_roles"]
}
```
No code changes, no new file, no registration step.
## Legacy Provider-Specific Compliance Framework
The legacy schema is still fully supported and remains the format used by most frameworks shipped today (CIS, NIST, ISO 27001, FedRAMP, PCI DSS, GDPR, HIPAA, ENS, etc.). It binds a framework to a single provider and validates each requirement against a framework-specific Pydantic attribute class.
The legacy schema spans **four layers** — a complete contribution must touch every layer that applies:
- **Layer 1 — Schema validation:** the Pydantic models in `prowler/lib/check/compliance_models.py` define the canonical schema for each attribute shape.
- **Layer 2 — JSON catalog:** the framework JSON file in `prowler/compliance/<provider>/` lists every requirement and maps it to checks.
- **Layer 3 — Output formatter:** the Python module in `prowler/lib/outputs/compliance/<framework>/` builds the CSV row model, the per-provider transformer, and the CLI summary table.
- **Layer 4 — Output dispatchers:** the dispatchers in `prowler/lib/outputs/compliance/compliance.py` and `prowler/lib/outputs/compliance/compliance_output.py` route findings to the right formatter based on the framework identifier.
The universal schema collapses Layers 3 and 4 into declarative configuration inside the JSON — that is the main reason it is preferred for new contributions.
### Directory structure and file naming
Compliance frameworks live at:
@@ -46,8 +234,8 @@ prowler/compliance/<provider>/<framework>_<version>_<provider>.json
The filename conventions are:
- All lowercase, words separated with underscores.
- `<provider>` is a supported provider identifier: `aws`, `azure`, `gcp`, `kubernetes`, `m365`, `github`, `googleworkspace`, `alibabacloud`, `oraclecloud`, `cloudflare`, `mongodbatlas`, `nhn`, `openstack`, `iac`, `llm`.
- `<version>` is optional. Omit it when the framework has no versioning, as in `ccc_aws.json`.
- `<provider>` is a supported provider identifier (same lowercase list as the universal section above).
- `<version>` is optional but recommended. Omit only when the framework has no versioning (e.g. `ccc_aws.json`).
- The file basename (without `.json`) is the framework key that Prowler CLI accepts via `--compliance`.
Examples:
@@ -62,48 +250,50 @@ The output formatter directory mirrors the framework name:
```
prowler/lib/outputs/compliance/<framework>/
├── <framework>.py # CLI summary-table dispatcher
├── <framework>.py # CLI summary-table dispatcher
├── <framework>_<provider>.py # Per-provider transformer class
├── models.py # Pydantic CSV row model
└── __init__.py
```
## JSON Schema Reference
### JSON schema reference
Every compliance file is a JSON document with the following top-level keys.
Every legacy compliance file is a JSON document with the following top-level keys. `Framework`, `Name` and `Provider` are validated non-empty by the root validator `framework_and_provider_must_not_be_empty` (`compliance_models.py:329`).
| Field | Type | Required | Description |
|---|---|---|---|
| `Framework` | string | Yes | Canonical framework identifier, for example `CIS`, `NIST-800-53-Revision-5`, `ENS`, `CCC`. |
| `Name` | string | Yes | Human-readable framework name displayed by Prowler App. |
| `Version` | string | Yes | Framework version, for example `2.0`. Use an empty string only for frameworks without versioning. See [Version Handling](#version-handling). |
| `Version` | string | Yes (recommended) | Framework version, e.g. `2.0`. See [Version Handling](#version-handling). |
| `Provider` | string | Yes | Upper-cased provider identifier: `AWS`, `AZURE`, `GCP`, `KUBERNETES`, `M365`, `GITHUB`, `GOOGLEWORKSPACE`, and so on. |
| `Description` | string | Yes | Short description of the framework's scope and purpose. |
| `Requirements` | array | Yes | List of [requirement objects](#requirement-object). |
### Requirement Object
#### Requirement Object
Each entry in `Requirements` describes one control or requirement.
| Field | Type | Required | Description |
|---|---|---|---|
| `Id` | string | Yes | Unique identifier within the framework, for example `1.10` or `CCC.Core.CN01.AR01`. |
| `Name` | string | No | Optional human-readable name used by frameworks that distinguish control name from description, such as NIST. |
| `Name` | string | No | Optional human-readable name (frameworks like NIST distinguish control name from description). |
| `Description` | string | Yes | Verbatim description from the source framework. |
| `Attributes` | array | Yes | List of [attribute objects](#attribute-objects). The shape depends on the framework. |
| `Checks` | array of strings | Yes | Prowler check identifiers that automate the requirement. Leave the list empty when the control cannot be automated. |
### Attribute Objects
#### Attribute Objects
Attributes carry the metadata that Prowler App and the CSV output display for each requirement. The object shape is framework-specific and is validated by a dedicated Pydantic model in `prowler/lib/check/compliance_models.py`. The most common shapes are summarized below.
`Attributes` is parsed against the union declared in `Compliance_Requirement.Attributes` (`compliance_models.py:293`). Pydantic v1 tries each member of the union in declaration order and falls back to `Generic_Compliance_Requirement_Attribute` (the last entry) when nothing else matches — so a brand-new shape that doesn't match any existing class will silently be accepted as Generic, losing its specific fields.
#### CIS_Requirement_Attribute
As of today, the registered attribute classes are: `CIS_Requirement_Attribute`, `ENS_Requirement_Attribute`, `ASDEssentialEight_Requirement_Attribute`, `ISO27001_2013_Requirement_Attribute`, `AWS_Well_Architected_Requirement_Attribute`, `KISA_ISMSP_Requirement_Attribute`, `Prowler_ThreatScore_Requirement_Attribute`, `CCC_Requirement_Attribute`, `C5Germany_Requirement_Attribute`, `CSA_CCM_Requirement_Attribute`, and `Generic_Compliance_Requirement_Attribute` (fallback). MITRE-style frameworks use the separate `Mitre_Requirement` model with `Tactics` / `SubTechniques` / `Platforms` / `TechniqueURL` at the requirement top level. The most common shapes are summarized below.
##### CIS_Requirement_Attribute
Used by every CIS benchmark.
| Field | Type | Required | Notes |
|---|---|---|---|
| `Section` | string | Yes | Top-level section, for example `1 Identity and Access Management`. |
| `Section` | string | Yes | Top-level section, e.g. `1 Identity and Access Management`. |
| `SubSection` | string | No | Optional second-level grouping. |
| `Profile` | enum | Yes | One of `Level 1`, `Level 2`, `E3 Level 1`, `E3 Level 2`, `E5 Level 1`, `E5 Level 2`. |
| `AssessmentStatus` | enum | Yes | `Manual` or `Automated`. |
@@ -116,7 +306,7 @@ Used by every CIS benchmark.
| `DefaultValue` | string | No | Default configuration value, when relevant. |
| `References` | string | Yes | Colon-separated list of reference URLs. |
#### ENS_Requirement_Attribute
##### ENS_Requirement_Attribute
Used by the Spanish ENS (Esquema Nacional de Seguridad) frameworks.
@@ -132,13 +322,13 @@ Used by the Spanish ENS (Esquema Nacional de Seguridad) frameworks.
| `ModoEjecucion` | string | Yes | Execution mode (`manual`, `automático`, `híbrido`). |
| `Dependencias` | array of strings | Yes | Ids of prerequisite controls. Empty list when none. |
#### CCC_Requirement_Attribute
##### CCC_Requirement_Attribute
Used by the Common Cloud Controls Catalog.
| Field | Type | Required | Notes |
|---|---|---|---|
| `FamilyName` | string | Yes | Control family, for example `Data`. |
| `FamilyName` | string | Yes | Control family, e.g. `Data`. |
| `FamilyDescription` | string | Yes | Description of the family. |
| `Section` | string | Yes | Section title. |
| `SubSection` | string | Yes | Subsection title, or empty string. |
@@ -148,9 +338,9 @@ Used by the Common Cloud Controls Catalog.
| `SectionThreatMappings` | array of objects | Yes | Each entry has `ReferenceId` and `Identifiers`. |
| `SectionGuidelineMappings` | array of objects | Yes | Each entry has `ReferenceId` and `Identifiers`. |
#### Generic_Compliance_Requirement_Attribute
##### Generic_Compliance_Requirement_Attribute
The fallback attribute model used when no framework-specific schema applies (for example NIST 800-53, PCI DSS, GDPR, HIPAA).
The fallback attribute model used when no framework-specific schema applies (e.g. NIST 800-53, PCI DSS, GDPR, HIPAA). It is **always the last** element of the `Compliance_Requirement.Attributes` Union; that ordering is load-bearing.
| Field | Type | Required | Notes |
|---|---|---|---|
@@ -158,17 +348,17 @@ The fallback attribute model used when no framework-specific schema applies (for
| `Section` | string | No | Section name. |
| `SubSection` | string | No | Subsection name. |
| `SubGroup` | string | No | Subgroup name. |
| `Service` | string | No | Affected service, for example `aws`, `iam`. |
| `Service` | string | No | Affected service, e.g. `iam`. |
| `Type` | string | No | Control type. |
| `Comment` | string | No | Free-form comment. |
Additional per-framework attribute models exist for `AWS_Well_Architected_Requirement_Attribute`, `ISO27001_2013_Requirement_Attribute`, `Mitre_Requirement_Attribute_<Provider>`, `KISA_ISMSP_Requirement_Attribute`, `Prowler_ThreatScore_Requirement_Attribute`, `C5Germany_Requirement_Attribute`, and `CSA_CCM_Requirement_Attribute`. Consult `prowler/lib/check/compliance_models.py` for their full field sets.
For the remaining attribute classes (`AWS_Well_Architected_Requirement_Attribute`, `ISO27001_2013_Requirement_Attribute`, `Mitre_Requirement_Attribute_<Provider>`, `KISA_ISMSP_Requirement_Attribute`, `Prowler_ThreatScore_Requirement_Attribute`, `C5Germany_Requirement_Attribute`, `CSA_CCM_Requirement_Attribute`) consult `prowler/lib/check/compliance_models.py` for the full field sets.
<Note>
The `Attributes` field is a Pydantic `Union`. The generic attribute model must remain the last element of that Union, otherwise Pydantic v1 silently coerces every framework into the generic shape and your specialized fields are dropped.
The `Attributes` field is a Pydantic `Union`. The generic attribute model **must** remain the last element of that Union — otherwise Pydantic v1 silently coerces every framework into the generic shape and your specialized fields are dropped. Adding a brand-new attribute shape requires inserting the Pydantic class **before** `Generic_Compliance_Requirement_Attribute`.
</Note>
## Minimal Working Example
#### Minimal working example
The following snippet is a complete, valid framework file named `my_framework_1.0_aws.json`, saved at `prowler/compliance/aws/my_framework_1.0_aws.json`. It uses the generic attribute shape for simplicity.
@@ -214,26 +404,26 @@ The following snippet is a complete, valid framework file named `my_framework_1.
}
```
## Mapping Checks to Requirements
### Mapping checks to requirements
Each requirement links to the Prowler checks that, together, produce a PASS or FAIL verdict for that control.
- **Include every requirement from the source catalog.** The framework file must mirror the full control list, one-to-one. Compliance percentages, dashboards, and exported metadata are computed against the total requirement count, so omitting an unmappable control inflates coverage and misrepresents the framework.
- List every check by its canonical identifier, the value of `CheckID` inside the check's `.metadata.json` file.
- **Include every requirement from the source catalog.** The framework file must mirror the full control list, one-to-one. Compliance percentages, dashboards, and exported metadata are computed against the total requirement count.
- List every check by its canonical identifier — the value of `CheckID` inside the check's `.metadata.json` file.
- One requirement can reference multiple checks. The requirement is evaluated as FAIL when any referenced check produces a FAIL finding for a resource in scope.
- Leave `Checks` as an empty array when the requirement cannot be automated. The requirement still appears in the report, contributes to the total, and resolves to `MANUAL`. An empty mapping is valid; a missing requirement is not.
- Leave `Checks` (legacy) or `checks.<provider>` (universal) as an empty array when the requirement cannot be automated. The requirement still appears in the report and contributes to the total.
- Reuse checks across requirements when the same control applies in multiple places. Do not duplicate check logic to match framework structure.
- Avoid referencing checks from a different provider. A compliance file is bound to one provider, and cross-provider checks will never match findings in the scan.
- Avoid referencing checks from a different provider. A legacy compliance file is bound to one provider, and cross-provider checks will never match findings in the scan.
To discover available checks, run:
To discover available checks:
```bash
poetry run python prowler-cli.py <provider> --list-checks
uv run python prowler-cli.py <provider> --list-checks
```
## Supporting Multiple Providers
### Supporting multiple providers (legacy)
Each compliance file targets a single provider. To cover several providers with the same framework (for example CIS across AWS, Azure, and GCP), ship one JSON file per provider:
The legacy schema binds each file to a single provider. To cover several providers with the same framework, ship one JSON file per provider:
```
prowler/compliance/aws/cis_2.0_aws.json
@@ -241,15 +431,15 @@ prowler/compliance/azure/cis_2.0_azure.json
prowler/compliance/gcp/cis_2.0_gcp.json
```
Keep the `Framework` and `Version` values identical across the files so the dispatcher matches them, and change only the `Provider`, `Checks`, and provider-specific metadata.
Keep the `Framework` and `Version` values identical across the files so the dispatcher matches them; change only the `Provider`, `Checks`, and provider-specific metadata. The CIS output formatter already supports every provider listed above.
The CIS output formatter already supports every provider listed above. For a brand-new framework that spans several providers, add one transformer per provider in `prowler/lib/outputs/compliance/<framework>/` and extend the summary-table dispatcher accordingly. See [Output Formatter](#output-formatter).
For a brand-new framework that spans several providers, **prefer the universal schema** — it covers every provider from a single file. If you must use the legacy schema, add one transformer per provider in `prowler/lib/outputs/compliance/<framework>/` and extend the summary-table dispatcher accordingly. See [Output Formatter](#output-formatter).
## Output Formatter
### Output formatter
Prowler renders every compliance framework in two forms: a detailed CSV report written to disk, and a summary table printed in the CLI. Both are produced by the output formatter package for the framework.
Legacy frameworks render in two forms: a detailed CSV report written to disk, and a summary table printed in the CLI. Both are produced by the output formatter package for the framework. Universal frameworks do **not** need a Python output formatter — the `outputs` config inside the JSON drives rendering — so this section applies only to the legacy schema.
For a new framework named `my_framework`, create:
For a new legacy framework named `my_framework`, create:
```
prowler/lib/outputs/compliance/my_framework/
@@ -259,19 +449,19 @@ prowler/lib/outputs/compliance/my_framework/
└── models.py # CSV row Pydantic model
```
### Step 1 – Define the CSV Row Model
#### Step 1 — Define the CSV row model
In `models.py`, declare a Pydantic v1 model with one field per CSV column. Use existing models such as `AWSCISModel` in `prowler/lib/outputs/compliance/cis/models.py` as the reference. Fields typically include `Provider`, `Description`, `AccountId`, `Region`, `AssessmentDate`, `Requirements_Id`, `Requirements_Description`, one `Requirements_Attributes_*` field per attribute key, plus the finding fields `Status`, `StatusExtended`, `ResourceId`, `ResourceName`, `CheckId`, `Muted`, `Framework`, `Name`.
### Step 2 – Implement the Transformer Class
#### Step 2 — Implement the transformer
In `my_framework_aws.py`, subclass `ComplianceOutput` from `prowler.lib.outputs.compliance.compliance_output` and implement `transform(findings, compliance, compliance_name)`. Iterate over `findings`, match each finding to the requirements it satisfies through `finding.compliance.get(compliance_name, [])`, and append one row per attribute to `self._data`.
### Step 3 – Add the Summary-Table Dispatcher
#### Step 3 — Add the summary-table dispatcher
In `my_framework.py`, implement `get_my_framework_table(findings, bulk_checks_metadata, compliance_framework, output_filename, output_directory, compliance_overview)` following the pattern in `prowler/lib/outputs/compliance/cis/cis.py`.
### Step 4 – Register the Framework in the Dispatchers
#### Step 4 — Register the framework in the dispatchers
- Add the dispatcher call in `prowler/lib/outputs/compliance/compliance.py`, inside `display_compliance_table`, with a branch such as `elif "my_framework" in compliance_framework:`.
- Register the CSV model and transformer in `prowler/lib/outputs/compliance/compliance_output.py` so the CSV file is emitted during the scan.
@@ -280,49 +470,94 @@ In `my_framework.py`, implement `get_my_framework_table(findings, bulk_checks_me
For NIST-style catalogs that use `Generic_Compliance_Requirement_Attribute`, no custom formatter is needed. The generic formatter in `prowler/lib/outputs/compliance/generic/` handles them automatically, provided the JSON validates against the generic attribute schema.
</Note>
## Version Handling
### Legacy-to-universal adapter
At load time, every legacy file is transparently adapted to a `ComplianceFramework` via `adapt_legacy_to_universal()` (`compliance_models.py:819`), which: (a) flattens the first element of `Attributes` into a flat `attributes` dict, (b) wraps `Checks` as `{provider_lower: [...]}`, (c) infers `attributes_metadata` from the matched Pydantic class via `_infer_attribute_metadata()`. The rest of Prowler (CSV/OCSF/PDF output, CLI table) then treats both formats identically.
Loader-error behaviour differs between the two entry points:
- `load_compliance_framework()` (legacy) is **fail-fast**: it calls `sys.exit(1)` on any `ValidationError` (`compliance_models.py:464`).
- `load_compliance_framework_universal()` is more lenient — it logs the error and returns `None`, so `get_bulk_compliance_frameworks_universal()` simply skips the broken file and keeps loading the rest.
## Version handling
Prowler matches frameworks by concatenating `Framework` and `Version`. A missing or empty `Version` collapses several frameworks to the same key and breaks CLI filtering with `--compliance`.
- Always set `Version` to a non-empty string, even for frameworks that rename editions rather than version them. Use the edition identifier (for example `RD2022`, `v2025.10`, `4.0`).
- Always set `Version` (or `version` for universal frameworks) to a non-empty string, even for frameworks that rename editions rather than version them. Use the edition identifier (for example `RD2022`, `v2025.10`, `4.0`, `2022/2554`).
- When the source catalog has no version, use the first year of adoption or the release date.
- Make sure the version substring embedded in the filename matches `Version`, because the CLI dispatcher reads `compliance_framework.split("_")[1]` to select the correct version.
- For **legacy** files, make sure the version substring embedded in the filename matches `Version`, because the CLI dispatcher reads `compliance_framework.split("_")[1]` to select the correct version.
## Validating the Framework Locally
## Validating Your Framework
Follow the steps below before opening a pull request.
Before opening a PR, validate the JSON loads cleanly against the model and that every referenced check actually exists.
### 1. Run the Compliance Model Validator
### 1. Schema validation
```bash
poetry run python prowler-cli.py <provider> --list-compliance
For **universal** frameworks, load the file and inspect what was parsed. The framework key inside `bulk` is the **basename of the JSON file** (without `.json`); for `prowler/compliance/dora.json` that key is `dora`, for `prowler/compliance/aws/cis_5.0_aws.json` it is `cis_5.0_aws`.
```python
from prowler.lib.check.compliance_models import (
load_compliance_framework_universal,
get_bulk_compliance_frameworks_universal,
)
fw = load_compliance_framework_universal("prowler/compliance/<your_framework>.json")
assert fw is not None, "load returned None — check the logs for the validation error"
print(fw.framework, len(fw.requirements), fw.get_providers())
bulk = get_bulk_compliance_frameworks_universal("aws")
assert "<your_framework_filename_without_json>" in bulk
```
The framework must appear in the output. A validation error indicates a schema mismatch between the JSON file and the attribute model.
### 2. Check existence cross-check
### 2. Run a Scan Filtered by the Framework
There is **no automatic check-existence validation** at load time. Cross-check that every check name in your framework maps to a real check directory:
```python
import os
real = set()
for svc in os.listdir("prowler/providers/aws/services"):
svc_path = f"prowler/providers/aws/services/{svc}"
if not os.path.isdir(svc_path):
continue
for entry in os.listdir(svc_path):
if os.path.isfile(f"{svc_path}/{entry}/{entry}.metadata.json"):
real.add(entry)
referenced = {c for r in fw.requirements for c in r.checks.get("aws", [])}
missing = referenced - real
assert not missing, f"checks referenced in framework but not found in repo: {sorted(missing)}"
```
### 3. CLI smoke test
```bash
poetry run python prowler-cli.py <provider> \
--compliance <framework>_<version>_<provider> \
uv run python prowler-cli.py <provider> --list-compliance
```
The framework must appear in the output. A validation error indicates a schema mismatch.
```bash
uv run python prowler-cli.py <provider> \
--compliance <framework_key> \
--log-level ERROR
```
Verify that:
- Prowler produces a CSV file under `output/compliance/` with the expected name.
- The CLI summary table lists every section in the framework.
- The CLI summary table lists every section / pillar of the framework.
- Findings roll up under the expected requirements.
### 3. Inspect the CSV Output
### 4. Inspect the CSV output
Open the generated CSV and confirm:
- All columns defined in `models.py` appear.
- Every requirement has at least one row per scanned resource.
- Values such as `Requirements_Attributes_Section` reflect the JSON content.
- All columns defined in `models.py` (legacy) or in `attributes_metadata` (universal) appear.
- Every requirement has at least one row per scanned resource (when there are findings).
- Attribute values such as `Requirements_Attributes_Section` reflect the JSON content.
### 4. Verify the Framework in Prowler App
### 5. Verify the framework in Prowler App
Launch Prowler App locally (`docker compose up` from the repository root) and run a scan with the new compliance framework. Confirm the compliance page renders the requirements, sections, and status widgets correctly.
@@ -331,49 +566,65 @@ Launch Prowler App locally (`docker compose up` from the repository root) and ru
Compliance contributions require two layers of tests.
- **Schema tests** exercise the Pydantic models. Extend `tests/lib/check/universal_compliance_models_test.py` with a case that loads the new JSON file and asserts the attribute type matches the expected model.
- **Output tests** exercise the transformer. Mirror the structure under `tests/lib/outputs/compliance/<framework>/` with fixtures that feed synthetic findings through the transformer and assert the resulting CSV rows.
- **Output tests** (legacy frameworks only) exercise the transformer. Mirror the structure under `tests/lib/outputs/compliance/<framework>/` with fixtures that feed synthetic findings through the transformer and assert the resulting CSV rows.
Run the suite with:
```bash
poetry run pytest -n auto tests/lib/check/universal_compliance_models_test.py \
uv run pytest -n auto tests/lib/check/universal_compliance_models_test.py \
tests/lib/outputs/compliance/
```
For guidance on writing Prowler SDK tests, refer to [Unit Testing](/developer-guide/unit-testing).
## Submitting the Pull Request
## Running and listing your framework
Once the file is in place, the CLI auto-discovers it:
```sh
prowler <provider> --list-compliance # framework appears in the list
prowler <provider> --compliance <framework_key> --list-checks
prowler <provider> --compliance <framework_key> # full scan + compliance report
prowler <provider> --compliance <framework_key> --list-compliance-requirements <framework_key>
```
For end-user-facing tutorials (recommended for high-profile frameworks), add a dedicated page under `docs/user-guide/compliance/tutorials/` and register it in the `"Compliance"` group of `docs/docs.json`. See `docs/user-guide/compliance/tutorials/threatscore.mdx` as a reference.
## Submitting the pull request
Before opening the pull request:
1. Run the complete QA pipeline:
```bash
poetry run pre-commit run --all-files
poetry run pytest -n auto
uv run pre-commit run --all-files
uv run pytest -n auto
```
2. Add a changelog entry under the `### 🚀 Added` section of `prowler/CHANGELOG.md`, describing the new framework and the providers it covers.
3. Follow the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md) and set the PR title using Conventional Commits, for example `feat(compliance): add My Framework 1.0 for AWS`.
3. Follow the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md) and set the PR title using Conventional Commits, e.g. `feat(compliance): add My Framework 1.0 for AWS`.
4. Request review from the compliance codeowners listed in `.github/CODEOWNERS`.
## Troubleshooting
The following issues are the most common when contributing a compliance framework.
- **`ValidationError: field required` during scan.** The JSON is missing a required attribute field. Re-check the matching Pydantic model in `prowler/lib/check/compliance_models.py`.
- **All attributes collapse to `Generic_Compliance_Requirement_Attribute` values.** The Pydantic `Union` is ordered incorrectly, or the JSON matches only the generic shape. Move the generic model to the last Union position and ensure every required field is present in the JSON.
- **`--compliance` filter does not find the framework.** The filename does not match the expected pattern `<framework>_<version>_<provider>.json`, the version is empty, or the file lives outside `prowler/compliance/<provider>/`.
- **CLI summary table is empty but the CSV is populated.** The dispatcher branch in `prowler/lib/outputs/compliance/compliance.py` is missing or its substring match does not catch the framework key.
- **CSV file is missing after the scan.** The transformer class is not registered in `prowler/lib/outputs/compliance/compliance_output.py`, or `transform()` raises silently. Run the scan with `--log-level DEBUG`.
- **Findings do not roll up under a requirement.** A check listed in `Checks` either does not exist for that provider or is spelled incorrectly. Run `--list-checks | grep <check_name>` to confirm.
- **`ValidationError: field required` during scan (legacy).** The JSON is missing a required attribute field. Re-check the matching Pydantic model in `prowler/lib/check/compliance_models.py`.
- **All attributes collapse to `Generic_Compliance_Requirement_Attribute` values (legacy).** The Pydantic `Union` is ordered incorrectly, or the JSON matches only the generic shape. Keep the generic model in the last Union position and ensure every required field is present in the JSON.
- **`attributes_metadata validation failed` (universal).** The root validator in `compliance_models.py:669` rejected the file. The error message lists each offending requirement; common causes are unknown attribute keys (typo or missing entry in `attributes_metadata`), enum violations, or missing required keys.
- **`--compliance` filter does not find the framework.** For legacy: the filename does not match `<framework>_<version>_<provider>.json`, the version is empty, or the file lives outside `prowler/compliance/<provider>/`. For universal: the file is not at the top level of `prowler/compliance/` or it loaded as `None` (check logs for the validation error).
- **CLI summary table is empty but the CSV is populated (legacy).** The dispatcher branch in `prowler/lib/outputs/compliance/compliance.py` is missing or its substring match does not catch the framework key.
- **CSV file is missing after the scan (legacy).** The transformer class is not registered in `prowler/lib/outputs/compliance/compliance_output.py`, or `transform()` raises silently. Run the scan with `--log-level DEBUG`.
- **Findings do not roll up under a requirement.** A check listed in `Checks` either does not exist for that provider or is spelled incorrectly. Run `--list-checks | grep <check_name>` to confirm, or run the check-existence cross-check from "Validating Your Framework".
## Reference Examples
## Reference examples
Use the following files as templates when modeling a new contribution.
- `prowler/compliance/aws/cis_2.0_aws.json` – CIS attribute shape.
- `prowler/compliance/aws/nist_800_53_revision_5_aws.json` – Generic attribute shape.
- `prowler/compliance/aws/ccc_aws.json` – CCC attribute shape.
- `prowler/compliance/azure/ens_rd2022_azure.json` – ENS attribute shape.
- `prowler/lib/check/compliance_models.py` – Canonical Pydantic schemas.
- `prowler/lib/outputs/compliance/cis/` – Reference implementation of a multi-provider output formatter.
- `prowler/lib/outputs/compliance/generic/` – Reference implementation of a generic output formatter.
- `prowler/compliance/dora.json` — universal schema, single-provider populated (AWS), ready to extend with more providers.
- `prowler/compliance/csa_ccm_4.0.json` — universal schema, multi-provider populated (AWS, Azure, GCP, AlibabaCloud, OracleCloud).
- `prowler/compliance/aws/cis_2.0_aws.json` — legacy CIS attribute shape.
- `prowler/compliance/aws/nist_800_53_revision_5_aws.json` — legacy generic attribute shape.
- `prowler/compliance/aws/ccc_aws.json` — legacy CCC attribute shape.
- `prowler/compliance/azure/ens_rd2022_azure.json` — legacy ENS attribute shape.
- `prowler/lib/check/compliance_models.py` — canonical Pydantic schemas for both formats.
- `prowler/lib/outputs/compliance/cis/` — reference implementation of a multi-provider legacy output formatter.
- `prowler/lib/outputs/compliance/generic/` — reference implementation of a legacy generic output formatter.
+1 -1
View File
@@ -23,7 +23,7 @@ Within this folder the following files are also to be created:
- `<new_service_name>_service.py` – Contains all the logic and API calls of the service.
- `<new_service_name>_client_.py` – Contains the initialization of the freshly created service's class so that the checks can use it.
Once the files are create, you can check that the service has been created by running the following command: `poetry run python prowler-cli.py <provider> --list-services | grep <new_service_name>`.
Once the files are create, you can check that the service has been created by running the following command: `uv run python prowler-cli.py <provider> --list-services | grep <new_service_name>`.
## Service Structure and Initialisation
+730
View File
@@ -0,0 +1,730 @@
---
title: 'StackIT Provider'
---
This page details the [StackIT Cloud](https://www.stackit.de/) provider implementation in Prowler.
By default, Prowler audits a single StackIT project per scan. To configure it, provide the project ID and either a service account key file path or inline service account key JSON.
## StackIT Provider Classes Architecture
The StackIT provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the StackIT-specific implementation, highlighting how the generic provider concepts are realized for StackIT in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).
### `StackitProvider` (Main Class)
- **Location:** [`prowler/providers/stackit/stackit_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/stackit_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for StackIT-specific logic, API authentication, credential validation, and configuration.
- **Key StackIT Responsibilities:**
- Initializes StackIT SDK authentication via a service account key file or inline service account key JSON. The SDK mints and refreshes access tokens internally.
- Validates the service account credentials and project ID (UUID format validation).
- Loads and manages configuration, mutelist, and fixer settings.
- Provides properties and methods for downstream StackIT service classes to access credentials, identity, and configuration data.
### Data Models
- **Location:** [`prowler/providers/stackit/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/models.py)
- **Purpose:** Define structured data for StackIT identity and output configuration.
- **Key StackIT Models:**
- `StackITIdentityInfo`: Holds StackIT identity metadata, including project ID and project name (fetched automatically from Resource Manager API).
- `StackITOutputOptions`: Customizes default output filenames so StackIT reports include the audited project ID.
- IaaS resource models such as `SecurityGroup` and `SecurityGroupRule` are defined in the IaaS service module.
### StackIT Services
- **Location:** [`prowler/providers/stackit/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services)
- **Purpose:** Implement StackIT service clients and resource collection logic following the generic [service pattern](/developer-guide/services#service-base-class).
- **Current Implementation:** The `IaaSService` collects security groups, rules, and network interface usage across supported StackIT regions.
### Exception Handling
- **Location:** [`prowler/providers/stackit/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for StackIT-specific error handling, such as credential validation, API connection, and configuration errors.
- **Key Exception Classes:**
- `StackITBaseException`: Base exception for all StackIT provider errors.
- `StackITCredentialsError`: Raised when credentials are invalid or missing.
- `StackITInvalidProjectIdError`: Raised when project ID is invalid or not in UUID format.
- `StackITAPIError`: Raised when StackIT API calls fail.
## Authentication
### Service Account Creation and Key Generation
StackIT uses service account keys for API authentication. Service account keys are RSA key-pair based and provide secure, short-lived access tokens.
### Creating a Service Account Key
#### Method 1: Via StackIT Portal
1. **Navigate to Service Accounts**
- Go to the [StackIT Portal](https://portal.stackit.cloud/)
- Select your project
- Click on **Service Accounts** in the left sidebar
2. **Create or Select Service Account**
- If you don't have a service account, click **Create Service Account**
- Provide a name and description
- Assign necessary permissions:
- For IaaS security checks: `iaas.viewer` or `project.owner`
- For comprehensive audits: `project.owner`
3. **Generate Service Account Key**
- Select your service account
- Navigate to **Service Account Keys**
- Click **Create key**
- Choose one of the following options:
- **STACKIT-generated key pair** (Recommended): Let STACKIT automatically generate an RSA key-pair
- **User-provided key pair**: Upload your own RSA 2048 public key
4. **Download and Save the Key**
- Download the generated service account key file (JSON format)
- **Important**: Save the key securely - it contains your private key and will only be available once
- Store the key file in a secure location (e.g., `~/.stackit/sa_key.json`)
#### Method 2: Via StackIT CLI
```bash
# Install STACKIT CLI (if not already installed)
# Follow instructions at: https://github.com/stackitcloud/stackit-cli
# Create service account key (STACKIT-generated)
stackit service-account key create --email my-service-account@example.com
# Or create with your own RSA 2048 public key
# First, generate your RSA key pair:
openssl genrsa -out private-key.pem 2048
openssl rsa -in private-key.pem -pubout -out public-key.pem
# Then create the key with your public key:
stackit service-account key create \
--email my-service-account@example.com \
--public-key "$(cat public-key.pem)"
```
### Finding Your Project ID
Your StackIT project ID is a UUID that can be found:
1. In the StackIT Portal URL when viewing your project: `https://portal.stackit.cloud/projects/{PROJECT_ID}/...`
2. In the project settings page
3. Using the StackIT CLI: `stackit project list`
### Passing the Service Account Key to Prowler
Prowler accepts the service account credentials in two equivalent forms; both go through the same StackIT SDK flow and refresh access tokens internally.
#### Option 1: Key File Path (key persisted on disk)
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
export STACKIT_PROJECT_ID="12345678-1234-1234-1234-123456789abc"
prowler stackit
```
Or as CLI flags:
```bash
prowler stackit \
--stackit-service-account-key-path ~/.stackit/sa-key.json \
--stackit-project-id 12345678-1234-1234-1234-123456789abc
```
#### Option 2: Inline Key Content (CI/CD, secret managers)
```bash
export STACKIT_SERVICE_ACCOUNT_KEY="$(vault kv get -field=key stackit/sa)"
export STACKIT_PROJECT_ID="12345678-1234-1234-1234-123456789abc"
prowler stackit
```
Prefer the environment variable over the matching `--stackit-service-account-key` CLI flag; passing the secret on the command line leaks it through process listings and shell history.
### Credential Lookup Order
Prowler resolves credentials in this order:
1. **Command-line arguments**:
- `--stackit-service-account-key`
- `--stackit-service-account-key-path`
- `--stackit-project-id`
2. **Environment variables**:
- `STACKIT_SERVICE_ACCOUNT_KEY`
- `STACKIT_SERVICE_ACCOUNT_KEY_PATH`
- `STACKIT_PROJECT_ID`
When both the inline key and the key file path are set, the inline content takes precedence.
## Configuration
### Command-Line Arguments
StackIT-specific command-line arguments:
| Argument | Description | Required | Default |
|----------|-------------|----------|---------|
| `--stackit-service-account-key-path` | Path to a StackIT service account key JSON file | Yes* | `$STACKIT_SERVICE_ACCOUNT_KEY_PATH` |
| `--stackit-service-account-key` | Inline JSON content of a StackIT service account key (preferred env var: `STACKIT_SERVICE_ACCOUNT_KEY`) | Yes* | `$STACKIT_SERVICE_ACCOUNT_KEY` |
| `--stackit-project-id` | StackIT project ID (UUID format) | Yes* | `$STACKIT_PROJECT_ID` |
| `--stackit-region` | StackIT region(s) to scan | No | All available regions |
\* Required unless provided via environment variables.
### Input Validation
The StackIT provider performs comprehensive input validation:
- **Service Account Credentials**:
- At least one of `service_account_key_path` (file path) or `service_account_key` (inline JSON) must be supplied; both empty raises `StackITNonExistentTokenError`
- When both are provided the inline content takes precedence
- The key file path is logged as-is; the inline content is redacted in the credentials box
- **Project ID**:
- Must not be empty
- Must be a valid UUID format (e.g., `12345678-1234-1234-1234-123456789abc`)
- Validated using Python's UUID constructor
Invalid credentials will result in clear error messages before any API calls are made.
## Available Services
### IaaS (Infrastructure as a Service)
- **Service Class:** `IaaSService`
- **Location:** [`prowler/providers/stackit/services/iaas/iaas_service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/services/iaas/iaas_service.py)
- **SDK:** Uses the [stackit-iaas](https://pypi.org/project/stackit-iaas/) Python SDK
- **Purpose:** Manages IaaS resources including security groups, servers, and network interfaces.
**Supported Resources:**
- Security Groups and Rules
- Servers (Virtual Machines)
- Network Interfaces (NICs)
**Key Features:**
- Automatic discovery of all security groups in the project
- Security rule parsing with support for unrestricted access detection
- Network interface analysis to determine whether security groups are in use
- By default, reports only security groups attached to at least one NIC; `--scan-unused-services` includes unused security groups too
## Available Checks
The StackIT provider currently implements 4 security checks focused on network security:
### 1. iaas_security_group_ssh_unrestricted
- **Severity:** High
- **Description:** Detects security groups that allow unrestricted SSH access (port 22) from the internet.
- **Risk:** Unrestricted SSH access increases the attack surface and risk of brute-force attacks.
- **Detection Logic:**
- Checks for ingress rules allowing TCP port 22
- Flags rules with `ip_range=None` or `ip_range="0.0.0.0/0"` or `ip_range="::/0"`
- Reports security groups attached to NICs by default, or all security groups when `--scan-unused-services` is enabled
### 2. iaas_security_group_rdp_unrestricted
- **Severity:** High
- **Description:** Detects security groups that allow unrestricted RDP access (port 3389) from the internet.
- **Risk:** Unrestricted RDP access enables potential unauthorized remote desktop access.
- **Detection Logic:**
- Checks for ingress rules allowing TCP port 3389
- Flags unrestricted IP ranges (None, 0.0.0.0/0, ::/0)
- Reports security groups attached to NICs by default, or all security groups when `--scan-unused-services` is enabled
### 3. iaas_security_group_database_unrestricted
- **Severity:** High
- **Description:** Detects security groups that allow unrestricted access to common database ports.
- **Monitored Ports:**
- MySQL: 3306
- PostgreSQL: 5432
- MongoDB: 27017
- Redis: 6379
- SQL Server: 1433
- CouchDB: 5984
- **Risk:** Unrestricted database access can lead to data breaches and unauthorized data access.
### 4. iaas_security_group_all_traffic_unrestricted
- **Severity:** Critical
- **Description:** Detects security groups that allow all traffic from the internet.
- **Detection Logic:**
- Checks for rules with `port_range=None` (all ports)
- Checks for rules with port range covering 0-65535 or 1-65535
- Flags unrestricted IP ranges
- Critical security misconfiguration requiring immediate remediation
### Important Implementation Notes
**Self-Referencing Security Group Rules:**
Security group rules with `remoteSecurityGroupId` set are automatically filtered out from unrestricted access checks. These rules only allow traffic from instances within the same security group (self-referencing), not from the internet, and are therefore not flagged as security risks.
**Rule Display Names:**
All findings include user-friendly rule descriptions when available. If a security group rule has a description field set (the name shown in the StackIT UI), it will be displayed in the finding message along with the rule ID:
- With description: `'Allow SSH from office' (sgr-abc123)`
- Without description: `'sgr-abc123'`
**Network Interface (NIC) Usage Filtering:**
The IaaS service lists project NICs and records the security group IDs attached to them. Checks use that signal to decide whether a security group is in use:
1. **Default behavior:** Report security groups attached to at least one NIC.
2. **`--scan-unused-services`:** Report every security group, including unused ones.
3. **FAIL logic:** Internet exposure is driven by security group rules that allow unrestricted source ranges, not by the presence of a public IP on the NIC.
**Unrestricted IP Ranges:**
The StackIT API represents "unrestricted" in two ways:
- **`ip_range=null`**: No IP restriction specified (implicit unrestricted)
- **`ip_range="0.0.0.0/0"` or `"::/0"`**: Explicitly configured to allow all IPs
Both are flagged as unrestricted. A `null` value is **more permissive** than an explicit range and applies to all protocols/ports if other fields are also `null`.
## Requirements
### Python Version
- **Minimum:** Python 3.10+
- **Reason:** The StackIT SDK requires Python 3.10 or higher
### Dependencies
The StackIT provider requires the following Python packages (automatically installed with Prowler):
- **stackit-core** (v0.2.0): Core SDK for StackIT API authentication and configuration
- **stackit-iaas** (v1.4.0): IaaS service SDK for managing compute resources
- **stackit-resourcemanager** (v0.8.0): Resource Manager SDK for fetching project metadata (e.g., project names)
These dependencies are defined in `pyproject.toml` and installed automatically with:
```bash
poetry install
```
**Note:** The `stackit-resourcemanager` package enables automatic retrieval of project names for display in reports. If this package is not available, Prowler will still function normally but project names will be empty in the output.
## Region Support
### Supported Regions
- **Available Regions:** `eu01` (Germany South) and `eu02` (Austria West)
- **Default:** All scans use both `eu01` and `eu02` regions by default.
### Multi-Region Scanning
Prowler supports scanning multiple StackIT regions in a single execution. By default, it will scan all regions defined in the `stackit_regions_by_service.json` configuration file.
### CLI Argument
You can specify which regions to scan using the `--stackit-region` argument:
```bash
# Scan only eu01
prowler stackit --stackit-region eu01
# Scan both eu01 and eu02
prowler stackit --stackit-region eu01 eu02
```
### Implementation Details
- **Regional Clients:** Prowler generates a separate API client for each audited region.
- **Service Iteration:** Each service (e.g., IaaS) iterates through the regional clients to fetch and audit resources.
- **Identity Tracking:** The `audited_regions` are stored in the identity model for reporting.
### Future Enhancements
As StackIT adds more regions, they can be easily added to Prowler by updating the `prowler/providers/stackit/stackit_regions_by_service.json` file without requiring code changes.
## Command Examples
### Scan Specific Regions
Scan only the `eu01` region:
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--stackit-region eu01
```
Scan multiple regions:
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--stackit-region eu01 eu02
```
### Scan Specific Checks
Run only SSH unrestricted check:
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--checks iaas_security_group_ssh_unrestricted
```
### Scan All Security Group Checks
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--services iaas
```
### Output Formats
Generate JSON output:
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--output-formats json
```
Generate HTML report:
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--output-formats html
```
## Known Limitations
### Current Limitations
1. **Single Project Scope**: Only one project can be scanned at a time
2. **Service Coverage**: Only the IaaS service is currently implemented
3. **Check Coverage**: Limited to security group network security checks (4 checks total)
4. **No Compliance Frameworks**: Compliance framework mappings are not yet implemented
### Planned Enhancements
- Multi-project scanning capability
- Additional IaaS checks (volume encryption, server public IP exposure, backup status)
- Compliance framework mappings (CIS, custom StackIT best practices)
- StackIT CLI remediation examples in metadata
## Troubleshooting
### Authentication Errors
**Error:** `StackIT service account key was rejected`
**Solutions:**
1. Re-issue the service account key in the StackIT Portal
2. Verify the service account key file or inline JSON content is complete
3. Check that the service account has the necessary permissions (`iaas.viewer` or `project.owner`)
4. Ensure the service account key is provided through `STACKIT_SERVICE_ACCOUNT_KEY_PATH`, `STACKIT_SERVICE_ACCOUNT_KEY`, or the matching CLI arguments
**Error:** `StackIT credentials not found or are invalid`
**Solutions:**
1. Ensure the project ID and one service account credential source are provided
2. Check that credentials are set via environment variables or command-line arguments
3. Verify there are no extra spaces or newlines in the credentials
**Error:** `Invalid StackIT project ID format`
**Solutions:**
1. Verify the project ID is a valid UUID format: `12345678-1234-1234-1234-123456789abc`
2. Copy the project ID directly from the StackIT Portal
3. Ensure there are no extra spaces or quotes around the UUID
### API Connection Errors
**Error:** `Failed to connect to StackIT API`
**Solutions:**
1. Check your internet connection
2. Verify the StackIT API endpoint is accessible from your network
3. Check if there are any firewall rules blocking HTTPS connections
4. Review the full error message for specific API error codes
**Error:** `HTTP 403 Forbidden`
**Solutions:**
1. Verify the service account has the correct permissions
2. Ensure the project ID is correct and you have access to it
3. Check that the service account is enabled (not disabled or expired)
4. Verify the service account key has not been revoked
**Error:** `HTTP 404 Not Found`
**Solutions:**
1. Verify the project ID exists and is correct
2. Check that the IaaS service is enabled in your project
3. Ensure you're using the correct region (eu01)
### Empty Results
**Issue:** No security groups or findings reported
**Solutions:**
1. Verify that security groups exist in your project
2. Check that the IaaS service is properly configured
3. Ensure the service account has `iaas.viewer` permission
4. Check Prowler logs for any API errors (use `--log-level DEBUG`)
### Debug Mode
Enable debug logging for detailed troubleshooting:
```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
prowler stackit \
--stackit-project-id "your-project-id" \
--log-level DEBUG
```
This will show:
- API authentication details (with inline service account keys redacted)
- Resource discovery progress
- Security rule parsing details
- Any API errors or warnings
## Specific Patterns in StackIT Services
The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:
- Directly in the code, in location [`prowler/providers/stackit/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other StackIT services as reference.
### StackIT Service Common Patterns
- Services communicate with StackIT using the StackIT Python SDK, you can find the documentation [here](https://github.com/stackitcloud/stackit-sdk-python).
- Service constructors receive a `StackitProvider` instance and use it to access credentials, identity, and configuration.
- The provider builds StackIT SDK `Configuration` objects from the service account key path or inline key content.
- Resource containers **must** be initialized in the constructor, typically as lists or dictionaries.
- Do not manipulate `os.environ` for credentials inside services. Use the provider session and SDK configuration helpers.
- All StackIT resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- StackIT SDK calls are wrapped in try/except blocks, with specific handling for API errors, always logging errors.
- **Centralized Error Handling**: Use `provider.handle_api_error(exception)` for consistent authentication error detection across all services.
- **SDK Warning Suppression**: StackIT SDK prints deprecation warnings to stderr - use the `suppress_stderr()` context manager during SDK initialization and API calls.
- **Unrestricted Access Detection**: In StackIT API, `None` values mean "allow all" (more permissive than explicit 0.0.0.0/0).
- `protocol=None` → All protocols allowed
- `ip_range=None` → All source IPs allowed (unrestricted!)
- `port_range=None` → All ports allowed
- `remote_security_group_id` set → Only allows traffic from the same security group (not unrestricted!)
### IaaS Service Specific Patterns
**Security Group Discovery:**
```python
# List all security groups
security_groups = client.list_security_groups(
project_id=self.project_id,
region=region,
)
# List network interfaces to determine security group usage
nics = client.list_project_nics(
project_id=self.project_id,
region=region,
)
# Checks report in-use security groups by default. Use --scan-unused-services
# to include security groups that are not attached to any NIC.
```
**Centralized Authentication Error Handling:**
```python
def _handle_api_call(self, api_function, *args, **kwargs):
"""Wrapper for API calls with centralized error handling."""
try:
with suppress_stderr(): # Suppress SDK warnings
return api_function(*args, **kwargs)
except Exception as e:
# Use centralized error handler from provider
self.provider.handle_api_error(e) # Detects 401 and raises StackITInvalidTokenError
```
**Unrestricted Access Detection:**
```python
def is_unrestricted(rule):
"""Check if a rule allows unrestricted access."""
# Filter out self-referencing rules
if rule.remote_security_group_id is not None:
return False
# Check for unrestricted IP ranges
return rule.ip_range is None or rule.ip_range in ["0.0.0.0/0", "::/0"]
def is_tcp(rule):
"""Check if a rule applies to TCP protocol."""
# None means all protocols (including TCP)
return rule.protocol is None or rule.protocol.lower() in ["tcp", "all"]
def includes_port(rule, port):
"""Check if a rule includes a specific port."""
# None means all ports
if rule.port_range is None:
return True
return rule.port_range.min <= port <= rule.port_range.max
```
## Specific Patterns in StackIT Checks
The StackIT checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks:
- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/stackit/services/iaas/iaas_security_group_ssh_unrestricted/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services/iaas/iaas_security_group_ssh_unrestricted))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.
The best reference to understand how to implement a new check is following the [check creation documentation](/developer-guide/checks#creating-a-check) and taking other similar StackIT checks as reference.
### Check Report Class
The `CheckReportStackIT` class models a single finding for a StackIT resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.
#### Purpose
`CheckReportStackIT` extends the base report structure with StackIT-specific fields, enabling detailed tracking of the resource, project, and location associated with each finding.
#### Constructor and Attribute Population
When you instantiate `CheckReportStackIT`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its StackIT-specific attributes from the resource, using the following logic:
- **`resource_id`**:
- Uses `resource.id` if present.
- Otherwise, uses `resource.resource_id` if present.
- Defaults to an empty string if none are available.
- **`resource_name`**:
- Uses `resource.name` if present.
- Defaults to an empty string if not available.
- **`project_id`**:
- Uses `resource.project_id` if present.
- Defaults to an empty string if not available (should be set in check logic).
- **`location`**:
- Uses `resource.region` if present.
- Otherwise, uses `resource.location` if present.
- Defaults to an empty string if not available.
If the resource object does not contain the required attributes, you must set them manually in the check logic.
Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.
#### Example Usage
```python
from prowler.lib.check.models import CheckReportStackIT
report = CheckReportStackIT(
metadata=self.metadata(),
resource=security_group
)
report.status = "FAIL"
report.status_extended = f"Security group {security_group.name} allows unrestricted SSH access from the internet."
report.resource_id = security_group.id
report.resource_name = security_group.name
report.project_id = security_group.project_id
report.location = security_group.region
```
### Common Check Pattern
```python
from prowler.lib.check.models import Check, CheckReportStackIT
from prowler.providers.stackit.services.iaas.iaas_client import iaas_client
class iaas_security_group_ssh_unrestricted(Check):
"""Check if IaaS security groups allow unrestricted SSH access."""
def execute(self):
findings = []
for security_group in iaas_client.security_groups:
if not (iaas_client.scan_unused_services or security_group.in_use):
continue
report = CheckReportStackIT(
metadata=self.metadata(),
resource=security_group
)
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} does not allow unrestricted SSH access."
# Check each rule
for rule in security_group.rules:
if (rule.is_ingress() and
rule.is_tcp() and
rule.includes_port(22) and
rule.is_unrestricted()):
report.status = "FAIL"
report.status_extended = f"Security group {security_group.name} allows unrestricted SSH access from the internet."
break
findings.append(report)
return findings
```
## Resources
### Official StackIT Documentation
- **StackIT Portal**: [https://portal.stackit.cloud/](https://portal.stackit.cloud/)
- **StackIT Documentation**: [https://docs.stackit.cloud/](https://docs.stackit.cloud/)
- **StackIT API Documentation**: [https://docs.api.eu01.stackit.cloud/](https://docs.api.eu01.stackit.cloud/)
### Python SDK
- **StackIT Python SDK (GitHub)**: [https://github.com/stackitcloud/stackit-sdk-python](https://github.com/stackitcloud/stackit-sdk-python)
- **stackit-core (PyPI)**: [https://pypi.org/project/stackit-core/](https://pypi.org/project/stackit-core/)
- **stackit-iaas (PyPI)**: [https://pypi.org/project/stackit-iaas/](https://pypi.org/project/stackit-iaas/)
- **IaaS Models**: [https://github.com/stackitcloud/stackit-sdk-python/tree/main/services/iaas/src/stackit/iaas/models](https://github.com/stackitcloud/stackit-sdk-python/tree/main/services/iaas/src/stackit/iaas/models)
### Prowler Resources
- **Provider Implementation**: [`prowler/providers/stackit/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/)
- **IaaS Service**: [`prowler/providers/stackit/services/iaas/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services/iaas/)
- **Prowler Hub**: [https://hub.prowler.com/](https://hub.prowler.com/)
- **GitHub Issues**: [https://github.com/prowler-cloud/prowler/issues](https://github.com/prowler-cloud/prowler/issues)
## Contributing
If you'd like to contribute to the StackIT provider:
1. **Add New Checks**: Follow the [check creation guide](/developer-guide/checks#creating-a-check) and use existing StackIT checks as templates
2. **Enhance Services**: Implement additional IaaS resource discovery or add new services
3. **Improve Documentation**: Add metadata enhancements, CLI remediation examples, or Terraform code samples
4. **Report Issues**: Submit bug reports or feature requests on [GitHub](https://github.com/prowler-cloud/prowler/issues)
### Quick Start for Contributors
1. **Install dependencies**: `poetry install` (includes stackit-core and stackit-iaas)
2. **Set credentials**: Export `STACKIT_SERVICE_ACCOUNT_KEY_PATH` and `STACKIT_PROJECT_ID`
3. **Run checks**: `prowler stackit`
4. **View code**: Start in `prowler/providers/stackit/`
5. **Add checks**: Create new check directories under `services/iaas/`
6. **Run tests**: `poetry run pytest tests/providers/stackit/ -v`
### Code Quality Standards
The StackIT provider should follow the same quality expectations as the rest of the Prowler SDK:
- Keep service and check logic covered by unit tests.
- Redact inline service account keys from generated output.
- Keep documentation aligned with the implemented services and checks.
- Follow existing provider, service, and check patterns before adding StackIT-specific abstractions.