fix(outputs): refresh scan timestamps per run (#9272)

This commit is contained in:
Hugo Pereira Brito
2025-11-20 13:12:39 +01:00
committed by GitHub
parent b6ba6c6e31
commit dc7d2d5aeb
5 changed files with 65 additions and 4 deletions

View File

@@ -27,6 +27,7 @@ All notable changes to the **Prowler API** are documented in this file.
### Fixed
- Scans no longer fail when findings have UIDs exceeding 300 characters; such findings are now skipped with detailed logging [(#9246)](https://github.com/prowler-cloud/prowler/pull/9246)
- Refresh output report timestamps for each scan [(#9272)](https://github.com/prowler-cloud/prowler/pull/9272)
### Security
- Django updated to the latest 5.1 security release, 5.1.14, due to problems with potential [SQL injection](https://github.com/prowler-cloud/prowler/security/dependabot/113) and [denial-of-service vulnerability](https://github.com/prowler-cloud/prowler/security/dependabot/114) [(#9176)](https://github.com/prowler-cloud/prowler/pull/9176)

View File

@@ -15,6 +15,7 @@ from prowler.config.config import (
html_file_suffix,
json_asff_file_suffix,
json_ocsf_file_suffix,
set_output_timestamp,
)
from prowler.lib.outputs.asff.asff import ASFF
from prowler.lib.outputs.compliance.aws_well_architected.aws_well_architected import (
@@ -275,6 +276,8 @@ def _build_output_path(
with rls_transaction(tenant_id):
started_at = Scan.objects.get(id=scan_id).started_at
set_output_timestamp(started_at)
timestamp = started_at.strftime("%Y%m%d%H%M%S")
if subdirectory:

View File

@@ -148,10 +148,11 @@ class TestOutputs:
)
mock_logger.assert_called()
@patch("tasks.jobs.export.set_output_timestamp")
@patch("tasks.jobs.export.rls_transaction")
@patch("tasks.jobs.export.Scan")
def test_generate_output_directory_creates_paths(
self, mock_scan, mock_rls_transaction, tmpdir
self, mock_scan, mock_rls_transaction, mock_set_timestamp, tmpdir
):
# Mock the scan object with a started_at timestamp
mock_scan_instance = MagicMock()
@@ -198,10 +199,11 @@ class TestOutputs:
assert ens.endswith(f"{provider}-{expected_timestamp}")
assert "/ens/" in ens
@patch("tasks.jobs.export.set_output_timestamp")
@patch("tasks.jobs.export.rls_transaction")
@patch("tasks.jobs.export.Scan")
def test_generate_output_directory_invalid_character(
self, mock_scan, mock_rls_transaction, tmpdir
self, mock_scan, mock_rls_transaction, mock_set_timestamp, tmpdir
):
# Mock the scan object with a started_at timestamp
mock_scan_instance = MagicMock()

View File

@@ -74,6 +74,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Rename `get_oci_assessment_summary` to `get_oraclecloud_assessment_summary` in HTML output [(#9200)](https://github.com/prowler-cloud/prowler/pull/9200)
- Fix Validation and other errors in Azure provider [(#8915)](https://github.com/prowler-cloud/prowler/pull/8915)
- Update documentation URLs from docs.prowler.cloud to docs.prowler.com [(#9240)](https://github.com/prowler-cloud/prowler/pull/9240)
- Refresh output report timestamps for each scan [(#9272)](https://github.com/prowler-cloud/prowler/pull/9272)
- Fix file name parsing for checks on Windows [(#9268)](https://github.com/prowler-cloud/prowler/pull/9268)
- Remove typo for Prowler ThreatScore - M365 [(#9274)](https://github.com/prowler-cloud/prowler/pull/9274)

View File

@@ -3,6 +3,7 @@ import pathlib
from datetime import datetime, timezone
from enum import Enum
from os import getcwd
from typing import Tuple
import requests
import yaml
@@ -10,8 +11,33 @@ from packaging import version
from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
class _MutableTimestamp:
"""Lightweight proxy to keep timestamp references in sync across modules."""
def __init__(self, value: datetime) -> None:
self.value = value
def set(self, value: datetime) -> None:
self.value = value
def __getattr__(self, name):
return getattr(self.value, name)
def __str__(self) -> str: # pragma: no cover - trivial forwarder
return str(self.value)
def __repr__(self) -> str: # pragma: no cover - trivial forwarder
return repr(self.value)
def __eq__(self, other) -> bool:
if isinstance(other, _MutableTimestamp):
return self.value == other.value
return self.value == other
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.14.0"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
@@ -84,6 +110,34 @@ encoding_format_utf_8 = "utf-8"
available_output_formats = ["csv", "json-asff", "json-ocsf", "html"]
def set_output_timestamp(
new_timestamp: datetime,
) -> Tuple[datetime, datetime, str, str]:
"""
Override the global output timestamps so generated artifacts reflect a specific scan.
Returns the previous values so callers can restore them afterwards.
"""
global timestamp, timestamp_utc, output_file_timestamp, timestamp_iso
previous_values = (
timestamp.value,
timestamp_utc.value,
output_file_timestamp,
timestamp_iso,
)
timestamp.set(new_timestamp)
timestamp_utc.set(
new_timestamp.astimezone(timezone.utc)
if new_timestamp.tzinfo
else new_timestamp.replace(tzinfo=timezone.utc)
)
output_file_timestamp = timestamp.strftime("%Y%m%d%H%M%S")
timestamp_iso = timestamp.isoformat(sep=" ", timespec="seconds")
return previous_values
def get_default_mute_file_path(provider: str):
"""
get_default_mute_file_path returns the default mute file path for the provider