diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 36fbbc2ffc..05c06d8d58 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -22,10 +22,8 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🐞 Fixed - Compliance frameworks contributed by several external packages under the same provider are now merged instead of overwritten, so every entry-point directory a provider contributes is discovered [(#11578)](https://github.com/prowler-cloud/prowler/pull/11578) - -### 🐞 Fixed - - Azure PostgreSQL flexible server collection no longer drops the remaining servers in a subscription when one server fails to collect; the `connection_throttle.enable` parameter (removed in PostgreSQL 16+) is treated as absent only when the Azure SDK reports it as not found, so unexpected lookup failures are not silently reported as throttling disabled [(#11595)](https://github.com/prowler-cloud/prowler/pull/11595) +- Azure `keyvault_logging_enabled` now accepts Key Vault diagnostic settings that enable the explicit `AuditEvent` category, avoiding false failures when Azure returns category-based logs without category groups [(#11660)](https://github.com/prowler-cloud/prowler/pull/11660) --- 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 index 164a43b5da..241c95ec7f 100644 --- 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 @@ -9,7 +9,7 @@ "Severity": "high", "ResourceType": "microsoft.keyvault/vaults", "ResourceGroup": "security", - "Description": "**Azure Key Vault** diagnostic settings capture **audit logs** (`AuditEvent`) when category groups `audit` and `allLogs` are enabled and routed to a supported destination. Logged events include management and data-plane operations on vaults, keys, secrets, and certificates.", + "Description": "**Azure Key Vault** diagnostic settings capture **audit logs** (`AuditEvent`) when the `AuditEvent` category is enabled, or when category groups `audit` and `allLogs` are enabled, and routed to a supported destination. Logged events include management and data-plane operations on vaults, keys, secrets, and certificates.", "Risk": "Without **Key Vault audit logging**, access and changes to keys, secrets, and certificates are untracked.\n\nAttackers can misuse keys to decrypt data, alter or delete crypto material, and evade detection-eroding **confidentiality** and **integrity** and delaying **incident response**.", "RelatedUrl": "", "AdditionalURLs": [ @@ -20,13 +20,13 @@ ], "Remediation": { "Code": { - "CLI": "az monitor diagnostic-settings create --name --resource --workspace --logs '[{\"categoryGroup\":\"audit\",\"enabled\":true},{\"categoryGroup\":\"allLogs\",\"enabled\":true}]'", - "NativeIaC": "```bicep\n// Enable Key Vault diagnostic settings with audit + allLogs\nparam keyVaultName string\nparam workspaceId string\n\nresource kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {\n name: keyVaultName\n}\n\nresource diag 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {\n name: ''\n scope: kv\n properties: {\n workspaceId: workspaceId\n logs: [\n {\n categoryGroup: 'audit' // critical: enables audit logs\n enabled: true // required to pass the check\n }\n {\n categoryGroup: 'allLogs' // critical: enables allLogs group\n enabled: true // required to pass the check\n }\n ]\n }\n}\n```", - "Other": "1. In Azure Portal, go to your Key Vault > Monitoring > Diagnostic settings\n2. Click Add diagnostic setting\n3. Under Category groups, select audit and allLogs\n4. Choose a destination (e.g., Send to Log Analytics workspace) and select the workspace\n5. Click Save", - "Terraform": "```hcl\n# Enable diagnostic settings on Key Vault with audit + allLogs\nresource \"azurerm_monitor_diagnostic_setting\" \"\" {\n name = \"\"\n target_resource_id = \"\" # Key Vault resource ID\n log_analytics_workspace_id = \"\" # Destination workspace ID\n\n enabled_log { # critical: audit category group\n category_group = \"audit\" # enables audit logs\n }\n enabled_log { # critical: allLogs category group\n category_group = \"allLogs\" # enables all logs\n }\n}\n```" + "CLI": "az monitor diagnostic-settings create --name --resource --workspace --logs '[{\"category\":\"AuditEvent\",\"enabled\":true}]'", + "NativeIaC": "```bicep\n// Enable Key Vault AuditEvent diagnostic logs\nparam keyVaultName string\nparam workspaceId string\n\nresource kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {\n name: keyVaultName\n}\n\nresource diag 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {\n name: ''\n scope: kv\n properties: {\n workspaceId: workspaceId\n logs: [\n {\n category: 'AuditEvent'\n enabled: true\n }\n ]\n }\n}\n```", + "Other": "1. In Azure Portal, go to your Key Vault > Monitoring > Diagnostic settings\n2. Click Add diagnostic setting\n3. Enable AuditEvent audit logs, or select the audit and allLogs category groups\n4. Choose a destination (e.g., Send to Log Analytics workspace) and select the workspace\n5. Click Save", + "Terraform": "```hcl\n# Enable AuditEvent diagnostic logs on Key Vault\nresource \"azurerm_monitor_diagnostic_setting\" \"\" {\n name = \"\"\n target_resource_id = \"\"\n log_analytics_workspace_id = \"\"\n\n enabled_log {\n category = \"AuditEvent\"\n }\n}\n```" }, "Recommendation": { - "Text": "Enable **diagnostic settings** to collect `AuditEvent` logs-covering category groups `audit` and `allLogs`-and send them to a central sink. Apply **least privilege** to log access, enforce secure **retention/immutability**, monitor with alerts for anomalous operations, and use **separation of duties** to prevent logging bypass.", + "Text": "Enable **diagnostic settings** to collect `AuditEvent` logs and send them to a central sink. Apply **least privilege** to log access, enforce secure **retention/immutability**, monitor with alerts for anomalous operations, and use **separation of duties** to prevent logging bypass.", "Url": "https://hub.prowler.com/check/keyvault_logging_enabled" } }, 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 index 68ef149cd7..ea80b920c5 100644 --- 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 @@ -16,14 +16,17 @@ class keyvault_logging_enabled(Check): report.status = "FAIL" report.status_extended = f"Key Vault {keyvault.name} in subscription {subscription_name} ({subscription_id}) does not have a diagnostic setting with audit logging." for diagnostic_setting in keyvault.monitor_diagnostic_settings or []: - has_audit = False + has_audit_category = False + has_audit_group = False has_all_logs = False for log in diagnostic_setting.logs: + if log.category == "AuditEvent" and log.enabled: + has_audit_category = True if log.category_group == "audit" and log.enabled: - has_audit = True + has_audit_group = True if log.category_group == "allLogs" and log.enabled: has_all_logs = True - if has_audit and has_all_logs: + if has_audit_category or (has_audit_group and has_all_logs): report.status = "PASS" report.status_extended = f"Key Vault {keyvault.name} in subscription {subscription_name} ({subscription_id}) has a diagnostic setting with audit logging." break 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 index 2fff845a7d..6050263af3 100644 --- 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 @@ -244,6 +244,158 @@ class Test_keyvault_logging_enabled: assert result[0].resource_name == "name_keyvault" assert result[0].resource_id == "id" + def test_diagnostic_setting_with_audit_event_category_logging(self): + keyvault_client = mock.MagicMock + keyvault_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.monitor.monitor_service.Monitor", + new=mock.MagicMock(), + ), + 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, + ) + from prowler.providers.azure.services.keyvault.keyvault_service import ( + KeyVaultInfo, + ) + from prowler.providers.azure.services.monitor.monitor_service import ( + DiagnosticSetting, + ) + + keyvault_client.key_vaults = { + AZURE_SUBSCRIPTION_ID: [ + KeyVaultInfo( + id="id", + name="name_keyvault", + location="westeurope", + resource_group="resource_group", + properties=VaultProperties( + tenant_id="tenantid", + sku="sku", + enable_rbac_authorization=False, + ), + keys=[], + secrets=[], + monitor_diagnostic_settings=[ + DiagnosticSetting( + id="id/ds1", + logs=[ + mock.MagicMock( + category_group=None, + category="AuditEvent", + enabled=True, + ), + mock.MagicMock( + category_group=None, + category="AzurePolicyEvaluationDetails", + enabled=False, + ), + ], + storage_account_name="sa1", + storage_account_id="sa_id1", + name="ds_audit_event", + ), + ], + ), + ] + } + check = keyvault_logging_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Key Vault name_keyvault in subscription {AZURE_SUBSCRIPTION_DISPLAY} has a diagnostic setting with audit logging." + ) + assert result[0].resource_name == "name_keyvault" + assert result[0].resource_id == "id" + + def test_diagnostic_setting_with_audit_event_category_disabled(self): + keyvault_client = mock.MagicMock + keyvault_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.monitor.monitor_service.Monitor", + new=mock.MagicMock(), + ), + 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, + ) + from prowler.providers.azure.services.keyvault.keyvault_service import ( + KeyVaultInfo, + ) + from prowler.providers.azure.services.monitor.monitor_service import ( + DiagnosticSetting, + ) + + keyvault_client.key_vaults = { + AZURE_SUBSCRIPTION_ID: [ + KeyVaultInfo( + id="id", + name="name_keyvault", + location="westeurope", + resource_group="resource_group", + properties=VaultProperties( + tenant_id="tenantid", + sku="sku", + enable_rbac_authorization=False, + ), + keys=[], + secrets=[], + monitor_diagnostic_settings=[ + DiagnosticSetting( + id="id/ds1", + logs=[ + mock.MagicMock( + category_group=None, + category="AuditEvent", + enabled=False, + ), + mock.MagicMock( + category_group=None, + category="AzurePolicyEvaluationDetails", + enabled=False, + ), + ], + storage_account_name="sa1", + storage_account_id="sa_id1", + name="ds_audit_event_disabled", + ), + ], + ), + ] + } + check = keyvault_logging_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Key Vault name_keyvault in subscription {AZURE_SUBSCRIPTION_DISPLAY} does not have a diagnostic setting with audit logging." + ) + assert result[0].resource_name == "name_keyvault" + assert result[0].resource_id == "id" + def test_multiple_diagnostic_settings_one_compliant(self): keyvault_client = mock.MagicMock keyvault_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}