Compare commits

...

6 Commits

Author SHA1 Message Date
dependabot[bot]
31662b6e91 chore(deps-dev): bump pre-commit from 4.2.0 to 4.5.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.2.0 to 4.5.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.2.0...v4.5.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-version: 4.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 13:45:39 +00:00
Hugo Pereira Brito
cccb3a4b94 chore(sdk,mcp): pin direct dependencies to exact versions (#10593)
Co-authored-by: Adrián Jesús Peña Rodríguez <adrianjpr@gmail.com>
2026-04-09 14:42:49 +01:00
Daniel Barranquero
ca50b24d77 docs: add Vercel Cloud getting started (#10609) 2026-04-09 15:40:44 +02:00
mintlify[bot]
7eb204fff0 docs: classify supported providers by category on main page (#10621)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
2026-04-09 15:39:43 +02:00
Pedro Martín
56c370d3a4 chore(ccc): update with latest version and improve mapping (#10625) 2026-04-09 15:27:18 +02:00
Pedro Martín
b0d8534907 feat(api): add needed changes for GoogleWorkspace compliance (#10629) 2026-04-09 14:36:55 +02:00
25 changed files with 18510 additions and 12880 deletions

View File

@@ -11,6 +11,7 @@ All notable changes to the **Prowler API** are documented in this file.
- `VALKEY_SCHEME`, `VALKEY_USERNAME`, and `VALKEY_PASSWORD` environment variables to configure Celery broker TLS/auth connection details for Valkey/ElastiCache [(#10420)](https://github.com/prowler-cloud/prowler/pull/10420)
- `Vercel` provider support [(#10190)](https://github.com/prowler-cloud/prowler/pull/10190)
- Finding groups list and latest endpoints support `sort=delta`, ordering by `new_count` then `changed_count` so groups with the most new findings rank highest [(#10606)](https://github.com/prowler-cloud/prowler/pull/10606)
- Handle CIS and CISA SCuBA compliance framework from google workspace [(#10629)](https://github.com/prowler-cloud/prowler/pull/10629)
### 🔄 Changed

View File

@@ -32,9 +32,13 @@ from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS
from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS
from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
from prowler.lib.outputs.compliance.cis.cis_googleworkspace import GoogleWorkspaceCIS
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
from prowler.lib.outputs.compliance.cisa_scuba.cisa_scuba_googleworkspace import (
GoogleWorkspaceCISASCuBA,
)
from prowler.lib.outputs.compliance.csa.csa_alibabacloud import AlibabaCloudCSA
from prowler.lib.outputs.compliance.csa.csa_aws import AWSCSA
from prowler.lib.outputs.compliance.csa.csa_azure import AzureCSA
@@ -93,7 +97,7 @@ COMPLIANCE_CLASS_MAP = {
(lambda name: name.startswith("iso27001_"), AWSISO27001),
(lambda name: name.startswith("kisa"), AWSKISAISMSP),
(lambda name: name == "prowler_threatscore_aws", ProwlerThreatScoreAWS),
(lambda name: name == "ccc_aws", CCC_AWS),
(lambda name: name.startswith("ccc_"), CCC_AWS),
(lambda name: name.startswith("c5_"), AWSC5),
(lambda name: name.startswith("csa_"), AWSCSA),
],
@@ -102,7 +106,7 @@ COMPLIANCE_CLASS_MAP = {
(lambda name: name == "mitre_attack_azure", AzureMitreAttack),
(lambda name: name.startswith("ens_"), AzureENS),
(lambda name: name.startswith("iso27001_"), AzureISO27001),
(lambda name: name == "ccc_azure", CCC_Azure),
(lambda name: name.startswith("ccc_"), CCC_Azure),
(lambda name: name == "prowler_threatscore_azure", ProwlerThreatScoreAzure),
(lambda name: name == "c5_azure", AzureC5),
(lambda name: name.startswith("csa_"), AzureCSA),
@@ -113,7 +117,7 @@ COMPLIANCE_CLASS_MAP = {
(lambda name: name.startswith("ens_"), GCPENS),
(lambda name: name.startswith("iso27001_"), GCPISO27001),
(lambda name: name == "prowler_threatscore_gcp", ProwlerThreatScoreGCP),
(lambda name: name == "ccc_gcp", CCC_GCP),
(lambda name: name.startswith("ccc_"), CCC_GCP),
(lambda name: name == "c5_gcp", GCPC5),
(lambda name: name.startswith("csa_"), GCPCSA),
],
@@ -133,6 +137,10 @@ COMPLIANCE_CLASS_MAP = {
"github": [
(lambda name: name.startswith("cis_"), GithubCIS),
],
"googleworkspace": [
(lambda name: name.startswith("cis_"), GoogleWorkspaceCIS),
(lambda name: name.startswith("cisa_scuba_"), GoogleWorkspaceCISASCuBA),
],
"iac": [
# IaC provider doesn't have specific compliance frameworks yet
# Trivy handles its own compliance checks

View File

@@ -163,6 +163,8 @@ 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.
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).
<Note>

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -21,29 +21,57 @@
## Supported Providers
The supported providers right now are:
Prowler supports a wide range of providers organized by category:
| Provider | Support | Audit Scope/Entities | Interface |
| -------------------------------------------------------------------------------- | ---------- | ---------------------------- | ------------ |
| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | Accounts | UI, API, CLI |
| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | Subscriptions | UI, API, CLI |
| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | Projects | UI, API, CLI |
| [Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s) | Official | Clusters | UI, API, CLI |
| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | Tenants | UI, API, CLI |
| [Github](/user-guide/providers/github/getting-started-github) | Official | Organizations / Repositories | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI |
| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official | Accounts | UI, API, CLI |
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | Repositories | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | Organizations | UI, API, CLI |
| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI |
| [Vercel](/user-guide/providers/vercel/getting-started-vercel) | Official | Teams / Projects | CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI |
| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images | CLI, API |
| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace) | Official | Domains | CLI |
| **NHN** | Unofficial | Tenants | CLI |
### Cloud Service Providers (Infrastructure)
For more information about the checks and compliance of each provider visit [Prowler Hub](https://hub.prowler.com).
| Provider | Support | Audit Scope/Entities | Interface |
| -------------------------------------------------------------------------------- | ---------- | ------------------------ | ------------ |
| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official | Accounts | UI, API, CLI |
| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | Accounts | UI, API, CLI |
| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | Subscriptions | UI, API, CLI |
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI |
| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | Projects | UI, API, CLI |
| **NHN** | Unofficial | Tenants | CLI |
| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI |
### Infrastructure as Code Providers
| Provider | Support | Audit Scope/Entities | Interface |
| --------------------------------------------------------------------- | -------- | -------------------- | ------------ |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | Repositories | UI, API, CLI |
### Software as a Service (SaaS) Providers
| Provider | Support | Audit Scope/Entities | Interface |
| ----------------------------------------------------------------------------------------- | -------- | ---------------------------- | ------------ |
| [GitHub](/user-guide/providers/github/getting-started-github) | Official | Organizations / Repositories | UI, API, CLI |
| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace) | Official | Domains | CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI |
| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | Tenants | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | Organizations | UI, API, CLI |
| [Vercel](/user-guide/providers/vercel/getting-started-vercel) | Official | Teams / Projects | CLI |
### Kubernetes
| Provider | Support | Audit Scope/Entities | Interface |
| -------------------------------------------------------------------------- | -------- | -------------------- | ------------ |
| [Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s) | Official | Clusters | UI, API, CLI |
### Containers
| Provider | Support | Audit Scope/Entities | Interface |
| ------------------------------------------------------------------- | -------- | -------------------- | --------- |
| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images / Registries | CLI, API |
### Custom Providers (Prowler Cloud Enterprise Only)
| Provider | Support | Audit Scope/Entities | Interface |
| -------------------- | -------- | -------------------- | --------- |
| VMware/Broadcom VCF | Official | Infrastructure | CLI |
For more information about the checks and compliance of each provider, visit [Prowler Hub](https://hub.prowler.com).
## Where to go next?

View File

@@ -13,9 +13,63 @@ Set up authentication for Vercel with the [Vercel Authentication](/user-guide/pr
- Create a Vercel API Token with access to the target team
- Identify the Team ID (optional, required to scope the scan to a single team)
<CardGroup cols={2}>
<Card title="Prowler Cloud" icon="cloud" href="#prowler-cloud">
Onboard Vercel using Prowler Cloud
</Card>
<Card title="Prowler CLI" icon="terminal" href="#prowler-cli">
Onboard Vercel using Prowler CLI
</Card>
</CardGroup>
## Prowler Cloud
<VersionBadge version="5.23.0" />
### Step 1: Add the Provider
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app).
2. Navigate to "Configuration" > "Cloud Providers".
![Cloud Providers Page](/images/prowler-app/cloud-providers-page.png)
3. Click "Add Cloud Provider".
![Add a Cloud Provider](/images/prowler-app/add-cloud-provider.png)
4. Select "Vercel".
![Select Vercel](/images/providers/select-vercel-prowler-cloud.png)
5. Enter the **Team ID** and an optional alias, then click "Next".
![Add Vercel Team ID](/images/providers/vercel-team-id-form.png)
<Note>
The Team ID can be found in the Vercel Dashboard under "Settings" > "General". It follows the format `team_xxxxxxxxxxxxxxxxxxxx`. For detailed instructions, see the [Authentication guide](/user-guide/providers/vercel/authentication).
</Note>
### Step 2: Provide Credentials
1. Enter the **API Token** created in the Vercel Dashboard.
![API Token Form](/images/providers/vercel-token-form.png)
For the complete token creation workflow, follow the [Authentication guide](/user-guide/providers/vercel/authentication#api-token).
### Step 3: Launch the Scan
1. Review the connection summary.
2. Choose the scan schedule: run a single scan or set up daily scans (every 24 hours).
3. Click **Launch Scan** to start auditing Vercel.
![Launch Scan](/images/providers/vercel-launch-scan.png)
---
## Prowler CLI
<VersionBadge version="5.22.0" />
<VersionBadge version="5.23.0" />
### Step 1: Set Up Authentication

View File

@@ -8,6 +8,10 @@ All notable changes to the **Prowler MCP Server** are documented in this file.
- Resource events tool to get timeline for a resource (who, what, when) [(#10412)](https://github.com/prowler-cloud/prowler/pull/10412)
### 🔄 Changed
- Pin `httpx` dependency to exact version for reproducible installs [(#10593)](https://github.com/prowler-cloud/prowler/pull/10593)
### 🔐 Security
- `authlib` bumped from 1.6.5 to 1.6.9 to fix CVE-2026-28802 (JWT `alg: none` validation bypass) [(#10579)](https://github.com/prowler-cloud/prowler/pull/10579)

View File

@@ -5,7 +5,7 @@ requires = ["setuptools>=61.0", "wheel"]
[project]
dependencies = [
"fastmcp==2.14.0",
"httpx>=0.28.0"
"httpx==0.28.1"
]
description = "MCP server for Prowler ecosystem"
name = "prowler-mcp"

2
mcp_server/uv.lock generated
View File

@@ -727,7 +727,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "fastmcp", specifier = "==2.14.0" },
{ name = "httpx", specifier = ">=0.28.0" },
{ name = "httpx", specifier = "==0.28.1" },
]
[[package]]

10
poetry.lock generated
View File

@@ -4444,14 +4444,14 @@ files = [
[[package]]
name = "pre-commit"
version = "4.2.0"
version = "4.5.1"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"},
{file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"},
{file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"},
{file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"},
]
[package.dependencies]
@@ -6743,4 +6743,4 @@ files = [
[metadata]
lock-version = "2.1"
python-versions = ">=3.10,<3.13"
content-hash = "91739ee5e383337160f9f08b76944ab4e8629c94084c8a9d115246862557f7c5"
content-hash = "2b72ed4419b22be8dde521c7ce1a54e44d3390ff548cc055bab177f609a3504c"

View File

@@ -21,11 +21,13 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
- `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234)
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
- CCC improvements with the latest checks and new mappings [(#10625)](https://github.com/prowler-cloud/prowler/pull/10625)
### 🔄 Changed
- Added `internet-exposed` category to 13 AWS checks (CloudFront, CodeArtifact, EC2, EFS, RDS, SageMaker, Shield, VPC) [(#10502)](https://github.com/prowler-cloud/prowler/pull/10502)
- Minimum Python version from 3.9 to 3.10 and updated classifiers to reflect supported versions (3.10, 3.11, 3.12) [(#10464)](https://github.com/prowler-cloud/prowler/pull/10464)
- Pin direct SDK dependencies to exact versions and rely on `poetry.lock` artifact hashes for reproducible installs [(#10593)](https://github.com/prowler-cloud/prowler/pull/10593)
- Sensitive CLI flags now warn when values are passed directly, recommending environment variables instead [(#10532)](https://github.com/prowler-cloud/prowler/pull/10532)
### 🐞 Fixed

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
from colorama import Fore, Style
from tabulate import tabulate
from prowler.config.config import orange_color
def get_ccc_table(
findings: list,
bulk_checks_metadata: dict,
compliance_framework: str,
output_filename: str,
output_directory: str,
compliance_overview: bool,
):
section_table = {
"Provider": [],
"Section": [],
"Status": [],
"Muted": [],
}
pass_count = []
fail_count = []
muted_count = []
sections = {}
for index, finding in enumerate(findings):
check = bulk_checks_metadata[finding.check_metadata.CheckID]
check_compliances = check.Compliance
for compliance in check_compliances:
if compliance.Framework == "CCC":
for requirement in compliance.Requirements:
for attribute in requirement.Attributes:
section = attribute.Section
if section not in sections:
sections[section] = {"FAIL": 0, "PASS": 0, "Muted": 0}
if finding.muted:
if index not in muted_count:
muted_count.append(index)
sections[section]["Muted"] += 1
else:
if finding.status == "FAIL" and index not in fail_count:
fail_count.append(index)
sections[section]["FAIL"] += 1
elif finding.status == "PASS" and index not in pass_count:
pass_count.append(index)
sections[section]["PASS"] += 1
sections = dict(sorted(sections.items()))
for section in sections:
section_table["Provider"].append(compliance.Provider)
section_table["Section"].append(section)
if sections[section]["FAIL"] > 0:
section_table["Status"].append(
f"{Fore.RED}FAIL({sections[section]['FAIL']}){Style.RESET_ALL}"
)
else:
if sections[section]["PASS"] > 0:
section_table["Status"].append(
f"{Fore.GREEN}PASS({sections[section]['PASS']}){Style.RESET_ALL}"
)
else:
section_table["Status"].append(f"{Fore.GREEN}PASS{Style.RESET_ALL}")
section_table["Muted"].append(
f"{orange_color}{sections[section]['Muted']}{Style.RESET_ALL}"
)
if (
len(fail_count) + len(pass_count) + len(muted_count) > 1
): # If there are no resources, don't print the compliance table
print(
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
)
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
overview_table = [
[
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
]
]
print(tabulate(overview_table, tablefmt="rounded_grid"))
if not compliance_overview:
if len(fail_count) > 0 and len(section_table["Section"]) > 0:
print(
f"\nFramework {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Results:"
)
print(
tabulate(
section_table,
tablefmt="rounded_grid",
headers="keys",
)
)
print(f"\nDetailed results of {compliance_framework.upper()} are in:")
print(
f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n"
)

View File

@@ -3,6 +3,7 @@ import sys
from prowler.lib.check.models import Check_Report
from prowler.lib.logger import logger
from prowler.lib.outputs.compliance.c5.c5 import get_c5_table
from prowler.lib.outputs.compliance.ccc.ccc import get_ccc_table
from prowler.lib.outputs.compliance.cis.cis import get_cis_table
from prowler.lib.outputs.compliance.csa.csa import get_csa_table
from prowler.lib.outputs.compliance.ens.ens import get_ens_table
@@ -104,6 +105,15 @@ def display_compliance_table(
output_directory,
compliance_overview,
)
elif compliance_framework.startswith("ccc_"):
get_ccc_table(
findings,
bulk_checks_metadata,
compliance_framework,
output_filename,
output_directory,
compliance_overview,
)
else:
get_generic_compliance_table(
findings,

View File

@@ -49,11 +49,11 @@ dependencies = [
"cryptography==46.0.6",
"dash==3.1.1",
"dash-bootstrap-components==2.0.3",
"defusedxml>=0.7.1",
"defusedxml==0.7.1",
"detect-secrets==1.5.0",
"dulwich==0.23.0",
"google-api-python-client==2.163.0",
"google-auth-httplib2>=0.1,<0.3",
"google-auth-httplib2==0.2.0",
"jsonschema==4.23.0",
"kubernetes==32.0.1",
"markdown==3.10.2",
@@ -63,9 +63,9 @@ dependencies = [
"openstacksdk==4.2.0",
"pandas==2.2.3",
"py-ocsf-models==0.8.1",
"pydantic (>=2.0,<3.0)",
"pydantic==2.12.5",
"pygithub==2.8.0",
"python-dateutil (>=2.9.0.post0,<3.0.0)",
"python-dateutil==2.9.0.post0",
"pytz==2025.1",
"schema==0.7.5",
"shodan==1.31.0",
@@ -127,7 +127,7 @@ mock = "5.2.0"
moto = {extras = ["all"], version = "5.1.11"}
openapi-schema-validator = "0.6.3"
openapi-spec-validator = "0.7.1"
pre-commit = "4.2.0"
pre-commit = "4.5.1"
pylint = "3.3.4"
pytest = "8.3.5"
pytest-cov = "6.0.0"

View File

@@ -0,0 +1,138 @@
from io import StringIO
from unittest import mock
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS
from prowler.lib.outputs.compliance.ccc.models import CCC_AWSModel
from tests.lib.outputs.compliance.fixtures import CCC_AWS_FIXTURE
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1
class TestAWSCCC:
def test_output_transform_evaluated_requirement(self):
findings = [
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
]
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
output_data = output.data[0]
assert isinstance(output_data, CCC_AWSModel)
assert output_data.Provider == "aws"
assert output_data.AccountId == AWS_ACCOUNT_NUMBER
assert output_data.Region == AWS_REGION_EU_WEST_1
assert output_data.Description == CCC_AWS_FIXTURE.Description
assert output_data.Requirements_Id == CCC_AWS_FIXTURE.Requirements[0].Id
assert (
output_data.Requirements_Description
== CCC_AWS_FIXTURE.Requirements[0].Description
)
attribute = CCC_AWS_FIXTURE.Requirements[0].Attributes[0]
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
assert (
output_data.Requirements_Attributes_FamilyDescription
== attribute.FamilyDescription
)
assert output_data.Requirements_Attributes_Section == attribute.Section
assert output_data.Requirements_Attributes_SubSection == attribute.SubSection
assert (
output_data.Requirements_Attributes_SubSectionObjective
== attribute.SubSectionObjective
)
assert (
output_data.Requirements_Attributes_Applicability == attribute.Applicability
)
assert (
output_data.Requirements_Attributes_Recommendation
== attribute.Recommendation
)
assert (
output_data.Requirements_Attributes_SectionThreatMappings
== attribute.SectionThreatMappings
)
assert (
output_data.Requirements_Attributes_SectionGuidelineMappings
== attribute.SectionGuidelineMappings
)
assert output_data.Status == "PASS"
assert output_data.StatusExtended == ""
assert output_data.ResourceId == ""
assert output_data.ResourceName == ""
assert output_data.CheckId == "service_test_check_id"
assert output_data.Muted is False
def test_output_transform_manual_requirement(self):
# Use a finding for the evaluated requirement so the manual one is appended
# by the manual-loop branch (Checks=[]).
findings = [
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
]
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
# data[0] is the evaluated PASS row, data[1] is the manual row
manual_row = output.data[1]
assert isinstance(manual_row, CCC_AWSModel)
assert manual_row.Provider == "aws"
assert manual_row.AccountId == ""
assert manual_row.Region == ""
assert manual_row.Description == CCC_AWS_FIXTURE.Description
assert manual_row.Requirements_Id == CCC_AWS_FIXTURE.Requirements[1].Id
manual_attribute = CCC_AWS_FIXTURE.Requirements[1].Attributes[0]
assert (
manual_row.Requirements_Attributes_FamilyName == manual_attribute.FamilyName
)
assert manual_row.Requirements_Attributes_Section == manual_attribute.Section
assert manual_row.Status == "MANUAL"
assert manual_row.StatusExtended == "Manual check"
assert manual_row.ResourceId == "manual_check"
assert manual_row.ResourceName == "Manual check"
assert manual_row.CheckId == "manual"
assert manual_row.Muted is False
@freeze_time("2025-01-01 00:00:00")
@mock.patch(
"prowler.lib.outputs.compliance.ccc.ccc_aws.timestamp",
"2025-01-01 00:00:00",
)
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
]
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
# Header check: AWS-specific columns must be present
header = content.split("\r\n", 1)[0]
assert "ACCOUNTID" in header
assert "REGION" in header
assert "REQUIREMENTS_ATTRIBUTES_FAMILYNAME" in header
assert "REQUIREMENTS_ATTRIBUTES_SECTION" in header
assert "REQUIREMENTS_ATTRIBUTES_APPLICABILITY" in header
assert "REQUIREMENTS_ATTRIBUTES_SECTIONTHREATMAPPINGS" in header
# Header should NOT contain Azure or GCP-only columns
assert "SUBSCRIPTIONID" not in header
assert "PROJECTID" not in header
# Body checks: evaluated row + manual row
rows = [r for r in content.split("\r\n") if r]
assert len(rows) == 3 # header + evaluated + manual
assert "CCC.Core.CN01.AR01" in rows[1]
assert "PASS" in rows[1]
assert AWS_ACCOUNT_NUMBER in rows[1]
assert AWS_REGION_EU_WEST_1 in rows[1]
assert "CCC.IAM.CN01.AR01" in rows[2]
assert "MANUAL" in rows[2]
assert "manual_check" in rows[2]
# The frozen timestamp should appear
assert "2025-01-01 00:00:00" in rows[1]

View File

@@ -0,0 +1,99 @@
from io import StringIO
from unittest import mock
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure
from prowler.lib.outputs.compliance.ccc.models import CCC_AzureModel
from tests.lib.outputs.compliance.fixtures import CCC_AZURE_FIXTURE
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION_ID
AZURE_LOCATION = "westeurope"
class TestAzureCCC:
def test_output_transform_evaluated_requirement(self):
findings = [
generate_finding_output(
provider="azure",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=AZURE_SUBSCRIPTION_ID,
region=AZURE_LOCATION,
)
]
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
output_data = output.data[0]
assert isinstance(output_data, CCC_AzureModel)
assert output_data.Provider == "azure"
assert output_data.SubscriptionId == AZURE_SUBSCRIPTION_ID
assert output_data.Location == AZURE_LOCATION
assert output_data.Description == CCC_AZURE_FIXTURE.Description
assert output_data.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[0].Id
attribute = CCC_AZURE_FIXTURE.Requirements[0].Attributes[0]
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
assert output_data.Requirements_Attributes_Section == attribute.Section
assert (
output_data.Requirements_Attributes_Applicability == attribute.Applicability
)
assert output_data.Status == "PASS"
assert output_data.CheckId == "service_test_check_id"
def test_output_transform_manual_requirement(self):
findings = [
generate_finding_output(
provider="azure",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=AZURE_SUBSCRIPTION_ID,
region=AZURE_LOCATION,
)
]
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
manual_row = output.data[1]
assert isinstance(manual_row, CCC_AzureModel)
assert manual_row.Provider == "azure"
assert manual_row.SubscriptionId == ""
assert manual_row.Location == ""
assert manual_row.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[1].Id
assert manual_row.Status == "MANUAL"
assert manual_row.CheckId == "manual"
@freeze_time("2025-01-01 00:00:00")
@mock.patch(
"prowler.lib.outputs.compliance.ccc.ccc_azure.timestamp",
"2025-01-01 00:00:00",
)
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [
generate_finding_output(
provider="azure",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=AZURE_SUBSCRIPTION_ID,
region=AZURE_LOCATION,
)
]
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
header = content.split("\r\n", 1)[0]
assert "SUBSCRIPTIONID" in header
assert "LOCATION" in header
assert "ACCOUNTID" not in header
assert "PROJECTID" not in header
assert "REGION" not in header
rows = [r for r in content.split("\r\n") if r]
assert len(rows) == 3
assert "CCC.Core.CN01.AR01" in rows[1]
assert AZURE_SUBSCRIPTION_ID in rows[1]
assert "CCC.IAM.CN01.AR01" in rows[2]
assert "MANUAL" in rows[2]

View File

@@ -0,0 +1,99 @@
from io import StringIO
from unittest import mock
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP
from prowler.lib.outputs.compliance.ccc.models import CCC_GCPModel
from tests.lib.outputs.compliance.fixtures import CCC_GCP_FIXTURE
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
GCP_PROJECT_ID = "test-project"
GCP_LOCATION = "europe-west1"
class TestGCPCCC:
def test_output_transform_evaluated_requirement(self):
findings = [
generate_finding_output(
provider="gcp",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=GCP_PROJECT_ID,
region=GCP_LOCATION,
)
]
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
output_data = output.data[0]
assert isinstance(output_data, CCC_GCPModel)
assert output_data.Provider == "gcp"
assert output_data.ProjectId == GCP_PROJECT_ID
assert output_data.Location == GCP_LOCATION
assert output_data.Description == CCC_GCP_FIXTURE.Description
assert output_data.Requirements_Id == CCC_GCP_FIXTURE.Requirements[0].Id
attribute = CCC_GCP_FIXTURE.Requirements[0].Attributes[0]
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
assert output_data.Requirements_Attributes_Section == attribute.Section
assert (
output_data.Requirements_Attributes_Applicability == attribute.Applicability
)
assert output_data.Status == "PASS"
assert output_data.CheckId == "service_test_check_id"
def test_output_transform_manual_requirement(self):
findings = [
generate_finding_output(
provider="gcp",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=GCP_PROJECT_ID,
region=GCP_LOCATION,
)
]
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
manual_row = output.data[1]
assert isinstance(manual_row, CCC_GCPModel)
assert manual_row.Provider == "gcp"
assert manual_row.ProjectId == ""
assert manual_row.Location == ""
assert manual_row.Requirements_Id == CCC_GCP_FIXTURE.Requirements[1].Id
assert manual_row.Status == "MANUAL"
assert manual_row.CheckId == "manual"
@freeze_time("2025-01-01 00:00:00")
@mock.patch(
"prowler.lib.outputs.compliance.ccc.ccc_gcp.timestamp",
"2025-01-01 00:00:00",
)
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [
generate_finding_output(
provider="gcp",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=GCP_PROJECT_ID,
region=GCP_LOCATION,
)
]
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
header = content.split("\r\n", 1)[0]
assert "PROJECTID" in header
assert "LOCATION" in header
assert "ACCOUNTID" not in header
assert "SUBSCRIPTIONID" not in header
assert "REGION" not in header
rows = [r for r in content.split("\r\n") if r]
assert len(rows) == 3
assert "CCC.Core.CN01.AR01" in rows[1]
assert GCP_PROJECT_ID in rows[1]
assert "CCC.IAM.CN01.AR01" in rows[2]
assert "MANUAL" in rows[2]

View File

@@ -1,5 +1,6 @@
from prowler.lib.check.compliance_models import (
AWS_Well_Architected_Requirement_Attribute,
CCC_Requirement_Attribute,
CIS_Requirement_Attribute,
Compliance,
Compliance_Requirement,
@@ -1022,3 +1023,169 @@ PROWLER_THREATSCORE_M365 = Compliance(
),
],
)
# CCC fixtures cover the three providers Prowler ships catalogs for. Each
# fixture has one auto-evaluated requirement (with Checks) and one manual
# requirement (Checks=[]) so test suites can exercise both paths.
CCC_AWS_FIXTURE = Compliance(
Framework="CCC",
Name="Common Cloud Controls Catalog (CCC)",
Provider="AWS",
Version="v2025.10",
Description="Common Cloud Controls Catalog (CCC) for AWS",
Requirements=[
Compliance_Requirement(
Checks=["service_test_check_id"],
Id="CCC.Core.CN01.AR01",
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Data",
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
Section="CCC.Core.CN01 Encrypt Data for Transmission",
SubSection="",
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
Recommendation="Most cloud services enable TLS 1.3 by default.",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
],
)
],
),
Compliance_Requirement(
Checks=[],
Id="CCC.IAM.CN01.AR01",
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Identity and Access Management",
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
SubSection="",
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
Recommendation="",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
],
)
],
),
],
)
CCC_AZURE_FIXTURE = Compliance(
Framework="CCC",
Name="Common Cloud Controls Catalog (CCC)",
Provider="Azure",
Version="v2025.10",
Description="Common Cloud Controls Catalog (CCC) for Azure",
Requirements=[
Compliance_Requirement(
Checks=["service_test_check_id"],
Id="CCC.Core.CN01.AR01",
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Data",
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
Section="CCC.Core.CN01 Encrypt Data for Transmission",
SubSection="",
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
Recommendation="Most cloud services enable TLS 1.3 by default.",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
],
)
],
),
Compliance_Requirement(
Checks=[],
Id="CCC.IAM.CN01.AR01",
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Identity and Access Management",
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
SubSection="",
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
Recommendation="",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
],
)
],
),
],
)
CCC_GCP_FIXTURE = Compliance(
Framework="CCC",
Name="Common Cloud Controls Catalog (CCC)",
Provider="GCP",
Version="v2025.10",
Description="Common Cloud Controls Catalog (CCC) for GCP",
Requirements=[
Compliance_Requirement(
Checks=["service_test_check_id"],
Id="CCC.Core.CN01.AR01",
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Data",
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
Section="CCC.Core.CN01 Encrypt Data for Transmission",
SubSection="",
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
Recommendation="Most cloud services enable TLS 1.3 by default.",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
],
)
],
),
Compliance_Requirement(
Checks=[],
Id="CCC.IAM.CN01.AR01",
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Identity and Access Management",
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
SubSection="",
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
Recommendation="",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
],
)
],
),
],
)