diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 87ab42f4a7..c8c845dacf 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -19,6 +19,10 @@ All notable changes to the **Prowler SDK** are documented in this file. - `OktaProvider.test_connection` accepts an optional `provider_id` (org domain) and raises `OktaInvalidProviderIdError` (14007) when it doesn't match the authenticated org — guards against stored UID drifting from the credentials' org [(#11184)](https://github.com/prowler-cloud/prowler/pull/11184) - Use single-quoted strings for credential variables in the M365 provider PowerShell session, following PowerShell best practices for literal values [(#9997)](https://github.com/prowler-cloud/prowler/pull/9997) +### 🐞 Fixed + +- OCI Audit service configuration lookup when the configured region differs from the tenancy home region [(#10347)](https://github.com/prowler-cloud/prowler/pull/10347) + --- ## [5.27.1] (Prowler v5.27.1) diff --git a/prowler/providers/oraclecloud/oraclecloud_provider.py b/prowler/providers/oraclecloud/oraclecloud_provider.py index 22fa10b276..5aa959d985 100644 --- a/prowler/providers/oraclecloud/oraclecloud_provider.py +++ b/prowler/providers/oraclecloud/oraclecloud_provider.py @@ -161,12 +161,14 @@ class OraclecloudProvider(Provider): # Get regions self._regions = self.get_regions_to_audit(region) - self._home_region = None - if self._regions: - self._home_region = next( - (region.key for region in self._regions if region.is_home_region), - self._regions[0].key, - ) + # Determine the tenancy home region from the full subscription list, independent of + # the --region filter, so tenancy-level APIs (e.g. the Audit configuration) always + # target the home region instead of a filtered, non-home region. + all_subscribed_regions = self.get_regions_to_audit() + self._home_region = next( + (region.key for region in all_subscribed_regions if region.is_home_region), + self._regions[0].key if self._regions else "us-ashburn-1", + ) logger.info(f"Home region is: {self._home_region}") # Get compartments diff --git a/prowler/providers/oraclecloud/services/audit/audit_service.py b/prowler/providers/oraclecloud/services/audit/audit_service.py index fe7751a10e..38022d1d87 100644 --- a/prowler/providers/oraclecloud/services/audit/audit_service.py +++ b/prowler/providers/oraclecloud/services/audit/audit_service.py @@ -19,9 +19,15 @@ class Audit(OCIService): def __get_configuration__(self): """Get Audit configuration.""" try: - audit_client = self._create_oci_client(oci.audit.AuditClient) + home_region = self.provider.home_region + audit_client = self._create_oci_client( + oci.audit.AuditClient, + config_overrides={"region": home_region}, + ) - logger.info("Audit - Getting Configuration...") + logger.info( + f"Audit - Getting Configuration from home region ({home_region})..." + ) try: config = audit_client.get_configuration( diff --git a/tests/providers/oraclecloud/oraclecloud_provider_test.py b/tests/providers/oraclecloud/oraclecloud_provider_test.py index 8f162ba232..7c437a35ac 100644 --- a/tests/providers/oraclecloud/oraclecloud_provider_test.py +++ b/tests/providers/oraclecloud/oraclecloud_provider_test.py @@ -255,3 +255,61 @@ class TestOraclecloudProviderInit: assert provider.compartments == mock_compartments assert provider.home_region == "us-ashburn-1" mock_set_global.assert_called_once_with(provider) + + def test_home_region_uses_full_subscription_list_not_region_filter(self): + """Home region must come from the full subscription list, not the --region filter. + + When auditing a single non-home region, the tenancy home region must still be + resolved correctly so tenancy-level APIs (e.g. the Audit configuration) target it. + """ + mock_session = OCISession( + config={"region": "eu-frankfurt-1"}, signer=None, profile="DEFAULT" + ) + mock_identity = OCIIdentityInfo( + tenancy_id="ocid1.tenancy.oc1..aaaaaaaexample", + tenancy_name="test-tenancy", + user_id="ocid1.user.oc1..aaaaaaaexample", + region="eu-frankfurt-1", + profile="DEFAULT", + audited_regions=set(), + audited_compartments=[], + ) + # The audited set is the non-home region; the full subscription list includes home + audited_regions = [ + OCIRegion( + key="eu-frankfurt-1", name="eu-frankfurt-1", is_home_region=False + ), + ] + all_subscribed_regions = [ + OCIRegion( + key="eu-frankfurt-1", name="eu-frankfurt-1", is_home_region=False + ), + OCIRegion(key="us-ashburn-1", name="us-ashburn-1", is_home_region=True), + ] + with ( + patch( + "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.setup_session", + return_value=mock_session, + ), + patch( + "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.set_identity", + return_value=mock_identity, + ), + patch( + "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_regions_to_audit", + side_effect=[audited_regions, all_subscribed_regions], + ), + patch( + "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_compartments_to_audit", + return_value=["ocid1.compartment.oc1..aaaaaaaexample"], + ), + patch("prowler.providers.common.provider.Provider.set_global_provider"), + ): + provider = OraclecloudProvider( + region={"eu-frankfurt-1"}, + config_content={"dummy": True}, + mutelist_content={"Accounts": {}}, + ) + + assert provider.regions == audited_regions + assert provider.home_region == "us-ashburn-1" diff --git a/tests/providers/oraclecloud/services/audit/audit_service_test.py b/tests/providers/oraclecloud/services/audit/audit_service_test.py index ab3710fa3d..9a3e73f9fb 100644 --- a/tests/providers/oraclecloud/services/audit/audit_service_test.py +++ b/tests/providers/oraclecloud/services/audit/audit_service_test.py @@ -1,6 +1,11 @@ -from unittest.mock import patch +from unittest.mock import MagicMock, patch -from tests.providers.oraclecloud.oci_fixtures import set_mocked_oraclecloud_provider +import oci + +from tests.providers.oraclecloud.oci_fixtures import ( + OCI_TENANCY_ID, + set_mocked_oraclecloud_provider, +) class TestAuditService: @@ -8,21 +13,58 @@ class TestAuditService: """Test that audit service can be instantiated and mocked""" oraclecloud_provider = set_mocked_oraclecloud_provider() - # Mock the entire service initialization - with patch( - "prowler.providers.oraclecloud.services.audit.audit_service.Audit.__init__", - return_value=None, - ): - from prowler.providers.oraclecloud.services.audit.audit_service import Audit + mock_config_response = MagicMock() + mock_config_response.data.retention_period_days = 365 + mock_audit_client = MagicMock() + mock_audit_client.get_configuration.return_value = mock_config_response + + from prowler.providers.oraclecloud.services.audit.audit_service import Audit + + with patch.object(Audit, "_create_oci_client", return_value=mock_audit_client): audit_client = Audit(oraclecloud_provider) - # Manually set required attributes since __init__ was mocked - audit_client.service = "audit" - audit_client.provider = oraclecloud_provider - audit_client.audited_compartments = {} - audit_client.regional_clients = {} - - # Verify service name assert audit_client.service == "audit" assert audit_client.provider == oraclecloud_provider + + def test_get_configuration_uses_home_region_not_configured_region(self): + """Test Audit uses the tenancy home region instead of the configured region.""" + oraclecloud_provider = set_mocked_oraclecloud_provider(region="eu-frankfurt-1") + # The tenancy home region differs from the configured session region + oraclecloud_provider.home_region = "us-ashburn-1" + mock_config_response = MagicMock() + mock_config_response.data.retention_period_days = 365 + + mock_audit_client = MagicMock() + mock_audit_client.get_configuration.return_value = mock_config_response + + from prowler.providers.oraclecloud.services.audit.audit_service import Audit + + with patch.object( + Audit, "_create_oci_client", return_value=mock_audit_client + ) as mock_create_oci_client: + audit = Audit(oraclecloud_provider) + + mock_create_oci_client.assert_called_once_with( + oci.audit.AuditClient, + config_overrides={"region": "us-ashburn-1"}, + ) + assert audit.configuration is not None + assert audit.configuration.retention_period_days == 365 + assert audit.configuration.compartment_id == OCI_TENANCY_ID + + def test_get_configuration_handles_api_error(self): + """Test audit configuration falls back to 90 days on API errors.""" + oraclecloud_provider = set_mocked_oraclecloud_provider() + + mock_audit_client = MagicMock() + mock_audit_client.get_configuration.side_effect = Exception("404 Not Found") + + from prowler.providers.oraclecloud.services.audit.audit_service import Audit + + with patch.object(Audit, "_create_oci_client", return_value=mock_audit_client): + audit = Audit(oraclecloud_provider) + + assert audit.configuration is not None + assert audit.configuration.retention_period_days == 90 + assert audit.configuration.compartment_id == OCI_TENANCY_ID