mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
Merge branch 'master' into PROWLER-415-open-stack-provider-in-the-cli-sdk
This commit is contained in:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
All notable changes to the **Prowler API** are documented in this file.
|
All notable changes to the **Prowler API** are documented in this file.
|
||||||
|
|
||||||
|
## [1.18.1] (Prowler v5.17.1)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improve API startup process by `manage.py` argument detection [(#9856)](https://github.com/prowler-cloud/prowler/pull/9856)
|
||||||
|
- Deleting providers don't try to delete a `None` Neo4j database when an Attack Paths scan is scheduled [(#9858)](https://github.com/prowler-cloud/prowler/pull/9858)
|
||||||
|
|
||||||
## [1.18.0] (Prowler v5.17.0)
|
## [1.18.0] (Prowler v5.17.0)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -19,7 +26,6 @@ All notable changes to the **Prowler API** are documented in this file.
|
|||||||
- `pyasn1` to v0.6.2 to address [CVE-2026-23490](https://nvd.nist.gov/vuln/detail/CVE-2026-23490) [(#9818)](https://github.com/prowler-cloud/prowler/pull/9818)
|
- `pyasn1` to v0.6.2 to address [CVE-2026-23490](https://nvd.nist.gov/vuln/detail/CVE-2026-23490) [(#9818)](https://github.com/prowler-cloud/prowler/pull/9818)
|
||||||
- `django-allauth[saml]` to v65.13.0 to address [CVE-2025-65431](https://nvd.nist.gov/vuln/detail/CVE-2025-65431) [(#9575)](https://github.com/prowler-cloud/prowler/pull/9575)
|
- `django-allauth[saml]` to v65.13.0 to address [CVE-2025-65431](https://nvd.nist.gov/vuln/detail/CVE-2025-65431) [(#9575)](https://github.com/prowler-cloud/prowler/pull/9575)
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.17.1] (Prowler v5.16.1)
|
## [1.17.1] (Prowler v5.16.1)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class RetryableSession:
|
|||||||
except (
|
except (
|
||||||
neo4j.exceptions.ServiceUnavailable,
|
neo4j.exceptions.ServiceUnavailable,
|
||||||
ConnectionResetError,
|
ConnectionResetError,
|
||||||
|
BrokenPipeError,
|
||||||
) as exc: # pragma: no cover - depends on infra
|
) as exc: # pragma: no cover - depends on infra
|
||||||
last_exc = exc
|
last_exc = exc
|
||||||
attempt += 1
|
attempt += 1
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
from cartography.config import Config as CartographyConfig
|
from cartography.config import Config as CartographyConfig
|
||||||
|
|
||||||
from api.db_utils import rls_transaction
|
from api.db_utils import rls_transaction
|
||||||
@@ -153,9 +154,15 @@ def get_provider_graph_database_names(tenant_id: str, provider_id: str) -> list[
|
|||||||
Note: For accesing the `AttackPathsScan` we need to use `all_objects` manager because the provider is soft-deleted.
|
Note: For accesing the `AttackPathsScan` we need to use `all_objects` manager because the provider is soft-deleted.
|
||||||
"""
|
"""
|
||||||
with rls_transaction(tenant_id):
|
with rls_transaction(tenant_id):
|
||||||
graph_databases_names_qs = ProwlerAPIAttackPathsScan.all_objects.filter(
|
graph_databases_names_qs = (
|
||||||
provider_id=provider_id,
|
ProwlerAPIAttackPathsScan.all_objects.filter(
|
||||||
is_graph_database_deleted=False,
|
~Q(graph_database=""),
|
||||||
).values_list("graph_database", flat=True)
|
graph_database__isnull=False,
|
||||||
|
provider_id=provider_id,
|
||||||
|
is_graph_database_deleted=False,
|
||||||
|
)
|
||||||
|
.values_list("graph_database", flat=True)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
return list(graph_databases_names_qs)
|
return list(graph_databases_names_qs)
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `OpenStack` provider support with Compute service including 1 security check [(#9811)](https://github.com/prowler-cloud/prowler/pull/9811)
|
- `OpenStack` provider support with Compute service including 1 security check [(#9811)](https://github.com/prowler-cloud/prowler/pull/9811)
|
||||||
|
- `compute_instance_suspended_without_persistent_disks` check for GCP provider [(#9747)](https://github.com/prowler-cloud/prowler/pull/9747)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Update Azure App Service service metadata to new format [(#9613)](https://github.com/prowler-cloud/prowler/pull/9613)
|
- Update Azure App Service service metadata to new format [(#9613)](https://github.com/prowler-cloud/prowler/pull/9613)
|
||||||
|
- Update Azure Application Insights service metadata to new format [(#9614)](https://github.com/prowler-cloud/prowler/pull/9614)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,36 @@
|
|||||||
{
|
{
|
||||||
"Provider": "azure",
|
"Provider": "azure",
|
||||||
"CheckID": "appinsights_ensure_is_configured",
|
"CheckID": "appinsights_ensure_is_configured",
|
||||||
"CheckTitle": "Ensure Application Insights are Configured.",
|
"CheckTitle": "Subscription has at least one Application Insights resource configured",
|
||||||
"CheckType": [],
|
"CheckType": [],
|
||||||
"ServiceName": "appinsights",
|
"ServiceName": "appinsights",
|
||||||
"SubServiceName": "",
|
"SubServiceName": "",
|
||||||
"ResourceIdTemplate": "",
|
"ResourceIdTemplate": "",
|
||||||
"Severity": "low",
|
"Severity": "low",
|
||||||
"ResourceType": "Microsoft.Insights/components",
|
"ResourceType": "microsoft.insights/components",
|
||||||
"ResourceGroup": "monitoring",
|
"ResourceGroup": "monitoring",
|
||||||
"Description": "Application Insights within Azure act as an Application Performance Monitoring solution providing valuable data into how well an application performs and additional information when performing incident response. The types of log data collected include application metrics, telemetry data, and application trace logging data providing organizations with detailed information about application activity and application transactions. Both data sets help organizations adopt a proactive and retroactive means to handle security and performance related metrics within their modern applications.",
|
"Description": "**Azure subscription** contains at least one **Application Insights** resource collecting application telemetry (metrics, traces, logs) for monitored workloads.\n\nThe check determines whether telemetry collection exists at the subscription level, indicating that application monitoring is configured.",
|
||||||
"Risk": "Configuring Application Insights provides additional data not found elsewhere within Azure as part of a much larger logging and monitoring program within an organization's Information Security practice. The types and contents of these logs will act as both a potential cost saving measure (application performance) and a means to potentially confirm the source of a potential incident (trace logging). Metrics and Telemetry data provide organizations with a proactive approach to cost savings by monitoring an application's performance, while the trace logging data provides necessary details in a reactive incident response scenario by helping organizations identify the potential source of an incident within their application.",
|
"Risk": "If **Application Insights** is missing, applications run with reduced **observability**, limiting detection of anomalies and attacks.\n\nThis undermines **integrity** and accountability (fewer traces), degrades **availability** by slowing troubleshooting, and increases exposure to undetected data exfiltration or injection at the app layer.",
|
||||||
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview",
|
"RelatedUrl": "",
|
||||||
|
"AdditionalURLs": [
|
||||||
|
"https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview",
|
||||||
|
"https://www.tenable.com/audits/items/CIS_Microsoft_Azure_Foundations_v2.0.0_L2.audit:8a7a608d180042689ad9d3f16aa359f1"
|
||||||
|
],
|
||||||
"Remediation": {
|
"Remediation": {
|
||||||
"Code": {
|
"Code": {
|
||||||
"CLI": "az monitor app-insights component create --app <app name> --resource-group <resource group name> --location <location> --kind 'web' --retention-time <INT days to retain logs> --workspace <log analytics workspace ID> -- subscription <subscription ID>",
|
"CLI": "az monitor app-insights component create --app <APP_NAME> --resource-group <RESOURCE_GROUP> --location <LOCATION> --application-type web --subscription <SUBSCRIPTION_ID>",
|
||||||
"NativeIaC": "",
|
"NativeIaC": "```bicep\n// Create a minimal Application Insights resource\nresource appInsights 'Microsoft.Insights/components@2020-02-02' = {\n name: '<example_resource_name>'\n location: '<location>'\n properties: {\n Application_Type: 'web' // Critical: creates the App Insights component required to pass the check\n }\n}\n```",
|
||||||
"Other": "https://www.tenable.com/audits/items/CIS_Microsoft_Azure_Foundations_v2.0.0_L2.audit:8a7a608d180042689ad9d3f16aa359f1",
|
"Other": "1. In the Azure portal, go to Azure Monitor > Application Insights\n2. Click Create\n3. Select a Subscription and Resource group\n4. Enter a Name and choose a Region\n5. Click Review + create, then Create\n6. Verify the resource appears under Application Insights in the selected subscription",
|
||||||
"Terraform": ""
|
"Terraform": "```hcl\n# Critical: This resource creates an Application Insights component to satisfy the check\nresource \"azurerm_application_insights\" \"main\" {\n name = \"<example_resource_name>\"\n location = \"<location>\"\n resource_group_name = \"<example_resource_name>\"\n application_type = \"web\" # Critical: ensures creation of the component\n}\n```"
|
||||||
},
|
},
|
||||||
"Recommendation": {
|
"Recommendation": {
|
||||||
"Text": "1. Navigate to Application Insights 2. Under the Basics tab within the PROJECT DETAILS section, select the Subscription 3. Select the Resource group 4. Within the INSTANCE DETAILS, enter a Name 5. Select a Region 6. Next to Resource Mode, select Workspace-based 7. Within the WORKSPACE DETAILS, select the Subscription for the log analytics workspace 8. Select the appropriate Log Analytics Workspace 9. Click Next:Tags > 10. Enter the appropriate Tags as Name, Value pairs. 11. Click Next:Review+Create 12. Click Create.",
|
"Text": "Deploy **Application Insights** for all critical workloads and centralize data in a **Log Analytics workspace**. Configure actionable alerts and dashboards, enforce **least privilege** on telemetry, and set retention/export policies. Use private connectivity and appropriate sampling, and integrate with SIEM for **defense in depth**.",
|
||||||
"Url": ""
|
"Url": "https://hub.prowler.com/check/appinsights_ensure_is_configured"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Categories": [],
|
"Categories": [
|
||||||
|
"logging"
|
||||||
|
],
|
||||||
"DependsOn": [],
|
"DependsOn": [],
|
||||||
"RelatedTo": [],
|
"RelatedTo": [],
|
||||||
"Notes": "Because Application Insights relies on a Log Analytics Workspace, an organization will incur additional expenses when using this service."
|
"Notes": "Because Application Insights relies on a Log Analytics Workspace, an organization will incur additional expenses when using this service."
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"Provider": "gcp",
|
||||||
|
"CheckID": "compute_instance_suspended_without_persistent_disks",
|
||||||
|
"CheckTitle": "Suspended VM instance does not have persistent disks attached",
|
||||||
|
"CheckType": [],
|
||||||
|
"ServiceName": "compute",
|
||||||
|
"SubServiceName": "",
|
||||||
|
"ResourceIdTemplate": "",
|
||||||
|
"Severity": "medium",
|
||||||
|
"ResourceType": "compute.googleapis.com/Instance",
|
||||||
|
"ResourceGroup": "compute",
|
||||||
|
"Description": "This check identifies VM instances in a **SUSPENDED** or **SUSPENDING** state with persistent disks still attached.\n\nPersistent disks on suspended VMs remain accessible through the GCP API and could contain **sensitive data** while the instance is inactive, potentially creating security blind spots in long-forgotten infrastructure.",
|
||||||
|
"Risk": "Persistent disks on suspended VM instances remain accessible through the GCP API and may contain **sensitive data**, creating potential security risks:\n\n- **Unauthorized data access** if credentials are compromised or permissions are misconfigured\n- **Data exposure** from forgotten infrastructure that is no longer actively monitored\n- **Security blind spots** where suspended resources are overlooked during security reviews and audits",
|
||||||
|
"RelatedUrl": "",
|
||||||
|
"AdditionalURLs": [
|
||||||
|
"https://cloud.google.com/icompute/docs/instances/suspend-resume-instance",
|
||||||
|
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/ComputeEngine/persistent-disks-attached-to-suspended-vms.html"
|
||||||
|
],
|
||||||
|
"Remediation": {
|
||||||
|
"Code": {
|
||||||
|
"CLI": "gcloud compute instances delete INSTANCE_NAME --zone=ZONE",
|
||||||
|
"NativeIaC": "",
|
||||||
|
"Other": "1. Open the Google Cloud Console\n2. Navigate to Compute Engine > VM instances\n3. Identify suspended instances with attached disks\n4. If the instance is no longer needed, select it and click DELETE\n5. If the instance will be resumed, take no action or resume it with: gcloud compute instances resume INSTANCE_NAME --zone=ZONE",
|
||||||
|
"Terraform": "```hcl\n# To remediate, either delete the suspended instance or resume it\n# Delete by removing the resource from your Terraform configuration\n# Or resume by changing the desired_status\nresource \"google_compute_instance\" \"example_resource\" {\n name = \"example-instance\"\n machine_type = \"e2-medium\"\n zone = \"us-central1-a\"\n\n # Set desired_status to RUNNING to resume the instance\n desired_status = \"RUNNING\"\n\n boot_disk {\n initialize_params {\n image = \"debian-cloud/debian-11\"\n }\n }\n\n network_interface {\n network = \"default\"\n }\n}\n```"
|
||||||
|
},
|
||||||
|
"Recommendation": {
|
||||||
|
"Text": "Regularly review suspended VM instances to reduce your attack surface. Either **resume** instances if still needed, or **delete** them along with their attached disks to eliminate potential data exposure. Implement automated policies to detect and alert on long-suspended instances as part of your security monitoring.",
|
||||||
|
"Url": "https://hub.prowler.com/check/compute_instance_suspended_without_persistent_disks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Categories": [],
|
||||||
|
"DependsOn": [],
|
||||||
|
"RelatedTo": [
|
||||||
|
"compute_instance_disk_auto_delete_disabled"
|
||||||
|
],
|
||||||
|
"Notes": "This check is focused on security risks rather than cost optimization. Persistent disks on suspended VMs remain accessible and may contain sensitive data, creating potential unauthorized access risks."
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||||
|
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||||
|
|
||||||
|
|
||||||
|
class compute_instance_suspended_without_persistent_disks(Check):
|
||||||
|
"""
|
||||||
|
Ensure that VM instances in SUSPENDED state do not have persistent disks attached.
|
||||||
|
|
||||||
|
This check identifies VM instances that are in a SUSPENDED or SUSPENDING state
|
||||||
|
and have persistent disks still attached. Suspended VMs with attached disks
|
||||||
|
represent unused infrastructure that continues to incur storage costs.
|
||||||
|
|
||||||
|
- PASS: VM instance is not in SUSPENDED/SUSPENDING state, or is suspended but has no disks attached.
|
||||||
|
- FAIL: VM instance is in SUSPENDED/SUSPENDING state with persistent disks attached.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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} is not suspended."
|
||||||
|
|
||||||
|
if instance.status in ("SUSPENDED", "SUSPENDING"):
|
||||||
|
attached_disks = [disk.name for disk in instance.disks]
|
||||||
|
|
||||||
|
if attached_disks:
|
||||||
|
report.status = "FAIL"
|
||||||
|
report.status_extended = f"VM Instance {instance.name} is {instance.status.lower()} with {len(attached_disks)} persistent disk(s) attached: {', '.join(attached_disks)}."
|
||||||
|
else:
|
||||||
|
report.status_extended = f"VM Instance {instance.name} is {instance.status.lower()} but has no persistent disks attached."
|
||||||
|
|
||||||
|
findings.append(report)
|
||||||
|
|
||||||
|
return findings
|
||||||
@@ -198,6 +198,7 @@ class Compute(GCPService):
|
|||||||
"deletionProtection", False
|
"deletionProtection", False
|
||||||
),
|
),
|
||||||
network_interfaces=network_interfaces,
|
network_interfaces=network_interfaces,
|
||||||
|
status=instance.get("status", "RUNNING"),
|
||||||
on_host_maintenance=instance.get("scheduling", {}).get(
|
on_host_maintenance=instance.get("scheduling", {}).get(
|
||||||
"onHostMaintenance", "MIGRATE"
|
"onHostMaintenance", "MIGRATE"
|
||||||
),
|
),
|
||||||
@@ -700,6 +701,7 @@ class Instance(BaseModel):
|
|||||||
provisioning_model: str = "STANDARD"
|
provisioning_model: str = "STANDARD"
|
||||||
deletion_protection: bool = False
|
deletion_protection: bool = False
|
||||||
network_interfaces: list[NetworkInterface] = []
|
network_interfaces: list[NetworkInterface] = []
|
||||||
|
status: str = "RUNNING"
|
||||||
on_host_maintenance: str = "MIGRATE"
|
on_host_maintenance: str = "MIGRATE"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,538 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from tests.providers.gcp.gcp_fixtures import (
|
||||||
|
GCP_PROJECT_ID,
|
||||||
|
GCP_US_CENTER1_LOCATION,
|
||||||
|
set_mocked_gcp_provider,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestComputeInstanceSuspendedWithoutPersistentDisks:
|
||||||
|
|
||||||
|
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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
def test_instance_running_with_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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="running-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,
|
||||||
|
status="RUNNING",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== "VM Instance running-instance is not suspended."
|
||||||
|
)
|
||||||
|
assert result[0].resource_id == "1234567890"
|
||||||
|
assert result[0].resource_name == "running-instance"
|
||||||
|
assert result[0].location == GCP_US_CENTER1_LOCATION
|
||||||
|
assert result[0].project_id == GCP_PROJECT_ID
|
||||||
|
|
||||||
|
def test_instance_suspended_with_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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="suspended-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,
|
||||||
|
status="SUSPENDED",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== "VM Instance suspended-instance is suspended with 2 persistent disk(s) attached: boot-disk, data-disk."
|
||||||
|
)
|
||||||
|
assert result[0].resource_id == "1234567890"
|
||||||
|
assert result[0].resource_name == "suspended-instance"
|
||||||
|
assert result[0].location == GCP_US_CENTER1_LOCATION
|
||||||
|
assert result[0].project_id == GCP_PROJECT_ID
|
||||||
|
|
||||||
|
def test_instance_suspending_with_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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="suspending-instance",
|
||||||
|
id="9876543210",
|
||||||
|
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="boot-disk",
|
||||||
|
auto_delete=True,
|
||||||
|
boot=True,
|
||||||
|
encryption=False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
project_id=GCP_PROJECT_ID,
|
||||||
|
status="SUSPENDING",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== "VM Instance suspending-instance is suspending with 1 persistent disk(s) attached: boot-disk."
|
||||||
|
)
|
||||||
|
assert result[0].resource_id == "9876543210"
|
||||||
|
assert result[0].resource_name == "suspending-instance"
|
||||||
|
|
||||||
|
def test_instance_suspended_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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="suspended-no-disks",
|
||||||
|
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=[],
|
||||||
|
project_id=GCP_PROJECT_ID,
|
||||||
|
status="SUSPENDED",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== "VM Instance suspended-no-disks is suspended but has no persistent disks attached."
|
||||||
|
)
|
||||||
|
assert result[0].resource_id == "1111111111"
|
||||||
|
assert result[0].resource_name == "suspended-no-disks"
|
||||||
|
|
||||||
|
def test_instance_terminated_with_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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="terminated-instance",
|
||||||
|
id="2222222222",
|
||||||
|
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,
|
||||||
|
status="TERMINATED",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== "VM Instance terminated-instance is not suspended."
|
||||||
|
)
|
||||||
|
assert result[0].resource_id == "2222222222"
|
||||||
|
assert result[0].resource_name == "terminated-instance"
|
||||||
|
|
||||||
|
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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="running-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,
|
||||||
|
status="RUNNING",
|
||||||
|
),
|
||||||
|
Instance(
|
||||||
|
name="suspended-with-disks",
|
||||||
|
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="persistent-disk",
|
||||||
|
auto_delete=True,
|
||||||
|
boot=True,
|
||||||
|
encryption=False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
project_id=GCP_PROJECT_ID,
|
||||||
|
status="SUSPENDED",
|
||||||
|
),
|
||||||
|
Instance(
|
||||||
|
name="suspended-no-disks",
|
||||||
|
id="3333333333",
|
||||||
|
zone=f"{GCP_US_CENTER1_LOCATION}-c",
|
||||||
|
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=[],
|
||||||
|
project_id=GCP_PROJECT_ID,
|
||||||
|
status="SUSPENDED",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 3
|
||||||
|
|
||||||
|
# First instance - RUNNING with disks (PASS)
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert result[0].resource_name == "running-instance"
|
||||||
|
assert "is not suspended" in result[0].status_extended
|
||||||
|
|
||||||
|
# Second instance - SUSPENDED with disks (FAIL)
|
||||||
|
assert result[1].status == "FAIL"
|
||||||
|
assert result[1].resource_name == "suspended-with-disks"
|
||||||
|
assert (
|
||||||
|
"is suspended with 1 persistent disk(s) attached"
|
||||||
|
in result[1].status_extended
|
||||||
|
)
|
||||||
|
|
||||||
|
# Third instance - SUSPENDED without disks (PASS)
|
||||||
|
assert result[2].status == "PASS"
|
||||||
|
assert result[2].resource_name == "suspended-no-disks"
|
||||||
|
assert (
|
||||||
|
"is suspended but has no persistent disks attached"
|
||||||
|
in result[2].status_extended
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_instance_stopping_with_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_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks.compute_client",
|
||||||
|
new=compute_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.gcp.services.compute.compute_instance_suspended_without_persistent_disks.compute_instance_suspended_without_persistent_disks import (
|
||||||
|
compute_instance_suspended_without_persistent_disks,
|
||||||
|
)
|
||||||
|
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="stopping-instance",
|
||||||
|
id="4444444444",
|
||||||
|
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,
|
||||||
|
status="STOPPING",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
check = compute_instance_suspended_without_persistent_disks()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== "VM Instance stopping-instance is not suspended."
|
||||||
|
)
|
||||||
|
assert result[0].resource_id == "4444444444"
|
||||||
|
assert result[0].resource_name == "stopping-instance"
|
||||||
Reference in New Issue
Block a user