Skip to content

Commit 1250f58

Browse files
authored
fix(check): custom check folder validation (#9335)
1 parent bb43e92 commit 1250f58

File tree

4 files changed

+94
-1
lines changed

4 files changed

+94
-1
lines changed

prowler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
1414

1515
### Fixed
1616
- `sharepoint_external_sharing_managed` check to handle external sharing disabled at organization level [(#9298)](https://github.com/prowler-cloud/prowler/pull/9298)
17+
- Custom check folder metadata validation [(#9335)](https://github.com/prowler-cloud/prowler/pull/9335)
1718
- Support multiple Exchange mailbox policies in M365 `exchange_mailbox_policy_additional_storage_restricted` check [(#9241)](https://github.com/prowler-cloud/prowler/pull/9241)
1819

1920
---

prowler/__main__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
list_checks_json,
2525
list_fixers,
2626
list_services,
27+
load_custom_checks_metadata,
2728
parse_checks_from_file,
2829
parse_checks_from_folder,
2930
print_categories,
@@ -185,6 +186,11 @@ def prowler():
185186
logger.debug("Loading checks metadata from .metadata.json files")
186187
bulk_checks_metadata = CheckMetadata.get_bulk(provider)
187188

189+
# Load custom checks metadata before validation
190+
if checks_folder:
191+
custom_folder_metadata = load_custom_checks_metadata(checks_folder)
192+
bulk_checks_metadata.update(custom_folder_metadata)
193+
188194
if args.list_categories:
189195
print_categories(list_categories(bulk_checks_metadata))
190196
sys.exit()

prowler/lib/check/check.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import prowler
1515
from prowler.config.config import orange_color
1616
from prowler.lib.check.custom_checks_metadata import update_check_metadata
17-
from prowler.lib.check.models import Check
17+
from prowler.lib.check.models import Check, load_check_metadata
1818
from prowler.lib.check.utils import recover_checks_from_provider
1919
from prowler.lib.logger import logger
2020
from prowler.lib.outputs.outputs import report
@@ -110,6 +110,48 @@ def parse_checks_from_folder(provider, input_folder: str) -> set:
110110
sys.exit(1)
111111

112112

113+
def load_custom_checks_metadata(input_folder: str) -> dict:
114+
"""
115+
Load check metadata from a custom checks folder without copying the checks.
116+
This is used to validate check names before the provider is initialized.
117+
118+
Args:
119+
input_folder (str): Path to the folder containing custom checks.
120+
121+
Returns:
122+
dict: A dictionary with CheckID as key and CheckMetadata as value.
123+
"""
124+
custom_checks_metadata = {}
125+
126+
try:
127+
if not os.path.isdir(input_folder):
128+
return custom_checks_metadata
129+
130+
with os.scandir(input_folder) as checks:
131+
for check in checks:
132+
if check.is_dir():
133+
check_name = check.name
134+
metadata_file = os.path.join(
135+
input_folder, check_name, f"{check_name}.metadata.json"
136+
)
137+
if os.path.isfile(metadata_file):
138+
try:
139+
check_metadata = load_check_metadata(metadata_file)
140+
custom_checks_metadata[check_metadata.CheckID] = (
141+
check_metadata
142+
)
143+
except Exception as error:
144+
logger.warning(
145+
f"Could not load metadata from {metadata_file}: {error}"
146+
)
147+
return custom_checks_metadata
148+
except Exception as error:
149+
logger.error(
150+
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
151+
)
152+
return custom_checks_metadata
153+
154+
113155
# Load checks from custom folder
114156
def remove_custom_checks_module(input_folder: str, provider: str):
115157
# Check if input folder is a S3 URI

tests/lib/check/check_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
list_categories,
1919
list_checks_json,
2020
list_services,
21+
load_custom_checks_metadata,
2122
parse_checks_from_file,
2223
parse_checks_from_folder,
2324
remove_custom_checks_module,
@@ -483,6 +484,49 @@ def test_parse_checks_from_folder(self):
483484
)
484485
remove_custom_checks_module(check_folder, provider)
485486

487+
def test_load_custom_checks_metadata(self, tmp_path):
488+
"""Test loading check metadata from a custom checks folder."""
489+
check_name = "custom_test_check"
490+
check_folder = tmp_path / check_name
491+
check_folder.mkdir()
492+
493+
metadata = {
494+
"Provider": "aws",
495+
"CheckID": check_name,
496+
"CheckTitle": "Test Custom Check",
497+
"CheckType": [],
498+
"ServiceName": "custom",
499+
"SubServiceName": "",
500+
"ResourceIdTemplate": "arn:aws:custom:::resource",
501+
"Severity": "low",
502+
"ResourceType": "AwsCustomResource",
503+
"Description": "A test custom check",
504+
"Risk": "Test risk",
505+
"RelatedUrl": "https://example.com",
506+
"Remediation": {
507+
"Code": {"CLI": "", "NativeIaC": "", "Other": "", "Terraform": ""},
508+
"Recommendation": {"Text": "", "Url": ""},
509+
},
510+
"Categories": [],
511+
"DependsOn": [],
512+
"RelatedTo": [],
513+
"Notes": "",
514+
}
515+
metadata_file = check_folder / f"{check_name}.metadata.json"
516+
metadata_file.write_text(json.dumps(metadata))
517+
518+
result = load_custom_checks_metadata(str(tmp_path))
519+
520+
assert check_name in result
521+
assert result[check_name].CheckID == check_name
522+
assert result[check_name].Provider == "aws"
523+
assert result[check_name].Severity == "low"
524+
525+
def test_load_custom_checks_metadata_nonexistent_path(self):
526+
"""Test that nonexistent paths return empty dict."""
527+
result = load_custom_checks_metadata("/nonexistent/path/to/checks")
528+
assert result == {}
529+
486530
def test_exclude_checks_to_run(self):
487531
test_cases = [
488532
{

0 commit comments

Comments
 (0)