mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-05 03:12:14 +00:00
feat(azure): add cosmosdb_account_backup_policy_continuous check (#11032)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
co-authored by
Daniel Barranquero
parent
36fe48dbc5
commit
b5bb85c956
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+38
@@ -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": ""
|
||||
}
|
||||
+30
@@ -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
|
||||
+157
@@ -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"
|
||||
Reference in New Issue
Block a user