mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Compare commits
4 Commits
0d0dabe166
...
PRWLR-7459
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae87997057 | ||
|
|
8b88938eaa | ||
|
|
0eeb534aed | ||
|
|
8496d6e23c |
52
poetry.lock
generated
52
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "about-time"
|
||||
@@ -1919,6 +1919,54 @@ files = [
|
||||
{file = "dpath-2.1.3.tar.gz", hash = "sha256:d1a7a0e6427d0a4156c792c82caf1f0109603f68ace792e36ca4596fd2cb8d9d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dulwich"
|
||||
version = "0.23.0"
|
||||
description = "Python Git Library"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "dulwich-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c13b0d5a9009cde23ecb8cb201df6e23e2a7a82c5e2d6ba6443fbb322c9befc6"},
|
||||
{file = "dulwich-0.23.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a68faf8612bf93de1285048d6ad13160f0fb3c5596a86e694e78f4e212886fa5"},
|
||||
{file = "dulwich-0.23.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d971566826f16ec67c70641c1fbdb337323aa5b533799bc5a4641f4750e73b36"},
|
||||
{file = "dulwich-0.23.0-cp310-cp310-win32.whl", hash = "sha256:27d970adf539806dfc4fe3e4c9e8dc6ebf0318977a56e24d22f13413535a51ba"},
|
||||
{file = "dulwich-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:025178533e884ffdb0d9d8db4b8870745d438cbfecb782fd1b56c3b6438e86cf"},
|
||||
{file = "dulwich-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d68498fdda13ab00791b483daab3bcfe9f9721c037aa458695e6ad81640c57cc"},
|
||||
{file = "dulwich-0.23.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:cb7bb930b12471a1cfcea4b3d25a671dc0ad32573f0ad25684684298959a1527"},
|
||||
{file = "dulwich-0.23.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2abbce32fd2bc7902bcc5f69b10bf22576810de21651baaa864b78fd7aec261"},
|
||||
{file = "dulwich-0.23.0-cp311-cp311-win32.whl", hash = "sha256:9e3151f10ce2a9ff91bca64c74345217f53bdd947dc958032343822009832f7a"},
|
||||
{file = "dulwich-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:3ae9f1d9dc92d4e9a3f89ba2c55221f7b6442c5dd93b3f6f539a3c9eb3f37bdd"},
|
||||
{file = "dulwich-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52cdef66a7994d29528ca79ca59452518bbba3fd56a9c61c61f6c467c1c7956e"},
|
||||
{file = "dulwich-0.23.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d473888a6ab9ed5d4a4c3f053cbe5b77f72d54b6efdf5688fed76094316e571e"},
|
||||
{file = "dulwich-0.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:19fcf20224c641a61c774da92f098fbaae9938c7e17a52841e64092adf7e78f9"},
|
||||
{file = "dulwich-0.23.0-cp312-cp312-win32.whl", hash = "sha256:7fc8b76b704ef35cd001e993e3aa4e1d666a2064bf467c07c560f12b2959dcaf"},
|
||||
{file = "dulwich-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:cb0566b888b578325350b4d67c61a0de35d417e9877560e3a6df88cae4576a59"},
|
||||
{file = "dulwich-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:624e2223c8b705b3a217f9c8d3bfed3a573093be0b0ba033c46cba8411fb9630"},
|
||||
{file = "dulwich-0.23.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b4eaf326d15bb3fc5316c777b0312f0fe02f6f82a4368cd971d0ce2167b7ec34"},
|
||||
{file = "dulwich-0.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d754afaf7c133a015c75cc2be11703138b4be932e0eeeb2c70add56083f31109"},
|
||||
{file = "dulwich-0.23.0-cp313-cp313-win32.whl", hash = "sha256:ac53ec438bde3c1f479782c34240479b36cd47230d091979137b7ecc12c0242e"},
|
||||
{file = "dulwich-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:50d3b4ba45671fb8b7d2afbd02c10b4edbc3290a1f92260e64098b409e9ca35c"},
|
||||
{file = "dulwich-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8e18ea3fa49f10932077f39c0b960b5045870c550c3d7c74f3cfaac09457cd6"},
|
||||
{file = "dulwich-0.23.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3e6df0eb8cca21f210e3ddce2ccb64482646893dbec2fee9f3411d037595bf7b"},
|
||||
{file = "dulwich-0.23.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:90c0064d7df8e7fe83d3a03c7d60b9e07a92698b18442f926199b2c3f0bf34d4"},
|
||||
{file = "dulwich-0.23.0-cp39-cp39-win32.whl", hash = "sha256:84eef513aba501cbc1f223863f3b4b351fe732d3fb590cab9bdf5d33eb1a1248"},
|
||||
{file = "dulwich-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:dce943da48217c26e15790fd6df62d27a7f1d067102780351ebf2635fc0ba482"},
|
||||
{file = "dulwich-0.23.0-py3-none-any.whl", hash = "sha256:d8da6694ca332bb48775e35ee2215aa4673821164a91b83062f699c69f7cd135"},
|
||||
{file = "dulwich-0.23.0.tar.gz", hash = "sha256:0aa6c2489dd5e978b27e9b75983b7331a66c999f0efc54ebe37cab808ed322ae"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
urllib3 = ">=1.25"
|
||||
|
||||
[package.extras]
|
||||
dev = ["dissolve (>=0.1.1)", "mypy (==1.16.0)", "ruff (==0.11.13)"]
|
||||
fastimport = ["fastimport"]
|
||||
https = ["urllib3 (>=1.24.1)"]
|
||||
merge = ["merge3"]
|
||||
paramiko = ["paramiko"]
|
||||
pgp = ["gpg"]
|
||||
|
||||
[[package]]
|
||||
name = "durationpy"
|
||||
version = "0.9"
|
||||
@@ -6603,4 +6651,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">3.9.1,<3.13"
|
||||
content-hash = "d72c55b52949ba94f0c68004d5b778edb69514a05bbb7aba8d641b5058a99fd5"
|
||||
content-hash = "87c36db93b05afc33296b8a60a6eeb1955edf2555fdcd1c1431c6b66fe6d6a8e"
|
||||
|
||||
@@ -283,7 +283,7 @@ class Finding(BaseModel):
|
||||
output_data["region"] = check_output.location
|
||||
|
||||
elif provider.type == "iac":
|
||||
output_data["auth_method"] = "local" # Until we support remote repos
|
||||
output_data["auth_method"] = "None" # Until we support remote repos
|
||||
output_data["account_uid"] = "iac"
|
||||
output_data["account_name"] = "iac"
|
||||
output_data["resource_name"] = check_output.resource["resource"]
|
||||
|
||||
@@ -710,7 +710,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>IAC path:</b> {provider.scan_path}
|
||||
{"<b>IAC repository URL:</b> " + provider.scan_repository_url if provider.scan_repository_url else "<b>IAC path:</b> " + provider.scan_path}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -723,7 +723,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>IAC authentication method:</b> local
|
||||
<b>IAC authentication method:</b> None
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -246,6 +246,7 @@ class Provider(ABC):
|
||||
elif "iac" in provider_class_name.lower():
|
||||
provider_class(
|
||||
scan_path=arguments.scan_path,
|
||||
scan_repository_url=arguments.scan_repository_url,
|
||||
config_path=arguments.config_file,
|
||||
fixer_config=fixer_config,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import List
|
||||
|
||||
from colorama import Fore, Style
|
||||
from dulwich import porcelain
|
||||
|
||||
from prowler.config.config import (
|
||||
default_config_file_path,
|
||||
@@ -23,6 +26,7 @@ class IacProvider(Provider):
|
||||
def __init__(
|
||||
self,
|
||||
scan_path: str = ".",
|
||||
scan_repository_url: str = None,
|
||||
config_path: str = None,
|
||||
config_content: dict = None,
|
||||
fixer_config: dict = {},
|
||||
@@ -30,6 +34,7 @@ class IacProvider(Provider):
|
||||
logger.info("Instantiating IAC Provider...")
|
||||
|
||||
self.scan_path = scan_path
|
||||
self.scan_repository_url = scan_repository_url
|
||||
self.region = "global"
|
||||
self.audited_account = "local-iac"
|
||||
self._session = None
|
||||
@@ -146,8 +151,38 @@ class IacProvider(Provider):
|
||||
report.muted = True
|
||||
return report
|
||||
|
||||
def _clone_repository(self, repository_url: str) -> str:
|
||||
"""
|
||||
Clone a git repository to a temporary directory.
|
||||
"""
|
||||
try:
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
logger.info(
|
||||
f"Cloning repository {repository_url} into {temporary_directory}..."
|
||||
)
|
||||
porcelain.clone(repository_url, temporary_directory)
|
||||
return temporary_directory
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
def run(self) -> List[CheckReportIAC]:
|
||||
return self.run_scan(self.scan_path)
|
||||
temp_dir = None
|
||||
if self.scan_repository_url:
|
||||
scan_dir = temp_dir = self._clone_repository(self.scan_repository_url)
|
||||
else:
|
||||
scan_dir = self.scan_path
|
||||
|
||||
try:
|
||||
reports = self.run_scan(scan_dir)
|
||||
finally:
|
||||
if temp_dir:
|
||||
logger.info(f"Removing temporary directory {temp_dir}...")
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
return reports
|
||||
|
||||
def run_scan(self, directory: str) -> List[CheckReportIAC]:
|
||||
try:
|
||||
@@ -211,8 +246,20 @@ class IacProvider(Provider):
|
||||
sys.exit(1)
|
||||
|
||||
def print_credentials(self):
|
||||
report_lines = [
|
||||
f"Directory: {Fore.YELLOW}{self.scan_path}{Style.RESET_ALL}",
|
||||
]
|
||||
report_title = f"{Style.BRIGHT}Scanning local IaC directory:{Style.RESET_ALL}"
|
||||
if self.scan_repository_url:
|
||||
report_lines = [
|
||||
f"Repository: {Fore.YELLOW}{self.scan_repository_url}{Style.RESET_ALL}",
|
||||
]
|
||||
else:
|
||||
report_lines = [
|
||||
f"Directory: {Fore.YELLOW}{self.scan_path}{Style.RESET_ALL}",
|
||||
]
|
||||
if self.scan_repository_url:
|
||||
report_title = (
|
||||
f"{Style.BRIGHT}Scanning remote IaC repository:{Style.RESET_ALL}"
|
||||
)
|
||||
else:
|
||||
report_title = (
|
||||
f"{Style.BRIGHT}Scanning local IaC directory:{Style.RESET_ALL}"
|
||||
)
|
||||
print_boxes(report_lines, report_title)
|
||||
|
||||
@@ -13,3 +13,11 @@ def init_parser(self):
|
||||
default=".",
|
||||
help="Path to the folder containing your infrastructure-as-code files. Default: current directory",
|
||||
)
|
||||
|
||||
iac_scan_subparser.add_argument(
|
||||
"--scan-repository-url",
|
||||
"-R",
|
||||
dest="scan_repository_url",
|
||||
default=None,
|
||||
help="URL to the repository containing your infrastructure-as-code files.",
|
||||
)
|
||||
|
||||
@@ -41,6 +41,7 @@ dependencies = [
|
||||
"dash==2.18.2",
|
||||
"dash-bootstrap-components==1.6.0",
|
||||
"detect-secrets==1.5.0",
|
||||
"dulwich==0.23.0",
|
||||
"google-api-python-client==2.163.0",
|
||||
"google-auth-httplib2>=0.1,<0.3",
|
||||
"jsonschema==4.23.0",
|
||||
|
||||
@@ -506,6 +506,55 @@ class TestFinding:
|
||||
assert finding_output.metadata.Notes == "mock_notes"
|
||||
assert finding_output.metadata.Compliance == []
|
||||
|
||||
def test_generate_output_iac_remote(self):
|
||||
# Mock provider
|
||||
provider = MagicMock()
|
||||
provider.type = "iac"
|
||||
provider.scan_repository_url = "https://github.com/user/repo"
|
||||
|
||||
# Mock check result
|
||||
check_output = MagicMock()
|
||||
check_output.file_path = "/path/to/iac/file.tf"
|
||||
check_output.resource_path = "/path/to/iac/file.tf"
|
||||
check_output.file_line_range = [1, 5]
|
||||
check_output.resource = {
|
||||
"resource": "aws_s3_bucket.example",
|
||||
"value": {},
|
||||
}
|
||||
check_output.resource_details = "test_resource_details"
|
||||
check_output.status = Status.PASS
|
||||
check_output.status_extended = "mock_status_extended"
|
||||
check_output.muted = False
|
||||
check_output.check_metadata = mock_check_metadata(provider="iac")
|
||||
check_output.compliance = {}
|
||||
|
||||
# Mock output options
|
||||
output_options = MagicMock()
|
||||
output_options.unix_timestamp = False
|
||||
|
||||
# Generate the finding
|
||||
finding_output = Finding.generate_output(provider, check_output, output_options)
|
||||
|
||||
# Finding
|
||||
assert isinstance(finding_output, Finding)
|
||||
assert finding_output.auth_method == "None"
|
||||
assert finding_output.resource_name == "aws_s3_bucket.example"
|
||||
assert finding_output.resource_uid == "aws_s3_bucket.example"
|
||||
assert finding_output.region == "/path/to/iac/file.tf"
|
||||
assert finding_output.status == Status.PASS
|
||||
assert finding_output.status_extended == "mock_status_extended"
|
||||
assert finding_output.muted is False
|
||||
|
||||
# Metadata
|
||||
assert finding_output.metadata.Provider == "iac"
|
||||
assert finding_output.metadata.CheckID == "mock_check_id"
|
||||
assert finding_output.metadata.CheckTitle == "mock_check_title"
|
||||
assert finding_output.metadata.CheckType == []
|
||||
assert finding_output.metadata.CheckAliases == []
|
||||
assert finding_output.metadata.ServiceName == "mock_service_name"
|
||||
assert finding_output.metadata.SubServiceName == ""
|
||||
assert finding_output.metadata.ResourceIdTemplate == ""
|
||||
|
||||
def assert_keys_lowercase(self, d):
|
||||
for k, v in d.items():
|
||||
assert k.islower()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -130,3 +132,58 @@ class TestIacProvider:
|
||||
reports = provider.run_scan("/test/directory")
|
||||
|
||||
assert len(reports) == 0
|
||||
|
||||
def test_provider_run_local_scan(self):
|
||||
scan_path = "."
|
||||
provider = IacProvider(scan_path=scan_path)
|
||||
with mock.patch(
|
||||
"prowler.providers.iac.iac_provider.IacProvider.run_scan",
|
||||
) as mock_run_scan:
|
||||
provider.run()
|
||||
mock_run_scan.assert_called_with(scan_path)
|
||||
|
||||
def test_provider_run_remote_scan(self):
|
||||
scan_repository_url = "https://github.com/user/repo"
|
||||
provider = IacProvider(scan_repository_url=scan_repository_url)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.iac.iac_provider.IacProvider._clone_repository",
|
||||
return_value=temp_dir,
|
||||
) as mock_clone,
|
||||
mock.patch(
|
||||
"prowler.providers.iac.iac_provider.IacProvider.run_scan"
|
||||
) as mock_run_scan,
|
||||
):
|
||||
provider.run()
|
||||
mock_clone.assert_called_with(scan_repository_url)
|
||||
mock_run_scan.assert_called_with(temp_dir)
|
||||
|
||||
def test_print_credentials_local(self):
|
||||
scan_path = "/path/to/scan"
|
||||
provider = IacProvider(scan_path=scan_path)
|
||||
with mock.patch("builtins.print") as mock_print:
|
||||
provider.print_credentials()
|
||||
assert any(
|
||||
f"Directory: \x1b[33m{scan_path}\x1b[0m" in call.args[0]
|
||||
for call in mock_print.call_args_list
|
||||
)
|
||||
assert any(
|
||||
"Scanning local IaC directory:" in call.args[0]
|
||||
for call in mock_print.call_args_list
|
||||
)
|
||||
|
||||
def test_print_credentials_remote(self):
|
||||
repo_url = "https://github.com/user/repo"
|
||||
provider = IacProvider(scan_repository_url=repo_url)
|
||||
with mock.patch("builtins.print") as mock_print:
|
||||
provider.print_credentials()
|
||||
assert any(
|
||||
f"Repository: \x1b[33m{repo_url}\x1b[0m" in call.args[0]
|
||||
for call in mock_print.call_args_list
|
||||
)
|
||||
assert any(
|
||||
"Scanning remote IaC repository:" in call.args[0]
|
||||
for call in mock_print.call_args_list
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user