mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
feat(compliance): add csa ccm 4.0 for the aws provider (#10018)
This commit is contained in:
@@ -8,6 +8,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
|||||||
|
|
||||||
- Attack Paths: Queries definition now has short description and attribution [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
|
- Attack Paths: Queries definition now has short description and attribution [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
|
||||||
- Attack Paths: Internet node is created while scan [(#9992)](https://github.com/prowler-cloud/prowler/pull/9992)
|
- Attack Paths: Internet node is created while scan [(#9992)](https://github.com/prowler-cloud/prowler/pull/9992)
|
||||||
|
- Support CSA CCM for the AWS provider [(#10018)](https://github.com/prowler-cloud/prowler/pull/10018)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
|
|||||||
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
|
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_m365 import M365CIS
|
||||||
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
|
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
|
||||||
|
from prowler.lib.outputs.compliance.csa.csa_aws import AWSCSA
|
||||||
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
|
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
|
||||||
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
|
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
|
||||||
from prowler.lib.outputs.compliance.ens.ens_gcp import GCPENS
|
from prowler.lib.outputs.compliance.ens.ens_gcp import GCPENS
|
||||||
@@ -90,6 +91,7 @@ COMPLIANCE_CLASS_MAP = {
|
|||||||
(lambda name: name == "prowler_threatscore_aws", ProwlerThreatScoreAWS),
|
(lambda name: name == "prowler_threatscore_aws", ProwlerThreatScoreAWS),
|
||||||
(lambda name: name == "ccc_aws", CCC_AWS),
|
(lambda name: name == "ccc_aws", CCC_AWS),
|
||||||
(lambda name: name.startswith("c5_"), AWSC5),
|
(lambda name: name.startswith("c5_"), AWSC5),
|
||||||
|
(lambda name: name.startswith("csa_"), AWSCSA),
|
||||||
],
|
],
|
||||||
"azure": [
|
"azure": [
|
||||||
(lambda name: name.startswith("cis_"), AzureCIS),
|
(lambda name: name.startswith("cis_"), AzureCIS),
|
||||||
|
|||||||
31
dashboard/compliance/csa_ccm_4_0_aws.py
Normal file
31
dashboard/compliance/csa_ccm_4_0_aws.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import warnings
|
||||||
|
|
||||||
|
from dashboard.common_methods import get_section_containers_kisa_ismsp
|
||||||
|
|
||||||
|
warnings.filterwarnings("ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def get_table(data):
|
||||||
|
data["REQUIREMENTS_ID"] = (
|
||||||
|
data["REQUIREMENTS_ID"] + " - " + data["REQUIREMENTS_DESCRIPTION"]
|
||||||
|
)
|
||||||
|
|
||||||
|
data["REQUIREMENTS_ID"] = data["REQUIREMENTS_ID"].apply(
|
||||||
|
lambda x: x[:150] + "..." if len(str(x)) > 150 else x
|
||||||
|
)
|
||||||
|
|
||||||
|
aux = data[
|
||||||
|
[
|
||||||
|
"REQUIREMENTS_ID",
|
||||||
|
"REQUIREMENTS_ATTRIBUTES_SECTION",
|
||||||
|
"CHECKID",
|
||||||
|
"STATUS",
|
||||||
|
"REGION",
|
||||||
|
"ACCOUNTID",
|
||||||
|
"RESOURCEID",
|
||||||
|
]
|
||||||
|
].copy()
|
||||||
|
|
||||||
|
return get_section_containers_kisa_ismsp(
|
||||||
|
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
|
||||||
|
)
|
||||||
@@ -9,6 +9,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
|||||||
- `defender_safe_attachments_policy_enabled` check for M365 provider [(#9833)](https://github.com/prowler-cloud/prowler/pull/9833)
|
- `defender_safe_attachments_policy_enabled` check for M365 provider [(#9833)](https://github.com/prowler-cloud/prowler/pull/9833)
|
||||||
- `defender_safelinks_policy_enabled` check for M365 provider [(#9832)](https://github.com/prowler-cloud/prowler/pull/9832)
|
- `defender_safelinks_policy_enabled` check for M365 provider [(#9832)](https://github.com/prowler-cloud/prowler/pull/9832)
|
||||||
- AI Skills: Added a skill for creating new Attack Paths queries in openCypher, compatible with Neo4j and Neptune [(#9975)](https://github.com/prowler-cloud/prowler/pull/9975)
|
- AI Skills: Added a skill for creating new Attack Paths queries in openCypher, compatible with Neo4j and Neptune [(#9975)](https://github.com/prowler-cloud/prowler/pull/9975)
|
||||||
|
- CSA CCM 4.0 for the AWS provider [(#10018)](https://github.com/prowler-cloud/prowler/pull/10018)
|
||||||
|
|
||||||
### 🔄 Changed
|
### 🔄 Changed
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ 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_m365 import M365CIS
|
||||||
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
|
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
|
||||||
from prowler.lib.outputs.compliance.compliance import display_compliance_table
|
from prowler.lib.outputs.compliance.compliance import display_compliance_table
|
||||||
|
from prowler.lib.outputs.compliance.csa.csa_aws import AWSCSA
|
||||||
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
|
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
|
||||||
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
|
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
|
||||||
from prowler.lib.outputs.compliance.ens.ens_gcp import GCPENS
|
from prowler.lib.outputs.compliance.ens.ens_gcp import GCPENS
|
||||||
@@ -626,6 +627,18 @@ def prowler():
|
|||||||
)
|
)
|
||||||
generated_outputs["compliance"].append(c5)
|
generated_outputs["compliance"].append(c5)
|
||||||
c5.batch_write_data_to_file()
|
c5.batch_write_data_to_file()
|
||||||
|
elif compliance_name == "csa_ccm_4.0_aws":
|
||||||
|
filename = (
|
||||||
|
f"{output_options.output_directory}/compliance/"
|
||||||
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
||||||
|
)
|
||||||
|
csa_ccm_4_0_aws = AWSCSA(
|
||||||
|
findings=finding_outputs,
|
||||||
|
compliance=bulk_compliance_frameworks[compliance_name],
|
||||||
|
file_path=filename,
|
||||||
|
)
|
||||||
|
generated_outputs["compliance"].append(csa_ccm_4_0_aws)
|
||||||
|
csa_ccm_4_0_aws.batch_write_data_to_file()
|
||||||
else:
|
else:
|
||||||
filename = (
|
filename = (
|
||||||
f"{output_options.output_directory}/compliance/"
|
f"{output_options.output_directory}/compliance/"
|
||||||
|
|||||||
7604
prowler/compliance/aws/csa_ccm_4.0_aws.json
Normal file
7604
prowler/compliance/aws/csa_ccm_4.0_aws.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -226,6 +226,18 @@ class C5Germany_Requirement_Attribute(BaseModel):
|
|||||||
ComplementaryCriteria: str
|
ComplementaryCriteria: str
|
||||||
|
|
||||||
|
|
||||||
|
# CSA CCM v4 Requirement Attribute
|
||||||
|
class CSA_CCM_Requirement_Attribute(BaseModel):
|
||||||
|
"""CSA Cloud Controls Matrix (CCM) v4 Requirement Attribute"""
|
||||||
|
|
||||||
|
Section: str
|
||||||
|
CCMLite: str
|
||||||
|
IaaS: str
|
||||||
|
PaaS: str
|
||||||
|
SaaS: str
|
||||||
|
ScopeApplicability: list[dict]
|
||||||
|
|
||||||
|
|
||||||
# Base Compliance Model
|
# Base Compliance Model
|
||||||
# TODO: move this to compliance folder
|
# TODO: move this to compliance folder
|
||||||
class Compliance_Requirement(BaseModel):
|
class Compliance_Requirement(BaseModel):
|
||||||
@@ -244,6 +256,7 @@ class Compliance_Requirement(BaseModel):
|
|||||||
Prowler_ThreatScore_Requirement_Attribute,
|
Prowler_ThreatScore_Requirement_Attribute,
|
||||||
CCC_Requirement_Attribute,
|
CCC_Requirement_Attribute,
|
||||||
C5Germany_Requirement_Attribute,
|
C5Germany_Requirement_Attribute,
|
||||||
|
CSA_CCM_Requirement_Attribute,
|
||||||
# Generic_Compliance_Requirement_Attribute must be the last one since it is the fallback for generic compliance framework
|
# Generic_Compliance_Requirement_Attribute must be the last one since it is the fallback for generic compliance framework
|
||||||
Generic_Compliance_Requirement_Attribute,
|
Generic_Compliance_Requirement_Attribute,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from prowler.lib.check.models import Check_Report
|
|||||||
from prowler.lib.logger import logger
|
from prowler.lib.logger import logger
|
||||||
from prowler.lib.outputs.compliance.c5.c5 import get_c5_table
|
from prowler.lib.outputs.compliance.c5.c5 import get_c5_table
|
||||||
from prowler.lib.outputs.compliance.cis.cis import get_cis_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
|
from prowler.lib.outputs.compliance.ens.ens import get_ens_table
|
||||||
from prowler.lib.outputs.compliance.generic.generic_table import (
|
from prowler.lib.outputs.compliance.generic.generic_table import (
|
||||||
get_generic_compliance_table,
|
get_generic_compliance_table,
|
||||||
@@ -85,6 +86,15 @@ def display_compliance_table(
|
|||||||
output_directory,
|
output_directory,
|
||||||
compliance_overview,
|
compliance_overview,
|
||||||
)
|
)
|
||||||
|
elif "csa_ccm_" in compliance_framework:
|
||||||
|
get_csa_table(
|
||||||
|
findings,
|
||||||
|
bulk_checks_metadata,
|
||||||
|
compliance_framework,
|
||||||
|
output_filename,
|
||||||
|
output_directory,
|
||||||
|
compliance_overview,
|
||||||
|
)
|
||||||
elif "c5_" in compliance_framework:
|
elif "c5_" in compliance_framework:
|
||||||
get_c5_table(
|
get_c5_table(
|
||||||
findings,
|
findings,
|
||||||
|
|||||||
0
prowler/lib/outputs/compliance/csa/__init__.py
Normal file
0
prowler/lib/outputs/compliance/csa/__init__.py
Normal file
101
prowler/lib/outputs/compliance/csa/csa.py
Normal file
101
prowler/lib/outputs/compliance/csa/csa.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from colorama import Fore, Style
|
||||||
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
from prowler.config.config import orange_color
|
||||||
|
|
||||||
|
|
||||||
|
def get_csa_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 == "CSA-CCM"
|
||||||
|
and compliance.Version in compliance_framework
|
||||||
|
):
|
||||||
|
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"
|
||||||
|
)
|
||||||
96
prowler/lib/outputs/compliance/csa/csa_aws.py
Normal file
96
prowler/lib/outputs/compliance/csa/csa_aws.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
from prowler.config.config import timestamp
|
||||||
|
from prowler.lib.check.compliance_models import Compliance
|
||||||
|
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
|
||||||
|
from prowler.lib.outputs.compliance.csa.models import AWSCSAModel
|
||||||
|
from prowler.lib.outputs.finding import Finding
|
||||||
|
|
||||||
|
|
||||||
|
class AWSCSA(ComplianceOutput):
|
||||||
|
"""
|
||||||
|
This class represents the AWS CSA compliance output.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- _data (list): A list to store transformed data from findings.
|
||||||
|
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
- transform: Transforms findings into AWS CSA compliance format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def transform(
|
||||||
|
self,
|
||||||
|
findings: list[Finding],
|
||||||
|
compliance: Compliance,
|
||||||
|
compliance_name: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Transforms a list of findings into AWS CSA compliance format.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- findings (list): A list of findings.
|
||||||
|
- compliance (Compliance): A compliance model.
|
||||||
|
- compliance_name (str): The name of the compliance model.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- None
|
||||||
|
"""
|
||||||
|
for finding in findings:
|
||||||
|
# Get the compliance requirements for the finding
|
||||||
|
finding_requirements = finding.compliance.get(compliance_name, [])
|
||||||
|
for requirement in compliance.Requirements:
|
||||||
|
if requirement.Id in finding_requirements:
|
||||||
|
for attribute in requirement.Attributes:
|
||||||
|
compliance_row = AWSCSAModel(
|
||||||
|
Provider=finding.provider,
|
||||||
|
Description=compliance.Description,
|
||||||
|
AccountId=finding.account_uid,
|
||||||
|
Region=finding.region,
|
||||||
|
AssessmentDate=str(timestamp),
|
||||||
|
Requirements_Id=requirement.Id,
|
||||||
|
Requirements_Description=requirement.Description,
|
||||||
|
Requirements_Name=requirement.Name,
|
||||||
|
Requirements_Attributes_Section=attribute.Section,
|
||||||
|
Requirements_Attributes_CCMLite=attribute.CCMLite,
|
||||||
|
Requirements_Attributes_IaaS=attribute.IaaS,
|
||||||
|
Requirements_Attributes_PaaS=attribute.PaaS,
|
||||||
|
Requirements_Attributes_SaaS=attribute.SaaS,
|
||||||
|
Requirements_Attributes_ScopeApplicability=attribute.ScopeApplicability,
|
||||||
|
Status=finding.status,
|
||||||
|
StatusExtended=finding.status_extended,
|
||||||
|
ResourceId=finding.resource_uid,
|
||||||
|
ResourceName=finding.resource_name,
|
||||||
|
CheckId=finding.check_id,
|
||||||
|
Muted=finding.muted,
|
||||||
|
Framework=compliance.Framework,
|
||||||
|
Name=compliance.Name,
|
||||||
|
)
|
||||||
|
self._data.append(compliance_row)
|
||||||
|
# Add manual requirements to the compliance output
|
||||||
|
for requirement in compliance.Requirements:
|
||||||
|
if not requirement.Checks:
|
||||||
|
for attribute in requirement.Attributes:
|
||||||
|
compliance_row = AWSCSAModel(
|
||||||
|
Provider=compliance.Provider.lower(),
|
||||||
|
Description=compliance.Description,
|
||||||
|
AccountId="",
|
||||||
|
Region="",
|
||||||
|
AssessmentDate=str(timestamp),
|
||||||
|
Requirements_Id=requirement.Id,
|
||||||
|
Requirements_Description=requirement.Description,
|
||||||
|
Requirements_Name=requirement.Name,
|
||||||
|
Requirements_Attributes_Section=attribute.Section,
|
||||||
|
Requirements_Attributes_CCMLite=attribute.CCMLite,
|
||||||
|
Requirements_Attributes_IaaS=attribute.IaaS,
|
||||||
|
Requirements_Attributes_PaaS=attribute.PaaS,
|
||||||
|
Requirements_Attributes_SaaS=attribute.SaaS,
|
||||||
|
Requirements_Attributes_ScopeApplicability=attribute.ScopeApplicability,
|
||||||
|
Status="MANUAL",
|
||||||
|
StatusExtended="Manual check",
|
||||||
|
ResourceId="manual_check",
|
||||||
|
ResourceName="Manual check",
|
||||||
|
CheckId="manual",
|
||||||
|
Muted=False,
|
||||||
|
Framework=compliance.Framework,
|
||||||
|
Name=compliance.Name,
|
||||||
|
)
|
||||||
|
self._data.append(compliance_row)
|
||||||
30
prowler/lib/outputs/compliance/csa/models.py
Normal file
30
prowler/lib/outputs/compliance/csa/models.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from pydantic.v1 import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class AWSCSAModel(BaseModel):
|
||||||
|
"""
|
||||||
|
AWSCSAModel generates a finding's output in CSV CSA format for AWS.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Provider: str
|
||||||
|
Description: str
|
||||||
|
AccountId: str
|
||||||
|
Region: str
|
||||||
|
AssessmentDate: str
|
||||||
|
Requirements_Id: str
|
||||||
|
Requirements_Description: str
|
||||||
|
Requirements_Name: str
|
||||||
|
Requirements_Attributes_Section: str
|
||||||
|
Requirements_Attributes_CCMLite: str
|
||||||
|
Requirements_Attributes_IaaS: str
|
||||||
|
Requirements_Attributes_PaaS: str
|
||||||
|
Requirements_Attributes_SaaS: str
|
||||||
|
Requirements_Attributes_ScopeApplicability: list[dict]
|
||||||
|
Status: str
|
||||||
|
StatusExtended: str
|
||||||
|
ResourceId: str
|
||||||
|
CheckId: str
|
||||||
|
Muted: bool
|
||||||
|
ResourceName: str
|
||||||
|
Framework: str
|
||||||
|
Name: str
|
||||||
@@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
|||||||
### 🔄 Changed
|
### 🔄 Changed
|
||||||
|
|
||||||
- Attack Paths: Query list now shows their name and short description, when one is selected it also shows a longer description and an attribution if it has it [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
|
- Attack Paths: Query list now shows their name and short description, when one is selected it also shows a longer description and an attribution if it has it [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
|
||||||
|
- CSA CCM detailed view and small fix related with `Top Failed Sections` width [(#10018)](https://github.com/prowler-cloud/prowler/pull/10018)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -151,8 +151,9 @@ export default async function ComplianceDetail({
|
|||||||
{/* Mobile: each card on own row | Tablet: ThreatScore full row, others share row | Desktop: all 3 in one row */}
|
{/* Mobile: each card on own row | Tablet: ThreatScore full row, others share row | Desktop: all 3 in one row */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid grid-cols-1 gap-6 md:grid-cols-2",
|
"grid grid-cols-1 gap-6 md:grid-cols-[minmax(280px,400px)_1fr]",
|
||||||
isThreatScore && "xl:grid-cols-[minmax(280px,320px)_auto_1fr]",
|
isThreatScore &&
|
||||||
|
"xl:grid-cols-[minmax(280px,320px)_minmax(280px,400px)_1fr]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isThreatScore && (
|
{isThreatScore && (
|
||||||
@@ -209,7 +210,7 @@ const SSRComplianceContent = async ({
|
|||||||
if (!scanId || type === "tasks") {
|
if (!scanId || type === "tasks") {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-[minmax(280px,400px)_1fr]">
|
||||||
<RequirementsStatusCard pass={0} fail={0} manual={0} />
|
<RequirementsStatusCard pass={0} fail={0} manual={0} />
|
||||||
<TopFailedSectionsCard sections={[]} />
|
<TopFailedSectionsCard sections={[]} />
|
||||||
{/* <SectionsFailureRateCard categories={[]} /> */}
|
{/* <SectionsFailureRateCard categories={[]} /> */}
|
||||||
@@ -244,8 +245,9 @@ const SSRComplianceContent = async ({
|
|||||||
{/* Mobile: each card on own row | Tablet: ThreatScore full row, others share row | Desktop: all 3 in one row */}
|
{/* Mobile: each card on own row | Tablet: ThreatScore full row, others share row | Desktop: all 3 in one row */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid grid-cols-1 gap-6 md:grid-cols-2",
|
"grid grid-cols-1 gap-6 md:grid-cols-[minmax(280px,400px)_1fr]",
|
||||||
threatScoreData && "xl:grid-cols-[minmax(280px,320px)_auto_1fr]",
|
threatScoreData &&
|
||||||
|
"xl:grid-cols-[minmax(280px,320px)_minmax(280px,400px)_1fr]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{threatScoreData && (
|
{threatScoreData && (
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { cn } from "@/lib";
|
||||||
|
import { CSA_MAPPING_SECTIONS } from "@/lib/compliance/csa";
|
||||||
|
import { Requirement } from "@/types/compliance";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ComplianceBadge,
|
||||||
|
ComplianceBadgeContainer,
|
||||||
|
ComplianceDetailContainer,
|
||||||
|
ComplianceDetailSection,
|
||||||
|
ComplianceDetailText,
|
||||||
|
} from "./shared-components";
|
||||||
|
|
||||||
|
interface CSADetailsProps {
|
||||||
|
requirement: Requirement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CSACustomDetails = ({ requirement }: CSADetailsProps) => {
|
||||||
|
const mappingSections = CSA_MAPPING_SECTIONS.map((section) => ({
|
||||||
|
...section,
|
||||||
|
data: requirement[section.key] as Array<{
|
||||||
|
ReferenceId: string;
|
||||||
|
Identifiers: string[];
|
||||||
|
}>,
|
||||||
|
})).filter((section) => section.data && section.data.length > 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ComplianceDetailContainer>
|
||||||
|
{requirement.description && (
|
||||||
|
<ComplianceDetailSection title="Description">
|
||||||
|
<ComplianceDetailText>{requirement.description}</ComplianceDetailText>
|
||||||
|
</ComplianceDetailSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ComplianceBadgeContainer>
|
||||||
|
{requirement.ccm_lite && (
|
||||||
|
<ComplianceBadge
|
||||||
|
label="CCM Lite"
|
||||||
|
value={requirement.ccm_lite as string}
|
||||||
|
color={requirement.ccm_lite === "Yes" ? "green" : "gray"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{requirement.iaas && (
|
||||||
|
<ComplianceBadge
|
||||||
|
label="IaaS"
|
||||||
|
value={requirement.iaas as string}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{requirement.paas && (
|
||||||
|
<ComplianceBadge
|
||||||
|
label="PaaS"
|
||||||
|
value={requirement.paas as string}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{requirement.saas && (
|
||||||
|
<ComplianceBadge
|
||||||
|
label="SaaS"
|
||||||
|
value={requirement.saas as string}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ComplianceBadgeContainer>
|
||||||
|
|
||||||
|
{mappingSections.map((section) => (
|
||||||
|
<ComplianceDetailSection key={section.title} title={section.title}>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{section.data.map((mapping, index) => (
|
||||||
|
<div key={index} className="flex flex-col gap-1">
|
||||||
|
<span className="text-muted-foreground text-xs font-medium">
|
||||||
|
{mapping.ReferenceId}
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{mapping.Identifiers.map((identifier, idx) => (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset",
|
||||||
|
section.colorClasses,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{identifier}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ComplianceDetailSection>
|
||||||
|
))}
|
||||||
|
</ComplianceDetailContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import C5Logo from "./c5.svg";
|
|||||||
import CCCLogo from "./ccc.svg";
|
import CCCLogo from "./ccc.svg";
|
||||||
import CISLogo from "./cis.svg";
|
import CISLogo from "./cis.svg";
|
||||||
import CISALogo from "./cisa.svg";
|
import CISALogo from "./cisa.svg";
|
||||||
|
import CSALogo from "./csa.svg";
|
||||||
import ENSLogo from "./ens.png";
|
import ENSLogo from "./ens.png";
|
||||||
import FedRAMPLogo from "./fedramp.svg";
|
import FedRAMPLogo from "./fedramp.svg";
|
||||||
import FFIECLogo from "./ffiec.svg";
|
import FFIECLogo from "./ffiec.svg";
|
||||||
@@ -40,6 +41,7 @@ const COMPLIANCE_LOGOS = {
|
|||||||
nis2: NIS2Logo,
|
nis2: NIS2Logo,
|
||||||
c5: C5Logo,
|
c5: C5Logo,
|
||||||
ccc: CCCLogo,
|
ccc: CCCLogo,
|
||||||
|
csa: CSALogo,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const getComplianceIcon = (complianceTitle: string) => {
|
export const getComplianceIcon = (complianceTitle: string) => {
|
||||||
|
|||||||
1
ui/components/icons/compliance/csa.svg
Normal file
1
ui/components/icons/compliance/csa.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.4 KiB |
@@ -4,6 +4,7 @@ import { AWSWellArchitectedCustomDetails } from "@/components/compliance/complia
|
|||||||
import { C5CustomDetails } from "@/components/compliance/compliance-custom-details/c5-details";
|
import { C5CustomDetails } from "@/components/compliance/compliance-custom-details/c5-details";
|
||||||
import { CCCCustomDetails } from "@/components/compliance/compliance-custom-details/ccc-details";
|
import { CCCCustomDetails } from "@/components/compliance/compliance-custom-details/ccc-details";
|
||||||
import { CISCustomDetails } from "@/components/compliance/compliance-custom-details/cis-details";
|
import { CISCustomDetails } from "@/components/compliance/compliance-custom-details/cis-details";
|
||||||
|
import { CSACustomDetails } from "@/components/compliance/compliance-custom-details/csa-details";
|
||||||
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details";
|
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details";
|
||||||
import { GenericCustomDetails } from "@/components/compliance/compliance-custom-details/generic-details";
|
import { GenericCustomDetails } from "@/components/compliance/compliance-custom-details/generic-details";
|
||||||
import { ISOCustomDetails } from "@/components/compliance/compliance-custom-details/iso-details";
|
import { ISOCustomDetails } from "@/components/compliance/compliance-custom-details/iso-details";
|
||||||
@@ -37,6 +38,10 @@ import {
|
|||||||
toAccordionItems as toCISAccordionItems,
|
toAccordionItems as toCISAccordionItems,
|
||||||
} from "./cis";
|
} from "./cis";
|
||||||
import { calculateCategoryHeatmapData, getTopFailedSections } from "./commons";
|
import { calculateCategoryHeatmapData, getTopFailedSections } from "./commons";
|
||||||
|
import {
|
||||||
|
mapComplianceData as mapCSAComplianceData,
|
||||||
|
toAccordionItems as toCSAAccordionItems,
|
||||||
|
} from "./csa";
|
||||||
import {
|
import {
|
||||||
mapComplianceData as mapENSComplianceData,
|
mapComplianceData as mapENSComplianceData,
|
||||||
toAccordionItems as toENSAccordionItems,
|
toAccordionItems as toENSAccordionItems,
|
||||||
@@ -179,6 +184,15 @@ const getComplianceMappers = (): Record<string, ComplianceMapper> => ({
|
|||||||
getDetailsComponent: (requirement: Requirement) =>
|
getDetailsComponent: (requirement: Requirement) =>
|
||||||
createElement(CCCCustomDetails, { requirement }),
|
createElement(CCCCustomDetails, { requirement }),
|
||||||
},
|
},
|
||||||
|
"CSA-CCM": {
|
||||||
|
mapComplianceData: mapCSAComplianceData,
|
||||||
|
toAccordionItems: toCSAAccordionItems,
|
||||||
|
getTopFailedSections,
|
||||||
|
calculateCategoryHeatmapData: (data: Framework[]) =>
|
||||||
|
calculateCategoryHeatmapData(data),
|
||||||
|
getDetailsComponent: (requirement: Requirement) =>
|
||||||
|
createElement(CSACustomDetails, { requirement }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
143
ui/lib/compliance/csa.tsx
Normal file
143
ui/lib/compliance/csa.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { ClientAccordionContent } from "@/components/compliance/compliance-accordion/client-accordion-content";
|
||||||
|
import { ComplianceAccordionRequirementTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title";
|
||||||
|
import { ComplianceAccordionTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-title";
|
||||||
|
import { AccordionItemProps } from "@/components/ui/accordion/Accordion";
|
||||||
|
import { FindingStatus } from "@/components/ui/table/status-finding-badge";
|
||||||
|
import {
|
||||||
|
AttributesData,
|
||||||
|
CSAAttributesMetadata,
|
||||||
|
Framework,
|
||||||
|
Requirement,
|
||||||
|
REQUIREMENT_STATUS,
|
||||||
|
RequirementsData,
|
||||||
|
RequirementStatus,
|
||||||
|
} from "@/types/compliance";
|
||||||
|
|
||||||
|
import {
|
||||||
|
calculateFrameworkCounters,
|
||||||
|
createRequirementsMap,
|
||||||
|
findOrCreateCategory,
|
||||||
|
findOrCreateControl,
|
||||||
|
findOrCreateFramework,
|
||||||
|
} from "./commons";
|
||||||
|
|
||||||
|
export interface CSAMappingSection {
|
||||||
|
title: string;
|
||||||
|
key: keyof Requirement;
|
||||||
|
colorClasses: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CSA_MAPPING_SECTIONS: CSAMappingSection[] = [
|
||||||
|
{
|
||||||
|
title: "Scope Applicability",
|
||||||
|
key: "scope_applicability",
|
||||||
|
colorClasses:
|
||||||
|
"bg-blue-50 text-blue-700 ring-blue-600/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/20",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getStatusCounters = (status: RequirementStatus) => ({
|
||||||
|
pass: status === REQUIREMENT_STATUS.PASS ? 1 : 0,
|
||||||
|
fail: status === REQUIREMENT_STATUS.FAIL ? 1 : 0,
|
||||||
|
manual: status === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mapComplianceData = (
|
||||||
|
attributesData: AttributesData,
|
||||||
|
requirementsData: RequirementsData,
|
||||||
|
): Framework[] => {
|
||||||
|
const attributes = attributesData?.data || [];
|
||||||
|
const requirementsMap = createRequirementsMap(requirementsData);
|
||||||
|
const frameworks: Framework[] = [];
|
||||||
|
|
||||||
|
for (const attributeItem of attributes) {
|
||||||
|
const id = attributeItem.id;
|
||||||
|
const metadataArray = attributeItem.attributes?.attributes
|
||||||
|
?.metadata as unknown as CSAAttributesMetadata[];
|
||||||
|
const attrs = metadataArray?.[0];
|
||||||
|
if (!attrs) continue;
|
||||||
|
|
||||||
|
const requirementData = requirementsMap.get(id);
|
||||||
|
if (!requirementData) continue;
|
||||||
|
|
||||||
|
const frameworkName = attributeItem.attributes.framework;
|
||||||
|
const categoryName = attrs.Section;
|
||||||
|
const requirementName = attributeItem.attributes.name || "";
|
||||||
|
const description = attributeItem.attributes.description;
|
||||||
|
const status = requirementData.attributes.status || "";
|
||||||
|
const checks = attributeItem.attributes.attributes.check_ids || [];
|
||||||
|
|
||||||
|
const framework = findOrCreateFramework(frameworks, frameworkName);
|
||||||
|
const category = findOrCreateCategory(framework.categories, categoryName);
|
||||||
|
// Use a single control per category to keep a flat 2-level structure
|
||||||
|
const control = findOrCreateControl(category.controls, categoryName);
|
||||||
|
|
||||||
|
const finalStatus: RequirementStatus = status as RequirementStatus;
|
||||||
|
const requirement: Requirement = {
|
||||||
|
name: requirementName ? `${id} - ${requirementName}` : id,
|
||||||
|
description,
|
||||||
|
status: finalStatus,
|
||||||
|
check_ids: checks,
|
||||||
|
...getStatusCounters(finalStatus),
|
||||||
|
ccm_lite: attrs.CCMLite,
|
||||||
|
iaas: attrs.IaaS,
|
||||||
|
paas: attrs.PaaS,
|
||||||
|
saas: attrs.SaaS,
|
||||||
|
scope_applicability: attrs.ScopeApplicability,
|
||||||
|
};
|
||||||
|
|
||||||
|
control.requirements.push(requirement);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateFrameworkCounters(frameworks);
|
||||||
|
|
||||||
|
return frameworks;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toAccordionItems = (
|
||||||
|
data: Framework[],
|
||||||
|
scanId: string | undefined,
|
||||||
|
): AccordionItemProps[] => {
|
||||||
|
const safeId = scanId || "";
|
||||||
|
|
||||||
|
return data.flatMap((framework) =>
|
||||||
|
framework.categories.map((category) => ({
|
||||||
|
key: `${framework.name}-${category.name}`,
|
||||||
|
title: (
|
||||||
|
<ComplianceAccordionTitle
|
||||||
|
label={category.name}
|
||||||
|
pass={category.pass}
|
||||||
|
fail={category.fail}
|
||||||
|
manual={category.manual}
|
||||||
|
isParentLevel={true}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
content: "",
|
||||||
|
// Flatten: requirements are direct children of the section
|
||||||
|
items: category.controls.flatMap((control) =>
|
||||||
|
control.requirements.map((requirement, reqIndex) => ({
|
||||||
|
key: `${framework.name}-${category.name}-req-${reqIndex}`,
|
||||||
|
title: (
|
||||||
|
<ComplianceAccordionRequirementTitle
|
||||||
|
type=""
|
||||||
|
name={requirement.name}
|
||||||
|
status={requirement.status as FindingStatus}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
content: (
|
||||||
|
<ClientAccordionContent
|
||||||
|
key={`content-${framework.name}-${category.name}-req-${reqIndex}`}
|
||||||
|
requirement={requirement}
|
||||||
|
scanId={safeId}
|
||||||
|
framework={framework.name}
|
||||||
|
disableFindings={
|
||||||
|
requirement.check_ids.length === 0 && requirement.manual === 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
items: [],
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -189,6 +189,18 @@ export interface GenericAttributesMetadata {
|
|||||||
Type: string | null;
|
Type: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CSAAttributesMetadata {
|
||||||
|
Section: string;
|
||||||
|
CCMLite: string;
|
||||||
|
IaaS: string;
|
||||||
|
PaaS: string;
|
||||||
|
SaaS: string;
|
||||||
|
ScopeApplicability: Array<{
|
||||||
|
ReferenceId: string;
|
||||||
|
Identifiers: string[];
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CCCAttributesMetadata {
|
export interface CCCAttributesMetadata {
|
||||||
FamilyName: string;
|
FamilyName: string;
|
||||||
FamilyDescription: string;
|
FamilyDescription: string;
|
||||||
@@ -227,6 +239,7 @@ export interface AttributesItemData {
|
|||||||
| C5AttributesMetadata[]
|
| C5AttributesMetadata[]
|
||||||
| MITREAttributesMetadata[]
|
| MITREAttributesMetadata[]
|
||||||
| CCCAttributesMetadata[]
|
| CCCAttributesMetadata[]
|
||||||
|
| CSAAttributesMetadata[]
|
||||||
| GenericAttributesMetadata[];
|
| GenericAttributesMetadata[];
|
||||||
check_ids: string[];
|
check_ids: string[];
|
||||||
// MITRE structure
|
// MITRE structure
|
||||||
|
|||||||
Reference in New Issue
Block a user