From 345033e58acee52036146508afe143b37681a0ec Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Sat, 29 Nov 2025 12:55:49 +0100 Subject: [PATCH 1/6] Fix attack paths demo neo4j conneciton (#9352) Add retryable Neo4j session. --- api/src/backend/api/attack_paths/database.py | 25 +++++- .../api/attack_paths/retryable_session.py | 87 +++++++++++++++++++ 2 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 api/src/backend/api/attack_paths/retryable_session.py diff --git a/api/src/backend/api/attack_paths/database.py b/api/src/backend/api/attack_paths/database.py index da8217fa55..c8d48099bd 100644 --- a/api/src/backend/api/attack_paths/database.py +++ b/api/src/backend/api/attack_paths/database.py @@ -11,10 +11,14 @@ from django.conf import settings import neo4j.exceptions +from api.attack_paths.retryable_session import RetryableSession + # Without this Celery goes crazy with Neo4j logging logging.getLogger("neo4j").setLevel(logging.ERROR) logging.getLogger("neo4j").propagate = False +SERVICE_UNAVAILABLE_MAX_RETRIES = 3 + # Module-level process-wide driver singleton _driver: neo4j.Driver | None = None _lock = threading.Lock() @@ -39,7 +43,10 @@ def init_driver() -> neo4j.Driver: config = settings.DATABASES["neo4j"] _driver = neo4j.GraphDatabase.driver( - uri, auth=(config["USER"], config["PASSWORD"]) + uri, + auth=(config["USER"], config["PASSWORD"]), + max_connection_lifetime=604800, # 7 days + keep_alive=True, # It's the default value, but let's being explicit ) _driver.verify_connectivity() @@ -62,14 +69,24 @@ def close_driver() -> None: # TODO: Use it @contextmanager -def get_session(database: str | None = None) -> Iterator[neo4j.Session]: +def get_session(database: str | None = None) -> Iterator[RetryableSession]: + session_wrapper: RetryableSession | None = None + try: - with get_driver().session(database=database) as session: - yield session + session_wrapper = RetryableSession( + session_factory=lambda: get_driver().session(database=database), + close_driver=close_driver, # Just to avoid circular imports + max_retries=SERVICE_UNAVAILABLE_MAX_RETRIES, + ) + yield session_wrapper except neo4j.exceptions.Neo4jError as exc: raise GraphDatabaseQueryException(message=exc.message, code=exc.code) + finally: + if session_wrapper is not None: + session_wrapper.close() + def create_database(database: str) -> None: query = "CREATE DATABASE $database IF NOT EXISTS" diff --git a/api/src/backend/api/attack_paths/retryable_session.py b/api/src/backend/api/attack_paths/retryable_session.py new file mode 100644 index 0000000000..1c79a17555 --- /dev/null +++ b/api/src/backend/api/attack_paths/retryable_session.py @@ -0,0 +1,87 @@ +import logging +from collections.abc import Callable +from typing import Any + +import neo4j +import neo4j.exceptions + +logger = logging.getLogger(__name__) + + +class RetryableSession: + """ + Wrapper around `neo4j.Session` that retries `neo4j.exceptions.ServiceUnavailable` errors. + """ + + def __init__( + self, + session_factory: Callable[[], neo4j.Session], + close_driver: Callable[[], None], # Just to avoid circular imports + max_retries: int, + logger: logging.Logger | None = None, + ) -> None: + self._session_factory = session_factory + self._close_driver = close_driver + self._max_retries = max(0, max_retries) + self._session = self._session_factory() + + def close(self) -> None: + if self._session is not None: + self._session.close() + self._session = None + + def __enter__(self) -> "RetryableSession": + return self + + def __exit__(self, exc_type: Any, exc: Any, exc_tb: Any) -> None: + self.close() + + def run(self, *args: Any, **kwargs: Any) -> Any: + return self._call_with_retry("run", *args, **kwargs) + + def write_transaction(self, *args: Any, **kwargs: Any) -> Any: + return self._call_with_retry("write_transaction", *args, **kwargs) + + def read_transaction(self, *args: Any, **kwargs: Any) -> Any: + return self._call_with_retry("read_transaction", *args, **kwargs) + + def execute_write(self, *args: Any, **kwargs: Any) -> Any: + return self._call_with_retry("execute_write", *args, **kwargs) + + def execute_read(self, *args: Any, **kwargs: Any) -> Any: + return self._call_with_retry("execute_read", *args, **kwargs) + + def __getattr__(self, item: str) -> Any: + return getattr(self._session, item) + + def _call_with_retry(self, method_name: str, *args: Any, **kwargs: Any) -> Any: + attempt = 0 + last_exc: neo4j.exceptions.ServiceUnavailable | None = None + + while attempt <= self._max_retries: + try: + method = getattr(self._session, method_name) + return method(*args, **kwargs) + + except ( + neo4j.exceptions.ServiceUnavailable + ) as exc: # pragma: no cover - depends on infra + last_exc = exc + attempt += 1 + + if attempt > self._max_retries: + raise + + logger.warning( + f"Neo4j session {method_name} failed with ServiceUnavailable ({attempt}/{self._max_retries} attempts). Retrying..." + ) + self._refresh_session() + + raise last_exc if last_exc else RuntimeError("Unexpected retry loop exit") + + def _refresh_session(self) -> None: + if self._session is not None: + self._session.close() + + self._close_driver() + self._session = self._session_factory() From 48f19d0f11ea2088d40ded6be5a3caf8e70e4d46 Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Mon, 1 Dec 2025 10:31:18 +0100 Subject: [PATCH 2/6] fix(attack-paths): neo4j.exceptions import (#9356) --- api/src/backend/api/attack_paths/database.py | 3 +-- api/src/backend/api/attack_paths/retryable_session.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/src/backend/api/attack_paths/database.py b/api/src/backend/api/attack_paths/database.py index c8d48099bd..badb2f911e 100644 --- a/api/src/backend/api/attack_paths/database.py +++ b/api/src/backend/api/attack_paths/database.py @@ -6,11 +6,10 @@ from typing import Iterator from uuid import UUID import neo4j +import neo4j.exceptions from django.conf import settings -import neo4j.exceptions - from api.attack_paths.retryable_session import RetryableSession # Without this Celery goes crazy with Neo4j logging diff --git a/api/src/backend/api/attack_paths/retryable_session.py b/api/src/backend/api/attack_paths/retryable_session.py index 1c79a17555..8452b428ed 100644 --- a/api/src/backend/api/attack_paths/retryable_session.py +++ b/api/src/backend/api/attack_paths/retryable_session.py @@ -1,4 +1,5 @@ import logging + from collections.abc import Callable from typing import Any @@ -18,7 +19,6 @@ class RetryableSession: session_factory: Callable[[], neo4j.Session], close_driver: Callable[[], None], # Just to avoid circular imports max_retries: int, - logger: logging.Logger | None = None, ) -> None: self._session_factory = session_factory self._close_driver = close_driver From 95d9e9a59f8e52e4096a5c17bf839e515b918239 Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Thu, 18 Dec 2025 15:52:15 +0100 Subject: [PATCH 3/6] feat(attack-paths): Update Cartography dependency and its usage (#9593) --- api/Dockerfile | 5 - api/poetry.lock | 1012 ++++++++++++++++- api/pyproject.toml | 3 +- api/src/backend/api/attack_paths/database.py | 5 +- .../backend/tasks/jobs/attack_paths/aws.py | 27 +- .../tasks/jobs/attack_paths/prowler.py | 2 +- .../backend/tasks/jobs/attack_paths/scan.py | 8 +- poetry.lock | 45 +- pyproject.toml | 6 +- 9 files changed, 1030 insertions(+), 83 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index de7c84b077..2d7883a957 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -2,9 +2,6 @@ FROM python:3.12.10-slim-bookworm AS build LABEL maintainer="https://github.com/prowler-cloud/api" -ARG CARTOGRAPHY_VERSION=0.117.0 -ENV CARTOGRAPHY_VERSION=${CARTOGRAPHY_VERSION} - ARG POWERSHELL_VERSION=7.5.0 ENV POWERSHELL_VERSION=${POWERSHELL_VERSION} @@ -82,8 +79,6 @@ ENV PATH="/home/prowler/.local/bin:$PATH" RUN poetry install --no-root && \ rm -rf ~/.cache/pip -RUN poetry run python -m pip install cartography==${CARTOGRAPHY_VERSION} - RUN poetry run python "$(poetry env info --path)/src/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py" COPY src/backend/ ./backend/ diff --git a/api/poetry.lock b/api/poetry.lock index a8af36f966..88558a76d4 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -12,6 +12,83 @@ files = [ {file = "about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341"}, ] +[[package]] +name = "adal" +version = "1.2.7" +description = "Note: This library is already replaced by MSAL Python, available here: https://pypi.org/project/msal/ .ADAL Python remains available here as a legacy. The ADAL for Python library makes it easy for python application to authenticate to Azure Active Directory (AAD) in order to access AAD protected web resources." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "adal-1.2.7-py2.py3-none-any.whl", hash = "sha256:2a7451ed7441ddbc57703042204a3e30ef747478eea022c70f789fc7f084bc3d"}, + {file = "adal-1.2.7.tar.gz", hash = "sha256:d74f45b81317454d96e982fd1c50e6fb5c99ac2223728aea8764433a39f566f1"}, +] + +[package.dependencies] +cryptography = ">=1.1.0" +PyJWT = ">=1.0.0,<3" +python-dateutil = ">=2.1.0,<3" +requests = ">=2.0.0,<3" + +[[package]] +name = "aioboto3" +version = "15.5.0" +description = "Async boto3 wrapper" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aioboto3-15.5.0-py3-none-any.whl", hash = "sha256:cc880c4d6a8481dd7e05da89f41c384dbd841454fc1998ae25ca9c39201437a6"}, + {file = "aioboto3-15.5.0.tar.gz", hash = "sha256:ea8d8787d315594842fbfcf2c4dce3bac2ad61be275bc8584b2ce9a3402a6979"}, +] + +[package.dependencies] +aiobotocore = {version = "2.25.1", extras = ["boto3"]} +aiofiles = ">=23.2.1" + +[package.extras] +chalice = ["chalice (>=1.24.0)"] +s3cse = ["cryptography (>=44.0.1)"] + +[[package]] +name = "aiobotocore" +version = "2.25.1" +description = "Async client for aws services using botocore and aiohttp" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiobotocore-2.25.1-py3-none-any.whl", hash = "sha256:eb6daebe3cbef5b39a0bb2a97cffbe9c7cb46b2fcc399ad141f369f3c2134b1f"}, + {file = "aiobotocore-2.25.1.tar.gz", hash = "sha256:ea9be739bfd7ece8864f072ec99bb9ed5c7e78ebb2b0b15f29781fbe02daedbc"}, +] + +[package.dependencies] +aiohttp = ">=3.9.2,<4.0.0" +aioitertools = ">=0.5.1,<1.0.0" +boto3 = {version = ">=1.40.46,<1.40.62", optional = true, markers = "extra == \"boto3\""} +botocore = ">=1.40.46,<1.40.62" +jmespath = ">=0.7.1,<2.0.0" +multidict = ">=6.0.0,<7.0.0" +python-dateutil = ">=2.1,<3.0.0" +wrapt = ">=1.10.10,<2.0.0" + +[package.extras] +awscli = ["awscli (>=1.42.46,<1.42.62)"] +boto3 = ["boto3 (>=1.40.46,<1.40.62)"] +httpx = ["httpx (>=0.25.1,<0.29)"] + +[[package]] +name = "aiofiles" +version = "25.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695"}, + {file = "aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2"}, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -132,6 +209,18 @@ yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] +[[package]] +name = "aioitertools" +version = "0.13.0" +description = "itertools and builtins for AsyncIO and mixed iterables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be"}, + {file = "aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c"}, +] + [[package]] name = "aiosignal" version = "1.4.0" @@ -211,6 +300,33 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.26.1)"] +[[package]] +name = "applicationinsights" +version = "0.11.10" +description = "This project extends the Application Insights API surface to support Python." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "applicationinsights-0.11.10-py2.py3-none-any.whl", hash = "sha256:e89a890db1c6906b6a7d0bcfd617dac83974773c64573147c8d6654f9cf2a6ea"}, + {file = "applicationinsights-0.11.10.tar.gz", hash = "sha256:0b761f3ef0680acf4731906dfc1807faa6f2a57168ae74592db0084a6099f7b3"}, +] + +[[package]] +name = "argcomplete" +version = "3.5.3" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61"}, + {file = "argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + [[package]] name = "asgiref" version = "3.9.1" @@ -313,6 +429,58 @@ files = [ {file = "awsipranges-0.3.3.tar.gz", hash = "sha256:4f0b3f22a9dc1163c85b513bed812b6c92bdacd674e6a7b68252a3c25b99e2c0"}, ] +[[package]] +name = "azure-cli-core" +version = "2.81.0" +description = "Microsoft Azure Command-Line Tools Core Module" +optional = false +python-versions = ">=3.10.0" +groups = ["main"] +files = [ + {file = "azure_cli_core-2.81.0-py3-none-any.whl", hash = "sha256:2c711ff890d003b10bb40f932d008a872c6451324ee533fbbb861c950c3f0f93"}, + {file = "azure_cli_core-2.81.0.tar.gz", hash = "sha256:bae30dcd6f8c1883c8e32fbcb7c43dbe2dbfe522e428c7629edef271933f7b1e"}, +] + +[package.dependencies] +argcomplete = ">=3.5.2,<3.6.0" +azure-cli-telemetry = "==1.1.0.*" +azure-core = ">=1.35.0,<1.36.0" +azure-mgmt-core = ">=1.2.0,<2" +cryptography = "*" +distro = {version = "*", markers = "sys_platform == \"linux\""} +humanfriendly = ">=10.0,<11.0" +jmespath = "*" +knack = ">=0.11.0,<0.12.0" +microsoft-security-utilities-secret-masker = ">=1.0.0b4,<1.1.0" +msal = [ + {version = "1.34.0b1", extras = ["broker"], markers = "sys_platform == \"win32\""}, + {version = "1.34.0b1", markers = "sys_platform != \"win32\""}, +] +msal-extensions = "1.2.0" +packaging = ">=20.9" +pkginfo = ">=1.5.0.1" +psutil = {version = ">=5.9", markers = "sys_platform != \"cygwin\""} +py-deviceid = "*" +PyJWT = ">=2.1.0" +pyopenssl = ">=17.1.0" +requests = {version = "*", extras = ["socks"]} + +[[package]] +name = "azure-cli-telemetry" +version = "1.1.0" +description = "Microsoft Azure CLI Telemetry Package" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "azure-cli-telemetry-1.1.0.tar.gz", hash = "sha256:d922379cda1b48952be75fb3bd2ac5e7ceecf569492a6088bab77894c624a278"}, + {file = "azure_cli_telemetry-1.1.0-py3-none-any.whl", hash = "sha256:2fc12608c0cf0ea6e69b392af9cab92f1249340b8caff7e9674cf91b3becb337"}, +] + +[package.dependencies] +applicationinsights = ">=0.11.1,<0.12" +portalocker = ">=1.6,<3" + [[package]] name = "azure-common" version = "1.1.28" @@ -454,6 +622,23 @@ azure-mgmt-core = ">=1.3.2" isodate = ">=0.6.1" typing-extensions = ">=4.6.0" +[[package]] +name = "azure-mgmt-containerinstance" +version = "10.1.0" +description = "Microsoft Azure Container Instance Client Library for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "azure-mgmt-containerinstance-10.1.0.zip", hash = "sha256:78d437adb28574f448c838ed5f01f9ced378196098061deb59d9f7031704c17e"}, + {file = "azure_mgmt_containerinstance-10.1.0-py3-none-any.whl", hash = "sha256:ee7977b7b70f2233e44ec6ce8c99027f3f7892bb3452b4bad46df340d9f98959"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.2,<2.0.0" +isodate = ">=0.6.1,<1.0.0" + [[package]] name = "azure-mgmt-containerregistry" version = "12.0.0" @@ -540,6 +725,42 @@ azure-common = ">=1.1,<2.0" azure-mgmt-core = ">=1.3.2,<2.0.0" isodate = ">=0.6.1,<1.0.0" +[[package]] +name = "azure-mgmt-datafactory" +version = "9.2.0" +description = "Microsoft Azure Data Factory Management Client Library for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "azure_mgmt_datafactory-9.2.0-py3-none-any.whl", hash = "sha256:d870a7a6099227e91d1c258a956c2aa32c2ea4c0a4409913d8f215887349f128"}, + {file = "azure_mgmt_datafactory-9.2.0.tar.gz", hash = "sha256:5132e9c24c441ac225f2a60225924baa55079ca81eff7db99a70d661d64bb0d7"}, +] + +[package.dependencies] +azure-common = ">=1.1" +azure-mgmt-core = ">=1.3.2" +isodate = ">=0.6.1" +typing-extensions = ">=4.6.0" + +[[package]] +name = "azure-mgmt-eventgrid" +version = "10.4.0" +description = "Microsoft Azure Event Grid Management Client Library for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "azure_mgmt_eventgrid-10.4.0-py3-none-any.whl", hash = "sha256:5e4637245bbff33298d5f427971b870dbb03d873a3ef68f328190a7b7a38c56f"}, + {file = "azure_mgmt_eventgrid-10.4.0.tar.gz", hash = "sha256:303e5e27cf4bb5ec833ba4e5a9ef70b5bc410e190412ec47cde59d82e413fb7e"}, +] + +[package.dependencies] +azure-common = ">=1.1" +azure-mgmt-core = ">=1.3.2" +isodate = ">=0.6.1" +typing-extensions = ">=4.6.0" + [[package]] name = "azure-mgmt-keyvault" version = "10.3.1" @@ -575,6 +796,23 @@ azure-common = ">=1.1,<2.0" azure-mgmt-core = ">=1.2.0,<2.0.0" msrest = ">=0.6.21" +[[package]] +name = "azure-mgmt-logic" +version = "10.0.0" +description = "Microsoft Azure Logic Apps Management Client Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "azure-mgmt-logic-10.0.0.zip", hash = "sha256:b3fa4864f14aaa7af41d778d925f051ed29b6016f46344765ecd0f49d0f04dd6"}, + {file = "azure_mgmt_logic-10.0.0-py3-none-any.whl", hash = "sha256:525c78afedf3edb35eb0a16152c8beba89769ee1bc6af01bcdc42842a551e443"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.0,<2.0.0" +msrest = ">=0.6.21" + [[package]] name = "azure-mgmt-monitor" version = "6.0.2" @@ -841,6 +1079,18 @@ typing-extensions = ">=4.6.0" [package.extras] aio = ["azure-core[aio] (>=1.30.0)"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +groups = ["main"] +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "bandit" version = "1.7.9" @@ -892,34 +1142,34 @@ files = [ [[package]] name = "boto3" -version = "1.39.15" +version = "1.40.61" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.39.15-py3-none-any.whl", hash = "sha256:38fc54576b925af0075636752de9974e172c8a2cf7133400e3e09b150d20fb6a"}, - {file = "boto3-1.39.15.tar.gz", hash = "sha256:b4483625f0d8c35045254dee46cd3c851bbc0450814f20b9b25bee1b5c0d8409"}, + {file = "boto3-1.40.61-py3-none-any.whl", hash = "sha256:6b9c57b2a922b5d8c17766e29ed792586a818098efe84def27c8f582b33f898c"}, + {file = "boto3-1.40.61.tar.gz", hash = "sha256:d6c56277251adf6c2bdd25249feae625abe4966831676689ff23b4694dea5b12"}, ] [package.dependencies] -botocore = ">=1.39.15,<1.40.0" +botocore = ">=1.40.61,<1.41.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.13.0,<0.14.0" +s3transfer = ">=0.14.0,<0.15.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.39.15" +version = "1.40.61" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.39.15-py3-none-any.whl", hash = "sha256:eb9cfe918ebfbfb8654e1b153b29f0c129d586d2c0d7fb4032731d49baf04cff"}, - {file = "botocore-1.39.15.tar.gz", hash = "sha256:2aa29a717f14f8c7ca058c2e297aaed0aa10ecea24b91514eee802814d1b7600"}, + {file = "botocore-1.40.61-py3-none-any.whl", hash = "sha256:17ebae412692fd4824f99cde0f08d50126dc97954008e5ba2b522eb049238aa7"}, + {file = "botocore-1.40.61.tar.gz", hash = "sha256:a2487ad69b090f9cccd64cf07c7021cd80ee9c0655ad974f87045b02f3ef52cd"}, ] [package.dependencies] @@ -928,7 +1178,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.23.8)"] +crt = ["awscrt (==0.27.6)"] [[package]] name = "cachetools" @@ -942,6 +1192,75 @@ files = [ {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, ] +[[package]] +name = "cartography" +version = "0.0.1.dev1267+g7c7d6d501" +description = "Explore assets and their relationships across your technical infrastructure." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +adal = ">=1.2.4" +aioboto3 = ">=13.0.0" +azure-cli-core = ">=2.26.0" +azure-identity = ">=1.5.0" +azure-mgmt-authorization = ">=0.60.0" +azure-mgmt-compute = ">=5.0.0" +azure-mgmt-containerinstance = ">=10.0.0" +azure-mgmt-containerservice = ">=30.0.0" +azure-mgmt-cosmosdb = ">=6.0.0" +azure-mgmt-datafactory = ">=8.0.0" +azure-mgmt-eventgrid = ">=10.0.0" +azure-mgmt-logic = ">=10.0.0" +azure-mgmt-monitor = ">=3.0.0" +azure-mgmt-network = ">=25.0.0" +azure-mgmt-resource = ">=10.2.0" +azure-mgmt-security = ">=5.0.0" +azure-mgmt-sql = ">=3.0.1,<4" +azure-mgmt-storage = ">=16.0.0" +azure-mgmt-web = ">=7.0.0" +backoff = ">=2.1.2" +boto3 = ">=1.15.1" +botocore = ">=1.18.1" +cloudflare = ">=4.1.0,<5.0.0" +crowdstrike-falconpy = ">=0.5.1" +dnspython = ">=1.15.0" +duo-client = "*" +google-api-python-client = ">=1.7.8" +google-auth = ">=2.37.0" +google-cloud-asset = ">=1.0.0" +google-cloud-resource-manager = ">=1.14.2" +httpx = ">=0.24.0" +kubernetes = ">=22.6.0" +marshmallow = ">=3.0.0rc7" +msgraph-sdk = "*" +msrestazure = ">=0.6.4" +neo4j = ">=5.28.2,<6.0.0" +oci = ">=2.71.0" +okta = "<1.0.0" +packaging = "*" +pdpyras = ">=4.3.0" +policyuniverse = ">=1.1.0.0" +python-dateutil = "*" +python-digitalocean = ">=1.16.0" +pyyaml = ">=5.3.1" +requests = ">=2.22.0" +scaleway = ">=2.10.0" +slack-sdk = ">=3.37.0" +statsd = "*" +typer = ">=0.9.0" +types-aiobotocore-ecr = "*" +xmltodict = "*" + +[package.source] +type = "git" +url = "https://github.com/prowler-cloud/cartography" +reference = "azure-mgmt-sql-3" +resolved_reference = "7c7d6d5014bf079066ee1645e9517c67fb36fe67" + [[package]] name = "celery" version = "5.4.0" @@ -1261,6 +1580,26 @@ prompt-toolkit = ">=3.0.36" [package.extras] testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] +[[package]] +name = "cloudflare" +version = "4.3.1" +description = "The official Python library for the cloudflare API" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cloudflare-4.3.1-py3-none-any.whl", hash = "sha256:6927135a5ee5633d6e2e1952ca0484745e933727aeeb189996d2ad9d292071c6"}, + {file = "cloudflare-4.3.1.tar.gz", hash = "sha256:b1e1c6beeb8d98f63bfe0a1cba874fc4e22e000bcc490544f956c689b3b5b258"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +typing-extensions = ">=4.10,<5" + [[package]] name = "colorama" version = "0.4.6" @@ -1272,7 +1611,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} +markers = {dev = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "contextlib2" @@ -1458,6 +1797,25 @@ files = [ [package.extras] dev = ["polib"] +[[package]] +name = "crowdstrike-falconpy" +version = "1.5.4" +description = "The CrowdStrike Falcon SDK for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "crowdstrike_falconpy-1.5.4-py3-none-any.whl", hash = "sha256:f698d4571d0dfac1dc450a69329959e1827d49a92ce9f81d2f9963f6da41b670"}, + {file = "crowdstrike_falconpy-1.5.4.tar.gz", hash = "sha256:b357850664639af5e8441d1ed5efb905b03af74c7c5f49b4ade161202e8e45c7"}, +] + +[package.dependencies] +requests = "*" +urllib3 = "*" + +[package.extras] +dev = ["bandit", "coverage", "flake8", "pydocstyle", "pylint", "pytest", "pytest-cov"] + [[package]] name = "cryptography" version = "44.0.1" @@ -2174,6 +2532,21 @@ merge = ["merge3"] paramiko = ["paramiko"] pgp = ["gpg"] +[[package]] +name = "duo-client" +version = "5.5.0" +description = "Reference client for Duo Security APIs" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "duo_client-5.5.0-py3-none-any.whl", hash = "sha256:4fbf1e97a2b25ef64e9f88171ab817162cf45bafc1c63026af4883baf8892a12"}, + {file = "duo_client-5.5.0.tar.gz", hash = "sha256:303109e047fe7525ba4fc4a294c1f3deb4125066e89c10d33f7430378867b1d6"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "durationpy" version = "0.10" @@ -2483,6 +2856,8 @@ files = [ [package.dependencies] google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" +grpcio = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} +grpcio-status = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} proto-plus = ">=1.22.3,<2.0.0" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" requests = ">=2.18.0,<3.0.0" @@ -2555,6 +2930,103 @@ files = [ google-auth = "*" httplib2 = ">=0.19.0" +[[package]] +name = "google-cloud-access-context-manager" +version = "0.3.0" +description = "Google Cloud Access Context Manager Protobufs" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_cloud_access_context_manager-0.3.0-py3-none-any.whl", hash = "sha256:5d15ad51547f06c281e35f16b4ffcb3e98bb2d898b01470f88b94edfb2eeb0a3"}, + {file = "google_cloud_access_context_manager-0.3.0.tar.gz", hash = "sha256:f3aa35c9225b7aaef85ecdacedcc1577789be8d458b7a41b6ad23b504786e5f9"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[[package]] +name = "google-cloud-asset" +version = "4.1.0" +description = "Google Cloud Asset API client library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_cloud_asset-4.1.0-py3-none-any.whl", hash = "sha256:2ce567bf002ad5099e173f6106393a7abc4782598c5d0d27de7d86ac26736e06"}, + {file = "google_cloud_asset-4.1.0.tar.gz", hash = "sha256:00ba110085ff9f284b49961bcb9d2da5b5863fb91643c16d173ed38d73bfe35c"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +google-cloud-access-context-manager = ">=0.1.2,<1.0.0" +google-cloud-org-policy = ">=0.1.2,<2.0.0" +google-cloud-os-config = ">=1.0.0,<2.0.0" +grpc-google-iam-v1 = ">=0.14.0,<1.0.0" +grpcio = ">=1.33.2,<2.0.0" +proto-plus = ">=1.22.3,<2.0.0" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[[package]] +name = "google-cloud-org-policy" +version = "1.15.0" +description = "Google Cloud Org Policy API client library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_cloud_org_policy-1.15.0-py3-none-any.whl", hash = "sha256:5da410288236b334b8d05010501ea6180c5dc9e30888ff09488f2f107632f35b"}, + {file = "google_cloud_org_policy-1.15.0.tar.gz", hash = "sha256:271d16a10e75347eace60d02cde322b2b1b613bcc99917109e0ebf2a4102253a"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +grpcio = ">=1.33.2,<2.0.0" +proto-plus = ">=1.22.3,<2.0.0" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[[package]] +name = "google-cloud-os-config" +version = "1.22.0" +description = "Google Cloud Os Config API client library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_cloud_os_config-1.22.0-py3-none-any.whl", hash = "sha256:92afa402aa3b94d765751907fded1ef5908a6a5322f1fc88dee9e4c7f1cd7e54"}, + {file = "google_cloud_os_config-1.22.0.tar.gz", hash = "sha256:d79a310f6fa1ce7470aaa084c70e38dc05d98531f468f821b3a526e4d33a70e4"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +grpcio = ">=1.33.2,<2.0.0" +proto-plus = ">=1.22.3,<2.0.0" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[[package]] +name = "google-cloud-resource-manager" +version = "1.15.0" +description = "Google Cloud Resource Manager API client library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_cloud_resource_manager-1.15.0-py3-none-any.whl", hash = "sha256:0ccde5db644b269ddfdf7b407a2c7b60bdbf459f8e666344a5285601d00c7f6d"}, + {file = "google_cloud_resource_manager-1.15.0.tar.gz", hash = "sha256:3d0b78c3daa713f956d24e525b35e9e9a76d597c438837171304d431084cedaf"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +grpc-google-iam-v1 = ">=0.14.0,<1.0.0" +grpcio = ">=1.33.2,<2.0.0" +proto-plus = ">=1.22.3,<2.0.0" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -2568,6 +3040,7 @@ files = [ ] [package.dependencies] +grpcio = {version = ">=1.44.0,<2.0.0", optional = true, markers = "extra == \"grpc\""} protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" [package.extras] @@ -2601,6 +3074,117 @@ files = [ dev = ["pytest"] docs = ["sphinx", "sphinx-autobuild"] +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.3" +description = "IAM API client library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6"}, + {file = "grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389"}, +] + +[package.dependencies] +googleapis-common-protos = {version = ">=1.56.0,<2.0.0", extras = ["grpc"]} +grpcio = ">=1.44.0,<2.0.0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[[package]] +name = "grpcio" +version = "1.76.0" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc"}, + {file = "grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3"}, + {file = "grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b"}, + {file = "grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b"}, + {file = "grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a"}, + {file = "grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00"}, + {file = "grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054"}, + {file = "grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d"}, + {file = "grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8"}, + {file = "grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882"}, + {file = "grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958"}, + {file = "grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347"}, + {file = "grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2"}, + {file = "grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42"}, + {file = "grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f"}, + {file = "grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8"}, + {file = "grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62"}, + {file = "grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc"}, + {file = "grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e"}, + {file = "grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e"}, + {file = "grpcio-1.76.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783"}, + {file = "grpcio-1.76.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886"}, + {file = "grpcio-1.76.0-cp39-cp39-win32.whl", hash = "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f"}, + {file = "grpcio-1.76.0-cp39-cp39-win_amd64.whl", hash = "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a"}, + {file = "grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73"}, +] + +[package.dependencies] +typing-extensions = ">=4.12,<5.0" + +[package.extras] +protobuf = ["grpcio-tools (>=1.76.0)"] + +[[package]] +name = "grpcio-status" +version = "1.76.0" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18"}, + {file = "grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.76.0" +protobuf = ">=6.31.1,<7.0.0" + [[package]] name = "gunicorn" version = "23.0.0" @@ -2726,6 +3310,21 @@ http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "humanfriendly" +version = "10.0" +description = "Human friendly output for text interfaces using Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] + +[package.dependencies] +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} + [[package]] name = "hyperframe" version = "6.1.0" @@ -2969,6 +3568,25 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "jsonpickle" +version = "4.1.1" +description = "jsonpickle encodes/decodes any Python object to/from JSON" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jsonpickle-4.1.1-py3-none-any.whl", hash = "sha256:bb141da6057898aa2438ff268362b126826c812a1721e31cf08a6e142910dc91"}, + {file = "jsonpickle-4.1.1.tar.gz", hash = "sha256:f86e18f13e2b96c1c1eede0b7b90095bbb61d99fedc14813c44dc2f361dbbae1"}, +] + +[package.extras] +cov = ["pytest-cov"] +dev = ["black", "pyupgrade"] +docs = ["furo", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +packaging = ["build", "setuptools (>=61.2)", "setuptools_scm[toml] (>=6.0)", "twine"] +testing = ["PyYAML", "atheris (>=2.3.0,<2.4.0) ; python_version < \"3.12\"", "bson", "ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=6.0,!=8.1.*)", "pytest-benchmark", "pytest-benchmark[histogram]", "pytest-checkdocs (>=1.2.3)", "pytest-enabler (>=1.0.1)", "pytest-ruff (>=0.2.1)", "scikit-learn", "scipy (>=1.9.3) ; python_version > \"3.10\"", "scipy ; python_version <= \"3.10\"", "simplejson", "sqlalchemy", "ujson"] + [[package]] name = "jsonschema" version = "4.23.0" @@ -3117,6 +3735,26 @@ files = [ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] +[[package]] +name = "knack" +version = "0.11.0" +description = "A Command-Line Interface framework" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "knack-0.11.0-py3-none-any.whl", hash = "sha256:6704c867840978a119a193914a90e2e98c7be7dff764c8fcd8a2286c5a978d00"}, + {file = "knack-0.11.0.tar.gz", hash = "sha256:eb6568001e9110b1b320941431c51033d104cc98cda2254a5c2b09ba569fd494"}, +] + +[package.dependencies] +argcomplete = "*" +jmespath = "*" +packaging = "*" +pygments = "*" +pyyaml = "*" +tabulate = "*" + [[package]] name = "kombu" version = "5.5.4" @@ -3358,7 +3996,7 @@ version = "4.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, @@ -3453,7 +4091,7 @@ version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, @@ -3564,7 +4202,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -3684,21 +4322,38 @@ files = [ [package.dependencies] microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" +[[package]] +name = "microsoft-security-utilities-secret-masker" +version = "1.0.0b4" +description = "A tool for detecting and masking secrets" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "microsoft_security_utilities_secret_masker-1.0.0b4-py3-none-any.whl", hash = "sha256:0429fcaad10fc8ae3f940ab84fd2926e4f50ede134162144123b35937be831a8"}, + {file = "microsoft_security_utilities_secret_masker-1.0.0b4.tar.gz", hash = "sha256:a30bd361ac18c8b52f6844076bc26465335949ea9c7a004d95f5196ec6fdef3e"}, +] + [[package]] name = "msal" -version = "1.33.0" +version = "1.34.0b1" description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "msal-1.33.0-py3-none-any.whl", hash = "sha256:c0cd41cecf8eaed733ee7e3be9e040291eba53b0f262d3ae9c58f38b04244273"}, - {file = "msal-1.33.0.tar.gz", hash = "sha256:836ad80faa3e25a7d71015c990ce61f704a87328b1e73bcbb0623a18cbf17510"}, + {file = "msal-1.34.0b1-py3-none-any.whl", hash = "sha256:3b6373325e3509d97873e36965a75e9cc9393f1b579d12cc03c0ca0ef6d37eb4"}, + {file = "msal-1.34.0b1.tar.gz", hash = "sha256:86cdbfec14955e803379499d017056c6df4ed40f717fd6addde94bdeb4babd78"}, ] [package.dependencies] cryptography = ">=2.5,<48" PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} +pymsalruntime = [ + {version = ">=0.14,<0.19", optional = true, markers = "python_version >= \"3.6\" and platform_system == \"Windows\" and extra == \"broker\""}, + {version = ">=0.17,<0.19", optional = true, markers = "python_version >= \"3.8\" and platform_system == \"Darwin\" and extra == \"broker\""}, + {version = ">=0.18,<0.19", optional = true, markers = "python_version >= \"3.8\" and platform_system == \"Linux\" and extra == \"broker\""}, +] requests = ">=2.0.0,<3" [package.extras] @@ -3706,21 +4361,19 @@ broker = ["pymsalruntime (>=0.14,<0.19) ; python_version >= \"3.6\" and platform [[package]] name = "msal-extensions" -version = "1.3.1" +version = "1.2.0" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, - {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, + {file = "msal_extensions-1.2.0-py3-none-any.whl", hash = "sha256:cf5ba83a2113fa6dc011a254a72f1c223c88d7dfad74cc30617c4679a417704d"}, + {file = "msal_extensions-1.2.0.tar.gz", hash = "sha256:6f41b320bfd2933d631a215c91ca0dd3e67d84bd1a2f50ce917d5874ec646bef"}, ] [package.dependencies] msal = ">=1.29,<2" - -[package.extras] -portalocker = ["portalocker (>=1.4,<4)"] +portalocker = ">=1.4,<3" [[package]] name = "msgraph-core" @@ -3788,6 +4441,23 @@ requests-oauthlib = ">=0.5.0" [package.extras] async = ["aiodns ; python_version >= \"3.5\"", "aiohttp (>=3.0) ; python_version >= \"3.5\""] +[[package]] +name = "msrestazure" +version = "0.6.4.post1" +description = "AutoRest swagger generator Python client runtime. Azure-specific module." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "msrestazure-0.6.4.post1-py2.py3-none-any.whl", hash = "sha256:2264493b086c2a0a82ddf5fd87b35b3fffc443819127fed992ac5028354c151e"}, + {file = "msrestazure-0.6.4.post1.tar.gz", hash = "sha256:39842007569e8c77885ace5c46e4bf2a9108fcb09b1e6efdf85b6e2c642b55d4"}, +] + +[package.dependencies] +adal = ">=0.6.0,<2.0.0" +msrest = ">=0.6.0,<2.0.0" +six = "*" + [[package]] name = "multidict" version = "6.6.4" @@ -4119,6 +4789,22 @@ pytz = ">=2016.10" [package.extras] adk = ["docstring-parser (>=0.16) ; python_version >= \"3.10\" and python_version < \"4\"", "mcp (>=1.6.0) ; python_version >= \"3.10\" and python_version < \"4\"", "pydantic (>=2.10.6) ; python_version >= \"3.10\" and python_version < \"4\"", "rich (>=13.9.4) ; python_version >= \"3.10\" and python_version < \"4\""] +[[package]] +name = "okta" +version = "0.0.4" +description = "Okta client APIs" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "okta-0.0.4.tar.gz", hash = "sha256:53e792c68d3684ff4140b4cb1c02af3821090368f8110fde54c0bdb638449332"}, +] + +[package.dependencies] +python-dateutil = ">=2.4.2" +requests = ">=2.5.3" +six = ">=1.9.0" + [[package]] name = "openai" version = "1.101.0" @@ -4309,6 +4995,22 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "pdpyras" +version = "5.4.1" +description = "PagerDuty Python REST API Sessions." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "pdpyras-5.4.1-py2.py3-none-any.whl", hash = "sha256:e16020cf57e4c916ab3dace7c7dffe21a2e7059ab7411ce3ddf1e620c54e9c89"}, + {file = "pdpyras-5.4.1.tar.gz", hash = "sha256:36021aff5979a79f1d87edc95e0c46e98ce8549292bc0cab3d9f33501795703b"}, +] + +[package.dependencies] +requests = "*" +urllib3 = "*" + [[package]] name = "pillow" version = "11.3.0" @@ -4434,6 +5136,21 @@ tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "ole typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] +[[package]] +name = "pkginfo" +version = "1.12.1.2" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, + {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + [[package]] name = "platformdirs" version = "4.3.8" @@ -4491,6 +5208,42 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "policyuniverse" +version = "1.5.1.20231109" +description = "Parse and Process AWS IAM Policies, Statements, ARNs, and wildcards." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "policyuniverse-1.5.1.20231109-py2.py3-none-any.whl", hash = "sha256:0b0ece0ee8285af31fc39ce09c82a551ca62e62bc2842e23952503bccb973321"}, + {file = "policyuniverse-1.5.1.20231109.tar.gz", hash = "sha256:74e56d410560915c2c5132e361b0130e4bffe312a2f45230eac50d7c094bc40a"}, +] + +[package.extras] +dev = ["black", "pre-commit"] +tests = ["bandit", "coveralls", "pytest"] + +[[package]] +name = "portalocker" +version = "2.10.1" +description = "Wraps the portalocker recipe for easy usage" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf"}, + {file = "portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] + [[package]] name = "prompt-toolkit" version = "3.0.51" @@ -4653,7 +5406,7 @@ files = [ [[package]] name = "prowler" -version = "5.14.0" +version = "5.15.0" description = "Prowler is an Open Source security tool to perform AWS, GCP and Azure security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness. It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme) and your custom security frameworks." optional = false python-versions = ">3.9.1,<3.13" @@ -4691,8 +5444,8 @@ azure-mgmt-subscription = "3.1.1" azure-mgmt-web = "8.0.0" azure-monitor-query = "2.0.0" azure-storage-blob = "12.24.1" -boto3 = "1.39.15" -botocore = "1.39.15" +boto3 = "1.40.61" +botocore = "1.40.61" colorama = "0.4.6" cryptography = "44.0.1" dash = "3.1.1" @@ -4718,15 +5471,15 @@ python-dateutil = ">=2.9.0.post0,<3.0.0" pytz = "2025.1" schema = "0.7.5" shodan = "1.31.0" -slack-sdk = "3.34.0" +slack-sdk = "3.39.0" tabulate = "0.9.0" tzlocal = "5.3.1" [package.source] type = "git" url = "https://github.com/prowler-cloud/prowler.git" -reference = "master" -resolved_reference = "de5aba6d4db54eed4c95cb7629443da186c17afd" +reference = "PROWLER-510-update-cartography-dependency" +resolved_reference = "221f6afbcf625e4b6334919f165a1419a1e7d5d4" [[package]] name = "psutil" @@ -4840,6 +5593,18 @@ files = [ {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] +[[package]] +name = "py-deviceid" +version = "0.1.1" +description = "A simple library to get or create a unique device id for a device in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "py_deviceid-0.1.1-py3-none-any.whl", hash = "sha256:c0e32815e87a08087a0811c18f4402ee88b28a321f997753d75ecdaab570321b"}, + {file = "py_deviceid-0.1.1.tar.gz", hash = "sha256:c3e7577ada23666e7f39e69370dfdaa76fe9de79c02635376d6aa0229bfa30e3"}, +] + [[package]] name = "py-iam-expand" version = "0.1.0" @@ -5126,7 +5891,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -5184,6 +5949,47 @@ tomlkit = ">=0.10.1" spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] +[[package]] +name = "pymsalruntime" +version = "0.18.1" +description = "The MSALRuntime Python Interop Package" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "sys_platform == \"win32\" and (platform_system == \"Windows\" or platform_system == \"Darwin\" or platform_system == \"Linux\")" +files = [ + {file = "pymsalruntime-0.18.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0c22e2e83faa10de422bbfaacc1bb2887c9025ee8a53f0fc2e4f7db01c4a7b66"}, + {file = "pymsalruntime-0.18.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8ce2944a0f944833d047bb121396091e00287e2b6373716106da86ea99abf379"}, + {file = "pymsalruntime-0.18.1-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:9f7945ae0ee78357e9ca87d381f1c19763629a7197391ae7f84f4967a9f06e5b"}, + {file = "pymsalruntime-0.18.1-cp310-cp310-win32.whl", hash = "sha256:10020abdfc34bbbf3414b86359de551d2d8bc7c241bc38c59a2468c4d49f21d5"}, + {file = "pymsalruntime-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:f9aec2f44470d71feae35b611d1d8f15a549d96446e4f60e1ca1fb71856fffed"}, + {file = "pymsalruntime-0.18.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e9320fb187fe1298d2165fa248af00907ca15d3a903a1d35fed86f6bc20b5880"}, + {file = "pymsalruntime-0.18.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9b2cecf3a570b7812d2007764df6dfbc27fca401a0d74532d5403aa20a9ef380"}, + {file = "pymsalruntime-0.18.1-cp311-cp311-manylinux_2_35_x86_64.whl", hash = "sha256:6f66fd99668abc3d4b8d93a9eb80c75178dc63186c79e6dbe133427b279835e0"}, + {file = "pymsalruntime-0.18.1-cp311-cp311-win32.whl", hash = "sha256:74416947b1071054f3258cac3448a7adf708888727bf283267df2bb27f0998f1"}, + {file = "pymsalruntime-0.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:beb926655aae3367b7e4bda2baad86f9271beefee1121f71642da0ed4de37fd2"}, + {file = "pymsalruntime-0.18.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6c07651cf4e07690d1b022da0977f56820ef553ac6dcbf4c9e68e9611020997"}, + {file = "pymsalruntime-0.18.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0b6c4f54ec13309cc7b717ac8760c2d9856d4924cefa2b794b6d03db4cfdeef8"}, + {file = "pymsalruntime-0.18.1-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:06c73a47f024fcf36006b89fe32f2f6f6a004aa661cf8a03d3e496d1ef84cfe8"}, + {file = "pymsalruntime-0.18.1-cp312-cp312-win32.whl", hash = "sha256:ace12bf9b7fcbf1bf21a03c227717e09ba99acd9190623fe0821a08832ece4eb"}, + {file = "pymsalruntime-0.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:f9fd8ea52395f52f7d62498e47754adf2bfe6530816ff57eff1ba6f524aee51b"}, + {file = "pymsalruntime-0.18.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:047a98b6709cddf6a1f50f78ee16d06fea0f42a44971b6d3e2988537277a1a17"}, + {file = "pymsalruntime-0.18.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:910e653c65cd66fa9ce46dec103d3948da2276f7d4d315631a145eaab968d9a8"}, + {file = "pymsalruntime-0.18.1-cp313-cp313-manylinux_2_35_x86_64.whl", hash = "sha256:7ae0b160983ea0715d8ac69b441bbd29e7a9f31c9a5a2c350c79a794f5599f38"}, + {file = "pymsalruntime-0.18.1-cp313-cp313-win32.whl", hash = "sha256:adf4200a1b423fe5d8e984c142cc64f0b76a9b0f7f8ff767490a2dde94fa642b"}, + {file = "pymsalruntime-0.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:5a759aa551d084b160799f6df59c9891898ab305eb75ff1705bf04281675eb4b"}, + {file = "pymsalruntime-0.18.1-cp38-cp38-macosx_14_0_arm64.whl", hash = "sha256:12b8990c4da1327ea46f6271bd57b28a90d3e795deacb370052914c3ff40d4c5"}, + {file = "pymsalruntime-0.18.1-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:8dd68f9fedc200950093378b30a2ade4517324cef060788a759b575ea58dc6b2"}, + {file = "pymsalruntime-0.18.1-cp38-cp38-manylinux_2_35_x86_64.whl", hash = "sha256:7183b1b1542a277db119fe55285c7609c661b8506b99cd7e53b7066ce6b838e4"}, + {file = "pymsalruntime-0.18.1-cp38-cp38-win32.whl", hash = "sha256:56c3d708ba86311f049b004de81aa97655fed82782d3ec67e14ae1e27d4f5e5b"}, + {file = "pymsalruntime-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:a8adc80fcf723b980976b81a0b409affe80f32d89ae6096d856fd20471d2f0c1"}, + {file = "pymsalruntime-0.18.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:600d0f2b9b03dfb457ee1e13f191c2c217c0f6bceca512f1741e5215bc4bc5dc"}, + {file = "pymsalruntime-0.18.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:daae8515ae8adac8662d8230f22af242f87c72d86f308ec51b7432f316199c1b"}, + {file = "pymsalruntime-0.18.1-cp39-cp39-manylinux_2_35_x86_64.whl", hash = "sha256:864b8b9555a180c6baf8a57df3976b2e511582d54099561fbfe73f9f0b95c9f5"}, + {file = "pymsalruntime-0.18.1-cp39-cp39-win32.whl", hash = "sha256:b90a3c8079ded9d5abc765bd90fdc34f6e49412793740ddbc6122a601008d50f"}, + {file = "pymsalruntime-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:852dc82b3eaad0cce2c583314705183bf216e7fa7178040defd3a13195c1c406"}, +] + [[package]] name = "pynacl" version = "1.5.0" @@ -5245,6 +6051,35 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyreadline3" +version = "3.5.4" +description = "A python implementation of GNU readline." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, + {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, +] + +[package.extras] +dev = ["build", "flake8", "mypy", "pytest", "twine"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + [[package]] name = "pytest" version = "8.2.2" @@ -5438,6 +6273,22 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-digitalocean" +version = "1.17.0" +description = "digitalocean.com API to manage Droplets and Images" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "python-digitalocean-1.17.0.tar.gz", hash = "sha256:107854fde1aafa21774e8053cf253b04173613c94531f75d5a039ad770562b24"}, + {file = "python_digitalocean-1.17.0-py3-none-any.whl", hash = "sha256:0032168e022e85fca314eb3f8dfaabf82087f2ed40839eb28f1eeeeca5afb1fa"}, +] + +[package.dependencies] +jsonpickle = "*" +requests = "*" + [[package]] name = "python-memcached" version = "1.62" @@ -5490,7 +6341,6 @@ description = "Python for Window Extensions" optional = false python-versions = "*" groups = ["main", "dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, @@ -5513,6 +6363,7 @@ files = [ {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, ] +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} [[package]] name = "pyyaml" @@ -5653,6 +6504,7 @@ files = [ certifi = ">=2017.4.17" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" +PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7", optional = true, markers = "extra == \"socks\""} urllib3 = ">=1.21.1,<3" [package.extras] @@ -5711,7 +6563,7 @@ version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, @@ -6010,14 +6862,14 @@ files = [ [[package]] name = "s3transfer" -version = "0.13.1" +version = "0.14.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724"}, - {file = "s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf"}, + {file = "s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456"}, + {file = "s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125"}, ] [package.dependencies] @@ -6081,6 +6933,38 @@ pydantic = "*" ruamel-yaml = ">=0.17.21" typing-extensions = ">=4.7.1" +[[package]] +name = "scaleway" +version = "2.10.3" +description = "Scaleway SDK for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "scaleway-2.10.3-py3-none-any.whl", hash = "sha256:dbf381440d6caf37c878cf16445a63f4969a4aac2257c9b72c744d10ff223a0c"}, + {file = "scaleway-2.10.3.tar.gz", hash = "sha256:b1f9dd1b1450767205234c6f5a345e5e25dc039c780253d698893b5c344ce594"}, +] + +[package.dependencies] +scaleway-core = "2.10.3" + +[[package]] +name = "scaleway-core" +version = "2.10.3" +description = "Scaleway SDK for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "scaleway_core-2.10.3-py3-none-any.whl", hash = "sha256:fd4112144554d6adae22ff737555eeb0e38cb1063250b3e88c9aebc1b957793b"}, + {file = "scaleway_core-2.10.3.tar.gz", hash = "sha256:56432f755d694669429de51d51c1d0b3361b28dc2f939b28e4cb954610ee76be"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.2,<3.0.0" +PyYAML = ">=6.0,<7.0" +requests = ">=2.28.1,<3.0.0" + [[package]] name = "schema" version = "0.7.5" @@ -6181,7 +7065,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -6220,18 +7104,18 @@ files = [ [[package]] name = "slack-sdk" -version = "3.34.0" +version = "3.39.0" description = "The Slack API Platform SDK for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "slack_sdk-3.34.0-py2.py3-none-any.whl", hash = "sha256:c61f57f310d85be83466db5a98ab6ae3bb2e5587437b54fa0daa8fae6a0feffa"}, - {file = "slack_sdk-3.34.0.tar.gz", hash = "sha256:ff61db7012160eed742285ea91f11c72b7a38a6500a7f6c5335662b4bc6b853d"}, + {file = "slack_sdk-3.39.0-py2.py3-none-any.whl", hash = "sha256:b1556b2f5b8b12b94e5ea3f56c4f2c7f04462e4e1013d325c5764ff118044fa8"}, + {file = "slack_sdk-3.39.0.tar.gz", hash = "sha256:6a56be10dc155c436ff658c6b776e1c082e29eae6a771fccf8b0a235822bbcb1"}, ] [package.extras] -optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<15)"] +optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<16)"] [[package]] name = "sniffio" @@ -6261,6 +7145,18 @@ files = [ dev = ["build", "hatch"] doc = ["sphinx"] +[[package]] +name = "statsd" +version = "4.0.1" +description = "A simple statsd client." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "statsd-4.0.1-py2.py3-none-any.whl", hash = "sha256:c2676519927f7afade3723aca9ca8ea986ef5b059556a980a867721ca69df093"}, + {file = "statsd-4.0.1.tar.gz", hash = "sha256:99763da81bfea8daf6b3d22d11aaccb01a8d0f52ea521daab37e758a4ca7d128"}, +] + [[package]] name = "std-uritemplate" version = "2.0.5" @@ -6381,7 +7277,7 @@ version = "0.16.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9"}, {file = "typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614"}, @@ -6393,6 +7289,21 @@ rich = ">=10.11.0" shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" +[[package]] +name = "types-aiobotocore-ecr" +version = "3.0.0" +description = "Type annotations for aiobotocore ECR 3.0.0 service generated with mypy-boto3-builder 8.12.0" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "types_aiobotocore_ecr-3.0.0-py3-none-any.whl", hash = "sha256:06288369b9ddf78661224ac99a61aefe3c8a49e872d5c7a1626435ea848a817e"}, + {file = "types_aiobotocore_ecr-3.0.0.tar.gz", hash = "sha256:a9f49aa3c83c6b6ab1cc7f10cc887ca35a549e0a29dfcdab40b285ce0846d06c"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.12\""} + [[package]] name = "typing-extensions" version = "4.14.1" @@ -6738,6 +7649,21 @@ files = [ [package.dependencies] lxml = ">=3.8" +[[package]] +name = "xmltodict" +version = "1.0.2" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d"}, + {file = "xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649"}, +] + +[package.extras] +test = ["pytest", "pytest-cov"] + [[package]] name = "yarl" version = "1.20.1" @@ -6880,4 +7806,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "18f0a242102d6583c645399c742040e4a324c08c0d16a76bf843700f8ce77f84" +content-hash = "1c2a75921edf35350cd01ff76bc4d4316ffd4f4aba8d02b4454013d8b70a730d" diff --git a/api/pyproject.toml b/api/pyproject.toml index e112745377..cdcf59834a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "drf-spectacular-jsonapi==0.5.1", "gunicorn==23.0.0", "lxml==5.3.2", - "prowler @ git+https://github.com/prowler-cloud/prowler.git@master", + "prowler @ git+https://github.com/prowler-cloud/prowler.git@PROWLER-510-update-cartography-dependency", "psycopg2-binary==2.9.9", "pytest-celery[redis] (>=1.0.1,<2.0.0)", "sentry-sdk[django] (>=2.20.0,<3.0.0)", @@ -37,6 +37,7 @@ dependencies = [ "matplotlib (>=3.10.6,<4.0.0)", "reportlab (>=4.4.4,<5.0.0)", "neo4j (<6.0.0)", + "cartography @ git+https://github.com/prowler-cloud/cartography@azure-mgmt-sql-3", ] description = "Prowler's API (Django/DRF)" license = "Apache-2.0" diff --git a/api/src/backend/api/attack_paths/database.py b/api/src/backend/api/attack_paths/database.py index badb2f911e..eaa9da3713 100644 --- a/api/src/backend/api/attack_paths/database.py +++ b/api/src/backend/api/attack_paths/database.py @@ -42,10 +42,7 @@ def init_driver() -> neo4j.Driver: config = settings.DATABASES["neo4j"] _driver = neo4j.GraphDatabase.driver( - uri, - auth=(config["USER"], config["PASSWORD"]), - max_connection_lifetime=604800, # 7 days - keep_alive=True, # It's the default value, but let's being explicit + uri, auth=(config["USER"], config["PASSWORD"]) ) _driver.verify_connectivity() diff --git a/api/src/backend/tasks/jobs/attack_paths/aws.py b/api/src/backend/tasks/jobs/attack_paths/aws.py index 66b132f305..b89524a2de 100644 --- a/api/src/backend/tasks/jobs/attack_paths/aws.py +++ b/api/src/backend/tasks/jobs/attack_paths/aws.py @@ -3,6 +3,7 @@ from typing import Any +import aioboto3 import boto3 import neo4j @@ -28,7 +29,7 @@ def start_aws_ingestion( attack_paths_scan: ProwlerAPIAttackPathsScan, ) -> dict[str, dict[str, str]]: """ - Code based on Cartography version 0.117.0, specifically on `cartography.intel.aws.__init__.py`. + Code based on Cartography version 0.122.0, specifically on `cartography.intel.aws.__init__.py`. For the scan progress updates: - The caller of this function (`tasks.jobs.attack_paths.scan.run`) has set it to 2. @@ -41,6 +42,7 @@ def start_aws_ingestion( "permission_relationships_file": cartography_config.permission_relationships_file, "aws_guardduty_severity_threshold": cartography_config.aws_guardduty_severity_threshold, "aws_cloudtrail_management_events_lookback_hours": cartography_config.aws_cloudtrail_management_events_lookback_hours, + "experimental_aws_inspector_batch": cartography_config.experimental_aws_inspector_batch, } boto3_session = get_boto3_session(prowler_api_provider, prowler_sdk_provider) @@ -157,6 +159,10 @@ def get_boto3_session( return boto3_session +def get_aioboto3_session(boto3_session: boto3.Session) -> aioboto3.Session: + return aioboto3.Session(botocore_session=boto3_session._session) + + def sync_aws_account( prowler_api_provider: ProwlerAPIProvider, requested_syncs: list[str], @@ -167,7 +173,7 @@ def sync_aws_account( max_progress = ( 87 # `cartography_aws.RESOURCE_FUNCTIONS["permission_relationships"]` - 1 ) - n_steps = len(requested_syncs) + n_steps = len(requested_syncs) - 2 # Excluding `permission_relationships` and `resourcegroupstaggingapi` progress_step = (max_progress - current_progress) / n_steps failed_syncs = {} @@ -185,15 +191,26 @@ def sync_aws_account( ) try: + # `ecr:image_layers` uses `aioboto3_session` instead of `boto3_session` + if func_name == "ecr:image_layers": + cartography_aws.RESOURCE_FUNCTIONS[func_name]( + neo4j_session=sync_args.get("neo4j_session"), + aioboto3_session=get_aioboto3_session(sync_args.get("boto3_session")), + regions=sync_args.get("regions"), + current_aws_account_id=sync_args.get("current_aws_account_id"), + update_tag=sync_args.get("update_tag"), + common_job_parameters=sync_args.get("common_job_parameters"), + ) + # Skip permission relationships and tags for now because they rely on data already being in the graph - if func_name not in [ + elif func_name in [ "permission_relationships", "resourcegroupstaggingapi", ]: - cartography_aws.RESOURCE_FUNCTIONS[func_name](**sync_args) + continue else: - continue + cartography_aws.RESOURCE_FUNCTIONS[func_name](**sync_args) except Exception as e: exception_message = utils.stringify_exception( diff --git a/api/src/backend/tasks/jobs/attack_paths/prowler.py b/api/src/backend/tasks/jobs/attack_paths/prowler.py index 366fe2da27..8678045811 100644 --- a/api/src/backend/tasks/jobs/attack_paths/prowler.py +++ b/api/src/backend/tasks/jobs/attack_paths/prowler.py @@ -80,7 +80,7 @@ CLEANUP_STATEMENT = """ def create_indexes(neo4j_session: neo4j.Session) -> None: """ - Code based on Cartography version 0.117.0, specifically on `cartography.intel.create_indexes.run`. + Code based on Cartography version 0.122.0, specifically on `cartography.intel.create_indexes.run`. """ logger.info("Creating indexes for Prowler node types.") diff --git a/api/src/backend/tasks/jobs/attack_paths/scan.py b/api/src/backend/tasks/jobs/attack_paths/scan.py index 0538c3f149..aa7bb7b756 100644 --- a/api/src/backend/tasks/jobs/attack_paths/scan.py +++ b/api/src/backend/tasks/jobs/attack_paths/scan.py @@ -7,6 +7,7 @@ from typing import Any, Callable from cartography.config import Config as CartographyConfig from cartography.intel import analysis as cartography_analysis from cartography.intel import create_indexes as cartography_create_indexes +from cartography.intel import ontology as cartography_ontology from celery.utils.log import get_task_logger from api.attack_paths import database as graph_database @@ -35,7 +36,7 @@ def get_cartography_ingestion_function(provider_type: str) -> Callable | None: def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: """ - Code based on Cartography version 0.117.0, specifically on `cartography.cli.main`, `cartography.cli.CLI.main`, + Code based on Cartography version 0.122.0, specifically on `cartography.cli.main`, `cartography.cli.CLI.main`, `cartography.sync.run_with_config` and `cartography.sync.Sync.run`. """ ingestion_exceptions = {} # This will hold any exceptions raised during ingestion @@ -100,9 +101,12 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: ) # Post-processing: Just keeping it to be more Cartography compliant - cartography_analysis.run(neo4j_session, cartography_config) + cartography_ontology.run(neo4j_session, cartography_config) db_utils.update_attack_paths_scan_progress(attack_paths_scan, 95) + cartography_analysis.run(neo4j_session, cartography_config) + db_utils.update_attack_paths_scan_progress(attack_paths_scan, 96) + # Adding Prowler nodes and relationships prowler.analysis( neo4j_session, prowler_api_provider, scan_id, cartography_config diff --git a/poetry.lock b/poetry.lock index ffcdeda73e..84635d36be 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "about-time" @@ -938,34 +938,34 @@ files = [ [[package]] name = "boto3" -version = "1.39.15" +version = "1.40.61" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "boto3-1.39.15-py3-none-any.whl", hash = "sha256:38fc54576b925af0075636752de9974e172c8a2cf7133400e3e09b150d20fb6a"}, - {file = "boto3-1.39.15.tar.gz", hash = "sha256:b4483625f0d8c35045254dee46cd3c851bbc0450814f20b9b25bee1b5c0d8409"}, + {file = "boto3-1.40.61-py3-none-any.whl", hash = "sha256:6b9c57b2a922b5d8c17766e29ed792586a818098efe84def27c8f582b33f898c"}, + {file = "boto3-1.40.61.tar.gz", hash = "sha256:d6c56277251adf6c2bdd25249feae625abe4966831676689ff23b4694dea5b12"}, ] [package.dependencies] -botocore = ">=1.39.15,<1.40.0" +botocore = ">=1.40.61,<1.41.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.13.0,<0.14.0" +s3transfer = ">=0.14.0,<0.15.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.39.15" +version = "1.40.61" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "botocore-1.39.15-py3-none-any.whl", hash = "sha256:eb9cfe918ebfbfb8654e1b153b29f0c129d586d2c0d7fb4032731d49baf04cff"}, - {file = "botocore-1.39.15.tar.gz", hash = "sha256:2aa29a717f14f8c7ca058c2e297aaed0aa10ecea24b91514eee802814d1b7600"}, + {file = "botocore-1.40.61-py3-none-any.whl", hash = "sha256:17ebae412692fd4824f99cde0f08d50126dc97954008e5ba2b522eb049238aa7"}, + {file = "botocore-1.40.61.tar.gz", hash = "sha256:a2487ad69b090f9cccd64cf07c7021cd80ee9c0655ad974f87045b02f3ef52cd"}, ] [package.dependencies] @@ -977,7 +977,7 @@ urllib3 = [ ] [package.extras] -crt = ["awscrt (==0.23.8)"] +crt = ["awscrt (==0.27.6)"] [[package]] name = "cachetools" @@ -2366,6 +2366,8 @@ python-versions = "*" groups = ["dev"] files = [ {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, ] [package.dependencies] @@ -4884,6 +4886,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -4892,6 +4895,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -4900,6 +4904,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -4908,6 +4913,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -4916,6 +4922,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -4923,14 +4930,14 @@ files = [ [[package]] name = "s3transfer" -version = "0.13.1" +version = "0.14.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724"}, - {file = "s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf"}, + {file = "s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456"}, + {file = "s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125"}, ] [package.dependencies] @@ -5075,18 +5082,18 @@ files = [ [[package]] name = "slack-sdk" -version = "3.34.0" +version = "3.39.0" description = "The Slack API Platform SDK for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "slack_sdk-3.34.0-py2.py3-none-any.whl", hash = "sha256:c61f57f310d85be83466db5a98ab6ae3bb2e5587437b54fa0daa8fae6a0feffa"}, - {file = "slack_sdk-3.34.0.tar.gz", hash = "sha256:ff61db7012160eed742285ea91f11c72b7a38a6500a7f6c5335662b4bc6b853d"}, + {file = "slack_sdk-3.39.0-py2.py3-none-any.whl", hash = "sha256:b1556b2f5b8b12b94e5ea3f56c4f2c7f04462e4e1013d325c5764ff118044fa8"}, + {file = "slack_sdk-3.39.0.tar.gz", hash = "sha256:6a56be10dc155c436ff658c6b776e1c082e29eae6a771fccf8b0a235822bbcb1"}, ] [package.extras] -optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<15)"] +optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<16)"] [[package]] name = "sniffio" @@ -5688,4 +5695,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">3.9.1,<3.13" -content-hash = "a367e65bc43c0a16495a3d0f6eab8b356cc49b509e329b61c6641cd87f374ff4" +content-hash = "82015f7b4b08e419ac5d28eab1a2d4b563b1980c84679e020ed3d42d3b4e9b85" diff --git a/pyproject.toml b/pyproject.toml index 56aeb194e1..fe3e130d1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,8 +40,8 @@ dependencies = [ "azure-mgmt-loganalytics==12.0.0", "azure-monitor-query==2.0.0", "azure-storage-blob==12.24.1", - "boto3==1.39.15", - "botocore==1.39.15", + "boto3==1.40.61", + "botocore==1.40.61", "colorama==0.4.6", "cryptography==44.0.1", "dash==3.1.1", @@ -64,7 +64,7 @@ dependencies = [ "pytz==2025.1", "schema==0.7.5", "shodan==1.31.0", - "slack-sdk==3.34.0", + "slack-sdk==3.39.0", "tabulate==0.9.0", "tzlocal==5.3.1", "py-iam-expand==0.1.0", From e95be697ef5f2e55fb1be70893c7c64837750025 Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Wed, 14 Jan 2026 16:19:02 +0100 Subject: [PATCH 4/6] Prowler 511 leaving one database per scan (#9795) --- .env | 1 + api/poetry.lock | 16 ++++---- api/pyproject.toml | 4 +- .../backend/tasks/jobs/attack_paths/aws.py | 8 +++- .../backend/tasks/jobs/attack_paths/scan.py | 40 +++++++++++++------ .../tasks/tests/test_attack_paths_scan.py | 4 ++ docker-compose-dev.yml | 21 +++++----- docker-compose.yml | 21 +++++----- 8 files changed, 71 insertions(+), 44 deletions(-) diff --git a/.env b/.env index 3d9b1fdcbc..1fbf3a630a 100644 --- a/.env +++ b/.env @@ -47,6 +47,7 @@ NEO4J_PORT=7687 NEO4J_USER=neo4j NEO4J_PASSWORD=neo4j_password # Neo4j settings +NEO4J_DBMS_MAX__DATABASES=1000000 NEO4J_SERVER_MEMORY_PAGECACHE_SIZE=1G NEO4J_SERVER_MEMORY_HEAP_INITIAL__SIZE=1G NEO4J_SERVER_MEMORY_HEAP_MAX__SIZE=1G diff --git a/api/poetry.lock b/api/poetry.lock index 88558a76d4..4bf8a3ddae 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1194,7 +1194,7 @@ files = [ [[package]] name = "cartography" -version = "0.0.1.dev1267+g7c7d6d501" +version = "0.0.1.dev1268+gc134846c0" description = "Explore assets and their relationships across your technical infrastructure." optional = false python-versions = ">=3.10" @@ -1258,8 +1258,8 @@ xmltodict = "*" [package.source] type = "git" url = "https://github.com/prowler-cloud/cartography" -reference = "azure-mgmt-sql-3" -resolved_reference = "7c7d6d5014bf079066ee1645e9517c67fb36fe67" +reference = "master" +resolved_reference = "c134846c0db64747340f880cf0b5085f5e473e03" [[package]] name = "celery" @@ -5478,8 +5478,8 @@ tzlocal = "5.3.1" [package.source] type = "git" url = "https://github.com/prowler-cloud/prowler.git" -reference = "PROWLER-510-update-cartography-dependency" -resolved_reference = "221f6afbcf625e4b6334919f165a1419a1e7d5d4" +reference = "attack-paths-demo" +resolved_reference = "95d9e9a59f8e52e4096a5c17bf839e515b918239" [[package]] name = "psutil" @@ -5696,7 +5696,7 @@ description = "PycURL -- A Python Interface To The cURL library" optional = false python-versions = ">=3.5" groups = ["main"] -markers = "sys_platform != \"win32\" and platform_python_implementation == \"CPython\"" +markers = "platform_python_implementation == \"CPython\" and sys_platform != \"win32\"" files = [ {file = "pycurl-7.45.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c31b390f1e2cd4525828f1bb78c1f825c0aab5d1588228ed71b22c4784bdb593"}, {file = "pycurl-7.45.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:942b352b69184cb26920db48e0c5cb95af39874b57dbe27318e60f1e68564e37"}, @@ -5956,7 +5956,7 @@ description = "The MSALRuntime Python Interop Package" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "sys_platform == \"win32\" and (platform_system == \"Windows\" or platform_system == \"Darwin\" or platform_system == \"Linux\")" +markers = "(platform_system == \"Windows\" or platform_system == \"Darwin\" or platform_system == \"Linux\") and sys_platform == \"win32\"" files = [ {file = "pymsalruntime-0.18.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0c22e2e83faa10de422bbfaacc1bb2887c9025ee8a53f0fc2e4f7db01c4a7b66"}, {file = "pymsalruntime-0.18.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8ce2944a0f944833d047bb121396091e00287e2b6373716106da86ea99abf379"}, @@ -7806,4 +7806,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "1c2a75921edf35350cd01ff76bc4d4316ffd4f4aba8d02b4454013d8b70a730d" +content-hash = "de57b503d0f96c22ac3f9b1e9845aefac0a5b32089c9d90928a357f991384db3" diff --git a/api/pyproject.toml b/api/pyproject.toml index cdcf59834a..f96fd1f54b 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "drf-spectacular-jsonapi==0.5.1", "gunicorn==23.0.0", "lxml==5.3.2", - "prowler @ git+https://github.com/prowler-cloud/prowler.git@PROWLER-510-update-cartography-dependency", + "prowler @ git+https://github.com/prowler-cloud/prowler.git@attack-paths-demo", "psycopg2-binary==2.9.9", "pytest-celery[redis] (>=1.0.1,<2.0.0)", "sentry-sdk[django] (>=2.20.0,<3.0.0)", @@ -37,7 +37,7 @@ dependencies = [ "matplotlib (>=3.10.6,<4.0.0)", "reportlab (>=4.4.4,<5.0.0)", "neo4j (<6.0.0)", - "cartography @ git+https://github.com/prowler-cloud/cartography@azure-mgmt-sql-3", + "cartography @ git+https://github.com/prowler-cloud/cartography@master", ] description = "Prowler's API (Django/DRF)" license = "Apache-2.0" diff --git a/api/src/backend/tasks/jobs/attack_paths/aws.py b/api/src/backend/tasks/jobs/attack_paths/aws.py index b89524a2de..e244b6cca7 100644 --- a/api/src/backend/tasks/jobs/attack_paths/aws.py +++ b/api/src/backend/tasks/jobs/attack_paths/aws.py @@ -173,7 +173,9 @@ def sync_aws_account( max_progress = ( 87 # `cartography_aws.RESOURCE_FUNCTIONS["permission_relationships"]` - 1 ) - n_steps = len(requested_syncs) - 2 # Excluding `permission_relationships` and `resourcegroupstaggingapi` + n_steps = ( + len(requested_syncs) - 2 + ) # Excluding `permission_relationships` and `resourcegroupstaggingapi` progress_step = (max_progress - current_progress) / n_steps failed_syncs = {} @@ -195,7 +197,9 @@ def sync_aws_account( if func_name == "ecr:image_layers": cartography_aws.RESOURCE_FUNCTIONS[func_name]( neo4j_session=sync_args.get("neo4j_session"), - aioboto3_session=get_aioboto3_session(sync_args.get("boto3_session")), + aioboto3_session=get_aioboto3_session( + sync_args.get("boto3_session") + ), regions=sync_args.get("regions"), current_aws_account_id=sync_args.get("current_aws_account_id"), update_tag=sync_args.get("update_tag"), diff --git a/api/src/backend/tasks/jobs/attack_paths/scan.py b/api/src/backend/tasks/jobs/attack_paths/scan.py index aa7bb7b756..6e1d3e1b64 100644 --- a/api/src/backend/tasks/jobs/attack_paths/scan.py +++ b/api/src/backend/tasks/jobs/attack_paths/scan.py @@ -46,19 +46,35 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: prowler_api_provider = ProwlerAPIProvider.objects.get(scan__pk=scan_id) prowler_sdk_provider = initialize_prowler_provider(prowler_api_provider) - # If the provider is still not supported, just return the current `ingestion_exceptions`, that is empty - if not get_cartography_ingestion_function(prowler_api_provider.provider): + # Attack Paths Scan necessary objects + cartography_ingestion_function = get_cartography_ingestion_function( + prowler_api_provider.provider + ) + attack_paths_scan = db_utils.retrieve_attack_paths_scan(tenant_id, scan_id) + + # Checks before starting the scan + if not cartography_ingestion_function: + ingestion_exceptions = { + "global_error": f"Provider {prowler_api_provider.provider} is not supported for Attack Paths scans" + } + if attack_paths_scan: + db_utils.finish_attack_paths_scan( + attack_paths_scan, StateChoices.COMPLETED, ingestion_exceptions + ) + + logger.warning( + f"Provider {prowler_api_provider.provider} is not supported for Attack Paths scans" + ) return ingestion_exceptions - # Getting the Attack Paths Scan object and starting it - attack_paths_scan = db_utils.retrieve_attack_paths_scan(tenant_id, scan_id) - if not attack_paths_scan: - logger.warning( - f"No Attack Paths Scan found for scan {scan_id} and tenant {tenant_id}, let's create it then" - ) - attack_paths_scan = db_utils.create_attack_paths_scan( - tenant_id, scan_id, prowler_api_provider.id - ) + else: + if not attack_paths_scan: + logger.warning( + f"No Attack Paths Scan found for scan {scan_id} and tenant {tenant_id}, let's create it then" + ) + attack_paths_scan = db_utils.create_attack_paths_scan( + tenant_id, scan_id, prowler_api_provider.id + ) # While creating the Cartography configuration, attributes `neo4j_user` and `neo4j_password` are not really needed in this config object cartography_config = CartographyConfig( @@ -92,7 +108,7 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: # The real scan, where iterates over cloud services ingestion_exceptions = _call_within_event_loop( - get_cartography_ingestion_function(prowler_api_provider.provider), + cartography_ingestion_function, neo4j_session, cartography_config, prowler_api_provider, diff --git a/api/src/backend/tasks/tests/test_attack_paths_scan.py b/api/src/backend/tasks/tests/test_attack_paths_scan.py index 334c02df11..df31b8aa67 100644 --- a/api/src/backend/tasks/tests/test_attack_paths_scan.py +++ b/api/src/backend/tasks/tests/test_attack_paths_scan.py @@ -74,6 +74,9 @@ class TestAttackPathsRun: patch( "tasks.jobs.attack_paths.scan.cartography_analysis.run" ) as mock_cartography_analysis, + patch( + "tasks.jobs.attack_paths.scan.cartography_ontology.run" + ) as mock_cartography_ontology, patch( "tasks.jobs.attack_paths.scan.prowler.create_indexes" ) as mock_prowler_indexes, @@ -115,6 +118,7 @@ class TestAttackPathsRun: mock_cartography_indexes.assert_called_once_with(mock_session, config) mock_prowler_indexes.assert_called_once_with(mock_session) mock_cartography_analysis.assert_called_once_with(mock_session, config) + mock_cartography_ontology.assert_called_once_with(mock_session, config) mock_prowler_analysis.assert_called_once_with( mock_session, provider, diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index bc16d52eb1..36b0942bdf 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -91,18 +91,19 @@ services: # Auth - NEO4J_AUTH=${NEO4J_USER}/${NEO4J_PASSWORD} # Memory limits - - NEO4J_server_memory_pagecache_size=${NEO4J_SERVER_MEMORY_PAGECACHE_SIZE} - - NEO4J_server_memory_heap_initial__size=${NEO4J_SERVER_MEMORY_HEAP_INITIAL__SIZE} - - NEO4J_server_memory_heap_max__size=${NEO4J_SERVER_MEMORY_HEAP_MAX__SIZE} + - NEO4J_dbms_max__databases=${NEO4J_DBMS_MAX__DATABASES:-1000000} + - NEO4J_server_memory_pagecache_size=${NEO4J_SERVER_MEMORY_PAGECACHE_SIZE:-1G} + - NEO4J_server_memory_heap_initial__size=${NEO4J_SERVER_MEMORY_HEAP_INITIAL__SIZE:-1G} + - NEO4J_server_memory_heap_max__size=${NEO4J_SERVER_MEMORY_HEAP_MAX__SIZE:-1G} # APOC - - apoc.export.file.enabled=${NEO4J_POC_EXPORT_FILE_ENABLED} - - apoc.import.file.enabled=${NEO4J_APOC_IMPORT_FILE_ENABLED} - - apoc.import.file.use_neo4j_config=${NEO4J_APOC_IMPORT_FILE_USE_NEO4J_CONFIG} - - NEO4J_PLUGINS=${NEO4J_PLUGINS} - - NEO4J_dbms_security_procedures_allowlist=${NEO4J_DBMS_SECURITY_PROCEDURES_ALLOWLIST} - - NEO4J_dbms_security_procedures_unrestricted=${NEO4J_DBMS_SECURITY_PROCEDURES_UNRESTRICTED} + - apoc.export.file.enabled=${NEO4J_POC_EXPORT_FILE_ENABLED:-true} + - apoc.import.file.enabled=${NEO4J_APOC_IMPORT_FILE_ENABLED:-true} + - apoc.import.file.use_neo4j_config=${NEO4J_APOC_IMPORT_FILE_USE_NEO4J_CONFIG:-true} + - "NEO4J_PLUGINS=${NEO4J_PLUGINS:-[\"apoc\"]}" + - "NEO4J_dbms_security_procedures_allowlist=${NEO4J_DBMS_SECURITY_PROCEDURES_ALLOWLIST:-apoc.*}" + - "NEO4J_dbms_security_procedures_unrestricted=${NEO4J_DBMS_SECURITY_PROCEDURES_UNRESTRICTED:-apoc.*}" # Networking - - dbms.connector.bolt.listen_address=${NEO4J_DBMS_CONNECTOR_BOLT_LISTEN_ADDRESS} + - "dbms.connector.bolt.listen_address=${NEO4J_DBMS_CONNECTOR_BOLT_LISTEN_ADDRESS:-0.0.0.0:7687}" # 7474 is the UI port ports: - 7474:7474 diff --git a/docker-compose.yml b/docker-compose.yml index 5a94d5faa0..ee9c088c3f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,18 +73,19 @@ services: # Auth - NEO4J_AUTH=${NEO4J_USER}/${NEO4J_PASSWORD} # Memory limits - - NEO4J_server_memory_pagecache_size=${NEO4J_SERVER_MEMORY_PAGECACHE_SIZE} - - NEO4J_server_memory_heap_initial__size=${NEO4J_SERVER_MEMORY_HEAP_INITIAL__SIZE} - - NEO4J_server_memory_heap_max__size=${NEO4J_SERVER_MEMORY_HEAP_MAX__SIZE} + - NEO4J_dbms_max__databases=${NEO4J_DBMS_MAX__DATABASES:-1000000} + - NEO4J_server_memory_pagecache_size=${NEO4J_SERVER_MEMORY_PAGECACHE_SIZE:-1G} + - NEO4J_server_memory_heap_initial__size=${NEO4J_SERVER_MEMORY_HEAP_INITIAL__SIZE:-1G} + - NEO4J_server_memory_heap_max__size=${NEO4J_SERVER_MEMORY_HEAP_MAX__SIZE:-1G} # APOC - - apoc.export.file.enabled=${NEO4J_POC_EXPORT_FILE_ENABLED} - - apoc.import.file.enabled=${NEO4J_APOC_IMPORT_FILE_ENABLED} - - apoc.import.file.use_neo4j_config=${NEO4J_APOC_IMPORT_FILE_USE_NEO4J_CONFIG} - - NEO4J_PLUGINS=${NEO4J_PLUGINS} - - NEO4J_dbms_security_procedures_allowlist=${NEO4J_DBMS_SECURITY_PROCEDURES_ALLOWLIST} - - NEO4J_dbms_security_procedures_unrestricted=${NEO4J_DBMS_SECURITY_PROCEDURES_UNRESTRICTED} + - apoc.export.file.enabled=${NEO4J_POC_EXPORT_FILE_ENABLED:-true} + - apoc.import.file.enabled=${NEO4J_APOC_IMPORT_FILE_ENABLED:-true} + - apoc.import.file.use_neo4j_config=${NEO4J_APOC_IMPORT_FILE_USE_NEO4J_CONFIG:-true} + - "NEO4J_PLUGINS=${NEO4J_PLUGINS:-[\"apoc\"]}" + - "NEO4J_dbms_security_procedures_allowlist=${NEO4J_DBMS_SECURITY_PROCEDURES_ALLOWLIST:-apoc.*}" + - "NEO4J_dbms_security_procedures_unrestricted=${NEO4J_DBMS_SECURITY_PROCEDURES_UNRESTRICTED:-apoc.*}" # Networking - - dbms.connector.bolt.listen_address=${NEO4J_DBMS_CONNECTOR_BOLT_LISTEN_ADDRESS} + - "dbms.connector.bolt.listen_address=${NEO4J_DBMS_CONNECTOR_BOLT_LISTEN_ADDRESS:-0.0.0.0:7687}" ports: - ${NEO4J_PORT:-7687}:7687 healthcheck: From 4bcaf29b3212257b6610622c0d33ad2e4516ad82 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:59:27 +0100 Subject: [PATCH 5/6] feat(attack-paths): improve graph path highlighting (#9769) --- .../_components/graph/attack-path-graph.tsx | 178 ++++++++++++++++-- .../_components/graph/graph-legend.tsx | 11 ++ .../query-builder/_lib/graph-colors.ts | 4 +- .../query-builder/_lib/graph-utils.ts | 52 +++++ .../(workflow)/query-builder/_lib/index.ts | 3 + .../(workflow)/query-builder/page.tsx | 2 +- 6 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/attack-path-graph.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/attack-path-graph.tsx index 16871fb319..680fab22a2 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/attack-path-graph.tsx +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/attack-path-graph.tsx @@ -18,7 +18,10 @@ import { formatNodeLabel, getNodeBorderColor, getNodeColor, + getPathEdges, + GRAPH_ALERT_BORDER_COLOR, GRAPH_EDGE_COLOR, + GRAPH_EDGE_HIGHLIGHT_COLOR, GRAPH_SELECTION_COLOR, } from "../../_lib"; @@ -73,17 +76,31 @@ const AttackPathGraphComponent = forwardRef< const nodeShapesRef = useRef > | null>(null); + const linkElementsRef = useRef + > | null>(null); const resourcesWithFindingsRef = useRef>(new Set()); + const selectedNodeIdRef = useRef(null); + const edgesDataRef = useRef< + Array<{ + sourceId: string; + targetId: string; + }> + >([]); + + // Keep selectedNodeIdRef in sync with selectedNodeId + useEffect(() => { + selectedNodeIdRef.current = selectedNodeId ?? null; + }, [selectedNodeId]); // Update ref when onNodeClick changes useEffect(() => { onNodeClickRef.current = onNodeClick; }, [onNodeClick]); - // Update selected node styling without re-rendering + // Update selected node styling and edge highlighting without re-rendering useEffect(() => { if (nodeShapesRef.current) { - const ALERT_BORDER_COLOR = "#ef4444"; // Red 500 nodeShapesRef.current .attr("stroke", (d: NodeData) => { const isFinding = d.data.labels.some((label) => @@ -93,11 +110,11 @@ const AttackPathGraphComponent = forwardRef< // Resources with findings always keep red border if (!isFinding && hasFindings) { - return ALERT_BORDER_COLOR; + return GRAPH_ALERT_BORDER_COLOR; } - // Selected nodes get selection color + // Selected nodes get highlight color (orange) if (d.id === selectedNodeId) { - return GRAPH_SELECTION_COLOR; + return GRAPH_EDGE_HIGHLIGHT_COLOR; } // Default border color return getNodeBorderColor(d.data.labels, d.data.properties); @@ -115,6 +132,24 @@ const AttackPathGraphComponent = forwardRef< return d.id === selectedNodeId ? 3 : isFinding ? 2 : 1.5; }); } + + // Update edge highlighting for selected node - highlight entire path + if (linkElementsRef.current && edgesDataRef.current.length > 0) { + const pathEdges = selectedNodeId + ? getPathEdges(selectedNodeId, edgesDataRef.current) + : new Set(); + + linkElementsRef.current.each(function (edgeData: { + sourceId: string; + targetId: string; + }) { + const edgeId = `${edgeData.sourceId}-${edgeData.targetId}`; + const isInPath = pathEdges.has(edgeId); + select(this) + .attr("stroke", isInPath ? GRAPH_EDGE_HIGHLIGHT_COLOR : GRAPH_EDGE_COLOR) + .attr("marker-end", isInPath ? "url(#arrowhead-highlight)" : "url(#arrowhead)"); + }); + } }, [selectedNodeId]); useImperativeHandle(ref, () => ({ @@ -299,6 +334,12 @@ const AttackPathGraphComponent = forwardRef< }); }); + // Store edges data in ref for path highlighting + edgesDataRef.current = edgesData.map((e) => ({ + sourceId: e.sourceId, + targetId: e.targetId, + })); + // Add defs for filters and markers FIRST (before using them) const defs = svg.append("defs"); @@ -329,10 +370,10 @@ const AttackPathGraphComponent = forwardRef< .attr("dx", "0") .attr("dy", "0") .attr("stdDeviation", "4") - .attr("flood-color", "#ef4444") + .attr("flood-color", GRAPH_ALERT_BORDER_COLOR) .attr("flood-opacity", "0.6"); - // Arrow marker - refX=10 places the arrow tip exactly at the line endpoint + // Arrow marker (default white) - refX=10 places the arrow tip exactly at the line endpoint defs .append("marker") .attr("id", "arrowhead") @@ -346,6 +387,20 @@ const AttackPathGraphComponent = forwardRef< .attr("d", "M 0 0 L 10 5 L 0 10 z") .attr("fill", GRAPH_EDGE_COLOR); + // Arrow marker (highlighted orange) for hover state + defs + .append("marker") + .attr("id", "arrowhead-highlight") + .attr("viewBox", "0 0 10 10") + .attr("refX", 10) + .attr("refY", 5) + .attr("markerWidth", 6) + .attr("markerHeight", 6) + .attr("orient", "auto") + .append("path") + .attr("d", "M 0 0 L 10 5 L 0 10 z") + .attr("fill", GRAPH_EDGE_HIGHLIGHT_COLOR); + // Add CSS animation for dashed lines and resource edge styles svg.append("style").text(` @keyframes dash { @@ -471,6 +526,12 @@ const AttackPathGraphComponent = forwardRef< select(this).style("visibility", visibility); }); + // Store linkElements reference for hover interactions + // D3 selection types don't match our ref type exactly; safe cast for internal use + linkElementsRef.current = linkElements as unknown as ReturnType< + typeof select + >; + // Draw nodes const nodesData = g.nodes().map((v) => { const node = g.node(v); @@ -495,6 +556,69 @@ const AttackPathGraphComponent = forwardRef< .style("display", (d) => hiddenNodeIdsRef.current.has(d.id) ? "none" : null, ) + .on("mouseenter", function (_event: PointerEvent, d) { + // Highlight entire path from this node + const pathEdges = getPathEdges(d.id, edgesData); + linkElements.each(function (edgeData) { + const edgeId = `${edgeData.sourceId}-${edgeData.targetId}`; + if (pathEdges.has(edgeId)) { + select(this) + .attr("stroke", GRAPH_EDGE_HIGHLIGHT_COLOR) + .attr("marker-end", "url(#arrowhead-highlight)"); + } + }); + + // Change node border to highlight color on hover + const nodeGroup = select(this); + const nodeShape = nodeGroup.select(".node-shape"); + const isFinding = d.data.labels.some((label) => + label.toLowerCase().includes("finding"), + ); + const hasFindings = resourcesWithFindings.has(d.id); + + // Don't change border for resources with findings (keep red) + if (!hasFindings || isFinding) { + nodeShape.attr("stroke", GRAPH_EDGE_HIGHLIGHT_COLOR); + } + }) + .on("mouseleave", function (_event: PointerEvent, d) { + const selectedId = selectedNodeIdRef.current; + + // Reset edges: keep selected node's path highlighted + const selectedPathEdges = selectedId + ? getPathEdges(selectedId, edgesData) + : new Set(); + + linkElements.each(function (edgeData) { + const edgeId = `${edgeData.sourceId}-${edgeData.targetId}`; + if (selectedPathEdges.has(edgeId)) { + select(this) + .attr("stroke", GRAPH_EDGE_HIGHLIGHT_COLOR) + .attr("marker-end", "url(#arrowhead-highlight)"); + } else { + select(this) + .attr("stroke", GRAPH_EDGE_COLOR) + .attr("marker-end", "url(#arrowhead)"); + } + }); + + // Reset node border + const nodeGroup = select(this); + const nodeShape = nodeGroup.select(".node-shape"); + const isFinding = d.data.labels.some((label) => + label.toLowerCase().includes("finding"), + ); + const hasFindings = resourcesWithFindings.has(d.id); + + // Determine the correct border color + if (!isFinding && hasFindings) { + nodeShape.attr("stroke", GRAPH_ALERT_BORDER_COLOR); + } else if (d.id === selectedId) { + nodeShape.attr("stroke", GRAPH_EDGE_HIGHLIGHT_COLOR); + } else { + nodeShape.attr("stroke", getNodeBorderColor(d.data.labels, d.data.properties)); + } + }) .on("click", function (event: PointerEvent, d) { event.stopPropagation(); @@ -704,9 +828,6 @@ const AttackPathGraphComponent = forwardRef< // Store in ref for use in selection updates resourcesWithFindingsRef.current = resourcesWithFindings; - // Red alert color for resources with findings - const ALERT_BORDER_COLOR = "#ef4444"; // Red 500 - // Add shapes - hexagons for findings, rounded pill shapes for resources nodeElements.each(function (d) { const group = select(this); @@ -751,7 +872,7 @@ const AttackPathGraphComponent = forwardRef< // Resources with findings get red border and red glow (even when selected) const strokeColor = hasFindings - ? ALERT_BORDER_COLOR + ? GRAPH_ALERT_BORDER_COLOR : d.id === selectedNodeId ? GRAPH_SELECTION_COLOR : borderColor; @@ -916,10 +1037,43 @@ const AttackPathGraphComponent = forwardRef< svg.call(zoomBehavior); - // Disable mouse wheel zoom (only allow programmatic zoom via buttons) + // Enable Ctrl + mouse wheel zoom only (disable regular scroll zoom) svg.on("wheel.zoom", null); svg.on("dblclick.zoom", null); + // Custom wheel handler that only zooms when Ctrl is pressed + svg.on("wheel", function (event: WheelEvent) { + if (event.ctrlKey || event.metaKey) { + event.preventDefault(); + const currentTransform = container.attr("transform"); + const k = currentTransform + ? parseFloat( + currentTransform.match(/scale\(([^)]+)\)/)?.[1] || "1", + ) + : 1; + const scaleFactor = event.deltaY > 0 ? 0.75 : 1.35; + const newK = Math.max(0.1, Math.min(10, k * scaleFactor)); + + if (zoomBehaviorRef.current && svgSelectionRef.current) { + const svgNode = svgRef.current; + if (svgNode) { + const rect = svgNode.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + svgSelectionRef.current + .transition() + .duration(100) + .call( + zoomBehaviorRef.current.scaleTo, + newK, + [mouseX, mouseY], + ); + } + } + } + }); + // Auto-fit to screen setTimeout(() => { if ( diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/graph-legend.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/graph-legend.tsx index 033d0893fb..f9c570cc89 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/graph-legend.tsx +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/graph/graph-legend.tsx @@ -490,6 +490,17 @@ export const GraphLegend = ({ data }: GraphLegendProps) => { )} + + {/* Zoom control hint */} +
+ + Ctrl + + + + + Scroll to zoom + +
diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-colors.ts b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-colors.ts index 8a9a46a154..eb207535ad 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-colors.ts +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-colors.ts @@ -51,10 +51,12 @@ export const GRAPH_NODE_BORDER_COLORS = { default: "#22d3ee", } as const; -export const GRAPH_EDGE_COLOR = "#f97316"; // Orange 500 +export const GRAPH_EDGE_COLOR = "#ffffff"; // White (default) +export const GRAPH_EDGE_HIGHLIGHT_COLOR = "#f97316"; // Orange 500 (on hover) export const GRAPH_EDGE_GLOW_COLOR = "#fb923c"; export const GRAPH_SELECTION_COLOR = "#ffffff"; export const GRAPH_BORDER_COLOR = "#374151"; +export const GRAPH_ALERT_BORDER_COLOR = "#ef4444"; // Red 500 - for resources with findings /** * Get node fill color based on labels and properties diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts new file mode 100644 index 0000000000..b8a5970a2f --- /dev/null +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts @@ -0,0 +1,52 @@ +/** + * Utility functions for attack path graph operations + */ + +/** + * Find edges in the path from a given node. + * Upstream: follows only ONE parent path (first parent at each level) to avoid lighting up siblings + * Downstream: follows ALL children recursively + * + * @param nodeId - The starting node ID + * @param edges - Array of edges with sourceId and targetId + * @returns Set of edge IDs in the format "sourceId-targetId" + */ +export const getPathEdges = ( + nodeId: string, + edges: Array<{ sourceId: string; targetId: string }>, +): Set => { + const pathEdgeIds = new Set(); + const visitedNodes = new Set(); + + // Traverse upstream - only follow ONE parent at each level (first found) + // This creates a single path to the root, not all paths + const traverseUpstream = (currentNodeId: string) => { + if (visitedNodes.has(`up-${currentNodeId}`)) return; + visitedNodes.add(`up-${currentNodeId}`); + + // Find the first parent edge only + const parentEdge = edges.find((edge) => edge.targetId === currentNodeId); + if (parentEdge) { + pathEdgeIds.add(`${parentEdge.sourceId}-${parentEdge.targetId}`); + traverseUpstream(parentEdge.sourceId); + } + }; + + // Traverse downstream (find ALL targets from this node) + const traverseDownstream = (currentNodeId: string) => { + if (visitedNodes.has(`down-${currentNodeId}`)) return; + visitedNodes.add(`down-${currentNodeId}`); + + edges.forEach((edge) => { + if (edge.sourceId === currentNodeId) { + pathEdgeIds.add(`${edge.sourceId}-${edge.targetId}`); + traverseDownstream(edge.targetId); + } + }); + }; + + traverseUpstream(nodeId); + traverseDownstream(nodeId); + + return pathEdgeIds; +}; diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/index.ts b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/index.ts index 04bfc406b7..4f90cf71fa 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/index.ts +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/index.ts @@ -4,10 +4,13 @@ export { exportGraphAsSVG, } from "./export"; export { formatNodeLabel, formatNodeLabels } from "./format"; +export { getPathEdges } from "./graph-utils"; export { getNodeBorderColor, getNodeColor, + GRAPH_ALERT_BORDER_COLOR, GRAPH_EDGE_COLOR, + GRAPH_EDGE_HIGHLIGHT_COLOR, GRAPH_NODE_BORDER_COLORS, GRAPH_NODE_COLORS, GRAPH_SELECTION_COLOR, diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/page.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/page.tsx index 7577b1448d..9cce39568e 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/page.tsx +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/page.tsx @@ -372,7 +372,7 @@ export default function AttackPathAnalysisPage() { {/* Info message and controls */}
From 39280c8b9b33bc2695e77e68c86c49d0f9df7c4c Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:01:21 +0100 Subject: [PATCH 6/6] feat(attack-paths): add Bedrock and AttachRolePolicy privilege escalation queries (#9793) --- .../api/attack_paths/query_definitions.py | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/api/src/backend/api/attack_paths/query_definitions.py b/api/src/backend/api/attack_paths/query_definitions.py index 8a65440d6c..c8de49bd78 100644 --- a/api/src/backend/api/attack_paths/query_definitions.py +++ b/api/src/backend/api/attack_paths/query_definitions.py @@ -336,6 +336,185 @@ _QUERY_DEFINITIONS: dict[str, list[AttackPathsQueryDefinition]] = { ), ], ), + AttackPathsQueryDefinition( + id="aws-iam-privesc-attach-role-policy-assume-role", + name="Privilege Escalation: iam:AttachRolePolicy + sts:AssumeRole", + description="Detect principals who can both attach policies to roles AND assume those roles. This two-step attack allows modifying a role's permissions then assuming it to gain elevated access. This is a principal-access escalation path (pathfinding.cloud: iam-014).", + provider="aws", + cypher=""" + // Create a virtual escalation outcome node (styled like a finding) + CALL apoc.create.vNode(['PrivilegeEscalation'], { + id: 'effective-administrator', + check_title: 'Privilege Escalation', + name: 'Effective Administrator', + status: 'FAIL', + severity: 'critical' + }) + YIELD node AS admin_outcome + + WITH admin_outcome + + // Find principals in the account + MATCH path_principal = (aws:AWSAccount {id: $provider_uid})--(principal:AWSPrincipal) + + // Find statements granting iam:AttachRolePolicy + MATCH path_attach = (principal)--(attach_policy:AWSPolicy)--(stmt_attach:AWSPolicyStatement) + WHERE stmt_attach.effect = 'Allow' + AND any(action IN stmt_attach.action WHERE + toLower(action) = 'iam:attachrolepolicy' + OR toLower(action) = 'iam:*' + OR action = '*' + ) + + // Find statements granting sts:AssumeRole + MATCH path_assume = (principal)--(assume_policy:AWSPolicy)--(stmt_assume:AWSPolicyStatement) + WHERE stmt_assume.effect = 'Allow' + AND any(action IN stmt_assume.action WHERE + toLower(action) = 'sts:assumerole' + OR toLower(action) = 'sts:*' + OR action = '*' + ) + + // Find target roles that the principal can both modify AND assume + MATCH path_target = (aws)--(target_role:AWSRole) + WHERE target_role.arn CONTAINS $provider_uid + // Can attach policy to this role + AND any(resource IN stmt_attach.resource WHERE + resource = '*' + OR target_role.arn CONTAINS resource + OR resource CONTAINS target_role.name + ) + // Can assume this role + AND any(resource IN stmt_assume.resource WHERE + resource = '*' + OR target_role.arn CONTAINS resource + OR resource CONTAINS target_role.name + ) + + // Deduplicate before creating virtual relationships + WITH DISTINCT admin_outcome, aws, principal, target_role + + // Create virtual relationships showing the attack path + CALL apoc.create.vRelationship(principal, 'CAN_MODIFY', { + via: 'iam:AttachRolePolicy' + }, target_role) + YIELD rel AS modify_rel + + CALL apoc.create.vRelationship(target_role, 'LEADS_TO', { + technique: 'iam:AttachRolePolicy + sts:AssumeRole', + via: 'sts:AssumeRole', + reference: 'https://pathfinding.cloud/paths/iam-014' + }, admin_outcome) + YIELD rel AS escalation_rel + + // Re-match paths for visualization + MATCH path_principal = (aws)--(principal) + MATCH path_target = (aws)--(target_role) + + UNWIND nodes(path_principal) + nodes(path_target) as n + OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding) + WHERE pf.status = 'FAIL' + + RETURN path_principal, path_target, + admin_outcome, modify_rel, escalation_rel, + collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr + """, + parameters=[], + ), + AttackPathsQueryDefinition( + id="aws-bedrock-privesc-passrole-code-interpreter", + name="Privilege Escalation: Bedrock Code Interpreter with PassRole", + description="Detect principals that can escalate privileges by passing a role to a Bedrock AgentCore Code Interpreter. The attacker creates a code interpreter with an arbitrary role, then invokes it to execute code with those credentials.", + provider="aws", + cypher=""" + CALL apoc.create.vNode(['PrivilegeEscalation'], { + id: 'effective-administrator-bedrock', + check_title: 'Privilege Escalation', + name: 'Effective Administrator (Bedrock)', + status: 'FAIL', + severity: 'critical' + }) + YIELD node AS escalation_outcome + + WITH escalation_outcome + + // Find principals in the account + MATCH path_principal = (aws:AWSAccount {id: $provider_uid})--(principal:AWSPrincipal) + + // Principal can assume roles (up to 2 hops) + OPTIONAL MATCH path_assume = (principal)-[:STS_ASSUMEROLE_ALLOW*0..2]->(acting_as:AWSRole) + WITH escalation_outcome, aws, principal, path_principal, path_assume, + CASE WHEN path_assume IS NULL THEN principal ELSE acting_as END AS effective_principal + + // Find iam:PassRole permission + MATCH path_passrole = (effective_principal)--(passrole_policy:AWSPolicy)--(passrole_stmt:AWSPolicyStatement) + WHERE passrole_stmt.effect = 'Allow' + AND any(action IN passrole_stmt.action WHERE toLower(action) = 'iam:passrole' OR action = '*') + + // Find Bedrock AgentCore permissions + MATCH (effective_principal)--(bedrock_policy:AWSPolicy)--(bedrock_stmt:AWSPolicyStatement) + WHERE bedrock_stmt.effect = 'Allow' + AND ( + any(action IN bedrock_stmt.action WHERE toLower(action) = 'bedrock-agentcore:createcodeinterpreter' OR action = '*' OR toLower(action) = 'bedrock-agentcore:*') + ) + AND ( + any(action IN bedrock_stmt.action WHERE toLower(action) = 'bedrock-agentcore:startsession' OR action = '*' OR toLower(action) = 'bedrock-agentcore:*') + ) + AND ( + any(action IN bedrock_stmt.action WHERE toLower(action) = 'bedrock-agentcore:invoke' OR action = '*' OR toLower(action) = 'bedrock-agentcore:*') + ) + + // Find target roles with elevated permissions that could be passed + MATCH (aws)--(target_role:AWSRole)--(target_policy:AWSPolicy)--(target_stmt:AWSPolicyStatement) + WHERE target_stmt.effect = 'Allow' + AND ( + any(action IN target_stmt.action WHERE action = '*') + OR any(action IN target_stmt.action WHERE toLower(action) = 'iam:*') + ) + + // Deduplicate per (principal, target_role) pair + WITH DISTINCT escalation_outcome, aws, principal, target_role + + // Group by principal, collect target_roles + WITH escalation_outcome, aws, principal, + collect(DISTINCT target_role) AS target_roles, + count(DISTINCT target_role) AS target_count + + // Create single virtual Bedrock node per principal + CALL apoc.create.vNode(['BedrockCodeInterpreter'], { + name: 'New Code Interpreter', + description: toString(target_count) + ' admin role(s) can be passed', + id: principal.arn, + target_role_count: target_count + }) + YIELD node AS bedrock_agent + + // Connect from principal (not effective_principal) to keep graph connected + CALL apoc.create.vRelationship(principal, 'CREATES_INTERPRETER', { + permissions: ['iam:PassRole', 'bedrock-agentcore:CreateCodeInterpreter', 'bedrock-agentcore:StartSession', 'bedrock-agentcore:Invoke'], + technique: 'new-passrole' + }, bedrock_agent) + YIELD rel AS create_rel + + // UNWIND target_roles to show which roles can be passed + UNWIND target_roles AS target_role + + CALL apoc.create.vRelationship(bedrock_agent, 'PASSES_ROLE', {}, target_role) + YIELD rel AS pass_rel + + CALL apoc.create.vRelationship(target_role, 'GRANTS_ACCESS', { + reference: 'https://pathfinding.cloud/paths/bedrock-001' + }, escalation_outcome) + YIELD rel AS grants_rel + + // Re-match path for visualization + MATCH path_principal = (aws)--(principal) + + RETURN path_principal, + bedrock_agent, target_role, escalation_outcome, create_rel, pass_rel, grants_rel, target_count + """, + parameters=[], + ), ], }