Skip to content

Commit 09ee069

Browse files
fix(attack-paths): clear Neo4j database cache after scan and queries (#9878)
Co-authored-by: Josema Camacho <josema@prowler.com>
1 parent fcc106a commit 09ee069

File tree

7 files changed

+25
-2
lines changed

7 files changed

+25
-2
lines changed

api/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ All notable changes to the **Prowler API** are documented in this file.
1414
- Use `Findings.all_objects` to avoid the `ActiveProviderPartitionedManager` [(#9869)](https://github.com/prowler-cloud/prowler/pull/9869)
1515
- Lazy load Neo4j driver for workers only [(#9872)](https://github.com/prowler-cloud/prowler/pull/9872)
1616
- Improve Cypher query for inserting Findings into Attack Paths scan graphs [(#9874)](https://github.com/prowler-cloud/prowler/pull/9874)
17+
- Clear Neo4j database cache after Attack Paths scan and each API query [(#9877)](https://github.com/prowler-cloud/prowler/pull/9877)
1718

1819
## [1.18.0] (Prowler v5.17.0)
1920

api/src/backend/api/attack_paths/database.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ def drop_subgraph(database: str, root_node_label: str, root_node_id: str) -> int
125125
return 0 # As there are no nodes to delete, the result is empty
126126

127127

128+
def clear_cache(database: str) -> None:
129+
query = "CALL db.clearQueryCaches()"
130+
131+
try:
132+
with get_session(database) as session:
133+
session.run(query)
134+
135+
except GraphDatabaseQueryException as exc:
136+
logging.warning(f"Failed to clear query cache for database `{database}`: {exc}")
137+
138+
128139
# Neo4j functions related to Prowler + Cartography
129140
DATABASE_NAME_TEMPLATE = "db-{attack_paths_scan_id}"
130141

api/src/backend/api/attack_paths/retryable_session.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ def _call_with_retry(self, method_name: str, *args: Any, **kwargs: Any) -> Any:
6464
return method(*args, **kwargs)
6565

6666
except (
67-
neo4j.exceptions.ServiceUnavailable,
68-
ConnectionResetError,
6967
BrokenPipeError,
68+
ConnectionResetError,
69+
neo4j.exceptions.ServiceUnavailable,
7070
) as exc: # pragma: no cover - depends on infra
7171
last_exc = exc
7272
attempt += 1

api/src/backend/api/tests/test_views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3867,6 +3867,7 @@ def test_run_attack_paths_query_returns_graph(
38673867
"api.v1.views.attack_paths_views_helpers.execute_attack_paths_query",
38683868
return_value=graph_payload,
38693869
) as mock_execute,
3870+
patch("api.v1.views.graph_database.clear_cache") as mock_clear_cache,
38703871
):
38713872
response = authenticated_client.post(
38723873
reverse(
@@ -3889,6 +3890,7 @@ def test_run_attack_paths_query_returns_graph(
38893890
query_definition,
38903891
prepared_parameters,
38913892
)
3893+
mock_clear_cache.assert_called_once_with(attack_paths_scan.graph_database)
38923894
result = response.json()["data"]
38933895
attributes = result["attributes"]
38943896
assert attributes["nodes"] == graph_payload["nodes"]
@@ -4000,6 +4002,7 @@ def test_run_attack_paths_query_returns_404_when_no_nodes_found(
40004002
"api.v1.views.attack_paths_views_helpers.execute_attack_paths_query",
40014003
return_value={"nodes": [], "relationships": []},
40024004
),
4005+
patch("api.v1.views.graph_database.clear_cache"),
40034006
):
40044007
response = authenticated_client.post(
40054008
reverse(

api/src/backend/api/v1/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
7878

7979
from api.attack_paths import (
80+
database as graph_database,
8081
get_queries_for_provider,
8182
get_query_by_id,
8283
views_helpers as attack_paths_views_helpers,
@@ -2435,6 +2436,7 @@ def run_attack_paths_query(self, request, pk=None):
24352436
graph = attack_paths_views_helpers.execute_attack_paths_query(
24362437
attack_paths_scan, query_definition, parameters
24372438
)
2439+
graph_database.clear_cache(attack_paths_scan.graph_database)
24382440

24392441
status_code = status.HTTP_200_OK
24402442
if not graph.get("nodes"):

api/src/backend/tasks/jobs/attack_paths/scan.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]:
137137
neo4j_session, prowler_api_provider, scan_id, cartography_config
138138
)
139139

140+
logger.info(
141+
f"Clearing Neo4j cache for database {cartography_config.neo4j_database}"
142+
)
143+
graph_database.clear_cache(cartography_config.neo4j_database)
144+
140145
logger.info(
141146
f"Completed Cartography ({attack_paths_scan.id}) for "
142147
f"{prowler_api_provider.provider.upper()} provider {prowler_api_provider.id}"

api/src/backend/tasks/tests/test_attack_paths_scan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def test_run_success_flow(self, tenants_fixture, providers_fixture, scans_fixtur
6868
"tasks.jobs.attack_paths.scan.graph_database.get_session",
6969
return_value=session_ctx,
7070
) as mock_get_session,
71+
patch("tasks.jobs.attack_paths.scan.graph_database.clear_cache"),
7172
patch(
7273
"tasks.jobs.attack_paths.scan.cartography_create_indexes.run"
7374
) as mock_cartography_indexes,

0 commit comments

Comments
 (0)