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

add prowler v4 parser #10338

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions docs/content/en/integrations/parsers/file/aws_prowler_v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: "AWS Prowler V3"
kagahd marked this conversation as resolved.
Show resolved Hide resolved
toc_hide: true
---

### File Types
DefectDojo parser accepts a .json-ocsf file. Please note: earlier versions of AWS Prowler create output data in a different format. See our other documentation if you are using an earlier version of AWS Prowler:
* [Prowler v2](https://documentation.defectdojo.com/integrations/parsers/file/aws_prowler/)
* [Prowler v3](https://documentation.defectdojo.com/integrations/parsers/file/aws_prowler_v3/)

JSON reports can be created from the [AWS Prowler V4 CLI](https://docs.prowler.cloud/en/latest/tutorials/reporting/#json) using the following command: `prowler <provider> -M json-ocsf`

### Acceptable JSON Format
The parser expects an array of assessments. All properties are strings and are required by the parser.

~~~

[{
"metadata": {
"event_code": "iam_role_administratoraccess_policy_permissive_trust_relationship",
"product": {
"name": "Prowler",
"vendor_name": "Prowler",
"version": "4.2.1"
},
"version": "1.2.0"
},
"severity_id": 4,
"severity": "High",
"status": "Suppressed",
"status_code": "FAIL",
"status_detail": "IAM Role myAdministratorExecutionRole has AdministratorAccess policy attached that has too permissive trust relationship.",
"status_id": 3,
"unmapped": {
"check_type": "",
"related_url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html#jf_administrator",
"categories": "trustboundaries",
"depends_on": "",
"related_to": "",
"notes": "CAF Security Epic: IAM",
"compliance": {}
},
"activity_name": "Create",
"activity_id": 1,
"finding_info": {
"created_time": "2024-06-03T14:15:19.382075",
"desc": "Ensure IAM Roles with attached AdministratorAccess policy have a well defined trust relationship",
"product_uid": "prowler",
"title": "Ensure IAM Roles with attached AdministratorAccess policy have a well defined trust relationship",
"uid": "prowler-aws-iam_role_administratoraccess_policy_permissive_trust_relationship-123456789012-us-east-1-myAdministratorExecutionRole"
},
"resources": [
{
"cloud_partition": "aws",
"region": "us-east-1",
"data": {
"details": ""
},
"group": {
"name": "iam"
},
"labels": [],
"name": "myAdministratorExecutionRole",
"type": "AwsIamRole",
"uid": "arn:aws:iam::123456789012:role/myAdministratorExecutionRole"
}
],
"category_name": "Findings",
"category_uid": 2,
"class_name": "DetectionFinding",
"class_uid": 2004,
"cloud": {
"account": {
"name": "",
"type": "AWS_Account",
"type_id": 10,
"uid": "123456789012",
"labels": []
},
"org": {
"name": "",
"uid": ""
},
"provider": "aws",
"region": "us-east-1"
},
"event_time": "2024-06-03T14:15:19.382075",
"remediation": {
"desc": "Apply the principle of least privilege. Instead of AdministratorAccess, assign only the permissions necessary for specific roles and tasks. Create custom IAM policies with minimal permissions based on the principle of least privilege. If a role really needs AdministratorAccess, the trust relationship must be well defined to restrict it usage only to the Principal, Action, Audience and Subject intended for it.",
"references": [
"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege"
]
},
"risk_details": "The AWS-managed AdministratorAccess policy grants all actions for all AWS services and for all resources in the account and as such exposes the customer to a significant data leakage threat. It is therefore particularly important that the trust relationship is well defined to restrict it usage only to the Principal, Action, Audience and Subject intended for it.",
"type_uid": 200401,
"type_name": "Create"
}]

~~~

### Sample Scan Data
Unit tests of AWS Prowler V4 JSON-OCSF can be found at https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/aws_prowler_v4.
Empty file.
108 changes: 108 additions & 0 deletions dojo/tools/aws_prowler_v4/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

import hashlib
import json
import textwrap
from datetime import date

from dojo.models import Finding


class AWSProwlerV4Parser:
SCAN_TYPE = ["AWS Prowler V4"]

def get_scan_types(self):
return AWSProwlerV4Parser.SCAN_TYPE

def get_label_for_scan_types(self, scan_type):
return AWSProwlerV4Parser.SCAN_TYPE[0]

def get_description_for_scan_types(self, scan_type):
return "Export of AWS Prowler V4 JSON OCSF v1.1.0 format."

def get_findings(self, file, test):
if file.name.lower().endswith('.json'):
return self.process_json(file, test)
else:
msg = 'Unknown file format'
raise ValueError(msg)

def process_json(self, file, test):
dupes = {}

data = json.load(file)
# mapping of json fields between Prowler v3 and v4:
# https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/reporting/#json
for deserialized in data:

status = deserialized.get('status_code')
if status.upper() != 'FAIL':
continue

account_id = deserialized.get('cloud', {}).get('account', {}).get("uid", '')
region = deserialized.get('resources', [{}])[0].get('region', '')
provider = deserialized.get('cloud', {}).get('provider', '')
compliance = ''
compliance_field = deserialized.get('unmapped', {}).get("compliance", {})
if compliance_field:
compliance = ' | '.join([f"{key}:{','.join(value)}" for key, value in compliance_field.items()])
result_extended = deserialized.get('status_detail')
general_description = deserialized.get('finding_info', {}).get('desc', '')
asff_compliance_type = deserialized.get('unmapped', {}).get('check_type', '')
severity = deserialized.get('severity', 'Info').capitalize()
aws_service_name = deserialized.get('resources', [{}])[0].get('group', {}).get('name', '')
impact = deserialized.get('risk_details')
mitigation = deserialized.get('remediation', {}).get("desc", '')
documentation = deserialized.get('remediation', {}).get("references", '')
documentation = str(documentation) + "\n" + str(deserialized.get('unmapped', {}).get('related_url', ''))
security_domain = deserialized.get('resources', [{}])[0].get('type', '')
timestamp = deserialized.get("event_time")
resource_arn = deserialized.get('resources', [{}])[0].get('uid', '')
resource_id = deserialized.get('resources', [{}])[0].get('name', '')
unique_id_from_tool = deserialized.get('finding_info', {}).get('uid', '')
if not resource_arn or resource_arn == "":
component_name = str(provider) + "-" + str(account_id) + "-" + str(region) + "-" + str(resource_id)
else:
component_name = resource_arn

description = "**Issue:** " + str(result_extended) + \
"\n**Description:** " + str(general_description) + \
"\n**AWS Account:** " + str(account_id) + \
"\n**Region:** " + str(region) + \
"\n**AWS Service:** " + str(aws_service_name) + \
"\n**Security Domain:** " + str(security_domain) + \
"\n**Compliance:** " + str(compliance) + \
"\n**ASFF Compliance Type:** " + str(asff_compliance_type)

# improving key to get duplicates
dupe_key = hashlib.sha256(unique_id_from_tool.encode('utf-8')).hexdigest()
if dupe_key in dupes:
find = dupes[dupe_key]
if description is not None:
find.description += description + "\n\n"
find.nb_occurences += 1
else:
find = Finding(
title=textwrap.shorten(result_extended, 150),
cwe=1032, # Security Configuration Weaknesses, would like to fine tune
test=test,
description=description,
component_name=component_name,
unique_id_from_tool=unique_id_from_tool,
severity=severity,
references=documentation,
date=date.fromisoformat(timestamp[:10]),
static_finding=True,
dynamic_finding=False,
nb_occurences=1,
mitigation=mitigation,
impact=impact,
)
dupes[dupe_key] = find

return list(dupes.values())

def formatview(self, depth):
if depth > 1:
return "* "
else:
return ""
Loading
Loading