diff --git a/docs/tutorials/developer-guide.md b/docs/tutorials/developer-guide.md index 36234639e6..2b893b77a5 100644 --- a/docs/tutorials/developer-guide.md +++ b/docs/tutorials/developer-guide.md @@ -44,15 +44,190 @@ You can see all dependencies in file `Pipfile`. ## Create a new check for a Provider -### If the check you want to create belongs to an existing service: +### If the check you want to create belongs to an existing service -### If the check you want to create belongs to a service not supported already by Prowler you will need to create a new service first: +To create a new check, you will need to create a folder inside the specific service, i.e. `prowler/providers//services///`, with the name of check following the pattern: `service_subservice_action`. +Inside that folder, create the following files: + +- An empty `__init__.py`: to make Python treat this check folder as a package. +- A `check_name.py` containing the check's logic, for example: +``` +# Import the Check_Report of the specific provider +from prowler.lib.check.models import Check, Check_Report_AWS +# Import the client of the specific service +from prowler.providers.aws.services.ec2.ec2_client import ec2_client + +# Create the class for the check +class ec2_ebs_volume_encryption(Check): + def execute(self): + findings = [] + # Iterate the service's asset that want to be analyzed + for volume in ec2_client.volumes: + # Initialize a Check Report for each item and assign the region, resource_id, resource_arn and resource_tags + report = Check_Report_AWS(self.metadata()) + report.region = volume.region + report.resource_id = volume.id + report.resource_arn = volume.arn + report.resource_tags = volume.tags + # Make the logic with conditions and create a PASS and a FAIL with a status and a status_extended + if volume.encrypted: + report.status = "PASS" + report.status_extended = f"EBS Snapshot {volume.id} is encrypted." + else: + report.status = "FAIL" + report.status_extended = f"EBS Snapshot {volume.id} is unencrypted." + findings.append(report) # Append a report for each item + + return findings +``` +- A `check_name.metadata.json` containing the check's metadata, for example: +``` +{ + "Provider": "aws", + "CheckID": "ec2_ebs_volume_encryption", + "CheckTitle": "Ensure there are no EBS Volumes unencrypted.", + "CheckType": [ + "Data Protection" + ], + "ServiceName": "ec2", + "SubServiceName": "volume", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsEc2Volume", + "Description": "Ensure there are no EBS Volumes unencrypted.", + "Risk": "Data encryption at rest prevents data visibility in the event of its unauthorized access or theft.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Encrypt all EBS volumes and Enable Encryption by default You can configure your AWS account to enforce the encryption of the new EBS volumes and snapshot copies that you create. For example; Amazon EBS encrypts the EBS volumes created when you launch an instance and the snapshots that you copy from an unencrypted snapshot.", + "Url": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} +``` + +### If the check you want to create belongs to a service not supported already by Prowler you will need to create a new service first + +To create a new service, you will need to create a folder inside the specific provider, i.e. `prowler/providers//services//`. +Inside that folder, create the following files: + +- An empty `__init__.py`: to make Python treat this service folder as a package. +- A `_service.py`, containing all the service's logic and API Calls: +``` +# You must import the following libraries +import threading +from typing import Optional + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.aws.aws_provider import generate_regional_clients + + +# Create a class for the Service +################## +class : + def __init__(self, audit_info): + self.service = "" # The name of the service boto3 client + self.session = audit_info.audit_session + self.audited_account = audit_info.audited_account + self.audit_resources = audit_info.audit_resources + self.regional_clients = generate_regional_clients(self.service, audit_info) + self. = [] # Create an empty list of the items to be gathered, e.g., instances + self.__threading_call__(self.__describe___) + self.__describe___() # Optionally you can create another function to retrieve more data about each item + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __describe___(self, regional_client): + """Get ALL """ + logger.info(" - Describing ...") + try: + describe__paginator = regional_client.get_paginator("describe_") # Paginator to get every item + for page in describe__paginator.paginate(): + for in page[""]: + if not self.audit_resources or ( + is_resource_filtered([""], self.audit_resources) + ): + self..append( + ( + arn=stack[""], + name=stack[""], + tags=stack.get("Tags", []), + region=regional_client.region, + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __describe___(self): + """Get Details for a """ + logger.info(" - Describing to get specific details...") + try: + for in self.: + _details = self.regional_clients[.region].describe_( + =.name + ) + # For example, check if item is Public + .public = _details.get("Public", False) + + except Exception as error: + logger.error( + f"{.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class (BaseModel): + """ holds a """ + + arn: str + """[].Arn""" + name: str + """[].Name""" + public: bool + """[].Public""" + tags: Optional[list] = [] + region: str + +``` +- A `_client_.py`, containing the initialization of the service's class we have just created so the service's checks can use them: +``` +from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info +from prowler.providers.aws.services.._service import + +_client = (current_audit_info) +``` ## Create a new security compliance framework If you want to create or contribute with your own security frameworks or add public ones to Prowler you need to make sure the checks are available if not you have to create your own. Then create a compliance file per provider like in `prowler/compliance/aws/` and name it as `__.json` then follow the following format to create yours. -Each file version of a framework will have the following structure at high level with the case that each framework needs to be generally identified), one requirement can be also called one control but one requirement can be linked to multiple prowler checks.: +Each file version of a framework will have the following structure at high level with the case that each framework needs to be generally identified, one requirement can be also called one control but one requirement can be linked to multiple prowler checks.: - `Framework`: string. Indistiguish name of the framework, like CIS - `Provider`: string. Provider where the framework applies, such as AWS, Azure, OCI,... @@ -79,7 +254,10 @@ Each file version of a framework will have the following structure at high level } ] - } + }, + ... + ] +} ``` Finally, to have a proper output file for your reports, your framework data model has to be created in `prowler/lib/outputs/models.py` and also the CLI table output in `prowler/lib/outputs/compliance.py`.