feat(azure): add new check storage_geo_redundant_enabled (#7980)

Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
This commit is contained in:
Hugo Pereira Brito
2025-06-18 12:10:02 +02:00
committed by GitHub
parent 76f0d890e9
commit facc0627d7
7 changed files with 230 additions and 1 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
## [v5.8.0] (Prowler UNRELEASED)
### Added
- Add `storage_geo_redundant_enabled` check for Azure provider. [(#7980)](https://github.com/prowler-cloud/prowler/pull/7980)
- Add `storage_cross_tenant_replication_disabled` check for Azure provider. [(#7977)](https://github.com/prowler-cloud/prowler/pull/7977)
- CIS 1.11 compliance framework for Kubernetes [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790)
- Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720)
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "storage_geo_redundant_enabled",
"CheckTitle": "Ensure geo-redundant storage (GRS) is enabled on critical Azure Storage Accounts",
"CheckType": [],
"ServiceName": "storage",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AzureStorageAccount",
"Description": "Geo-redundant storage (GRS) must be enabled on critical Azure Storage Accounts to ensure data durability and availability in the event of a regional outage. GRS replicates data within the primary region and asynchronously to a secondary region, offering enhanced resilience and supporting disaster recovery strategies.",
"Risk": "Without GRS, critical data may be lost or become unavailable during a regional outage, compromising data durability and disaster recovery efforts.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy",
"Remediation": {
"Code": {
"CLI": "az storage account update --name <storage-account-name> --resource-group <resource-group-name> --sku Standard_GRS",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/StorageAccounts/enable-geo-redundant-storage.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable geo-redundant storage (GRS) for critical Azure Storage Accounts to ensure data durability and availability across regional failures.",
"Url": "https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,41 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.storage.storage_client import storage_client
from prowler.providers.azure.services.storage.storage_service import ReplicationSettings
class storage_geo_redundant_enabled(Check):
"""Check if geo-redundant storage (GRS) is enabled on critical Azure Storage Accounts.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> Check_Report_Azure:
"""Execute the check for geo-redundant storage (GRS).
This method checks if geo-redundant storage (GRS) is enabled on critical Azure Storage Accounts.
Returns:
Check_Report_Azure: A report containing the result of the check.
"""
findings = []
for subscription, storage_accounts in storage_client.storage_accounts.items():
for storage_account in storage_accounts:
report = Check_Report_Azure(
metadata=self.metadata(), resource=storage_account
)
report.subscription = subscription
if (
storage_account.replication_settings
== ReplicationSettings.STANDARD_GRS
):
report.status = "PASS"
report.status_extended = f"Storage account {storage_account.name} from subscription {subscription} has Geo-redundant storage (GRS) enabled."
else:
report.status = "FAIL"
report.status_extended = f"Storage account {storage_account.name} from subscription {subscription} does not have Geo-redundant storage (GRS) enabled."
findings.append(report)
return findings
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional
from azure.mgmt.storage import StorageManagementClient
@@ -34,6 +35,7 @@ class Storage(AzureService):
key_expiration_period_in_days = (
storage_account.key_policy.key_expiration_period_in_days
)
replication_settings = ReplicationSettings(storage_account.sku.name)
storage_accounts[subscription].append(
Account(
id=storage_account.id,
@@ -68,6 +70,7 @@ class Storage(AzureService):
],
key_expiration_period_in_days=key_expiration_period_in_days,
location=storage_account.location,
replication_settings=replication_settings,
allow_cross_tenant_replication=getattr(
storage_account, "allow_cross_tenant_replication", True
),
@@ -211,6 +214,17 @@ class PrivateEndpointConnection:
type: str
class ReplicationSettings(Enum):
STANDARD_LRS = "Standard_LRS"
STANDARD_GRS = "Standard_GRS"
STANDARD_RAGRS = "Standard_RAGRS"
STANDARD_ZRS = "Standard_ZRS"
PREMIUM_LRS = "Premium_LRS"
PREMIUM_ZRS = "Premium_ZRS"
STANDARD_GZRS = "Standard_GZRS"
STANDARD_RAGZRS = "Standard_RAGZRS"
@dataclass
class Account:
id: str
@@ -225,6 +239,7 @@ class Account:
private_endpoint_connections: List[PrivateEndpointConnection]
key_expiration_period_in_days: str
location: str
replication_settings: ReplicationSettings = ReplicationSettings.STANDARD_LRS
allow_cross_tenant_replication: bool = True
allow_shared_key_access: bool = True
blob_properties: Optional[BlobProperties] = None
@@ -0,0 +1,137 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import (
Account,
ReplicationSettings,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_storage_geo_redundant_enabled:
def test_no_storage_accounts(self):
storage_client = mock.MagicMock()
storage_client.storage_accounts = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled.storage_client",
new=storage_client,
),
):
from prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled import (
storage_geo_redundant_enabled,
)
check = storage_geo_redundant_enabled()
result = check.execute()
assert len(result) == 0
def test_storage_geo_redundant_enabled(self):
storage_account_id = str(uuid4())
storage_account_name = "Test Storage Account GRS"
storage_client = mock.MagicMock()
storage_client.storage_accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
replication_settings=ReplicationSettings.STANDARD_GRS,
)
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled.storage_client",
new=storage_client,
),
):
from prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled import (
storage_geo_redundant_enabled,
)
check = storage_geo_redundant_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has Geo-redundant storage (GRS) enabled."
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == storage_account_name
assert result[0].resource_id == storage_account_id
assert result[0].location == "westeurope"
def test_storage_account_geo_redundant_disabled(self):
storage_account_id = str(uuid4())
storage_account_name = "Test Storage Account LRS"
storage_client = mock.MagicMock()
storage_client.storage_accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
replication_settings=ReplicationSettings.STANDARD_LRS,
)
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled.storage_client",
new=storage_client,
),
):
from prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled import (
storage_geo_redundant_enabled,
)
check = storage_geo_redundant_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} does not have Geo-redundant storage (GRS) enabled."
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == storage_account_name
assert result[0].resource_id == storage_account_id
assert result[0].location == "westeurope"
@@ -4,6 +4,7 @@ from prowler.providers.azure.services.storage.storage_service import (
Account,
BlobProperties,
FileShare,
ReplicationSettings,
Storage,
)
from tests.providers.azure.azure_fixtures import (
@@ -40,6 +41,7 @@ def mock_storage_get_storage_accounts(_):
private_endpoint_connections=None,
location="westeurope",
blob_properties=blob_properties,
replication_settings=ReplicationSettings.STANDARD_LRS,
allow_cross_tenant_replication=True,
allow_shared_key_access=True,
file_shares=file_shares,
@@ -118,6 +120,10 @@ class Test_Storage_Service:
default_service_version=None,
container_delete_retention_policy=None,
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].replication_settings
== ReplicationSettings.STANDARD_LRS
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0