Skip to content

Create a Custom Output Format

Introduction

Prowler supports multiple output formats, allowing users to tailor findings presentation to their needs. Custom output formats are valuable when integrating Prowler with third-party tools, generating specialized reports, or adapting data for specific workflows. By defining a custom output format, users can refine how findings are structured, extracting and displaying only the most relevant information.

  • Output Organization in Prowler

    Prowler outputs are managed within the /lib/outputs directory. Each format—such as JSON, CSV, HTML—is implemented as a Python class.

  • Outputs are generated based on scan findings, which are stored as structured dictionaries containing details such as:

    • Resource IDs

    • Severities

    • Descriptions

    • Other relevant metadata

  • Creation Guidelines

    Refer to the Prowler Developer Guide for insights into Prowler’s architecture and best practices for creating custom outputs.

  • Identify the most suitable integration method for the output being targeted.

Steps to Create a Custom Output Format

Schema

  • Output Class:

    • The class must inherit from Output. Review the Output Class.

    • Create a class that encapsulates the required attributes and methods for interacting with the target platform. Below the code for the CSV class is presented:

    CSV Class
    class CSV(Output):
        def transform(self, findings: List[Finding]) -> None:
            """Transforms the findings into the CSV format.
    
            Args:
                findings (list[Finding]): a list of Finding objects
    
            """
        ...
    
    • Transform Method:

      • This method will transform the findings provided by Prowler to a specific format.

      Method Implementation

      The following example demonstrates the transform method for the CSV class:

      Transform
      def transform(self, findings: List[Finding]) -> None:
          """Transforms the findings into the CSV format.
      
          Args:
              findings (list[Finding]): a list of Finding objects
      
          """
          try:
              for finding in findings:
                  finding_dict = {}
                  finding_dict["AUTH_METHOD"] = finding.auth_method
                  finding_dict["TIMESTAMP"] = finding.timestamp
                  finding_dict["ACCOUNT_UID"] = finding.account_uid
                  finding_dict["ACCOUNT_NAME"] = finding.account_name
                  finding_dict["ACCOUNT_EMAIL"] = finding.account_email
                  finding_dict["ACCOUNT_ORGANIZATION_UID"] = (
                      finding.account_organization_uid
                  )
                  finding_dict["ACCOUNT_ORGANIZATION_NAME"] = (
                      finding.account_organization_name
                  )
                  finding_dict["ACCOUNT_TAGS"] = unroll_dict(
                      finding.account_tags, separator=":"
                  )
                  finding_dict["FINDING_UID"] = finding.uid
                  finding_dict["PROVIDER"] = finding.metadata.Provider
                  finding_dict["CHECK_ID"] = finding.metadata.CheckID
                  finding_dict["CHECK_TITLE"] = finding.metadata.CheckTitle
                  finding_dict["CHECK_TYPE"] = unroll_list(finding.metadata.CheckType)
                  finding_dict["STATUS"] = finding.status.value
                  finding_dict["STATUS_EXTENDED"] = finding.status_extended
                  finding_dict["MUTED"] = finding.muted
                  finding_dict["SERVICE_NAME"] = finding.metadata.ServiceName
                  finding_dict["SUBSERVICE_NAME"] = finding.metadata.SubServiceName
                  finding_dict["SEVERITY"] = finding.metadata.Severity.value
                  finding_dict["RESOURCE_TYPE"] = finding.metadata.ResourceType
                  finding_dict["RESOURCE_UID"] = finding.resource_uid
                  finding_dict["RESOURCE_NAME"] = finding.resource_name
                  finding_dict["RESOURCE_DETAILS"] = finding.resource_details
                  finding_dict["RESOURCE_TAGS"] = unroll_dict(finding.resource_tags)
                  finding_dict["PARTITION"] = finding.partition
                  finding_dict["REGION"] = finding.region
                  finding_dict["DESCRIPTION"] = finding.metadata.Description
                  finding_dict["RISK"] = finding.metadata.Risk
                  finding_dict["RELATED_URL"] = finding.metadata.RelatedUrl
                  finding_dict["REMEDIATION_RECOMMENDATION_TEXT"] = (
                      finding.metadata.Remediation.Recommendation.Text
                  )
                  finding_dict["REMEDIATION_RECOMMENDATION_URL"] = (
                      finding.metadata.Remediation.Recommendation.Url
                  )
                  finding_dict["REMEDIATION_CODE_NATIVEIAC"] = (
                      finding.metadata.Remediation.Code.NativeIaC
                  )
                  finding_dict["REMEDIATION_CODE_TERRAFORM"] = (
                      finding.metadata.Remediation.Code.Terraform
                  )
                  finding_dict["REMEDIATION_CODE_CLI"] = (
                      finding.metadata.Remediation.Code.CLI
                  )
                  finding_dict["REMEDIATION_CODE_OTHER"] = (
                      finding.metadata.Remediation.Code.Other
                  )
                  finding_dict["COMPLIANCE"] = unroll_dict(
                      finding.compliance, separator=": "
                  )
                  finding_dict["CATEGORIES"] = unroll_list(finding.metadata.Categories)
                  finding_dict["DEPENDS_ON"] = unroll_list(finding.metadata.DependsOn)
                  finding_dict["RELATED_TO"] = unroll_list(finding.metadata.RelatedTo)
                  finding_dict["NOTES"] = finding.metadata.Notes
                  finding_dict["PROWLER_VERSION"] = finding.prowler_version
                  self._data.append(finding_dict)
          except Exception as error:
              logger.error(
                  f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
              )
      
    • Batch Write Data To File Method:

      • This method will write the modeled object to a file.

      Method Implementation

      The following example demonstrates the batch_write_data_to_file method for the CSV class:

      Batch Write Data To File
      def batch_write_data_to_file(self) -> None:
          """Writes the findings to a file using the CSV format using the `Output._file_descriptor`."""
          try:
              if (
                  getattr(self, "_file_descriptor", None)
                  and not self._file_descriptor.closed
                  and self._data
              ):
                  csv_writer = DictWriter(
                      self._file_descriptor,
                      fieldnames=self._data[0].keys(),
                      delimiter=";",
                  )
                  csv_writer.writeheader()
                  for finding in self._data:
                      csv_writer.writerow(finding)
                  self._file_descriptor.close()
          except Exception as error:
              logger.error(
                  f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
              )
      

Integrating the Custom Output Format into Prowler

Once the custom output format is created, it must be integrated into Prowler to ensure compatibility with the existing architecture.

Reviewing Current Supported Outputs

Before implementing the new output format, examine the usage of currently supported formats to understand their structure and integration approach. Example: CSV Output Creation in Prowler

Below is an example of how Prowler generates and processes CSV output within its codebase:

CSV creation
if mode == "csv":
    csv_output = CSV(
        findings=finding_outputs,
        create_file_descriptor=True,
        file_path=f"{filename}{csv_file_suffix}",
    )
    generated_outputs["regular"].append(csv_output)
    # Write CSV Finding Object to file.
    csv_output.batch_write_data_to_file()

Testing

  • Verify that Prowler’s findings are accurately typed in the desired output format.

  • Error Handling – Simulate edge cases to assess robustness and failure recovery mechanisms.

Documentation

  • Ensure the following elements are included:

    • Setup Instructions – List all necessary dependencies and installation steps.
    • Configuration details.
    • Example Use Cases – Provide practical scenarios demonstrating functionality.
    • Troubleshooting Guide – Document common issues and resolution steps.
  • Comprehensive and clear documentation improves maintainability and simplifies onboarding of new users.