mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
feat(json): add json output (#1251)
* feat(json): add json output * feat(pydantic): add pydantic model to json output Co-authored-by: sergargar <sergio@verica.io>
This commit is contained in:
@@ -13,3 +13,4 @@ aws_services_json_file = "providers/aws/aws_regions_by_service.json"
|
|||||||
default_output_directory = getcwd() + "/output"
|
default_output_directory = getcwd() + "/output"
|
||||||
|
|
||||||
csv_file_suffix = timestamp.strftime("%Y%m%d%H%M%S") + ".csv"
|
csv_file_suffix = timestamp.strftime("%Y%m%d%H%M%S") + ".csv"
|
||||||
|
json_file_suffix = timestamp.strftime("%Y%m%d%H%M%S") + ".json"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from config.config import timestamp
|
from config.config import timestamp
|
||||||
from lib.check.models import Check_Report
|
from lib.check.models import Check_Report
|
||||||
@@ -13,6 +16,39 @@ class Compliance_Framework:
|
|||||||
Control: list
|
Control: list
|
||||||
|
|
||||||
|
|
||||||
|
class Check_Output_JSON(BaseModel):
|
||||||
|
AssessmentStartTime: Optional[str]
|
||||||
|
FindingUniqueId: Optional[str]
|
||||||
|
Provider: str
|
||||||
|
Profile: Optional[str]
|
||||||
|
AccountId: Optional[str]
|
||||||
|
OrganizationsInfo: Optional[dict]
|
||||||
|
Region: Optional[str]
|
||||||
|
CheckID: str
|
||||||
|
CheckName: str
|
||||||
|
CheckTitle: str
|
||||||
|
CheckType: str
|
||||||
|
ServiceName: str
|
||||||
|
SubServiceName: str
|
||||||
|
Status: Optional[str]
|
||||||
|
StatusExtended: Optional[str]
|
||||||
|
Severity: str
|
||||||
|
ResourceId: Optional[str]
|
||||||
|
ResourceArn: Optional[str]
|
||||||
|
ResourceType: str
|
||||||
|
ResourceDetails: Optional[str]
|
||||||
|
Tags: dict
|
||||||
|
Description: str
|
||||||
|
Risk: str
|
||||||
|
RelatedUrl: str
|
||||||
|
Remediation: dict
|
||||||
|
Categories: list
|
||||||
|
DependsOn: list
|
||||||
|
RelatedTo: list
|
||||||
|
Notes: str
|
||||||
|
Compliance: list
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Check_Output_CSV:
|
class Check_Output_CSV:
|
||||||
assessment_start_time: str
|
assessment_start_time: str
|
||||||
@@ -73,11 +109,12 @@ class Check_Output_CSV:
|
|||||||
self.provider = report.check_metadata.Provider
|
self.provider = report.check_metadata.Provider
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.account_id = account
|
self.account_id = account
|
||||||
self.account_name = organizations.account_details_name
|
if organizations:
|
||||||
self.account_email = organizations.account_details_email
|
self.account_name = organizations.account_details_name
|
||||||
self.account_arn = organizations.account_details_arn
|
self.account_email = organizations.account_details_email
|
||||||
self.account_org = organizations.account_details_org
|
self.account_arn = organizations.account_details_arn
|
||||||
self.account_tags = organizations.account_details_tags
|
self.account_org = organizations.account_details_org
|
||||||
|
self.account_tags = organizations.account_details_tags
|
||||||
self.region = report.region
|
self.region = report.region
|
||||||
self.check_id = report.check_metadata.CheckID
|
self.check_id = report.check_metadata.CheckID
|
||||||
self.check_name = report.check_metadata.CheckName
|
self.check_name = report.check_metadata.CheckName
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
from csv import DictWriter
|
from csv import DictWriter
|
||||||
|
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
|
|
||||||
from config.config import csv_file_suffix
|
from config.config import csv_file_suffix, json_file_suffix, timestamp
|
||||||
from lib.outputs.models import Check_Output_CSV
|
from lib.outputs.models import Check_Output_CSV, Check_Output_JSON
|
||||||
from lib.utils.utils import file_exists, open_file
|
from lib.utils.utils import file_exists, open_file
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +53,13 @@ def report(check_findings, output_options, audit_info):
|
|||||||
)
|
)
|
||||||
csv_writer.writerow(finding_output.__dict__)
|
csv_writer.writerow(finding_output.__dict__)
|
||||||
|
|
||||||
|
if "json" in file_descriptors:
|
||||||
|
finding_output = Check_Output_JSON(**finding.check_metadata.dict())
|
||||||
|
fill_json(finding_output, audit_info, finding)
|
||||||
|
|
||||||
|
json.dump(finding_output.dict(), file_descriptors["json"], indent=4)
|
||||||
|
file_descriptors["json"].write(",")
|
||||||
|
|
||||||
if file_descriptors:
|
if file_descriptors:
|
||||||
# Close all file descriptors
|
# Close all file descriptors
|
||||||
for file_descriptor in file_descriptors:
|
for file_descriptor in file_descriptors:
|
||||||
@@ -81,6 +90,23 @@ def fill_file_descriptors(output_modes, audited_account, output_directory, csv_f
|
|||||||
csv_writer.writeheader()
|
csv_writer.writeheader()
|
||||||
|
|
||||||
file_descriptors.update({output_mode: file_descriptor})
|
file_descriptors.update({output_mode: file_descriptor})
|
||||||
|
|
||||||
|
if output_mode == "json":
|
||||||
|
filename = f"{output_directory}/prowler-output-{audited_account}-{json_file_suffix}"
|
||||||
|
if file_exists(filename):
|
||||||
|
file_descriptor = open_file(
|
||||||
|
filename,
|
||||||
|
"a",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
file_descriptor = open_file(
|
||||||
|
filename,
|
||||||
|
"a",
|
||||||
|
)
|
||||||
|
file_descriptor.write("[")
|
||||||
|
|
||||||
|
file_descriptors.update({output_mode: file_descriptor})
|
||||||
|
|
||||||
return file_descriptors
|
return file_descriptors
|
||||||
|
|
||||||
|
|
||||||
@@ -104,3 +130,32 @@ def generate_csv_fields():
|
|||||||
for field in Check_Output_CSV.__dict__["__annotations__"].keys():
|
for field in Check_Output_CSV.__dict__["__annotations__"].keys():
|
||||||
csv_fields.append(field)
|
csv_fields.append(field)
|
||||||
return csv_fields
|
return csv_fields
|
||||||
|
|
||||||
|
|
||||||
|
def fill_json(finding_output, audit_info, finding):
|
||||||
|
finding_output.AssessmentStartTime = timestamp.isoformat()
|
||||||
|
finding_output.FindingUniqueId = ""
|
||||||
|
finding_output.Profile = audit_info.profile
|
||||||
|
finding_output.AccountId = audit_info.audited_account
|
||||||
|
if audit_info.organizations_metadata:
|
||||||
|
finding_output.OrganizationsInfo = audit_info.organizations_metadata.__dict__
|
||||||
|
finding_output.Region = finding.region
|
||||||
|
finding_output.Status = finding.status
|
||||||
|
finding_output.StatusExtended = finding.status_extended
|
||||||
|
finding_output.ResourceId = finding.resource_id
|
||||||
|
finding_output.ResourceArn = finding.resource_arn
|
||||||
|
finding_output.ResourceDetails = finding.resource_details
|
||||||
|
|
||||||
|
return finding_output
|
||||||
|
|
||||||
|
|
||||||
|
def close_json(output_directory, audited_account):
|
||||||
|
filename = f"{output_directory}/prowler-output-{audited_account}-{json_file_suffix}"
|
||||||
|
file_descriptor = open_file(
|
||||||
|
filename,
|
||||||
|
"a",
|
||||||
|
)
|
||||||
|
file_descriptor.seek(file_descriptor.tell() - 1, os.SEEK_SET)
|
||||||
|
file_descriptor.truncate()
|
||||||
|
file_descriptor.write("]")
|
||||||
|
file_descriptor.close()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"cat1",
|
"cat1",
|
||||||
"cat2"
|
"cat2"
|
||||||
],
|
],
|
||||||
"CheckAlias": "extra764",
|
"CheckAlias": "extra774",
|
||||||
"CheckID": "iam_disable_30_days_credentials",
|
"CheckID": "iam_disable_30_days_credentials",
|
||||||
"CheckName": "iam_disable_30_days_credentials",
|
"CheckName": "iam_disable_30_days_credentials",
|
||||||
"CheckTitle": "Ensure credentials unused for 30 days or greater are disabled",
|
"CheckTitle": "Ensure credentials unused for 30 days or greater are disabled",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"cat1",
|
"cat1",
|
||||||
"cat2"
|
"cat2"
|
||||||
],
|
],
|
||||||
"CheckAlias": "extra764",
|
"CheckAlias": "check13",
|
||||||
"CheckID": "iam_disable_90_days_credentials",
|
"CheckID": "iam_disable_90_days_credentials",
|
||||||
"CheckName": "iam_disable_90_days_credentials",
|
"CheckName": "iam_disable_90_days_credentials",
|
||||||
"CheckTitle": "Ensure credentials unused for 90 days or greater are disabled",
|
"CheckTitle": "Ensure credentials unused for 90 days or greater are disabled",
|
||||||
|
|||||||
7
prowler
7
prowler
@@ -23,6 +23,7 @@ from lib.check.check import (
|
|||||||
)
|
)
|
||||||
from lib.check.checks_loader import load_checks_to_execute
|
from lib.check.checks_loader import load_checks_to_execute
|
||||||
from lib.logger import logger, set_logging_config
|
from lib.logger import logger, set_logging_config
|
||||||
|
from lib.outputs.outputs import close_json
|
||||||
from providers.aws.aws_provider import provider_set_session
|
from providers.aws.aws_provider import provider_set_session
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -124,7 +125,7 @@ if __name__ == "__main__":
|
|||||||
"--output-modes",
|
"--output-modes",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
help="Output mode, by default csv",
|
help="Output mode, by default csv",
|
||||||
choices=["csv"],
|
choices=["csv", "json"],
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-o",
|
"-o",
|
||||||
@@ -269,3 +270,7 @@ if __name__ == "__main__":
|
|||||||
logger.error(
|
logger.error(
|
||||||
"There are no checks to execute. Please, check your input arguments"
|
"There are no checks to execute. Please, check your input arguments"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Close json file if exists
|
||||||
|
if "json" in output_modes:
|
||||||
|
close_json(output_directory, audit_info.audited_account)
|
||||||
|
|||||||
Reference in New Issue
Block a user