From 8496d6e23cb3a4b1cee246511c20e65fc473e52d Mon Sep 17 00:00:00 2001 From: "Andoni A." <14891798+andoniaf@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:11:05 +0200 Subject: [PATCH] feat(iac): add git clone scan, without handling auth yet --- poetry.lock | 52 +++++++++++++++- prowler/providers/common/provider.py | 1 + prowler/providers/iac/iac_provider.py | 62 +++++++++++++++++-- .../providers/iac/lib/arguments/arguments.py | 8 +++ pyproject.toml | 1 + 5 files changed, 117 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 58abe3af1c..e8fa6a8e5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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" diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index e7f4a74c2f..cbf9c673e2 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -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, ) diff --git a/prowler/providers/iac/iac_provider.py b/prowler/providers/iac/iac_provider.py index 540bb827d0..68061ef2b9 100644 --- a/prowler/providers/iac/iac_provider.py +++ b/prowler/providers/iac/iac_provider.py @@ -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,43 @@ class IacProvider(Provider): report.muted = True return report + def _clone_repository(self, repository_url: str, temporary_directory: str): + """ + Clone a git repository to a temporary directory. + """ + try: + logger.info( + f"Cloning repository {repository_url} into {temporary_directory}..." + ) + porcelain.clone(repository_url, 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) + scan_dir = self.scan_path + temp_dir = None + if self.scan_repository_url: + try: + temp_dir = tempfile.mkdtemp() + self._clone_repository(self.scan_repository_url, temp_dir) + scan_dir = temp_dir + except Exception as error: + logger.critical( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + sys.exit(1) + + 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 +251,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) diff --git a/prowler/providers/iac/lib/arguments/arguments.py b/prowler/providers/iac/lib/arguments/arguments.py index 87401aecf1..70ca6fbe21 100644 --- a/prowler/providers/iac/lib/arguments/arguments.py +++ b/prowler/providers/iac/lib/arguments/arguments.py @@ -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.", + ) diff --git a/pyproject.toml b/pyproject.toml index 401e88d341..ee1a0a095c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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",