feat(gcp): add compute check to ensure VM disks have auto-delete disabled (#9604)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
lydiavilchez
2025-12-23 10:57:11 +01:00
committed by GitHub
parent 3fbe157d10
commit ad5095595c
6 changed files with 480 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### Added
- Add Prowler ThreatScore for the Alibaba Cloud provider [(#9511)](https://github.com/prowler-cloud/prowler/pull/9511)
- `compute_instance_group_multiple_zones` check for GCP provider [(#9566)](https://github.com/prowler-cloud/prowler/pull/9566)
- `compute_instance_disk_auto_delete_disabled` check for GCP provider [(#9604)](https://github.com/prowler-cloud/prowler/pull/9604)
- Bedrock service pagination [(#9606)](https://github.com/prowler-cloud/prowler/pull/9606)
### Changed

View File

@@ -0,0 +1,36 @@
{
"Provider": "gcp",
"CheckID": "compute_instance_disk_auto_delete_disabled",
"CheckTitle": "VM instance attached disks have auto-delete disabled",
"CheckType": [],
"ServiceName": "compute",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "compute.googleapis.com/Instance",
"Description": "This check verifies whether GCP Compute Engine VM instances have **auto-delete** disabled for their attached persistent disks.\n\nWhen auto-delete is enabled, persistent disks are automatically removed when the associated VM instance is deleted, which can lead to unintended data loss.",
"Risk": "With auto-delete enabled, persistent disks are automatically deleted when the associated VM instance is terminated.\n\nThis could result in:\n- **Permanent data loss** if the instance is accidentally or intentionally deleted\n- **Recovery challenges** for mission-critical workloads\n- **Compliance violations** where data retention is required",
"RelatedUrl": "",
"AdditionalURLs": [
"https://cloud.google.com/compute/docs/disks/add-persistent-disk",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/ComputeEngine/disable-auto-delete.html"
],
"Remediation": {
"Code": {
"CLI": "gcloud compute instances set-disk-auto-delete INSTANCE_NAME --zone=ZONE --no-auto-delete --disk=DISK_NAME",
"NativeIaC": "",
"Other": "1. Open the Google Cloud Console\n2. Navigate to Compute Engine > VM instances\n3. Click the target VM instance name\n4. Click Edit\n5. In the Boot disk section, select 'Keep disk' from the 'When deleting instance' dropdown\n6. For Additional disks, click each disk and select 'Keep disk' under 'Deletion rule'\n7. Click Save",
"Terraform": "```hcl\nresource \"google_compute_instance\" \"example_resource\" {\n name = \"example-instance\"\n machine_type = \"e2-medium\"\n zone = \"us-central1-a\"\n\n boot_disk {\n # Disable auto-delete for the boot disk\n auto_delete = false\n\n initialize_params {\n image = \"debian-cloud/debian-11\"\n }\n }\n\n attached_disk {\n source = google_compute_disk.example_disk.id\n # Disable auto-delete for attached disks\n auto_delete = false\n }\n\n network_interface {\n network = \"default\"\n }\n}\n```"
},
"Recommendation": {
"Text": "Disable `auto-delete` for all persistent disks attached to **production** and **business-critical** VM instances to prevent **accidental data loss**. Regularly review disk configurations to ensure data retention requirements are met.",
"Url": "https://hub.prowler.com/check/compute_instance_disk_auto_delete_disabled"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,34 @@
from prowler.lib.check.models import Check, Check_Report_GCP
from prowler.providers.gcp.services.compute.compute_client import compute_client
class compute_instance_disk_auto_delete_disabled(Check):
"""
Ensure that VM instance attached disks have auto-delete disabled.
This check verifies whether GCP Compute Engine VM instances have auto-delete
disabled for their attached persistent disks to prevent accidental data loss
when the instance is terminated.
- PASS: All attached disks have auto-delete disabled.
- FAIL: One or more attached disks have auto-delete enabled.
"""
def execute(self) -> list[Check_Report_GCP]:
findings = []
for instance in compute_client.instances:
report = Check_Report_GCP(metadata=self.metadata(), resource=instance)
report.status = "PASS"
report.status_extended = f"VM Instance {instance.name} has auto-delete disabled for all attached disks."
auto_delete_disks = [
disk.name for disk in instance.disks if disk.auto_delete
]
if auto_delete_disks:
report.status = "FAIL"
report.status_extended = f"VM Instance {instance.name} has auto-delete enabled for the following disks: {', '.join(auto_delete_disks)}."
findings.append(report)
return findings

View File

@@ -138,6 +138,19 @@ class Compute(GCPService):
)
for disk in instance.get("disks", [])
],
disks=[
Disk(
name=disk["deviceName"],
auto_delete=disk.get("autoDelete", False),
boot=disk.get("boot", False),
encryption=bool(
disk.get("diskEncryptionKey", {}).get(
"sha256"
)
),
)
for disk in instance.get("disks", [])
],
automatic_restart=instance.get("scheduling", {}).get(
"automaticRestart", False
),
@@ -449,6 +462,13 @@ class Compute(GCPService):
)
class Disk(BaseModel):
name: str
auto_delete: bool = False
boot: bool
encryption: bool = False
class Instance(BaseModel):
name: str
id: str
@@ -463,6 +483,7 @@ class Instance(BaseModel):
service_accounts: list
ip_forward: bool
disks_encryption: list
disks: list[Disk] = []
automatic_restart: bool = False
preemptible: bool = False
provisioning_model: str = "STANDARD"

View File

@@ -0,0 +1,388 @@
from unittest import mock
from tests.providers.gcp.gcp_fixtures import (
GCP_PROJECT_ID,
GCP_US_CENTER1_LOCATION,
set_mocked_gcp_provider,
)
class TestComputeInstanceDiskAutoDeleteDisabled:
def test_compute_no_instances(self):
compute_client = mock.MagicMock()
compute_client.instances = []
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled import (
compute_instance_disk_auto_delete_disabled,
)
check = compute_instance_disk_auto_delete_disabled()
result = check.execute()
assert len(result) == 0
def test_instance_disk_auto_delete_disabled(self):
compute_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled import (
compute_instance_disk_auto_delete_disabled,
)
from prowler.providers.gcp.services.compute.compute_service import (
Disk,
Instance,
)
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.region = GCP_US_CENTER1_LOCATION
compute_client.instances = [
Instance(
name="test-instance",
id="1234567890",
zone=f"{GCP_US_CENTER1_LOCATION}-a",
region=GCP_US_CENTER1_LOCATION,
public_ip=False,
metadata={},
shielded_enabled_vtpm=True,
shielded_enabled_integrity_monitoring=True,
confidential_computing=False,
service_accounts=[
{"email": "123-compute@developer.gserviceaccount.com"}
],
ip_forward=False,
disks_encryption=[],
disks=[
Disk(
name="boot-disk",
auto_delete=False,
boot=True,
encryption=False,
),
Disk(
name="data-disk",
auto_delete=False,
boot=False,
encryption=False,
),
],
project_id=GCP_PROJECT_ID,
)
]
check = compute_instance_disk_auto_delete_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "VM Instance test-instance has auto-delete disabled for all attached disks."
)
assert result[0].resource_id == "1234567890"
assert result[0].resource_name == "test-instance"
assert result[0].location == GCP_US_CENTER1_LOCATION
assert result[0].project_id == GCP_PROJECT_ID
def test_instance_disk_auto_delete_enabled_single_disk(self):
compute_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled import (
compute_instance_disk_auto_delete_disabled,
)
from prowler.providers.gcp.services.compute.compute_service import (
Disk,
Instance,
)
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.region = GCP_US_CENTER1_LOCATION
compute_client.instances = [
Instance(
name="test-instance",
id="1234567890",
zone=f"{GCP_US_CENTER1_LOCATION}-a",
region=GCP_US_CENTER1_LOCATION,
public_ip=False,
metadata={},
shielded_enabled_vtpm=True,
shielded_enabled_integrity_monitoring=True,
confidential_computing=False,
service_accounts=[
{"email": "123-compute@developer.gserviceaccount.com"}
],
ip_forward=False,
disks_encryption=[],
disks=[
Disk(
name="boot-disk",
auto_delete=True,
boot=True,
encryption=False,
),
Disk(
name="data-disk",
auto_delete=False,
boot=False,
encryption=False,
),
],
project_id=GCP_PROJECT_ID,
)
]
check = compute_instance_disk_auto_delete_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "VM Instance test-instance has auto-delete enabled for the following disks: boot-disk."
)
assert result[0].resource_id == "1234567890"
assert result[0].resource_name == "test-instance"
assert result[0].location == GCP_US_CENTER1_LOCATION
assert result[0].project_id == GCP_PROJECT_ID
def test_instance_disk_auto_delete_enabled_multiple_disks(self):
compute_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled import (
compute_instance_disk_auto_delete_disabled,
)
from prowler.providers.gcp.services.compute.compute_service import (
Disk,
Instance,
)
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.region = GCP_US_CENTER1_LOCATION
compute_client.instances = [
Instance(
name="test-instance",
id="1234567890",
zone=f"{GCP_US_CENTER1_LOCATION}-a",
region=GCP_US_CENTER1_LOCATION,
public_ip=False,
metadata={},
shielded_enabled_vtpm=True,
shielded_enabled_integrity_monitoring=True,
confidential_computing=False,
service_accounts=[
{"email": "123-compute@developer.gserviceaccount.com"}
],
ip_forward=False,
disks_encryption=[],
disks=[
Disk(
name="boot-disk",
auto_delete=True,
boot=True,
encryption=False,
),
Disk(
name="data-disk",
auto_delete=True,
boot=False,
encryption=False,
),
],
project_id=GCP_PROJECT_ID,
)
]
check = compute_instance_disk_auto_delete_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "VM Instance test-instance has auto-delete enabled for the following disks: boot-disk, data-disk."
)
assert result[0].resource_id == "1234567890"
assert result[0].resource_name == "test-instance"
assert result[0].location == GCP_US_CENTER1_LOCATION
assert result[0].project_id == GCP_PROJECT_ID
def test_instance_no_disks(self):
compute_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled import (
compute_instance_disk_auto_delete_disabled,
)
from prowler.providers.gcp.services.compute.compute_service import Instance
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.region = GCP_US_CENTER1_LOCATION
compute_client.instances = [
Instance(
name="test-instance",
id="1234567890",
zone=f"{GCP_US_CENTER1_LOCATION}-a",
region=GCP_US_CENTER1_LOCATION,
public_ip=False,
metadata={},
shielded_enabled_vtpm=True,
shielded_enabled_integrity_monitoring=True,
confidential_computing=False,
service_accounts=[
{"email": "123-compute@developer.gserviceaccount.com"}
],
ip_forward=False,
disks_encryption=[],
disks=[],
project_id=GCP_PROJECT_ID,
)
]
check = compute_instance_disk_auto_delete_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "VM Instance test-instance has auto-delete disabled for all attached disks."
)
assert result[0].resource_id == "1234567890"
assert result[0].resource_name == "test-instance"
assert result[0].location == GCP_US_CENTER1_LOCATION
assert result[0].project_id == GCP_PROJECT_ID
def test_multiple_instances_mixed_results(self):
compute_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_disk_auto_delete_disabled.compute_instance_disk_auto_delete_disabled import (
compute_instance_disk_auto_delete_disabled,
)
from prowler.providers.gcp.services.compute.compute_service import (
Disk,
Instance,
)
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.region = GCP_US_CENTER1_LOCATION
compute_client.instances = [
Instance(
name="compliant-instance",
id="1111111111",
zone=f"{GCP_US_CENTER1_LOCATION}-a",
region=GCP_US_CENTER1_LOCATION,
public_ip=False,
metadata={},
shielded_enabled_vtpm=True,
shielded_enabled_integrity_monitoring=True,
confidential_computing=False,
service_accounts=[],
ip_forward=False,
disks_encryption=[],
disks=[
Disk(
name="boot-disk",
auto_delete=False,
boot=True,
encryption=False,
),
],
project_id=GCP_PROJECT_ID,
),
Instance(
name="non-compliant-instance",
id="2222222222",
zone=f"{GCP_US_CENTER1_LOCATION}-b",
region=GCP_US_CENTER1_LOCATION,
public_ip=False,
metadata={},
shielded_enabled_vtpm=True,
shielded_enabled_integrity_monitoring=True,
confidential_computing=False,
service_accounts=[],
ip_forward=False,
disks_encryption=[],
disks=[
Disk(
name="auto-delete-disk",
auto_delete=True,
boot=True,
encryption=False,
),
],
project_id=GCP_PROJECT_ID,
),
]
check = compute_instance_disk_auto_delete_disabled()
result = check.execute()
assert len(result) == 2
assert result[0].status == "PASS"
assert result[0].resource_name == "compliant-instance"
assert result[1].status == "FAIL"
assert result[1].resource_name == "non-compliant-instance"
assert "auto-delete-disk" in result[1].status_extended