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:
Hugo966
2024-03-15 15:51:58 +01:00
committed by GitHub
parent d7fbcce48c
commit b240c46973
12 changed files with 357 additions and 16 deletions
@@ -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."
}
@@ -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
@@ -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"
)
@@ -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",
),
]
}
@@ -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",
)
]
}
@@ -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",
),
]
}
@@ -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",
),
]
}