mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(azure): add postgresql_flexible_server_high_availability_enabled check (#11046)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
@@ -23,6 +23,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `mysql_flexible_server_geo_redundant_backup_enabled` check for Azure provider, verifying MySQL Flexible Servers have geo-redundant backup enabled so backups are replicated to the paired region [(#11041)](https://github.com/prowler-cloud/prowler/pull/11041)
|
||||
- `mysql_flexible_server_high_availability_enabled` check for Azure provider, verifying MySQL Flexible Servers have high availability enabled for automatic failover to a standby replica [(#11042)](https://github.com/prowler-cloud/prowler/pull/11042)
|
||||
- `postgresql_flexible_server_geo_redundant_backup_enabled` check for Azure provider, verifying PostgreSQL Flexible Servers have geo-redundant backup enabled so backups are replicated to the paired region [(#11045)](https://github.com/prowler-cloud/prowler/pull/11045)
|
||||
- `postgresql_flexible_server_high_availability_enabled` check for Azure provider, verifying PostgreSQL Flexible Servers have high availability enabled for automatic failover to a standby replica [(#11046)](https://github.com/prowler-cloud/prowler/pull/11046)
|
||||
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
|
||||
- Public `Provider.get_class()` method that resolves a provider class by name for both built-in and external (entry-point) providers [(#11398)](https://github.com/prowler-cloud/prowler/pull/11398)
|
||||
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
|
||||
|
||||
@@ -1301,7 +1301,8 @@
|
||||
"cosmosdb_account_automatic_failover_enabled",
|
||||
"mysql_flexible_server_geo_redundant_backup_enabled",
|
||||
"mysql_flexible_server_high_availability_enabled",
|
||||
"postgresql_flexible_server_geo_redundant_backup_enabled"
|
||||
"postgresql_flexible_server_geo_redundant_backup_enabled",
|
||||
"postgresql_flexible_server_high_availability_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "postgresql_flexible_server_high_availability_enabled",
|
||||
"CheckTitle": "PostgreSQL flexible server has high availability enabled",
|
||||
"CheckType": [],
|
||||
"ServiceName": "postgresql",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "microsoft.dbforpostgresql/flexibleservers",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "**Azure PostgreSQL Flexible Server** is evaluated for **high availability** mode. Zone-redundant or same-zone HA provisions a standby replica and provides automatic failover during planned and unplanned outages.",
|
||||
"Risk": "Without **high availability**, a server or zone failure causes **downtime** until manual recovery or redeployment. Critical databases experience extended unavailability and potential data loss for in-flight transactions during unplanned outages.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-high-availability",
|
||||
"https://learn.microsoft.com/en-us/azure/templates/microsoft.dbforpostgresql/flexibleservers"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "az postgres flexible-server update --name <server_name> --resource-group <resource_group_name> --high-availability ZoneRedundant",
|
||||
"NativeIaC": "```bicep\n// Bicep: PostgreSQL Flexible Server with zone-redundant high availability\nresource postgresql 'Microsoft.DBforPostgreSQL/flexibleServers@2023-06-01-preview' = {\n name: '<example_resource_name>'\n location: '<region>'\n sku: {\n name: 'Standard_D2ds_v4'\n tier: 'GeneralPurpose'\n }\n properties: {\n highAvailability: {\n mode: 'ZoneRedundant' // CRITICAL: provisions a standby replica with automatic failover\n }\n }\n}\n```",
|
||||
"Other": "1. In the Azure portal, open your Azure Database for PostgreSQL flexible server\n2. Under Settings, select High availability\n3. Enable High availability and choose Zone redundant (or Same zone)\n4. Save (high availability requires the General Purpose or Memory Optimized tier)",
|
||||
"Terraform": "```hcl\n# Terraform: PostgreSQL Flexible Server with zone-redundant high availability\nresource \"azurerm_postgresql_flexible_server\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"<region>\"\n sku_name = \"GP_Standard_D2ds_v4\"\n\n high_availability {\n mode = \"ZoneRedundant\" # CRITICAL: provisions a standby replica with automatic failover\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable **high availability** (zone-redundant where supported, otherwise same-zone) on production PostgreSQL Flexible Servers so a standby replica provides automatic failover. High availability requires the General Purpose or Memory Optimized tier; pair it with geo-redundant backup and periodic failover testing for full resilience.",
|
||||
"Url": "https://hub.prowler.com/check/postgresql_flexible_server_high_availability_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"resilience"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "High availability for Azure PostgreSQL Flexible Server requires the General Purpose or Memory Optimized compute tier and is not available on the Burstable tier."
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.services.postgresql.postgresql_client import (
|
||||
postgresql_client,
|
||||
)
|
||||
|
||||
|
||||
class postgresql_flexible_server_high_availability_enabled(Check):
|
||||
"""
|
||||
Ensure Azure PostgreSQL Flexible Servers have high availability enabled.
|
||||
|
||||
This check evaluates whether each Azure PostgreSQL Flexible Server is configured with high availability (zone-redundant or same-zone), providing automatic failover to a standby replica during outages.
|
||||
|
||||
- PASS: The server has high availability enabled (high_availability_mode is set and not "Disabled").
|
||||
- FAIL: The server does not have high availability enabled.
|
||||
"""
|
||||
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
for (
|
||||
subscription,
|
||||
flexible_servers,
|
||||
) in postgresql_client.flexible_servers.items():
|
||||
subscription_name = postgresql_client.subscriptions.get(
|
||||
subscription, subscription
|
||||
)
|
||||
for server in flexible_servers:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=server)
|
||||
report.subscription = subscription
|
||||
if (
|
||||
server.high_availability_mode is not None
|
||||
and server.high_availability_mode != "Disabled"
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Flexible Postgresql server {server.name} from subscription {subscription_name} ({subscription}) has high availability enabled."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Flexible Postgresql server {server.name} from subscription {subscription_name} ({subscription}) does not have high availability enabled."
|
||||
findings.append(report)
|
||||
return findings
|
||||
@@ -55,6 +55,7 @@ class PostgreSQL(AzureService):
|
||||
)
|
||||
location = server_details.location
|
||||
backup = getattr(server_details, "backup", None)
|
||||
ha = getattr(server_details, "high_availability", None)
|
||||
flexible_servers[subscription].append(
|
||||
Server(
|
||||
id=postgresql_server.id,
|
||||
@@ -73,6 +74,7 @@ class PostgreSQL(AzureService):
|
||||
geo_redundant_backup=getattr(
|
||||
backup, "geo_redundant_backup", None
|
||||
),
|
||||
high_availability_mode=getattr(ha, "mode", None),
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
@@ -245,3 +247,4 @@ class Server:
|
||||
log_retention_days: Optional[str]
|
||||
firewall: list[Firewall]
|
||||
geo_redundant_backup: Optional[str] = None
|
||||
high_availability_mode: Optional[str] = None
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.postgresql.postgresql_service import Server
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_DISPLAY,
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
AZURE_SUBSCRIPTION_NAME,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
def _make_server(server_id, server_name, high_availability_mode):
|
||||
return Server(
|
||||
id=server_id,
|
||||
name=server_name,
|
||||
resource_group="resource_group",
|
||||
location="eastus",
|
||||
require_secure_transport="ON",
|
||||
active_directory_auth="Enabled",
|
||||
entra_id_admins=[],
|
||||
log_checkpoints="ON",
|
||||
log_connections="ON",
|
||||
log_disconnections="ON",
|
||||
connection_throttling="ON",
|
||||
log_retention_days="3",
|
||||
firewall=[],
|
||||
high_availability_mode=high_availability_mode,
|
||||
)
|
||||
|
||||
|
||||
class Test_postgresql_flexible_server_high_availability_enabled:
|
||||
def test_no_postgresql_flexible_servers(self):
|
||||
postgresql_client = mock.MagicMock
|
||||
postgresql_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
|
||||
}
|
||||
postgresql_client.flexible_servers = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled.postgresql_client",
|
||||
new=postgresql_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled import (
|
||||
postgresql_flexible_server_high_availability_enabled,
|
||||
)
|
||||
|
||||
check = postgresql_flexible_server_high_availability_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_postgresql_high_availability_disabled(self):
|
||||
server_id = str(uuid4())
|
||||
server_name = "test-server"
|
||||
postgresql_client = mock.MagicMock
|
||||
postgresql_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
|
||||
}
|
||||
postgresql_client.flexible_servers = {
|
||||
AZURE_SUBSCRIPTION_ID: [_make_server(server_id, server_name, "Disabled")]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled.postgresql_client",
|
||||
new=postgresql_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled import (
|
||||
postgresql_flexible_server_high_availability_enabled,
|
||||
)
|
||||
|
||||
check = postgresql_flexible_server_high_availability_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Flexible Postgresql server {server_name} from subscription {AZURE_SUBSCRIPTION_DISPLAY} does not have high availability enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == server_name
|
||||
assert result[0].resource_id == server_id
|
||||
assert result[0].location == "eastus"
|
||||
|
||||
def test_postgresql_high_availability_not_set(self):
|
||||
server_id = str(uuid4())
|
||||
server_name = "test-server"
|
||||
postgresql_client = mock.MagicMock
|
||||
postgresql_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
|
||||
}
|
||||
postgresql_client.flexible_servers = {
|
||||
AZURE_SUBSCRIPTION_ID: [_make_server(server_id, server_name, None)]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled.postgresql_client",
|
||||
new=postgresql_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled import (
|
||||
postgresql_flexible_server_high_availability_enabled,
|
||||
)
|
||||
|
||||
check = postgresql_flexible_server_high_availability_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Flexible Postgresql server {server_name} from subscription {AZURE_SUBSCRIPTION_DISPLAY} does not have high availability enabled."
|
||||
)
|
||||
|
||||
def test_postgresql_high_availability_enabled(self):
|
||||
server_id = str(uuid4())
|
||||
server_name = "test-server"
|
||||
postgresql_client = mock.MagicMock
|
||||
postgresql_client.subscriptions = {
|
||||
AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME
|
||||
}
|
||||
postgresql_client.flexible_servers = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
_make_server(server_id, server_name, "ZoneRedundant")
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled.postgresql_client",
|
||||
new=postgresql_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_high_availability_enabled.postgresql_flexible_server_high_availability_enabled import (
|
||||
postgresql_flexible_server_high_availability_enabled,
|
||||
)
|
||||
|
||||
check = postgresql_flexible_server_high_availability_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Flexible Postgresql server {server_name} from subscription {AZURE_SUBSCRIPTION_DISPLAY} has high availability enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == server_name
|
||||
assert result[0].resource_id == server_id
|
||||
assert result[0].location == "eastus"
|
||||
Reference in New Issue
Block a user