mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-25 12:54:18 +00:00
fix(image): address PR review feedback
- Change exception error codes from 9000 to 11000 range to avoid Cloudflare conflict - Add EXTERNAL_TOOL_PROVIDERS constant to replace hardcoded provider tuples - Add resource_id field to CheckReportImage populated from Trivy finding IDs - Use resource_id for resource_uid in finding output - Join image names in summary table instead of showing count - Clear exceptions __init__.py re-exports
This commit is contained in:
7 files changed
+45
-72
No files matched your search
+7
-6
@@ -8,6 +8,7 @@ from colorama import Fore, Style
|
||||
from colorama import init as colorama_init
|
||||
|
||||
from prowler.config.config import (
|
||||
EXTERNAL_TOOL_PROVIDERS,
|
||||
csv_file_suffix,
|
||||
get_available_compliance_frameworks,
|
||||
html_file_suffix,
|
||||
@@ -208,8 +209,8 @@ def prowler():
|
||||
# Load compliance frameworks
|
||||
logger.debug("Loading compliance frameworks from .json files")
|
||||
|
||||
# Skip compliance frameworks for IAC, LLM, and Image providers
|
||||
if provider not in ("iac", "llm", "image"):
|
||||
# Skip compliance frameworks for external-tool providers
|
||||
if provider not in EXTERNAL_TOOL_PROVIDERS:
|
||||
bulk_compliance_frameworks = Compliance.get_bulk(provider)
|
||||
# Complete checks metadata with the compliance framework specification
|
||||
bulk_checks_metadata = update_checks_metadata_with_compliance(
|
||||
@@ -266,8 +267,8 @@ def prowler():
|
||||
if not args.only_logs:
|
||||
global_provider.print_credentials()
|
||||
|
||||
# Skip service and check loading for IAC, LLM, and Image providers
|
||||
if provider not in ("iac", "llm", "image"):
|
||||
# Skip service and check loading for external-tool providers
|
||||
if provider not in EXTERNAL_TOOL_PROVIDERS:
|
||||
# Import custom checks from folder
|
||||
if checks_folder:
|
||||
custom_checks = parse_checks_from_folder(global_provider, checks_folder)
|
||||
@@ -379,8 +380,8 @@ def prowler():
|
||||
# Execute checks
|
||||
findings = []
|
||||
|
||||
if provider in ("iac", "llm", "image"):
|
||||
# For IAC, LLM, and Image providers, run the scan directly
|
||||
if provider in EXTERNAL_TOOL_PROVIDERS:
|
||||
# For external-tool providers, run the scan directly
|
||||
if provider == "llm":
|
||||
|
||||
def streaming_callback(findings_batch):
|
||||
|
||||
@@ -66,6 +66,10 @@ class Provider(str, Enum):
|
||||
IMAGE = "image"
|
||||
|
||||
|
||||
# Providers that delegate scanning to an external tool (e.g. Trivy, promptfoo)
|
||||
# and bypass standard check/service loading.
|
||||
EXTERNAL_TOOL_PROVIDERS = frozenset({"iac", "llm", "image"})
|
||||
|
||||
# Compliance
|
||||
actual_directory = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any, Dict, Optional, Set
|
||||
from pydantic.v1 import BaseModel, Field, ValidationError, validator
|
||||
from pydantic.v1.error_wrappers import ErrorWrapper
|
||||
|
||||
from prowler.config.config import Provider
|
||||
from prowler.config.config import EXTERNAL_TOOL_PROVIDERS, Provider
|
||||
from prowler.lib.check.compliance_models import Compliance
|
||||
from prowler.lib.check.utils import recover_checks_from_provider
|
||||
from prowler.lib.logger import logger
|
||||
@@ -159,12 +159,7 @@ class CheckMetadata(BaseModel):
|
||||
raise ValueError("ServiceName must be a non-empty string")
|
||||
|
||||
check_id = values.get("CheckID")
|
||||
if (
|
||||
check_id
|
||||
and values.get("Provider") != "iac"
|
||||
and values.get("Provider") != "llm"
|
||||
and values.get("Provider") != "image"
|
||||
):
|
||||
if check_id and values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS:
|
||||
service_from_check_id = check_id.split("_")[0]
|
||||
if service_name != service_from_check_id:
|
||||
raise ValueError(
|
||||
@@ -180,12 +175,7 @@ class CheckMetadata(BaseModel):
|
||||
if not check_id:
|
||||
raise ValueError("CheckID must be a non-empty string")
|
||||
|
||||
if (
|
||||
check_id
|
||||
and values.get("Provider") != "iac"
|
||||
and values.get("Provider") != "llm"
|
||||
and values.get("Provider") != "image"
|
||||
):
|
||||
if check_id and values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS:
|
||||
if "-" in check_id:
|
||||
raise ValueError(
|
||||
f"CheckID {check_id} contains a hyphen, which is not allowed"
|
||||
@@ -872,6 +862,7 @@ class CheckReportImage(Check_Report):
|
||||
"""Contains the Container Image Check's finding information using Trivy."""
|
||||
|
||||
resource_name: str
|
||||
resource_id: str
|
||||
image_digest: str
|
||||
package_name: str
|
||||
installed_version: str
|
||||
@@ -898,6 +889,11 @@ class CheckReportImage(Check_Report):
|
||||
super().__init__(metadata, finding)
|
||||
|
||||
self.resource_name = image_name
|
||||
self.resource_id = (
|
||||
finding.get("VulnerabilityID", "")
|
||||
or finding.get("RuleID", "")
|
||||
or finding.get("ID", "")
|
||||
)
|
||||
self.image_digest = finding.get("PkgID", "")
|
||||
self.package_name = finding.get("PkgName", "")
|
||||
self.installed_version = finding.get("InstalledVersion", "")
|
||||
|
||||
@@ -387,7 +387,7 @@ class Finding(BaseModel):
|
||||
output_data["resource_name"] = getattr(
|
||||
check_output, "resource_name", ""
|
||||
)
|
||||
output_data["resource_uid"] = getattr(check_output, "resource_name", "")
|
||||
output_data["resource_uid"] = getattr(check_output, "resource_id", "")
|
||||
output_data["region"] = getattr(check_output, "region", "container")
|
||||
output_data["package_name"] = getattr(check_output, "package_name", "")
|
||||
output_data["installed_version"] = getattr(
|
||||
|
||||
@@ -95,10 +95,7 @@ def display_summary_table(
|
||||
)
|
||||
elif provider.type == "image":
|
||||
entity_type = "Image"
|
||||
if len(provider.images) == 1:
|
||||
audited_entities = provider.images[0]
|
||||
else:
|
||||
audited_entities = f"{len(provider.images)} images"
|
||||
audited_entities = ", ".join(provider.images)
|
||||
|
||||
# Check if there are findings and that they are not all MANUAL
|
||||
if findings and not all(finding.status == "MANUAL" for finding in findings):
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
from prowler.providers.image.exceptions.exceptions import (
|
||||
ImageBaseException,
|
||||
ImageFindingProcessingError,
|
||||
ImageInvalidScannerError,
|
||||
ImageInvalidSeverityError,
|
||||
ImageInvalidTimeoutError,
|
||||
ImageListFileNotFoundError,
|
||||
ImageListFileReadError,
|
||||
ImageNoImagesProvidedError,
|
||||
ImageScanError,
|
||||
ImageTrivyBinaryNotFoundError,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ImageBaseException",
|
||||
"ImageFindingProcessingError",
|
||||
"ImageInvalidScannerError",
|
||||
"ImageInvalidSeverityError",
|
||||
"ImageInvalidTimeoutError",
|
||||
"ImageListFileNotFoundError",
|
||||
"ImageListFileReadError",
|
||||
"ImageNoImagesProvidedError",
|
||||
"ImageScanError",
|
||||
"ImageTrivyBinaryNotFoundError",
|
||||
]
|
||||
@@ -1,52 +1,52 @@
|
||||
from prowler.exceptions.exceptions import ProwlerException
|
||||
|
||||
|
||||
# Exceptions codes from 9000 to 9999 are reserved for Image exceptions
|
||||
# Exceptions codes from 11000 to 11999 are reserved for Image exceptions
|
||||
class ImageBaseException(ProwlerException):
|
||||
"""Base class for Image provider errors."""
|
||||
|
||||
IMAGE_ERROR_CODES = {
|
||||
(9000, "ImageNoImagesProvidedError"): {
|
||||
(11000, "ImageNoImagesProvidedError"): {
|
||||
"message": "No container images provided for scanning.",
|
||||
"remediation": "Provide at least one image using --image or --image-list-file.",
|
||||
},
|
||||
(9001, "ImageListFileNotFoundError"): {
|
||||
(11001, "ImageListFileNotFoundError"): {
|
||||
"message": "Image list file not found.",
|
||||
"remediation": "Ensure the image list file exists at the specified path.",
|
||||
},
|
||||
(9002, "ImageListFileReadError"): {
|
||||
(11002, "ImageListFileReadError"): {
|
||||
"message": "Error reading image list file.",
|
||||
"remediation": "Check file permissions and format. The file should contain one image per line.",
|
||||
},
|
||||
(9003, "ImageFindingProcessingError"): {
|
||||
(11003, "ImageFindingProcessingError"): {
|
||||
"message": "Error processing image scan finding.",
|
||||
"remediation": "Check the Trivy output format and ensure the finding structure is valid.",
|
||||
},
|
||||
(9004, "ImageTrivyBinaryNotFoundError"): {
|
||||
(11004, "ImageTrivyBinaryNotFoundError"): {
|
||||
"message": "Trivy binary not found.",
|
||||
"remediation": "Install Trivy from https://trivy.dev/latest/getting-started/installation/",
|
||||
},
|
||||
(9005, "ImageScanError"): {
|
||||
(11005, "ImageScanError"): {
|
||||
"message": "Error scanning container image.",
|
||||
"remediation": "Check the image name and ensure it is accessible.",
|
||||
},
|
||||
(9006, "ImageInvalidTimeoutError"): {
|
||||
(11006, "ImageInvalidTimeoutError"): {
|
||||
"message": "Invalid timeout format.",
|
||||
"remediation": "Use a valid timeout like '5m', '300s', or '1h'.",
|
||||
},
|
||||
(9007, "ImageInvalidScannerError"): {
|
||||
(11007, "ImageInvalidScannerError"): {
|
||||
"message": "Invalid scanner type.",
|
||||
"remediation": "Use valid scanners: vuln, secret, misconfig, license.",
|
||||
},
|
||||
(9008, "ImageInvalidSeverityError"): {
|
||||
(11008, "ImageInvalidSeverityError"): {
|
||||
"message": "Invalid severity level.",
|
||||
"remediation": "Use valid severities: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN.",
|
||||
},
|
||||
(9009, "ImageInvalidNameError"): {
|
||||
(11009, "ImageInvalidNameError"): {
|
||||
"message": "Invalid container image name.",
|
||||
"remediation": "Use a valid image reference (e.g., 'alpine:3.18', 'registry.example.com/repo/image:tag').",
|
||||
},
|
||||
(9010, "ImageInvalidConfigScannerError"): {
|
||||
(11010, "ImageInvalidConfigScannerError"): {
|
||||
"message": "Invalid image config scanner type.",
|
||||
"remediation": "Use valid image config scanners: misconfig, secret.",
|
||||
},
|
||||
@@ -70,7 +70,7 @@ class ImageNoImagesProvidedError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9000, file=file, original_exception=original_exception, message=message
|
||||
11000, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class ImageListFileNotFoundError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9001, file=file, original_exception=original_exception, message=message
|
||||
11001, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class ImageListFileReadError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9002, file=file, original_exception=original_exception, message=message
|
||||
11002, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class ImageFindingProcessingError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9003, file=file, original_exception=original_exception, message=message
|
||||
11003, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class ImageTrivyBinaryNotFoundError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9004, file=file, original_exception=original_exception, message=message
|
||||
11004, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class ImageScanError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9005, file=file, original_exception=original_exception, message=message
|
||||
11005, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ class ImageInvalidTimeoutError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9006, file=file, original_exception=original_exception, message=message
|
||||
11006, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class ImageInvalidScannerError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9007, file=file, original_exception=original_exception, message=message
|
||||
11007, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class ImageInvalidSeverityError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9008, file=file, original_exception=original_exception, message=message
|
||||
11008, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ class ImageInvalidNameError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9009, file=file, original_exception=original_exception, message=message
|
||||
11009, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
@@ -160,5 +160,5 @@ class ImageInvalidConfigScannerError(ImageBaseException):
|
||||
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
9010, file=file, original_exception=original_exception, message=message
|
||||
11010, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
Reference in new issue
Block a user