mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(azure): New check related with logging in Azure Key Vault (#3496)
Co-authored-by: Hugo Gálvez Ureña <hugogalvezu96@gmail.com> Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
+30
@@ -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 <diagnostic settings name> --resource <key vault resource ID> --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 ID> --event-hub-rule <event hub auth rule ID> | --storage-account <storage account ID> |--workspace <log analytics workspace ID> | --marketplace-partner-id <full resource ID of third-party solution>]>",
|
||||
"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."
|
||||
}
|
||||
+43
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+137
@@ -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."
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
+2
@@ -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",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
+2
@@ -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",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
+2
@@ -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",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
+2
@@ -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",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user