diff --git a/prowler/changelog.d/scan-config-schema-app-providers.fixed.md b/prowler/changelog.d/scan-config-schema-app-providers.fixed.md new file mode 100644 index 0000000000..a8a0050d64 --- /dev/null +++ b/prowler/changelog.d/scan-config-schema-app-providers.fixed.md @@ -0,0 +1 @@ +Scan configuration schema no longer exposes SDK/CLI-only providers such as `e2enetworks`; the aggregated schema served by `/scan-configurations/schema` now includes only app providers (`sdk_only = False`) diff --git a/prowler/config/scan_config_schema.py b/prowler/config/scan_config_schema.py index 431c4cec60..fa445f33e9 100644 --- a/prowler/config/scan_config_schema.py +++ b/prowler/config/scan_config_schema.py @@ -225,9 +225,20 @@ def _build_aggregated_schema() -> dict: The output mirrors the layout of `prowler/config/config.yaml` (a mapping keyed by provider type) and is what the UI consumes via `ajv`. + + Only app-facing providers (`sdk_only = False`, see + `Provider.get_app_providers`) are included. SDK/CLI-only providers may + still have a schema registered in `SCHEMAS` so the CLI validates their + `config.yaml` (`load_and_validate_config_file` reads `SCHEMAS.get`), but + they must not surface in this app-facing schema. """ + from prowler.providers.common.provider import Provider + + app_providers = set(Provider.get_app_providers()) properties: dict[str, dict] = {} for provider, schema_cls in SCHEMAS.items(): + if provider not in app_providers: + continue properties[provider] = schema_cls.model_json_schema() return { "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/tests/config/schema/exclusions_test.py b/tests/config/schema/exclusions_test.py index a13ed2f786..1678ff1958 100644 --- a/tests/config/schema/exclusions_test.py +++ b/tests/config/schema/exclusions_test.py @@ -20,7 +20,10 @@ EXCLUSION_FIELDS = ("excluded_checks", "excluded_services") class Test_JSON_Schema_Exposes_Exclusion_Fields: - @pytest.mark.parametrize("provider", sorted(SCHEMAS)) + # The aggregated schema is app-facing and only carries app providers + # (``sdk_only = False``); iterate exactly what it exposes so SDK/CLI-only + # providers (still in ``SCHEMAS`` for CLI validation) are not asserted here. + @pytest.mark.parametrize("provider", sorted(SCAN_CONFIG_SCHEMA["properties"])) @pytest.mark.parametrize("field", EXCLUSION_FIELDS) def test_field_shape(self, provider, field): field_schema = SCAN_CONFIG_SCHEMA["properties"][provider]["properties"][field] diff --git a/tests/config/schema/scan_config_schema_test.py b/tests/config/schema/scan_config_schema_test.py index 037fcd9501..8b9e7938fa 100644 --- a/tests/config/schema/scan_config_schema_test.py +++ b/tests/config/schema/scan_config_schema_test.py @@ -13,11 +13,15 @@ from unittest.mock import call, patch import pytest from prowler.config.scan_config_schema import ( + SCAN_CONFIG_SCHEMA, + _build_aggregated_schema, _get_provider_check_ids, _get_provider_services, validate_and_normalize_scan_config, validate_scan_config, ) +from prowler.config.schema.registry import SCHEMAS +from prowler.providers.common.provider import Provider @pytest.fixture(autouse=True) @@ -391,3 +395,38 @@ class Test_Backward_Compatible_Wrapper: "message": "Scan config must be a mapping with provider sections.", } ] + + +class Test_Aggregated_Schema_Is_App_Facing: + """``SCAN_CONFIG_SCHEMA`` is served by the app + (``/scan-configurations/schema``) and consumed by the UI editor, so it must + expose only app providers (``sdk_only = False``). SDK/CLI-only providers + must not leak into it, even when they have a config schema registered in + ``SCHEMAS`` for CLI ``config.yaml`` validation.""" + + def test_sdk_only_provider_is_absent_from_schema(self): + # ``e2enetworks`` is ``sdk_only = True`` yet has a schema registered in + # ``SCHEMAS``; it must not surface in the app-facing aggregated schema. + assert "e2enetworks" not in SCAN_CONFIG_SCHEMA["properties"] + + def test_schema_contains_only_app_providers(self): + app_providers = set(Provider.get_app_providers()) + assert set(SCAN_CONFIG_SCHEMA["properties"]).issubset(app_providers) + + def test_app_provider_with_registered_schema_is_present(self): + assert "aws" in SCAN_CONFIG_SCHEMA["properties"] + + def test_registry_still_registers_sdk_only_provider_for_cli(self): + # Guard against "fixing" the leak by dropping ``e2enetworks`` from + # ``SCHEMAS``: ``load_and_validate_config_file()`` relies on + # ``SCHEMAS.get(provider)`` and a missing schema silently disables + # ``config.yaml`` validation for that provider on the CLI. + assert "e2enetworks" in SCHEMAS + + def test_build_filters_registry_by_app_providers(self): + # Deterministic mechanism check: only providers returned by + # ``get_app_providers()`` survive the aggregation, regardless of what + # ``SCHEMAS`` contains. + with patch.object(Provider, "get_app_providers", return_value=["aws"]): + schema = _build_aggregated_schema() + assert set(schema["properties"]) == {"aws"}