feat(azure/vm): add new check vm_ensure_using_approved_images (#8168)

This commit is contained in:
Rubén De la Torre Vico
2025-07-08 10:40:33 +02:00
committed by GitHub
parent f29c2ac9f0
commit 3628e7b3e8
6 changed files with 235 additions and 1 deletions
+1 -1
View File
@@ -6,8 +6,8 @@ All notable changes to the **Prowler SDK** are documented in this file.
### Added
- `storage_smb_channel_encryption_with_secure_algorithm` check for Azure provider [(#8123)](https://github.com/prowler-cloud/prowler/pull/8123)
- `vm_linux_enforce_ssh_authentication` check for Azure provider [(#8149)](https://github.com/prowler-cloud/prowler/pull/8149)
- `vm_ensure_using_approved_images` check for Azure provider [(#8168)](https://github.com/prowler-cloud/prowler/pull/8168)
### Changed
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "vm_ensure_using_approved_images",
"CheckTitle": "Ensure that Azure VMs are using an approved machine image.",
"CheckType": [],
"ServiceName": "vm",
"SubServiceName": "image",
"ResourceIdTemplate": "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Compute/images/<virtual-machine-image-id>",
"Severity": "medium",
"ResourceType": "Microsoft.Compute/images",
"Description": "Ensure that all your Azure virtual machine instances are launched from approved machine images only.",
"Risk": "An approved machine image is a custom virtual machine (VM) image that contains a pre-configured OS and a well-defined stack of server software approved by Azure, fully configured to run your application. Using approved (golden) machine images to launch new VM instances within your Azure cloud environment brings major benefits such as fast and stable application deployment and scaling, secure application stack upgrades, and versioning.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/virtual-machines/windows/create-vm-generalized-managed",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/VirtualMachines/approved-machine-image.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Re-create the required VM instances using the approved (golden) machine image.",
"Url": "https://docs.microsoft.com/en-us/azure/virtual-machines/windows/create-vm-generalized-managed"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check only validates if the VM was launched from a custom image. It does not validate the image content or security baseline."
}
@@ -0,0 +1,33 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.vm.vm_client import vm_client
class vm_ensure_using_approved_images(Check):
"""
Ensure that Azure VMs are using an approved (custom) machine image.
This check evaluates whether Azure Virtual Machines are launched from an approved (custom) machine image by checking the image reference ID format.
- PASS: The Azure VM is using an approved custom machine image.
- FAIL: The Azure VM is not using an approved custom machine image.
"""
def execute(self):
findings = []
for subscription_name, vms in vm_client.virtual_machines.items():
for vm in vms.values():
report = Check_Report_Azure(metadata=self.metadata(), resource=vm)
report.subscription = subscription_name
image_id = getattr(vm, "image_reference", None)
if (
image_id
and image_id.startswith("/subscriptions/")
and "/providers/Microsoft.Compute/images/" in image_id
):
report.status = "PASS"
report.status_extended = f"VM {vm.resource_name} in subscription {subscription_name} is using an approved machine image: {image_id.split('/')[-1]}."
else:
report.status = "FAIL"
report.status_extended = f"VM {vm.resource_name} in subscription {subscription_name} is not using an approved machine image."
findings.append(report)
return findings
@@ -104,6 +104,11 @@ class VirtualMachines(AzureService):
location=vm.location,
security_profile=getattr(vm, "security_profile", None),
extensions=extensions,
image_reference=getattr(
getattr(storage_profile, "image_reference", None),
"id",
None,
),
linux_configuration=linux_configuration,
)
}
@@ -204,6 +209,7 @@ class VirtualMachine(BaseModel):
security_profile: Optional[SecurityProfile]
extensions: list[VirtualMachineExtension]
storage_profile: Optional[StorageProfile] = None
image_reference: Optional[str] = None
linux_configuration: Optional[LinuxConfiguration] = None
@@ -0,0 +1,165 @@
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_ID,
set_mocked_azure_provider,
)
class Test_vm_ensure_using_approved_images:
def test_no_subscriptions(self):
vm_client = mock.MagicMock()
vm_client.virtual_machines = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images import (
vm_ensure_using_approved_images,
)
check = vm_ensure_using_approved_images()
result = check.execute()
assert len(result) == 0
def test_empty_vms_in_subscription(self):
vm_client = mock.MagicMock()
vm_client.virtual_machines = {AZURE_SUBSCRIPTION_ID: {}}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images import (
vm_ensure_using_approved_images,
)
check = vm_ensure_using_approved_images()
result = check.execute()
assert len(result) == 0
def test_vm_with_approved_image(self):
vm_id = str(uuid4())
approved_image_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/rg/providers/Microsoft.Compute/images/custom-image"
vm = VirtualMachine(
resource_id=vm_id,
resource_name="VMTestApproved",
location="westeurope",
security_profile=None,
extensions=[],
storage_profile=None,
image_reference=approved_image_id,
)
vm_client = mock.MagicMock()
vm_client.virtual_machines = {AZURE_SUBSCRIPTION_ID: {vm_id: vm}}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images import (
vm_ensure_using_approved_images,
)
check = vm_ensure_using_approved_images()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_name == "VMTestApproved"
assert result[0].resource_id == vm_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
expected_status_extended = f"VM VMTestApproved in subscription {AZURE_SUBSCRIPTION_ID} is using an approved machine image: custom-image."
assert result[0].status_extended == expected_status_extended
def test_vm_with_not_approved_image(self):
vm_id = str(uuid4())
not_approved_image_id = "/subscriptions/other/resourceGroups/rg/providers/Microsoft.Compute/otherResource/other-image"
vm = VirtualMachine(
resource_id=vm_id,
resource_name="VMTestNotApproved",
location="westeurope",
security_profile=None,
extensions=[],
storage_profile=None,
image_reference=not_approved_image_id,
)
vm_client = mock.MagicMock()
vm_client.virtual_machines = {AZURE_SUBSCRIPTION_ID: {vm_id: vm}}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images import (
vm_ensure_using_approved_images,
)
check = vm_ensure_using_approved_images()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "VMTestNotApproved"
assert result[0].resource_id == vm_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
expected_status_extended = f"VM VMTestNotApproved in subscription {AZURE_SUBSCRIPTION_ID} is not using an approved machine image."
assert result[0].status_extended == expected_status_extended
def test_vm_with_missing_image_reference(self):
vm_id = str(uuid4())
vm = VirtualMachine(
resource_id=vm_id,
resource_name="VMTestNoImageRef",
location="westeurope",
security_profile=None,
extensions=[],
storage_profile=None,
image_reference=None,
)
vm_client = mock.MagicMock()
vm_client.virtual_machines = {AZURE_SUBSCRIPTION_ID: {vm_id: vm}}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_ensure_using_approved_images.vm_ensure_using_approved_images import (
vm_ensure_using_approved_images,
)
check = vm_ensure_using_approved_images()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "VMTestNoImageRef"
assert result[0].resource_id == vm_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
expected_status_extended = f"VM VMTestNoImageRef in subscription {AZURE_SUBSCRIPTION_ID} is not using an approved machine image."
assert result[0].status_extended == expected_status_extended