diff --git a/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/__init__.py b/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled.metadata.json b/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled.metadata.json new file mode 100644 index 0000000000..48bb32d095 --- /dev/null +++ b/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "keyvault_logging_enabled", + "CheckTitle": "Ensure that logging for Azure Key Vault is 'Enabled'", + "CheckType": [], + "ServiceName": "keyvault", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "KeyVault", + "Description": "Enable AuditEvent logging for key vault instances to ensure interactions with key vaults are logged and available.", + "Risk": "Monitoring how and when key vaults are accessed, and by whom, enables an audit trail of interactions with confidential information, keys, and certificates managed by Azure Keyvault. Enabling logging for Key Vault saves information in an Azure storage account which the user provides. This creates a new container named insights-logs-auditevent automatically for the specified storage account. This same storage account can be used for collecting logs for multiple key vaults.", + "RelatedUrl": "https://docs.microsoft.com/en-us/azure/key-vault/key-vault-logging", + "Remediation": { + "Code": { + "CLI": "az monitor diagnostic-settings create --name --resource --logs'[{category:AuditEvents,enabled:true,retention-policy:{enabled:true,days:180}}]' --metrics'[{category:AllMetrics,enabled:true,retention-policy:{enabled:true,days:180}}]' <[--event-hub --event-hub-rule | --storage-account |--workspace | --marketplace-partner-id ]>", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/KeyVault/enable-audit-event-logging-for-azure-key-vaults.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Go to Key vaults 2. For each Key vault 3. Go to Diagnostic settings 4. Click on Edit Settings 5. Ensure that Archive to a storage account is Enabled 6. Ensure that AuditEvent is checked, and the retention days is set to 180 days or as appropriate", + "Url": "https://docs.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-data-protection#dp-8-ensure-security-of-key-and-certificate-repository" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "By default, Diagnostic AuditEvent logging is not enabled for Key Vault instances." +} diff --git a/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled.py b/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled.py new file mode 100644 index 0000000000..5c8ef4fab3 --- /dev/null +++ b/prowler/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled.py @@ -0,0 +1,43 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.keyvault.keyvault_client import keyvault_client + + +class keyvault_logging_enabled(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for subscription, key_vaults in keyvault_client.key_vaults.items(): + for keyvault in key_vaults: + keyvault_name = keyvault.name + subscription_name = subscription + if not keyvault.monitor_diagnostic_settings: + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = subscription_name + report.resource_name = keyvault.name + report.resource_id = keyvault.id + report.status_extended = f"There are no diagnostic settings capturing audit logs for Key Vault {keyvault_name} in subscription {subscription_name}." + findings.append(report) + else: + for diagnostic_setting in keyvault.monitor_diagnostic_settings: + report = Check_Report_Azure(self.metadata()) + report.subscription = subscription_name + report.resource_name = diagnostic_setting.name + report.resource_id = diagnostic_setting.id + report.status = "FAIL" + report.status_extended = f"Diagnostic setting {diagnostic_setting.name} for Key Vault {keyvault_name} in subscription {subscription_name} does not have audit logging." + audit = False + allLogs = False + for log in diagnostic_setting.logs: + if log.category_group == "audit" and log.enabled: + audit = True + if log.category_group == "allLogs" and log.enabled: + allLogs = True + if audit and allLogs: + report.status = "PASS" + report.status_extended = f"Diagnostic setting {diagnostic_setting.name} for Key Vault {keyvault_name} in subscription {subscription_name} has audit logging." + break + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/keyvault/keyvault_service.py b/prowler/providers/azure/services/keyvault/keyvault_service.py index 209a5fb8ad..8074dbb44b 100644 --- a/prowler/providers/azure/services/keyvault/keyvault_service.py +++ b/prowler/providers/azure/services/keyvault/keyvault_service.py @@ -11,9 +11,11 @@ from azure.mgmt.keyvault.v2023_07_01.models import ( from prowler.lib.logger import logger from prowler.providers.azure.lib.service.service import AzureService +from prowler.providers.azure.services.monitor.monitor_client import monitor_client +from prowler.providers.azure.services.monitor.monitor_service import DiagnosticSetting -########################## Storage +########################## KeyVault class KeyVault(AzureService): def __init__(self, audit_info): super().__init__(KeyVaultManagementClient, audit_info) @@ -47,6 +49,9 @@ class KeyVault(AzureService): properties=keyvault_properties, keys=keys, secrets=secrets, + monitor_diagnostic_settings=self.__get_vault_monitor_settings__( + keyvault_name, resource_group, subscription + ), ) ) except Exception as error: @@ -116,6 +121,25 @@ class KeyVault(AzureService): ) return secrets + def __get_vault_monitor_settings__( + self, keyvault_name, resource_group, subscription + ): + logger.info( + f"KeyVault - Getting monitor diagnostics settings for {keyvault_name}..." + ) + monitor_diagnostics_settings = [] + try: + monitor_diagnostics_settings = monitor_client.diagnostic_settings_with_uri( + self.subscriptions[subscription], + f"subscriptions/{self.subscriptions[subscription]}/resourceGroups/{resource_group}/providers/Microsoft.KeyVault/vaults/{keyvault_name}", + monitor_client.clients[subscription], + ) + except Exception as error: + logger.error( + f"Subscription name: {self.subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return monitor_diagnostics_settings + @dataclass class Key: @@ -145,3 +169,4 @@ class KeyVaultInfo: properties: VaultProperties keys: list[Key] = None secrets: list[Secret] = None + monitor_diagnostic_settings: list[DiagnosticSetting] = None diff --git a/prowler/providers/azure/services/monitor/monitor_service.py b/prowler/providers/azure/services/monitor/monitor_service.py index b7d644f260..4844277123 100644 --- a/prowler/providers/azure/services/monitor/monitor_service.py +++ b/prowler/providers/azure/services/monitor/monitor_service.py @@ -17,31 +17,42 @@ class Monitor(AzureService): def __get_diagnostics_settings__(self): logger.info("Monitor - Getting diagnostics settings...") + diagnostics_settings_list = [] diagnostics_settings = {} for subscription, client in self.clients.items(): try: - diagnostics_settings.update({subscription: []}) - settings = client.diagnostic_settings.list( - resource_uri=f"subscriptions/{self.subscriptions[subscription]}/" + diagnostics_settings_list = self.diagnostic_settings_with_uri( + subscription, + f"subscriptions/{self.subscriptions[subscription]}/", + client, ) - for setting in settings: - diagnostics_settings[subscription].append( - DiagnosticSetting( - id=setting.id, - storage_account_name=setting.storage_account_id.split("/")[ - -1 - ], - logs=setting.logs, - storage_account_id=setting.storage_account_id, - ) - ) - + diagnostics_settings.update({subscription: diagnostics_settings_list}) except Exception as error: logger.error( f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) return diagnostics_settings + def diagnostic_settings_with_uri(self, subscription, uri, client): + diagnostics_settings = [] + try: + settings = client.diagnostic_settings.list(resource_uri=uri) + for setting in settings: + diagnostics_settings.append( + DiagnosticSetting( + id=setting.id, + name=setting.id.split("/")[-1], + storage_account_name=setting.storage_account_id.split("/")[-1], + logs=setting.logs, + storage_account_id=setting.storage_account_id, + ) + ) + except Exception as error: + logger.error( + f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return diagnostics_settings + def get_alert_rules(self): logger.info("Monitor - Getting alert rules...") alert_rules = {} @@ -72,6 +83,7 @@ class DiagnosticSetting: storage_account_id: str storage_account_name: str logs: LogSettings + name: str @dataclass diff --git a/tests/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled_test.py b/tests/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled_test.py new file mode 100644 index 0000000000..bedc06d951 --- /dev/null +++ b/tests/providers/azure/services/keyvault/keyvault_logging_enabled/keyvault_logging_enabled_test.py @@ -0,0 +1,137 @@ +from unittest import mock + +from azure.mgmt.keyvault.v2023_07_01.models import VaultProperties + +from prowler.providers.azure.services.keyvault.keyvault_service import KeyVaultInfo +from prowler.providers.azure.services.monitor.monitor_service import DiagnosticSetting +from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION + + +class Test_keyvault_logging_enabled: + def test_keyvault_logging_enabled(self): + keyvault_client = mock.MagicMock + keyvault_client.key_vaults = {} + + with mock.patch( + "prowler.providers.azure.services.keyvault.keyvault_logging_enabled.keyvault_logging_enabled.keyvault_client", + new=keyvault_client, + ): + + from prowler.providers.azure.services.keyvault.keyvault_logging_enabled.keyvault_logging_enabled import ( + keyvault_logging_enabled, + ) + + check = keyvault_logging_enabled() + result = check.execute() + assert len(result) == 0 + + def test_no_diagnostic_settings(self): + keyvault_client = mock.MagicMock + keyvault_client.key_vaults = {AZURE_SUBSCRIPTION: []} + with mock.patch( + "prowler.providers.azure.services.keyvault.keyvault_logging_enabled.keyvault_logging_enabled.keyvault_client", + new=keyvault_client, + ): + from prowler.providers.azure.services.keyvault.keyvault_logging_enabled.keyvault_logging_enabled import ( + keyvault_logging_enabled, + ) + + check = keyvault_logging_enabled() + result = check.execute() + assert len(result) == 0 + + def test_diagnostic_settings_configured(self): + keyvault_client = mock.MagicMock + keyvault_client.key_vaults = { + AZURE_SUBSCRIPTION: [ + KeyVaultInfo( + id="id", + name="name_keyvault", + location="location", + resource_group="resource_group", + properties=VaultProperties( + tenant_id="tenantid", sku="sku", enable_rbac_authorization=False + ), + keys=[], + secrets=[], + monitor_diagnostic_settings=[ + DiagnosticSetting( + id="id/id", + logs=[ + mock.MagicMock( + category_group="audit", + category="None", + enabled=True, + ), + mock.MagicMock( + category_group="allLogs", + category="None", + enabled=False, + ), + ], + storage_account_name="storage_account_name", + storage_account_id="storage_account_id", + name="name_diagnostic_setting", + ), + ], + ), + KeyVaultInfo( + id="id2", + name="name_keyvault2", + location="location2", + resource_group="resource_group2", + properties=VaultProperties( + tenant_id="tenantid", sku="sku", enable_rbac_authorization=False + ), + keys=[], + secrets=[], + monitor_diagnostic_settings=[ + DiagnosticSetting( + id="id2/id2", + logs=[ + mock.MagicMock( + category_group="audit", + category="None", + enabled=True, + ), + mock.MagicMock( + category_group="allLogs", + category="None", + enabled=True, + ), + ], + storage_account_name="storage_account_name2", + storage_account_id="storage_account_id2", + name="name_diagnostic_setting2", + ), + ], + ), + ] + } + with mock.patch( + "prowler.providers.azure.services.keyvault.keyvault_logging_enabled.keyvault_logging_enabled.keyvault_client", + new=keyvault_client, + ): + from prowler.providers.azure.services.keyvault.keyvault_logging_enabled.keyvault_logging_enabled import ( + keyvault_logging_enabled, + ) + + check = keyvault_logging_enabled() + result = check.execute() + assert len(result) == 2 + assert result[0].status == "FAIL" + assert result[0].subscription == AZURE_SUBSCRIPTION + assert result[0].resource_name == "name_diagnostic_setting" + assert result[0].resource_id == "id/id" + assert ( + result[0].status_extended + == f"Diagnostic setting name_diagnostic_setting for Key Vault name_keyvault in subscription {AZURE_SUBSCRIPTION} does not have audit logging." + ) + assert result[1].status == "PASS" + assert result[1].subscription == AZURE_SUBSCRIPTION + assert result[1].resource_name == "name_diagnostic_setting2" + assert result[1].resource_id == "id2/id2" + assert ( + result[1].status_extended + == f"Diagnostic setting name_diagnostic_setting2 for Key Vault name_keyvault2 in subscription {AZURE_SUBSCRIPTION} has audit logging." + ) diff --git a/tests/providers/azure/services/keyvault/keyvault_service_test.py b/tests/providers/azure/services/keyvault/keyvault_service_test.py index 768d43aa7f..e25b1b6430 100644 --- a/tests/providers/azure/services/keyvault/keyvault_service_test.py +++ b/tests/providers/azure/services/keyvault/keyvault_service_test.py @@ -1,6 +1,8 @@ +from unittest import mock from unittest.mock import patch from prowler.providers.azure.services.keyvault.keyvault_service import ( + DiagnosticSetting, Key, KeyVault, KeyVaultInfo, @@ -38,6 +40,22 @@ def mock_keyvault_get_key_vaults(_, __): attributes=None, ) ], + monitor_diagnostic_settings=[ + DiagnosticSetting( + id="id", + storage_account_id="storage_account_id", + logs=[ + mock.MagicMock( + categoty_group="audit", category="None", enabled=True + ), + mock.MagicMock( + categoty_group="allLogs", category="None", enabled=False + ), + ], + name="name", + storage_account_name="storage_account_name", + ) + ], ) return {AZURE_SUBSCRIPTION: [keyvault_info]} @@ -97,3 +115,70 @@ class Test_keyvault_service: keyvault.key_vaults[AZURE_SUBSCRIPTION][0].secrets[0].location == "location" ) assert keyvault.key_vaults[AZURE_SUBSCRIPTION][0].secrets[0].attributes is None + + def test__get_vault_monitor_settings__(self): + keyvault = KeyVault(set_mocked_azure_audit_info()) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0].monitor_diagnostic_settings[0].id + == "id" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .storage_account_id + == "storage_account_id" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .logs[0] + .categoty_group + == "audit" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .logs[0] + .category + == "None" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .logs[0] + .enabled + is True + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .logs[1] + .categoty_group + == "allLogs" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .logs[1] + .category + == "None" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .logs[1] + .enabled + is False + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .name + == "name" + ) + assert ( + keyvault.key_vaults[AZURE_SUBSCRIPTION][0] + .monitor_diagnostic_settings[0] + .storage_account_name + == "storage_account_name" + ) diff --git a/tests/providers/azure/services/monitor/monitor_diagnostic_setting_with_appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories_test.py b/tests/providers/azure/services/monitor/monitor_diagnostic_setting_with_appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories_test.py index 1458d06d31..20fd170f4e 100644 --- a/tests/providers/azure/services/monitor/monitor_diagnostic_setting_with_appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories_test.py +++ b/tests/providers/azure/services/monitor/monitor_diagnostic_setting_with_appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories_test.py @@ -65,6 +65,7 @@ class Test_monitor_diagnostic_setting_with_appropriate_categories: ], storage_account_id="/subscriptions/1234a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname", storage_account_name="storageaccountname", + name="name", ), DiagnosticSetting( id="id2", @@ -80,6 +81,7 @@ class Test_monitor_diagnostic_setting_with_appropriate_categories: ], storage_account_id="/subscriptions/1224a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname2", storage_account_name="storageaccountname2", + name="name2", ), ] } diff --git a/tests/providers/azure/services/monitor/monitor_diagnostic_settings_exists/monitor_diagnostic_settings_exists_test.py b/tests/providers/azure/services/monitor/monitor_diagnostic_settings_exists/monitor_diagnostic_settings_exists_test.py index a4033ce8d5..fabb79d83b 100644 --- a/tests/providers/azure/services/monitor/monitor_diagnostic_settings_exists/monitor_diagnostic_settings_exists_test.py +++ b/tests/providers/azure/services/monitor/monitor_diagnostic_settings_exists/monitor_diagnostic_settings_exists_test.py @@ -64,6 +64,7 @@ class Test_monitor_diagnostic_settings_exists: ], storage_account_id="/subscriptions/1234a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname1", storage_account_name="storageaccountname1", + name="name", ), DiagnosticSetting( id="id2", @@ -78,6 +79,7 @@ class Test_monitor_diagnostic_settings_exists: ], storage_account_id="/subscriptions/1224a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname2", storage_account_name="storageaccountname2", + name="name2", ), ] } diff --git a/tests/providers/azure/services/monitor/monitor_service_test.py b/tests/providers/azure/services/monitor/monitor_service_test.py index a357112fd3..3fe38ee44a 100644 --- a/tests/providers/azure/services/monitor/monitor_service_test.py +++ b/tests/providers/azure/services/monitor/monitor_service_test.py @@ -33,6 +33,7 @@ def mock_monitor_get_diagnostics_settings(_): ], storage_account_id="/subscriptions/1234a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname", storage_account_name="storageaccountname", + name="name", ) ] } diff --git a/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_cmk_encrypted/monitor_storage_account_with_activity_logs_cmk_encrypted_test.py b/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_cmk_encrypted/monitor_storage_account_with_activity_logs_cmk_encrypted_test.py index ed3f981e67..9d659b92c0 100644 --- a/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_cmk_encrypted/monitor_storage_account_with_activity_logs_cmk_encrypted_test.py +++ b/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_cmk_encrypted/monitor_storage_account_with_activity_logs_cmk_encrypted_test.py @@ -57,6 +57,7 @@ class Test_monitor_storage_account_with_activity_logs_cmk_encrypted: ], storage_account_id="/subscriptions/1234a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname1", storage_account_name="storageaccountname1", + name="name", ), DiagnosticSetting( id="id2", @@ -71,6 +72,7 @@ class Test_monitor_storage_account_with_activity_logs_cmk_encrypted: ], storage_account_id="/subscriptions/1224a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname2", storage_account_name="storageaccountname2", + name="name2", ), ] } diff --git a/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_is_private/monitor_storage_account_with_activity_logs_is_private_test.py b/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_is_private/monitor_storage_account_with_activity_logs_is_private_test.py index 366ab87579..656f3af572 100644 --- a/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_is_private/monitor_storage_account_with_activity_logs_is_private_test.py +++ b/tests/providers/azure/services/monitor/monitor_storage_account_with_activity_logs_is_private/monitor_storage_account_with_activity_logs_is_private_test.py @@ -57,6 +57,7 @@ class Test_monitor_storage_account_with_activity_logs_is_private: ], storage_account_id="/subscriptions/1234a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname1", storage_account_name="storageaccountname1", + name="name", ), DiagnosticSetting( id="id2", @@ -71,6 +72,7 @@ class Test_monitor_storage_account_with_activity_logs_is_private: ], storage_account_id="/subscriptions/1224a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname2", storage_account_name="storageaccountname2", + name="name2", ), ] }