feat(azure): add cosmosdb_account_backup_policy_continuous check (#11032)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
s1ns3nz0
2026-06-15 19:20:38 +02:00
committed by GitHub
co-authored by Daniel Barranquero
parent 36fe48dbc5
commit b5bb85c956
8 changed files with 233 additions and 3 deletions
+1
View File
@@ -13,6 +13,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `cloudsql_instance_high_availability_enabled` check for GCP provider, verifying Cloud SQL primary instances use `REGIONAL` availability for automatic zone failover [(#11024)](https://github.com/prowler-cloud/prowler/pull/11024)
- `identity_storage_service_level_admins_scoped` check for OCI provider CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions [(#11523)](https://github.com/prowler-cloud/prowler/pull/11523)
- `cosmosdb_account_automatic_failover_enabled` check for Azure provider [(#11031)](https://github.com/prowler-cloud/prowler/pull/11031)
- `cosmosdb_account_backup_policy_continuous` check for Azure provider [(#11032)](https://github.com/prowler-cloud/prowler/pull/11032)
### 🔄 Changed
+2 -1
View File
@@ -438,7 +438,8 @@
"storage_geo_redundant_enabled",
"keyvault_recoverable",
"sqlserver_auditing_retention_90_days",
"postgresql_flexible_server_log_retention_days_greater_3"
"postgresql_flexible_server_log_retention_days_greater_3",
"cosmosdb_account_backup_policy_continuous"
]
},
{
@@ -1266,7 +1266,9 @@
"Check_Summary": "Backup copies of information, software and systems should be maintained and regularly tested in accordance with the agreed topic-specific policy on backup."
}
],
"Checks": []
"Checks": [
"cosmosdb_account_backup_policy_continuous"
]
},
{
"Id": "A.8.14",
+2 -1
View File
@@ -1088,7 +1088,8 @@
"storage_geo_redundant_enabled",
"vm_scaleset_associated_with_load_balancer",
"vm_scaleset_not_empty",
"cosmosdb_account_automatic_failover_enabled"
"cosmosdb_account_automatic_failover_enabled",
"cosmosdb_account_backup_policy_continuous"
],
"gcp": [
"compute_instance_automatic_restart_enabled",
@@ -0,0 +1,38 @@
{
"Provider": "azure",
"CheckID": "cosmosdb_account_backup_policy_continuous",
"CheckTitle": "Cosmos DB account uses continuous backup policy",
"CheckType": [],
"ServiceName": "cosmosdb",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "microsoft.documentdb/databaseaccounts",
"ResourceGroup": "database",
"Description": "**Azure Cosmos DB accounts** are evaluated for **continuous backup** policy. Continuous backup provides **point-in-time restore (PITR)** enabling recovery to any point within the retention window, unlike periodic backup which only supports full restores at fixed intervals.",
"Risk": "**Periodic backup** limits recovery to the last backup snapshot. Data changes between snapshots are lost during restore. **Continuous backup** enables **granular recovery** from accidental deletes, corruption, or ransomware.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"https://learn.microsoft.com/en-us/azure/cosmos-db/migrate-continuous-backup",
"https://learn.microsoft.com/en-us/azure/cosmos-db/restore-account-continuous-backup"
],
"Remediation": {
"Code": {
"CLI": "az cosmosdb update --name <COSMOS_ACCOUNT_NAME> --resource-group <RESOURCE_GROUP> --backup-policy-type Continuous --continuous-tier Continuous30Days",
"NativeIaC": "```bicep\n// Bicep: Switch a Cosmos DB account to Continuous backup (irreversible)\nresource account 'Microsoft.DocumentDB/databaseAccounts@2025-10-15' = {\n name: '<example_resource_name>'\n location: resourceGroup().location\n kind: 'GlobalDocumentDB'\n properties: {\n databaseAccountOfferType: 'Standard'\n locations: [{ locationName: resourceGroup().location }]\n backupPolicy: {\n type: 'Continuous' // Critical: Enables point-in-time restore. Migration from Periodic is one-way.\n continuousModeProperties: {\n tier: 'Continuous30Days' // or 'Continuous7Days' for the lower-cost tier\n }\n }\n }\n}\n```",
"Other": "1. Sign in to the Azure portal and open your Cosmos DB account\n2. In the left menu, select Backup & Restore\n3. Click Switch to Continuous backup\n4. Select the retention tier (Continuous30Days or Continuous7Days)\n5. Acknowledge that the migration from Periodic to Continuous is irreversible\n6. Click Save",
"Terraform": "```hcl\n# Terraform: Configure Cosmos DB account with Continuous backup (irreversible)\nresource \"azurerm_cosmosdb_account\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"<example_resource_name>\"\n offer_type = \"Standard\"\n kind = \"GlobalDocumentDB\"\n\n geo_location {\n location = \"<example_resource_name>\"\n failover_priority = 0\n }\n\n backup {\n type = \"Continuous\" # Critical: Enables point-in-time restore. One-way migration from Periodic.\n tier = \"Continuous30Days\" # or \"Continuous7Days\" for the lower-cost tier\n }\n}\n```"
},
"Recommendation": {
"Text": "Use **Continuous backup** for Cosmos DB accounts that require **point-in-time restore (PITR)**. Pick the retention tier (`Continuous7Days` or `Continuous30Days`) based on recovery objectives, and validate restore procedures with periodic drills. Note that switching from **Periodic** to **Continuous** is a **one-way** migration; plan the change and review pricing before applying.",
"Url": "https://hub.prowler.com/check/cosmosdb_account_backup_policy_continuous"
}
},
"Categories": [
"forensics-ready"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,30 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.cosmosdb.cosmosdb_client import cosmosdb_client
class cosmosdb_account_backup_policy_continuous(Check):
"""Ensure that Cosmos DB accounts use the continuous backup policy."""
def execute(self) -> Check_Report_Azure:
"""Execute the Cosmos DB continuous-backup check.
Iterates over every Cosmos DB account fetched by the service and reports
PASS when `backupPolicy.type` is `Continuous`, FAIL otherwise (including
when the property is missing).
Returns:
A list of Check_Report_Azure with one report per Cosmos DB account.
"""
findings = []
for subscription, accounts in cosmosdb_client.accounts.items():
for account in accounts:
report = Check_Report_Azure(metadata=self.metadata(), resource=account)
report.subscription = subscription
report.status = "FAIL"
report.status_extended = f"CosmosDB account {account.name} from subscription {subscription} does not use continuous backup policy."
if account.backup_policy_type == "Continuous":
report.status = "PASS"
report.status_extended = f"CosmosDB account {account.name} from subscription {subscription} uses continuous backup policy."
findings.append(report)
return findings
@@ -0,0 +1,157 @@
from unittest import mock
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_cosmosdb_account_backup_policy_continuous:
def test_no_subscriptions(self):
cosmosdb_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous.cosmosdb_client",
new=cosmosdb_client,
),
):
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous import (
cosmosdb_account_backup_policy_continuous,
)
cosmosdb_client.accounts = {}
check = cosmosdb_account_backup_policy_continuous()
result = check.execute()
assert len(result) == 0
def test_pass(self):
cosmosdb_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous.cosmosdb_client",
new=cosmosdb_client,
),
):
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous import (
cosmosdb_account_backup_policy_continuous,
)
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
Account,
)
cosmosdb_client.accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
name="test-account",
kind="GlobalDocumentDB",
type="Microsoft.DocumentDB/databaseAccounts",
tags={},
is_virtual_network_filter_enabled=False,
location="eastus",
private_endpoint_connections=[],
disable_local_auth=False,
backup_policy_type="Continuous",
)
]
}
check = cosmosdb_account_backup_policy_continuous()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_fail_periodic(self):
cosmosdb_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous.cosmosdb_client",
new=cosmosdb_client,
),
):
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous import (
cosmosdb_account_backup_policy_continuous,
)
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
Account,
)
cosmosdb_client.accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
name="test-account",
kind="GlobalDocumentDB",
type="Microsoft.DocumentDB/databaseAccounts",
tags={},
is_virtual_network_filter_enabled=False,
location="eastus",
private_endpoint_connections=[],
disable_local_auth=False,
backup_policy_type="Periodic",
)
]
}
check = cosmosdb_account_backup_policy_continuous()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_fail_no_backup_policy(self):
cosmosdb_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous.cosmosdb_client",
new=cosmosdb_client,
),
):
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_backup_policy_continuous.cosmosdb_account_backup_policy_continuous import (
cosmosdb_account_backup_policy_continuous,
)
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
Account,
)
cosmosdb_client.accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
name="test-account",
kind="GlobalDocumentDB",
type="Microsoft.DocumentDB/databaseAccounts",
tags={},
is_virtual_network_filter_enabled=False,
location="eastus",
private_endpoint_connections=[],
disable_local_auth=False,
backup_policy_type=None,
)
]
}
check = cosmosdb_account_backup_policy_continuous()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"