mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-02 01:15:31 +00:00
Merge remote-tracking branch 'origin/PROWLER-1391-provider-contract-dynamic-discovery' into PROWLER-1771-public-dynamic-provider-class-resolver
# Conflicts: # prowler/providers/common/provider.py
This commit is contained in:
commit
ece6af5dd3
95 files changed
+803
-172
No files matched your search
@@ -540,7 +540,9 @@ class TestCompliance:
|
||||
):
|
||||
object = mock.Mock()
|
||||
object.path = "/path/to/compliance"
|
||||
object.name = "framework1_aws"
|
||||
# list_compliance_modules yields dotted module names; get_bulk matches
|
||||
# the last segment exactly against the provider.
|
||||
object.name = "prowler.compliance.aws"
|
||||
mock_list_modules.return_value = [object]
|
||||
|
||||
mock_listdir.return_value = ["framework1_aws.json"]
|
||||
|
||||
@@ -70,6 +70,20 @@ class TestIsToolWrapperProvider:
|
||||
|
||||
assert is_tool_wrapper_provider("does-not-exist") is False
|
||||
|
||||
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
||||
def test_builtin_name_shortcircuits_before_loading_same_name_plugin(self, mock_eps):
|
||||
"""A plug-in registered under a built-in's name cannot flip the
|
||||
built-in onto the tool-wrapper path, and its module is never loaded."""
|
||||
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
||||
|
||||
malicious = _make_entry_point("aws", MagicMock(is_external_tool_provider=True))
|
||||
mock_eps.return_value = [malicious]
|
||||
|
||||
# `aws` is a built-in, so classification short-circuits to False...
|
||||
assert is_tool_wrapper_provider("aws") is False
|
||||
# ...and the shadowing plug-in's code is never executed via ep.load().
|
||||
malicious.load.assert_not_called()
|
||||
|
||||
|
||||
class TestLoadEpClass:
|
||||
"""_load_ep_class: cache, broken plug-ins, no-match."""
|
||||
|
||||
+77
-10
@@ -663,22 +663,25 @@ class TestProviderInitialization:
|
||||
|
||||
@patch("prowler.providers.common.provider.logger")
|
||||
@patch("prowler.providers.common.provider.load_and_validate_config_file")
|
||||
@patch("prowler.providers.common.provider.Provider._load_ep_provider")
|
||||
@patch("prowler.providers.common.provider.importlib.metadata.entry_points")
|
||||
@patch("prowler.providers.common.provider.import_module")
|
||||
@patch("prowler.providers.common.provider.Provider.is_builtin")
|
||||
def test_init_global_provider_warns_when_plugin_shadowed_by_builtin(
|
||||
self, mock_is_builtin, mock_import, mock_load_ep, mock_config, mock_logger
|
||||
self, mock_is_builtin, mock_import, mock_entry_points, mock_config, mock_logger
|
||||
):
|
||||
"""Regression guard: when a plug-in registers a provider name that
|
||||
collides with a built-in, the BUILT-IN wins and a warning is emitted
|
||||
naming the shadowed plug-in. Matches the precedence enforced by
|
||||
`_resolve_check_module` and `CheckMetadata.get_bulk` for checks. See
|
||||
PR #10700 review (HugoPBrito).
|
||||
naming the shadowed plug-in. Shadow detection matches by entry-point
|
||||
name only — the plug-in is never `ep.load()`-ed just to warn, so its
|
||||
module code cannot run during a built-in run. See PR #10700 review
|
||||
(HugoPBrito, Alan-TheGentleman).
|
||||
"""
|
||||
# Simulate a built-in `aws` that exists, AND a plug-in registered
|
||||
# under the same `aws` name via entry points.
|
||||
mock_is_builtin.return_value = True
|
||||
mock_load_ep.return_value = FakeExternalProvider # plug-in shadow
|
||||
shadow_ep = MagicMock()
|
||||
shadow_ep.name = "aws" # plug-in shadowing the built-in name
|
||||
mock_entry_points.return_value = [shadow_ep]
|
||||
mock_import.return_value = MagicMock(
|
||||
AwsProvider=MagicMock(side_effect=lambda **_kw: None)
|
||||
)
|
||||
@@ -723,6 +726,8 @@ class TestProviderInitialization:
|
||||
]
|
||||
assert warning_msgs, "expected a warning about the shadowed plug-in 'aws'"
|
||||
assert "IGNORED" in warning_msgs[0]
|
||||
# Shadow detected by name only — plug-in code never executed to warn
|
||||
shadow_ep.load.assert_not_called()
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
@@ -1354,6 +1359,63 @@ class TestCompliance:
|
||||
|
||||
assert "dup_framework" in bulk
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"provider, framework_segments",
|
||||
[
|
||||
# `cloud` is a substring of THREE built-in modules at once.
|
||||
("cloud", ["alibabacloud", "cloudflare", "oraclecloud"]),
|
||||
("git", ["github"]),
|
||||
("work", ["googleworkspace"]),
|
||||
("open", ["openstack"]),
|
||||
],
|
||||
)
|
||||
@patch("prowler.lib.check.compliance_models.importlib.metadata.entry_points")
|
||||
@patch("prowler.lib.check.compliance_models.list_compliance_modules")
|
||||
def test_compliance_get_bulk_matches_provider_segment_exactly(
|
||||
self, mock_list_modules, mock_ep, provider, framework_segments
|
||||
):
|
||||
"""Regression: a provider whose name is a substring of one or more
|
||||
framework modules must NOT load them. The old `provider in name`
|
||||
check captured overlapping built-ins (e.g. `cloud` matched
|
||||
alibabacloud, cloudflare and oraclecloud). See PR #10700 review
|
||||
(Alan-TheGentleman).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from prowler.lib.check.compliance_models import Compliance
|
||||
|
||||
mock_ep.return_value = []
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# The substring path the old code would have read from.
|
||||
os.mkdir(os.path.join(tmpdir, provider))
|
||||
json_data = {
|
||||
"Framework": "Custom",
|
||||
"Name": f"Should not load for '{provider}'",
|
||||
"Version": "1.0",
|
||||
"Provider": provider,
|
||||
"Description": "Test",
|
||||
"Requirements": [],
|
||||
}
|
||||
with open(os.path.join(tmpdir, provider, "wrong.json"), "w") as f:
|
||||
json.dump(json_data, f)
|
||||
|
||||
modules = []
|
||||
for segment in framework_segments:
|
||||
module = MagicMock()
|
||||
module.name = f"prowler.compliance.{segment}"
|
||||
module.module_finder.path = tmpdir
|
||||
modules.append(module)
|
||||
mock_list_modules.return_value = modules
|
||||
|
||||
bulk = Compliance.get_bulk(provider)
|
||||
|
||||
# Exact-segment match: the provider is not any of these modules.
|
||||
assert "wrong" not in bulk
|
||||
assert bulk == {}
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 7. Parser
|
||||
@@ -2104,11 +2166,11 @@ class TestGetClass:
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@patch("prowler.providers.common.provider.load_and_validate_config_file")
|
||||
@patch("prowler.providers.common.provider.Provider._load_ep_provider")
|
||||
@patch("prowler.providers.common.provider.importlib.metadata.entry_points")
|
||||
@patch("prowler.providers.common.provider.import_module")
|
||||
@patch("prowler.providers.common.provider.Provider.is_builtin")
|
||||
def test_init_global_provider_emits_collision_warning_for_builtin_ep_shadow(
|
||||
self, mock_is_builtin, mock_import, mock_load_ep, mock_config, caplog
|
||||
self, mock_is_builtin, mock_import, mock_entry_points, mock_config, caplog
|
||||
):
|
||||
"""init_global_provider (not get_class) emits the collision warning
|
||||
when a built-in provider has a same-named entry-point plug-in registered.
|
||||
@@ -2117,13 +2179,16 @@ class TestGetClass:
|
||||
the warning responsibility moved OUT of get_class and INTO
|
||||
init_global_provider, so users still see the message on CLI invocation
|
||||
but prowler --help and API calls (which never hit init_global_provider)
|
||||
do not spuriously emit it.
|
||||
do not spuriously emit it. The shadow is detected by entry-point name
|
||||
only — the plug-in is never loaded to warn.
|
||||
"""
|
||||
import logging
|
||||
import types
|
||||
|
||||
mock_is_builtin.return_value = True
|
||||
mock_load_ep.return_value = FakeExternalProvider # plug-in shadow
|
||||
shadow_ep = MagicMock()
|
||||
shadow_ep.name = "aws" # plug-in shadowing the built-in name
|
||||
mock_entry_points.return_value = [shadow_ep]
|
||||
|
||||
fake_module = types.ModuleType("fake_builtin_module")
|
||||
fake_module.AwsProvider = MagicMock(side_effect=lambda **_kw: None)
|
||||
@@ -2169,3 +2234,5 @@ class TestGetClass:
|
||||
"init_global_provider must emit the collision warning when a "
|
||||
"same-named EP plug-in exists for a built-in provider"
|
||||
)
|
||||
# Shadow detected by name only — the plug-in is never loaded to warn.
|
||||
shadow_ep.load.assert_not_called()
|
||||
Reference in new issue
Block a user