diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 8bb50d8871..40eefc0de6 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -747,6 +747,9 @@ def execute( is_finding_muted_args["tenancy_id"] = ( global_provider.identity.tenancy_id ) + else: + # External/custom provider — delegate identity args + is_finding_muted_args = global_provider.get_mutelist_finding_args() for finding in check_findings: if global_provider.type == "cloudflare": is_finding_muted_args["account_id"] = finding.account_id diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index 8613957a68..df7d5997aa 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -193,6 +193,17 @@ class Provider(ABC): f"{self.__class__.__name__} has not implemented generate_compliance_output()" ) + def get_mutelist_finding_args(self) -> dict: + """Return extra kwargs for mutelist.is_finding_muted() besides 'finding'. + + External providers must return a dict with the identity key their + Mutelist subclass expects, e.g. ``{"account_id": self.identity.account_id}``. + The ``finding`` kwarg is added automatically by the caller. + """ + raise NotImplementedError( + f"{self.__class__.__name__} has not implemented get_mutelist_finding_args()" + ) + @property def is_external_tool_provider(self) -> bool: """True for providers that delegate scanning to an external tool.""" diff --git a/tests/providers/external/test_dynamic_provider_loading.py b/tests/providers/external/test_dynamic_provider_loading.py index c7c705d17a..64b9ffdb67 100644 --- a/tests/providers/external/test_dynamic_provider_loading.py +++ b/tests/providers/external/test_dynamic_provider_loading.py @@ -84,6 +84,9 @@ class FakeExternalProvider(Provider): "region": "local", } + def get_mutelist_finding_args(self): + return {"host_id": self.identity.host_id} + def get_html_assessment_summary(self): return "
Fake Assessment
" @@ -1158,7 +1161,60 @@ class TestBaseContractDefaults: with pytest.raises(NotImplementedError): provider.generate_compliance_output([], {}, set(), MagicMock(), {}) + def test_get_mutelist_finding_args_raises_not_implemented(self): + """Base Provider.get_mutelist_finding_args raises NotImplementedError.""" + provider = FakeProviderNoHelpText() + with pytest.raises(NotImplementedError): + provider.get_mutelist_finding_args() + def test_is_external_tool_provider_defaults_to_false(self): """Base Provider.is_external_tool_provider returns False.""" provider = FakeProviderNoHelpText() assert provider.is_external_tool_provider is False + + +# =========================================================================== +# 10. Mutelist Dispatch for External Providers +# =========================================================================== + + +class TestMutelistDispatch: + """Tests for mutelist integration with external providers.""" + + def test_get_mutelist_finding_args_returns_identity(self, fake_provider): + """External provider returns identity kwargs for mutelist.""" + args = fake_provider.get_mutelist_finding_args() + + assert args == {"host_id": "fake-host-1"} + + def test_mutelist_dispatch_calls_external_provider(self, fake_provider): + """execute() uses get_mutelist_finding_args for unknown provider types.""" + from prowler.lib.check.check import execute + + # Create a mock check that returns one finding + finding = MagicMock() + finding.status = "FAIL" + finding.muted = False + finding.check_metadata.Provider = "fakeexternal" + + check = MagicMock() + check.execute.return_value = [finding] + check.CheckID = "fake_check" + check.ServiceName = "fake_service" + check.Severity.value = "high" + + # Setup mutelist on the provider + fake_provider.mutelist = MagicMock() + fake_provider.mutelist.mutelist = {"Accounts": {}} + fake_provider.mutelist.is_finding_muted.return_value = True + + output_options = MagicMock() + output_options.status = [] + output_options.unix_timestamp = False + + execute(check, fake_provider, None, output_options) + + # is_finding_muted should have been called with host_id + finding + fake_provider.mutelist.is_finding_muted.assert_called_once_with( + host_id="fake-host-1", finding=finding + )