diff --git a/prowler/providers/azure/services/vm/vm_service.py b/prowler/providers/azure/services/vm/vm_service.py index 327e61752a1..27c204d9f59 100644 --- a/prowler/providers/azure/services/vm/vm_service.py +++ b/prowler/providers/azure/services/vm/vm_service.py @@ -32,6 +32,7 @@ def __get_virtual_machines__(self): resource_name=vm.name, storage_profile=getattr(vm, "storage_profile", None), location=vm.location, + security_profile=vm.security_profile, ) } ) @@ -78,12 +79,25 @@ def __get_disks__(self): return disks +@dataclass +class UefiSettings: + secure_boot_enabled: bool + v_tpm_enabled: bool + + +@dataclass +class SecurityProfile: + security_type: str + uefi_settings: UefiSettings + + @dataclass class VirtualMachine: resource_id: str resource_name: str storage_profile: StorageProfile location: str + security_profile: SecurityProfile @dataclass diff --git a/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/__init__.py b/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled.metadata.json b/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled.metadata.json new file mode 100644 index 00000000000..6b0222968bf --- /dev/null +++ b/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "vm_trusted_launch_enabled", + "CheckTitle": "Ensure Trusted Launch is enabled on Virtual Machines", + "CheckType": [], + "ServiceName": "vm", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Microsoft.Compute/virtualMachines", + "Description": "When Secure Boot and vTPM are enabled together, they provide a strong foundation for protecting your VM from boot attacks. For example, if an attacker attempts to replace the bootloader with a malicious version, Secure Boot will prevent the VM from booting. If the attacker is able to bypass Secure Boot and install a malicious bootloader, vTPM can be used to detect the intrusion and alert you.", + "Risk": "Secure Boot and vTPM work together to protect your VM from a variety of boot attacks, including bootkits, rootkits, and firmware rootkits. Not enabling Trusted Launch in Azure VM can lead to increased vulnerability to rootkits and boot-level malware, reduced ability to detect and prevent unauthorized changes to the boot process, and a potential compromise of system integrity and data security.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-existing-vm?tabs=portal", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Go to Virtual Machines 2. For each VM, under Settings, click on Configuration on the left blade 3. Under Security Type, select 'Trusted Launch Virtual Machines' 4. Make sure Enable Secure Boot & Enable vTPM are checked 5. Click on Apply.", + "Url": "https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-existing-vm?tabs=portal#enable-trusted-launch-on-existing-vm" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Secure Boot and vTPM are not currently supported for Azure Generation 1 VMs. IMPORTANT: Before enabling Secure Boot and vTPM on a Generation 2 VM which does not already have both enabled, it is highly recommended to create a restore point of the VM prior to remediation." +} diff --git a/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled.py b/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled.py new file mode 100644 index 00000000000..9c26d87d9cd --- /dev/null +++ b/prowler/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.vm.vm_client import vm_client + + +class vm_trusted_launch_enabled(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for subscription_name, vms in vm_client.virtual_machines.items(): + for vm_id, vm in vms.items(): + report = Check_Report_Azure(self.metadata()) + report.status = "FAIL" + report.subscription = subscription_name + report.resource_name = vm.resource_name + report.resource_id = vm_id + report.status_extended = f"VM {vm.resource_name} has trusted launch disabled in subscription {subscription_name}" + + if ( + vm.security_profile.security_type == "TrustedLaunch" + and vm.security_profile.uefi_settings.secure_boot_enabled + and vm.security_profile.uefi_settings.v_tpm_enabled + ): + report.status = "PASS" + report.status_extended = f"VM {vm.resource_name} has trusted launch enabled in subscription {subscription_name}" + + findings.append(report) + + return findings diff --git a/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py b/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py index f16d7925258..2e1aef7585c 100644 --- a/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py +++ b/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py @@ -56,6 +56,13 @@ def test_vm_ensure_using_managed_disks(self): resource_id="/subscriptions/resource_id", resource_name="VMTest", location="location", + security_profile=mock.MagicMock( + security_type="TrustedLaunch", + uefi_settings=mock.MagicMock( + secure_boot_enabled=True, + v_tpm_enabled=True, + ), + ), storage_profile=mock.MagicMock( os_disk=mock.MagicMock( create_option="FromImage", @@ -63,7 +70,7 @@ def test_vm_ensure_using_managed_disks(self): ), data_disks=[], ), - ) + ), } } @@ -100,6 +107,13 @@ def test_vm_using_not_managed_os_disk(self): resource_id="/subscriptions/resource_id", resource_name="VMTest", location="location", + security_profile=mock.MagicMock( + security_type="TrustedLaunch", + uefi_settings=mock.MagicMock( + secure_boot_enabled=True, + v_tpm_enabled=True, + ), + ), storage_profile=mock.MagicMock( os_disk=mock.MagicMock( create_option="FromImage", @@ -144,6 +158,13 @@ def test_vm_using_not_managed_data_disks(self): resource_id="/subscriptions/resource_id", resource_name="VMTest", location="location", + security_profile=mock.MagicMock( + security_type="TrustedLaunch", + uefi_settings=mock.MagicMock( + secure_boot_enabled=True, + v_tpm_enabled=True, + ), + ), storage_profile=mock.MagicMock( os_disk=mock.MagicMock( create_option="FromImage", diff --git a/tests/providers/azure/services/vm/vm_service_test.py b/tests/providers/azure/services/vm/vm_service_test.py index b8689d67b8b..6d5e3ab655f 100644 --- a/tests/providers/azure/services/vm/vm_service_test.py +++ b/tests/providers/azure/services/vm/vm_service_test.py @@ -1,3 +1,4 @@ +from unittest import mock from unittest.mock import patch from azure.mgmt.compute.models import ManagedDiskParameters, OSDisk, StorageProfile @@ -20,6 +21,13 @@ def mock_vm_get_virtual_machines(_): resource_id="/subscriptions/resource_id", resource_name="VMTest", location="location", + security_profile=mock.MagicMock( + security_type="TrustedLaunch", + uefi_settings=mock.MagicMock( + secure_boot_enabled=True, + v_tpm_enabled=True, + ), + ), storage_profile=StorageProfile( os_disk=OSDisk( create_option="FromImage", @@ -84,6 +92,24 @@ def test__get_virtual_machines(self): ].resource_name == "VMTest" ) + assert ( + virtual_machines.virtual_machines[AZURE_SUBSCRIPTION_ID][ + "vm_id-1" + ].security_profile.security_type + == "TrustedLaunch" + ) + assert ( + virtual_machines.virtual_machines[AZURE_SUBSCRIPTION_ID][ + "vm_id-1" + ].security_profile.uefi_settings.secure_boot_enabled + is True + ) + assert ( + virtual_machines.virtual_machines[AZURE_SUBSCRIPTION_ID][ + "vm_id-1" + ].security_profile.uefi_settings.v_tpm_enabled + is True + ) assert ( virtual_machines.virtual_machines[AZURE_SUBSCRIPTION_ID][ "vm_id-1" diff --git a/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py b/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py new file mode 100644 index 00000000000..f9cabfe9af6 --- /dev/null +++ b/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py @@ -0,0 +1,131 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.azure.services.vm.vm_service import VirtualMachine +from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION + + +class Test_vm_trusted_launch_enabled: + def test_vm_no_subscriptions(self): + vm_client = mock.MagicMock + vm_client.virtual_machines = {} + + with mock.patch( + "prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled.vm_client", + new=vm_client, + ): + from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( + vm_trusted_launch_enabled, + ) + + check = vm_trusted_launch_enabled() + result = check.execute() + assert len(result) == 0 + + def test_vm_no_vm(self): + vm_client = mock.MagicMock + vm_client.virtual_machines = {AZURE_SUBSCRIPTION: {}} + + with mock.patch( + "prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled.vm_client", + new=vm_client, + ): + from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( + vm_trusted_launch_enabled, + ) + + check = vm_trusted_launch_enabled() + result = check.execute() + assert len(result) == 0 + + def test_vm_trusted_launch_enabled(self): + vm_id = str(uuid4()) + vm_client = mock.MagicMock + vm_client.virtual_machines = { + AZURE_SUBSCRIPTION: { + vm_id: VirtualMachine( + resource_id="/subscriptions/resource_id", + resource_name="VMTest", + security_profile=mock.MagicMock( + security_type="TrustedLaunch", + uefi_settings=mock.MagicMock( + secure_boot_enabled=True, + v_tpm_enabled=True, + ), + ), + storage_profile=mock.MagicMock( + os_disk=mock.MagicMock( + create_option="FromImage", + managed_disk=mock.MagicMock(id="managed_disk_id"), + ), + data_disks=[], + ), + ) + } + } + + with mock.patch( + "prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled.vm_client", + new=vm_client, + ): + from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( + vm_trusted_launch_enabled, + ) + + check = vm_trusted_launch_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == AZURE_SUBSCRIPTION + assert result[0].resource_name == "VMTest" + assert result[0].resource_id == vm_id + assert ( + result[0].status_extended + == f"VM VMTest has trusted launch enabled in subscription {AZURE_SUBSCRIPTION}" + ) + + def test_vm_trusted_launch_disabled(self): + vm_id = str(uuid4()) + vm_client = mock.MagicMock + vm_client.virtual_machines = { + AZURE_SUBSCRIPTION: { + vm_id: VirtualMachine( + resource_id="/subscriptions/resource_id", + resource_name="VMTest", + security_profile=mock.MagicMock( + security_type="TrustedLaunch", + uefi_settings=mock.MagicMock( + secure_boot_enabled=False, + v_tpm_enabled=False, + ), + ), + storage_profile=mock.MagicMock( + os_disk=mock.MagicMock( + create_option="FromImage", + managed_disk=mock.MagicMock(id="managed_disk_id"), + ), + data_disks=[], + ), + ) + } + } + + with mock.patch( + "prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled.vm_client", + new=vm_client, + ): + from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( + vm_trusted_launch_enabled, + ) + + check = vm_trusted_launch_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == AZURE_SUBSCRIPTION + assert result[0].resource_name == "VMTest" + assert result[0].resource_id == vm_id + assert ( + result[0].status_extended + == f"VM VMTest has trusted launch disabled in subscription {AZURE_SUBSCRIPTION}" + )