Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(resource metadata): add resource metadata to JSON OCSF #6592

Merged
merged 14 commits into from
Jan 23, 2025
Merged
  •  
  •  
  •  
105 changes: 40 additions & 65 deletions prowler/lib/check/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
import sys
from abc import ABC, abstractmethod
from dataclasses import dataclass
from dataclasses import asdict, dataclass, is_dataclass
from enum import Enum
from typing import Any, Dict, Set

Expand Down Expand Up @@ -405,36 +405,34 @@
status: str
status_extended: str
check_metadata: CheckMetadata
resource_metadata: dict
resource: dict
resource_details: str
resource_tags: list
muted: bool

def __init__(self, metadata: Dict, resource: Any = None) -> None:
def __init__(self, metadata: Dict, resource: Any) -> None:
"""Initialize the Check's finding information.

Args:
metadata: The metadata of the check.
resource: Basic information about the resource. Defaults to None.
Only accepted dict, list, BaseModels (dict attribute), custom models (with to_dict attribute) or objects with __dict__.
Only accepted dict, list, BaseModels (dict attribute), custom models (with to_dict attribute) and dataclasses.
"""
self.status = ""
self.check_metadata = CheckMetadata.parse_raw(metadata)
if isinstance(resource, dict):
self.resource_metadata = resource
elif isinstance(resource, list):
self.resource_metadata = dict(enumerate(resource))
self.resource = resource
elif hasattr(resource, "dict"):
self.resource_metadata = resource.dict()
self.resource = resource.dict()
elif hasattr(resource, "to_dict"):
self.resource_metadata = resource.to_dict()
elif hasattr(resource, "__dict__"):
self.resource_metadata = resource.__dict__
self.resource = resource.to_dict()

Check warning on line 428 in prowler/lib/check/models.py

View check run for this annotation

Codecov / codecov/patch

prowler/lib/check/models.py#L428

Added line #L428 was not covered by tests
elif is_dataclass(resource):
self.resource = asdict(resource)
else:
logger.error(
f"Resource metadata {type(resource)} could not be converted to dict"
f"Resource metadata {type(resource)} in {self.check_metadata.CheckID} could not be converted to dict"
)
self.resource_metadata = {}
self.resource = {}
self.status_extended = ""
self.resource_details = ""
self.resource_tags = getattr(resource, "tags", []) if resource else []
Expand All @@ -449,20 +447,13 @@
resource_arn: str
region: str

def __init__(self, metadata, resource_metadata=None):
super().__init__(metadata, resource_metadata)
if resource_metadata:
self.resource_id = (
getattr(resource_metadata, "id", None)
or getattr(resource_metadata, "name", None)
or ""
)
self.resource_arn = getattr(resource_metadata, "arn", "")
self.region = getattr(resource_metadata, "region", "")
else:
self.resource_id = ""
self.resource_arn = ""
self.region = ""
def __init__(self, metadata: Dict, resource: Any) -> None:
super().__init__(metadata, resource)
self.resource_id = (
getattr(resource, "id", None) or getattr(resource, "name", None) or ""
)
self.resource_arn = getattr(resource, "arn", "")
self.region = getattr(resource, "region", "")


@dataclass
Expand All @@ -474,34 +465,20 @@
subscription: str
location: str

def __init__(self, metadata: Dict, resource_metadata: Any = None) -> None:
def __init__(self, metadata: Dict, resource: Any) -> None:
"""Initialize the Azure Check's finding information.

Args:
metadata: The metadata of the check.
resource_metadata: Basic information about the resource. Defaults to None.
resource: Basic information about the resource. Defaults to None.
"""
super().__init__(metadata, resource_metadata)
self.resource_name = (
resource_metadata.name
if hasattr(resource_metadata, "name")
else (
resource_metadata.resource_name
if hasattr(resource_metadata, "resource_name")
else ""
)
)
self.resource_id = (
resource_metadata.id
if hasattr(resource_metadata, "id")
else (
resource_metadata.resource_id
if hasattr(resource_metadata, "resource_id")
else ""
)
super().__init__(metadata, resource)
self.resource_name = getattr(
resource, "name", getattr(resource, "resource_name", "")
)
self.resource_id = getattr(resource, "id", getattr(resource, "resource_id", ""))
self.subscription = ""
self.location = getattr(resource_metadata, "location", "global")
self.location = getattr(resource, "location", "global")


@dataclass
Expand All @@ -515,26 +492,26 @@

def __init__(
self,
metadata,
resource_metadata,
metadata: Dict,
resource: Any,
location=None,
resource_name=None,
resource_id=None,
project_id=None,
):
super().__init__(metadata, resource_metadata)
) -> None:
super().__init__(metadata, resource)
self.resource_id = (
resource_id
or getattr(resource_metadata, "id", None)
or getattr(resource_metadata, "name", None)
or getattr(resource, "id", None)
or getattr(resource, "name", None)
or ""
)
self.resource_name = resource_name or getattr(resource_metadata, "name", "")
self.project_id = project_id or getattr(resource_metadata, "project_id", "")
self.resource_name = resource_name or getattr(resource, "name", "")
self.project_id = project_id or getattr(resource, "project_id", "")
self.location = (
location
or getattr(resource_metadata, "location", "")
or getattr(resource_metadata, "region", "")
or getattr(resource, "location", "")
or getattr(resource, "region", "")
)


Expand All @@ -547,15 +524,13 @@
resource_id: str
namespace: str

def __init__(self, metadata, resource_metadata):
super().__init__(metadata, resource_metadata)
def __init__(self, metadata: Dict, resource: Any) -> None:
super().__init__(metadata, resource)

Check warning on line 528 in prowler/lib/check/models.py

View check run for this annotation

Codecov / codecov/patch

prowler/lib/check/models.py#L528

Added line #L528 was not covered by tests
self.resource_id = (
getattr(resource_metadata, "uid", None)
or getattr(resource_metadata, "name", None)
or ""
getattr(resource, "uid", None) or getattr(resource, "name", None) or ""
)
self.resource_name = getattr(resource_metadata, "name", "")
self.namespace = getattr(resource_metadata, "namespace", "cluster-wide")
self.resource_name = getattr(resource, "name", "")
self.namespace = getattr(resource, "namespace", "cluster-wide")

Check warning on line 533 in prowler/lib/check/models.py

View check run for this annotation

Codecov / codecov/patch

prowler/lib/check/models.py#L532-L533

Added lines #L532 - L533 were not covered by tests
if not self.namespace:
self.namespace = "cluster-wide"

Expand Down
2 changes: 1 addition & 1 deletion prowler/lib/outputs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def fill_common_finding_data(finding: dict, unix_timestamp: bool) -> dict:
"status_extended": finding.status_extended,
"muted": finding.muted,
"resource_details": finding.resource_details,
# "resource_metadata": finding.resource_metadata, TODO: add resource_metadata to the finding
"resource": finding.resource,
"resource_tags": unroll_tags(finding.resource_tags),
}
return finding_data
Expand Down
3 changes: 2 additions & 1 deletion prowler/lib/outputs/finding.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Finding(BaseModel):
status_extended: str
muted: bool = False
resource_uid: str
# resource_metadata: dict = Field(default_factory=dict) TODO: add resource_metadata to the finding
resource_metadata: dict = Field(default_factory=dict)
resource_name: str
resource_details: str
resource_tags: dict = Field(default_factory=dict)
Expand Down Expand Up @@ -121,6 +121,7 @@ def generate_output(
)
try:
output_data["provider"] = provider.type
output_data["resource_metadata"] = check_output.resource

if provider.type == "aws":
output_data["account_uid"] = get_nested_attribute(
Expand Down
17 changes: 11 additions & 6 deletions prowler/lib/outputs/ocsf/ocsf.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def transform(self, findings: List[Finding]) -> None:
region=finding.region,
data={
"details": finding.resource_details,
# "metadata": finding.resource_metadata, TODO: add the resource_metadata to the finding
"metadata": finding.resource_metadata,
},
)
]
Expand All @@ -127,7 +127,7 @@ def transform(self, findings: List[Finding]) -> None:
type=finding.metadata.ResourceType,
data={
"details": finding.resource_details,
# "metadata": finding.resource_metadata, TODO: add the resource_metadata to the finding
"metadata": finding.resource_metadata,
},
namespace=finding.region.replace("namespace: ", ""),
)
Expand Down Expand Up @@ -193,10 +193,15 @@ def batch_write_data_to_file(self) -> None:
):
self._file_descriptor.write("[")
for finding in self._data:
self._file_descriptor.write(
finding.json(exclude_none=True, indent=4)
)
self._file_descriptor.write(",")
try:
self._file_descriptor.write(
finding.json(exclude_none=True, indent=4)
)
self._file_descriptor.write(",")
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
if self._file_descriptor.tell() > 0:
if self._file_descriptor.tell() != 1:
self._file_descriptor.seek(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class accessanalyzer_enabled(Check):
def execute(self):
findings = []
for analyzer in accessanalyzer_client.analyzers:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=analyzer
)
report = Check_Report_AWS(metadata=self.metadata(), resource=analyzer)
if analyzer.status == "ACTIVE":
report.status = "PASS"
report.status_extended = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class accessanalyzer_enabled_without_findings(Check):
def execute(self):
findings = []
for analyzer in accessanalyzer_client.analyzers:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=analyzer
)
report = Check_Report_AWS(metadata=self.metadata(), resource=analyzer)
if analyzer.status == "ACTIVE":
report.status = "PASS"
report.status_extended = f"IAM Access Analyzer {analyzer.name} does not have active findings."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class account_maintain_current_contact_details(Check):
def execute(self):
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=account_client.contact_base
metadata=self.metadata(), resource=account_client.contact_base
)
report.region = account_client.region
report.resource_id = account_client.audited_account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def execute(self):
findings = []
if account_client.contact_base:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=account_client.contact_base
metadata=self.metadata(), resource=account_client.contact_base
)
report.resource_id = account_client.audited_account
report.resource_arn = account_client.audited_account_arn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class account_security_contact_information_is_registered(Check):
def execute(self):
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=account_client.contact_base
metadata=self.metadata(), resource=account_client.contact_base
)
report.region = account_client.region
report.resource_id = account_client.audited_account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class account_security_questions_are_registered_in_the_aws_account(Check):
def execute(self):
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=account_client.contacts_security
metadata=self.metadata(), resource=account_client.contacts_security
)
report.region = account_client.region
report.resource_id = account_client.audited_account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def execute(self):
for certificate in acm_client.certificates.values():
if certificate.in_use or acm_client.provider.scan_unused_services:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=certificate
metadata=self.metadata(), resource=certificate
)
if certificate.expiration_days > acm_client.audit_config.get(
"days_to_expire_threshold", 7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def execute(self):
for certificate in acm_client.certificates.values():
if certificate.in_use or acm_client.provider.scan_unused_services:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=certificate
metadata=self.metadata(), resource=certificate
)
if certificate.type == "IMPORTED":
report.status = "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def execute(self):
for certificate in acm_client.certificates.values():
if certificate.in_use or acm_client.provider.scan_unused_services:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=certificate
metadata=self.metadata(), resource=certificate
)

report.status = "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class apigateway_restapi_authorizers_enabled(Check):
def execute(self):
findings = []
for rest_api in apigateway_client.rest_apis:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=rest_api
)
report = Check_Report_AWS(metadata=self.metadata(), resource=rest_api)
report.resource_id = rest_api.name

# it there are not authorizers at api level and resources without methods (default case) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ def execute(self):
for rest_api in apigateway_client.rest_apis:
for stage in rest_api.stages:
if stage.cache_enabled:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=stage
)
report = Check_Report_AWS(metadata=self.metadata(), resource=stage)
report.region = rest_api.region
report.resource_id = rest_api.name
report.status = "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ def execute(self):
findings = []
for rest_api in apigateway_client.rest_apis:
for stage in rest_api.stages:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=stage
)
report = Check_Report_AWS(metadata=self.metadata(), resource=stage)
report.resource_id = rest_api.name
report.region = rest_api.region
if stage.client_certificate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ def execute(self):
findings = []
for rest_api in apigateway_client.rest_apis:
for stage in rest_api.stages:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=stage
)
report = Check_Report_AWS(metadata=self.metadata(), resource=stage)
report.resource_id = rest_api.name
report.region = rest_api.region
if stage.logging:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class apigateway_restapi_public(Check):
def execute(self):
findings = []
for rest_api in apigateway_client.rest_apis:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=rest_api
)
report = Check_Report_AWS(metadata=self.metadata(), resource=rest_api)
report.resource_id = rest_api.name

if rest_api.public_endpoint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ def execute(self):
findings = []
for rest_api in apigateway_client.rest_apis:
if rest_api.public_endpoint:
report = Check_Report_AWS(
metadata=self.metadata(), resource_metadata=rest_api
)
report = Check_Report_AWS(metadata=self.metadata(), resource=rest_api)
report.resource_id = rest_api.name

report.status = "PASS"
Expand Down
Loading
Loading