mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 23:05:05 +00:00
feat(checks): Select checks to run from provider using -C/--checks-file (#1200)
This commit is contained in:
0
lib/check/__init__.py
Normal file
0
lib/check/__init__.py
Normal file
207
lib/check/check.py
Normal file
207
lib/check/check.py
Normal file
@@ -0,0 +1,207 @@
|
||||
import importlib
|
||||
import json
|
||||
import pkgutil
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
|
||||
from lib.logger import logger
|
||||
from lib.outputs import report
|
||||
|
||||
|
||||
# Parse checks from file
|
||||
def parse_checks_from_file(checks_file):
|
||||
checks_to_execute = set()
|
||||
with open(checks_file) as f:
|
||||
for line in f:
|
||||
# Remove comments from file
|
||||
line = line.partition("#")[0].strip()
|
||||
# If file contains several checks comma-separated
|
||||
if "," in line:
|
||||
for check in line.split(","):
|
||||
checks_to_execute.add(check.strip())
|
||||
# If line is not empty
|
||||
elif len(line):
|
||||
checks_to_execute.add(line)
|
||||
return checks_to_execute
|
||||
|
||||
|
||||
def load_checks_to_execute(checks_file, check_list, provider):
|
||||
checks_to_execute = set()
|
||||
# LOADER
|
||||
# Handle if there are checks passed using -c/--checks
|
||||
if check_list:
|
||||
for check_name in check_list:
|
||||
checks_to_execute.add(check_name)
|
||||
|
||||
# Handle if there are checks passed using -C/--checks-file
|
||||
elif checks_file:
|
||||
# check if file exists or path
|
||||
# check permissions to read
|
||||
try:
|
||||
checks_to_execute = parse_checks_from_file(checks_file)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{checks_file}: {e.__class__.__name__}")
|
||||
|
||||
# If there are no checks passed as argument
|
||||
else:
|
||||
# Get all check modules to run with the specific provider
|
||||
modules = recover_modules_from_provider(provider)
|
||||
for check_module in modules:
|
||||
# Recover check name from import path (last part)
|
||||
# Format: "providers.{provider}.services.{service}.{check_name}.{check_name}"
|
||||
check_name = check_module.split(".")[-1]
|
||||
checks_to_execute.add(check_name)
|
||||
print(checks_to_execute)
|
||||
return checks_to_execute
|
||||
|
||||
|
||||
def recover_modules_from_provider(provider):
|
||||
modules = []
|
||||
for module_name in pkgutil.walk_packages(
|
||||
importlib.import_module(f"providers.{provider}.services").__path__,
|
||||
importlib.import_module(f"providers.{provider}.services").__name__ + ".",
|
||||
):
|
||||
# Format: "providers.{provider}.services.{service}.{check_name}.{check_name}"
|
||||
if module_name.name.count(".") == 5:
|
||||
modules.append(module_name.name)
|
||||
return modules
|
||||
|
||||
|
||||
def run_check(check):
|
||||
print(f"\nCheck Name: {check.CheckName}")
|
||||
logger.debug(f"Executing check: {check.CheckName}")
|
||||
findings = check.execute()
|
||||
report(findings)
|
||||
|
||||
|
||||
def import_check(check_path):
|
||||
lib = importlib.import_module(f"{check_path}")
|
||||
return lib
|
||||
|
||||
|
||||
@dataclass
|
||||
class Check_Report:
|
||||
status: str
|
||||
region: str
|
||||
result_extended: str
|
||||
|
||||
def __init__(self):
|
||||
self.status = ""
|
||||
self.region = ""
|
||||
self.result_extended = ""
|
||||
|
||||
|
||||
class Check(ABC):
|
||||
def __init__(self):
|
||||
try:
|
||||
self.metadata = self.__parse_metadata__(
|
||||
self.__class__.__module__.replace(".", "/") + ".metadata.json"
|
||||
)
|
||||
self.Provider = self.metadata["Provider"]
|
||||
self.CheckID = self.metadata["CheckID"]
|
||||
self.CheckName = self.metadata["CheckName"]
|
||||
self.CheckTitle = self.metadata["CheckTitle"]
|
||||
self.CheckAlias = self.metadata["CheckAlias"]
|
||||
self.CheckType = self.metadata["CheckType"]
|
||||
self.ServiceName = self.metadata["ServiceName"]
|
||||
self.SubServiceName = self.metadata["SubServiceName"]
|
||||
self.ResourceIdTemplate = self.metadata["ResourceIdTemplate"]
|
||||
self.Severity = self.metadata["Severity"]
|
||||
self.ResourceType = self.metadata["ResourceType"]
|
||||
self.Description = self.metadata["Description"]
|
||||
self.Risk = self.metadata["Risk"]
|
||||
self.RelatedUrl = self.metadata["RelatedUrl"]
|
||||
self.Remediation = self.metadata["Remediation"]
|
||||
self.Categories = self.metadata["Categories"]
|
||||
self.Tags = self.metadata["Tags"]
|
||||
self.DependsOn = self.metadata["DependsOn"]
|
||||
self.RelatedTo = self.metadata["RelatedTo"]
|
||||
self.Notes = self.metadata["Notes"]
|
||||
self.Compliance = self.metadata["Compliance"]
|
||||
except:
|
||||
print(f"Metadata check from file {self.__class__.__module__} not found")
|
||||
|
||||
@property
|
||||
def provider(self):
|
||||
return self.Provider
|
||||
|
||||
@property
|
||||
def checkID(self):
|
||||
return self.CheckID
|
||||
|
||||
@property
|
||||
def checkName(self):
|
||||
return self.CheckName
|
||||
|
||||
@property
|
||||
def checkTitle(self):
|
||||
return self.CheckTitle
|
||||
|
||||
@property
|
||||
def checkAlias(self):
|
||||
return self.CheckAlias
|
||||
|
||||
@property
|
||||
def checkType(self):
|
||||
return self.CheckType
|
||||
|
||||
@property
|
||||
def serviceName(self):
|
||||
return self.ServiceName
|
||||
|
||||
@property
|
||||
def subServiceName(self):
|
||||
return self.SubServiceName
|
||||
|
||||
@property
|
||||
def resourceIdTemplate(self):
|
||||
return self.ResourceIdTemplate
|
||||
|
||||
@property
|
||||
def resourceType(self):
|
||||
return self.ResourceType
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self.Description
|
||||
|
||||
@property
|
||||
def relatedUrl(self):
|
||||
return self.RelatedUrl
|
||||
|
||||
@property
|
||||
def remediation(self):
|
||||
return self.Remediation
|
||||
|
||||
@property
|
||||
def categories(self):
|
||||
return self.Categories
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return self.Tags
|
||||
|
||||
@property
|
||||
def relatedTo(self):
|
||||
return self.RelatedTo
|
||||
|
||||
@property
|
||||
def notes(self):
|
||||
return self.Notes
|
||||
|
||||
@property
|
||||
def compliance(self):
|
||||
return self.Compliance
|
||||
|
||||
def __parse_metadata__(self, metadata_file):
|
||||
# Opening JSON file
|
||||
f = open(metadata_file)
|
||||
check_metadata = json.load(f)
|
||||
return check_metadata
|
||||
|
||||
# Validate metadata
|
||||
|
||||
@abstractmethod
|
||||
def execute(self):
|
||||
pass
|
||||
39
lib/check/check_test.py
Normal file
39
lib/check/check_test.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
|
||||
from lib.check.check import parse_checks_from_file
|
||||
|
||||
|
||||
class Test_Check:
|
||||
# def test_import_check(self):
|
||||
# test_cases = [
|
||||
# {
|
||||
# "name": "Test valid check path",
|
||||
# "input": "providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials",
|
||||
# "expected": "providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials",
|
||||
# }
|
||||
# ]
|
||||
# for test in test_cases:
|
||||
# assert importlib.import_module(test["input"]).__name__ == test["expected"]
|
||||
|
||||
def test_parse_checks_from_file(checks_file):
|
||||
test_cases = [
|
||||
{
|
||||
"name": "Test valid check path",
|
||||
"input": f"{os.path.dirname(os.path.realpath(__file__))}/fixtures/checklistA.txt",
|
||||
"expected": {"check12", "check11", "extra72", "check13"},
|
||||
},
|
||||
{
|
||||
"name": "Test valid check path",
|
||||
"input": f"{os.path.dirname(os.path.realpath(__file__))}/fixtures/checklistB.txt",
|
||||
"expected": {
|
||||
"extra72",
|
||||
"check13",
|
||||
"check11",
|
||||
"check12",
|
||||
"check56",
|
||||
"check2423",
|
||||
},
|
||||
},
|
||||
]
|
||||
for test in test_cases:
|
||||
assert parse_checks_from_file(test["input"]) == test["expected"]
|
||||
6
lib/check/fixtures/checklistA.txt
Normal file
6
lib/check/fixtures/checklistA.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# You can add a comma seperated list of checks like this:
|
||||
check11,check12
|
||||
extra72 # You can also use newlines for each check
|
||||
check13 # This way allows you to add inline comments
|
||||
# Both of these can be combined if you have a standard list and want to add
|
||||
# inline comments for other checks.
|
||||
11
lib/check/fixtures/checklistB.txt
Normal file
11
lib/check/fixtures/checklistB.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
# You can add a comma seperated list of checks like this:
|
||||
check11,check12
|
||||
extra72 # You can also use newlines for each check
|
||||
check13 # This way allows you to add inline comments
|
||||
# Both of these can be combined if you have a standard list and want to add
|
||||
# inline comments for other checks.
|
||||
#
|
||||
#
|
||||
#
|
||||
# check11,check12
|
||||
check2423,check56
|
||||
Reference in New Issue
Block a user