mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-10 05:24:20 +00:00
chore(v3): show prowler cloud + version upgrade
This commit is contained in:
5 files changed
+175
-15
No files matched your search
+28
-1
@@ -3,10 +3,16 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from colorama import Fore, Style
|
||||
|
||||
from prowler.lib.banner import print_banner
|
||||
from prowler.config.config import get_available_update
|
||||
from prowler.lib.banner import (
|
||||
print_banner,
|
||||
print_prowler_cloud_banner,
|
||||
print_update_notice,
|
||||
)
|
||||
from prowler.lib.check.check import (
|
||||
bulk_load_checks_metadata,
|
||||
bulk_load_compliance_frameworks,
|
||||
@@ -73,6 +79,18 @@ def prowler():
|
||||
compliance_framework = args.compliance
|
||||
custom_checks_metadata_file = args.custom_checks_metadata_file
|
||||
|
||||
# Check in the background whether a newer Prowler release is available;
|
||||
# the result is printed at the end of the scan so the check never adds
|
||||
# latency. Skipped with --no-banner/--only-logs and via
|
||||
# PROWLER_NO_VERSION_CHECK/DO_NOT_TRACK (handled in get_available_update).
|
||||
available_update = {}
|
||||
update_check_thread = threading.Thread(
|
||||
target=lambda: available_update.update(latest=get_available_update()),
|
||||
daemon=True,
|
||||
)
|
||||
if not args.no_banner and not args.only_logs:
|
||||
update_check_thread.start()
|
||||
|
||||
if not args.no_banner:
|
||||
print_banner(args)
|
||||
|
||||
@@ -325,6 +343,15 @@ def prowler():
|
||||
audit_output_options.output_directory,
|
||||
)
|
||||
|
||||
# Promote Prowler Cloud as the last thing the user sees after the results,
|
||||
# preceded by an update notice when a newer release is available
|
||||
if not args.no_banner and not args.only_logs:
|
||||
if update_check_thread.is_alive():
|
||||
update_check_thread.join(timeout=2)
|
||||
if available_update.get("latest"):
|
||||
print_update_notice(available_update["latest"])
|
||||
print_prowler_cloud_banner()
|
||||
|
||||
# If custom checks were passed, remove the modules
|
||||
if checks_folder:
|
||||
remove_custom_checks_module(checks_folder, provider)
|
||||
|
||||
+40
-13
@@ -11,7 +11,7 @@ from prowler.lib.logger import logger
|
||||
|
||||
timestamp = datetime.today()
|
||||
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
|
||||
prowler_version = "3.16.17"
|
||||
prowler_version = "3.16.18"
|
||||
html_logo_url = "https://github.com/prowler-cloud/prowler/"
|
||||
html_logo_img = "https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png"
|
||||
square_logo_img = "https://user-images.githubusercontent.com/38561120/235905862-9ece5bd7-9aa3-4e48-807a-3a9035eb8bfb.png"
|
||||
@@ -64,23 +64,50 @@ default_config_file_path = (
|
||||
encoding_format_utf_8 = "utf-8"
|
||||
|
||||
|
||||
def check_current_version():
|
||||
def get_latest_release_version():
|
||||
"""Return the latest Prowler release tag name from GitHub, or None if it cannot be retrieved."""
|
||||
try:
|
||||
prowler_version_string = f"Prowler {prowler_version}"
|
||||
release_response = requests.get(
|
||||
"https://api.github.com/repos/prowler-cloud/prowler/tags", timeout=1
|
||||
)
|
||||
latest_version = release_response.json()[0]["name"]
|
||||
if latest_version != prowler_version:
|
||||
return f"{prowler_version_string} (latest is {latest_version}, upgrade for the latest features)"
|
||||
else:
|
||||
return (
|
||||
f"{prowler_version_string} (You are running the latest version, yay!)"
|
||||
)
|
||||
except requests.RequestException:
|
||||
return f"{prowler_version_string}"
|
||||
return release_response.json()[0]["name"]
|
||||
except Exception:
|
||||
return f"{prowler_version_string}"
|
||||
return None
|
||||
|
||||
|
||||
def _version_tuple(version_string):
|
||||
"""Return a comparable tuple from a dotted version string."""
|
||||
return tuple(int(part) for part in version_string.split("."))
|
||||
|
||||
|
||||
def get_available_update():
|
||||
"""Return the latest Prowler version if it is newer than the running one, None otherwise.
|
||||
|
||||
Honors the PROWLER_NO_VERSION_CHECK and DO_NOT_TRACK environment variables:
|
||||
when either is set, no network call is made and None is returned.
|
||||
"""
|
||||
if os.environ.get("PROWLER_NO_VERSION_CHECK") or os.environ.get("DO_NOT_TRACK"):
|
||||
return None
|
||||
latest_version = get_latest_release_version()
|
||||
try:
|
||||
if latest_version and _version_tuple(latest_version) > _version_tuple(
|
||||
prowler_version
|
||||
):
|
||||
return latest_version
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def check_current_version():
|
||||
prowler_version_string = f"Prowler {prowler_version}"
|
||||
latest_version = get_latest_release_version()
|
||||
if not latest_version:
|
||||
return prowler_version_string
|
||||
if latest_version != prowler_version:
|
||||
return f"{prowler_version_string} (latest is {latest_version}, upgrade for the latest features)"
|
||||
else:
|
||||
return f"{prowler_version_string} (You are running the latest version, yay!)"
|
||||
|
||||
|
||||
def change_config_var(variable: str, value: str, audit_info):
|
||||
|
||||
@@ -2,6 +2,73 @@ from colorama import Fore, Style
|
||||
|
||||
from prowler.config.config import banner_color, orange_color, prowler_version, timestamp
|
||||
|
||||
# Prowler Cloud landing URL used by the CLI banner. The visible text stays
|
||||
# "cloud.prowler.com" while the clickable target carries the UTM parameters so
|
||||
# terminals that support OSC 8 hyperlinks attribute the visit to the v3 CLI.
|
||||
CLOUD_DISPLAY_TEXT = "cloud.prowler.com"
|
||||
CLOUD_BANNER_URL = (
|
||||
"https://cloud.prowler.com/sign-up?utm_source=prowler-cli&utm_content=v3"
|
||||
)
|
||||
|
||||
|
||||
def _hyperlink(url, text):
|
||||
"""Wrap ``text`` in an OSC 8 terminal hyperlink pointing to ``url``.
|
||||
|
||||
Terminals that support OSC 8 render ``text`` as a clickable link to ``url``;
|
||||
those that do not simply display ``text`` unchanged.
|
||||
"""
|
||||
return f"\033]8;;{url}\033\\{text}\033]8;;\033\\"
|
||||
|
||||
|
||||
def print_update_notice(latest_version):
|
||||
"""
|
||||
Prints a notice that a newer Prowler version is available.
|
||||
|
||||
Parameters:
|
||||
- latest_version (str): The latest released Prowler version.
|
||||
|
||||
Returns:
|
||||
- None
|
||||
"""
|
||||
print(
|
||||
f"\n{Fore.YELLOW}A new version of Prowler is available: {prowler_version} → {latest_version}{Style.RESET_ALL}\n"
|
||||
f"Upgrading from Prowler v3 is a major version upgrade — see the release notes at\n"
|
||||
f"https://github.com/prowler-cloud/prowler/releases before upgrading.\n"
|
||||
f"Upgrade with: {Style.BRIGHT}pipx upgrade prowler{Style.RESET_ALL} "
|
||||
f"(disable this check with PROWLER_NO_VERSION_CHECK=1)"
|
||||
)
|
||||
|
||||
|
||||
def print_prowler_cloud_banner():
|
||||
"""
|
||||
Prints a promotional banner highlighting what Prowler Cloud adds on top of
|
||||
the open-source CLI.
|
||||
|
||||
Shown at the end of a scan to let users know about the managed platform
|
||||
capabilities they are missing.
|
||||
|
||||
Returns:
|
||||
- None
|
||||
"""
|
||||
check = f"{Fore.GREEN}✓{Style.RESET_ALL}"
|
||||
bar = f"{banner_color}│{Style.RESET_ALL}"
|
||||
print(f"""
|
||||
{bar} {Style.BRIGHT}You're getting a snapshot 📸. Prowler Cloud gives you the full picture:{Style.RESET_ALL}
|
||||
{bar}
|
||||
{bar} {check} {Style.BRIGHT}Send your findings{Style.RESET_ALL} - directly from the Prowler CLI to Prowler Cloud.
|
||||
{bar} {check} {Style.BRIGHT}Continuous Security Monitoring{Style.RESET_ALL} - custom scheduling and scan configuration with history, trends and alerts.
|
||||
{bar} {check} {Style.BRIGHT}Triage{Style.RESET_ALL} - review findings, flag false positives and track accepted risk with your team.
|
||||
{bar} {check} {Style.BRIGHT}Lighthouse AI + MCP{Style.RESET_ALL} - autonomous triage, custom dashboards, prioritization with prevention and remediation.
|
||||
{bar} {check} {Style.BRIGHT}Alerts{Style.RESET_ALL} - get notified when anything you want is happening.
|
||||
{bar} {check} {Style.BRIGHT}Live Compliance{Style.RESET_ALL} - dashboards for 50+ frameworks, always up to date.
|
||||
{bar} {check} {Style.BRIGHT}Remediation{Style.RESET_ALL} - complete guided remediation including Autonomous remediation with Lighthouse AI.
|
||||
{bar} {check} {Style.BRIGHT}Attack Path Visualization{Style.RESET_ALL} - see how attackers chain risks to reach your crown jewels.
|
||||
{bar} {check} {Style.BRIGHT}Bulk Provisioning{Style.RESET_ALL} - add your entire AWS Organization in seconds.
|
||||
{bar} {check} {Style.BRIGHT}Integrations{Style.RESET_ALL} - Anything with our MCP + Jira, Slack, AWS Security Hub, Amazon S3, SSO and RBAC.
|
||||
{bar}
|
||||
{bar} {banner_color}Start free at 👉 {_hyperlink(CLOUD_BANNER_URL, CLOUD_DISPLAY_TEXT)}{Style.RESET_ALL}
|
||||
""")
|
||||
|
||||
|
||||
def print_banner(args):
|
||||
banner = rf"""{banner_color} _
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ packages = [
|
||||
{include = "prowler"}
|
||||
]
|
||||
readme = "README.md"
|
||||
version = "3.16.17"
|
||||
version = "3.16.18"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
alive-progress = "3.1.5"
|
||||
|
||||
@@ -8,6 +8,7 @@ from prowler.config.config import (
|
||||
change_config_var,
|
||||
check_current_version,
|
||||
get_available_compliance_frameworks,
|
||||
get_available_update,
|
||||
load_and_validate_config_file,
|
||||
)
|
||||
from prowler.providers.aws.aws_provider import get_aws_available_regions
|
||||
@@ -134,6 +135,44 @@ class Test_Config:
|
||||
== f"Prowler {MOCK_OLD_PROWLER_VERSION} (latest is {MOCK_PROWLER_VERSION}, upgrade for the latest features)"
|
||||
)
|
||||
|
||||
@mock.patch(
|
||||
"prowler.config.config.requests.get", new=mock_prowler_get_latest_release
|
||||
)
|
||||
@mock.patch("prowler.config.config.prowler_version", new=MOCK_OLD_PROWLER_VERSION)
|
||||
def test_get_available_update_with_old_version(self):
|
||||
assert get_available_update() == MOCK_PROWLER_VERSION
|
||||
|
||||
@mock.patch(
|
||||
"prowler.config.config.requests.get", new=mock_prowler_get_latest_release
|
||||
)
|
||||
@mock.patch("prowler.config.config.prowler_version", new=MOCK_PROWLER_VERSION)
|
||||
def test_get_available_update_with_latest_version(self):
|
||||
assert get_available_update() is None
|
||||
|
||||
@mock.patch(
|
||||
"prowler.config.config.requests.get", new=mock_prowler_get_latest_release
|
||||
)
|
||||
@mock.patch("prowler.config.config.prowler_version", new=MOCK_OLD_PROWLER_VERSION)
|
||||
@mock.patch.dict(os.environ, {"PROWLER_NO_VERSION_CHECK": "1"})
|
||||
def test_get_available_update_opt_out_env_var(self):
|
||||
assert get_available_update() is None
|
||||
|
||||
@mock.patch(
|
||||
"prowler.config.config.requests.get", new=mock_prowler_get_latest_release
|
||||
)
|
||||
@mock.patch("prowler.config.config.prowler_version", new=MOCK_OLD_PROWLER_VERSION)
|
||||
@mock.patch.dict(os.environ, {"DO_NOT_TRACK": "1"})
|
||||
def test_get_available_update_do_not_track(self):
|
||||
assert get_available_update() is None
|
||||
|
||||
@mock.patch(
|
||||
"prowler.config.config.requests.get",
|
||||
new=mock.MagicMock(side_effect=Exception("network error")),
|
||||
)
|
||||
@mock.patch("prowler.config.config.prowler_version", new=MOCK_OLD_PROWLER_VERSION)
|
||||
def test_get_available_update_network_failure(self):
|
||||
assert get_available_update() is None
|
||||
|
||||
def test_change_config_var_aws(self):
|
||||
audit_info = AWS_Audit_Info(
|
||||
session_config=None,
|
||||
|
||||
Reference in new issue
Block a user