feat(azure): new check for Entra ID authentication for Azure PostgreSQL Flexible Server (#8764)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
johannes-engler-mw
2025-11-14 13:54:57 +01:00
committed by GitHub
parent 031548ca7e
commit 531ba5c31b
16 changed files with 437 additions and 35 deletions

29
poetry.lock generated
View File

@@ -621,6 +621,24 @@ azure-mgmt-core = ">=1.3.2"
isodate = ">=0.6.1"
typing-extensions = ">=4.6.0"
[[package]]
name = "azure-mgmt-postgresqlflexibleservers"
version = "1.1.0"
description = "Microsoft Azure Postgresqlflexibleservers Management Client Library for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "azure_mgmt_postgresqlflexibleservers-1.1.0-py3-none-any.whl", hash = "sha256:87ddb5a5e6d12c45769485d234cfe0322140e3a0a7636d0e61fb00ac544b5d20"},
{file = "azure_mgmt_postgresqlflexibleservers-1.1.0.tar.gz", hash = "sha256:9ede9d8ba63e9d2879cb74adc903c649af3bc5460a02787287b0cd18d754af14"},
]
[package.dependencies]
azure-common = ">=1.1"
azure-mgmt-core = ">=1.3.2"
isodate = ">=0.6.1"
typing-extensions = ">=4.6.0"
[[package]]
name = "azure-mgmt-rdbms"
version = "10.1.0"
@@ -2348,8 +2366,6 @@ python-versions = "*"
groups = ["dev"]
files = [
{file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"},
{file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"},
{file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"},
]
[package.dependencies]
@@ -4424,7 +4440,7 @@ version = "2025.9.18"
description = "Alternative regular expression module, to replace re."
optional = false
python-versions = ">=3.9"
groups = ["dev", "docs"]
groups = ["dev"]
files = [
{file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"},
{file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"},
@@ -4868,7 +4884,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"},
@@ -4877,7 +4892,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"},
@@ -4886,7 +4900,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"},
@@ -4895,7 +4908,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"},
@@ -4904,7 +4916,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"},
{file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"},
@@ -5677,4 +5688,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">3.9.1,<3.13"
content-hash = "ea79d82b4e255ec4604f440a507da6dac38b57af93356761ac793678aa615cf5"
content-hash = "a367e65bc43c0a16495a3d0f6eab8b356cc49b509e329b61c6641cd87f374ff4"

View File

@@ -22,6 +22,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- NIST CSF 2.0 compliance framework for the AWS provider [(#9185)](https://github.com/prowler-cloud/prowler/pull/9185)
- Add FedRAMP 20x KSI Low for AWS, Azure and GCP [(#9198)](https://github.com/prowler-cloud/prowler/pull/9198)
- Add verification for provider ID in MongoDB Atlas provider [(#9211)](https://github.com/prowler-cloud/prowler/pull/9211)
- Add `postgresql_flexible_server_entra_id_authentication_enabled` check for Azure provider [(#8764)](https://github.com/prowler-cloud/prowler/pull/8764)
### Changed
- Update AWS Direct Connect service metadata to new format [(#8855)](https://github.com/prowler-cloud/prowler/pull/8855)

View File

@@ -0,0 +1,36 @@
{
"Provider": "azure",
"CheckID": "postgresql_flexible_server_entra_id_authentication_enabled",
"CheckTitle": "PostgreSQL Flexible Server enforces Microsoft Entra ID authentication with administrators",
"CheckType": [],
"ServiceName": "postgresql",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "PostgreSQL",
"Description": "**PostgreSQL Flexible Servers** must set `authConfig.activeDirectoryAuth` to `Enabled` and keep at least one **Microsoft Entra administrator** assigned so database sessions inherit centrally governed identities instead of unmanaged PostgreSQL accounts.",
"Risk": "Without Entra ID authentication, stolen local passwords bypass **MFA** and conditional access, enabling persistent database logins. Missing administrators leaves the feature unusable, blocking security teams from rotating duties and allowing unauthorized access or **privilege escalation**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/security-entra-concepts",
"https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/security-entra-configure"
],
"Remediation": {
"Code": {
"CLI": "az postgres flexible-server update --resource-group <resourceGroupName> --name <serverName> --active-directory-auth Enabled\naz postgres flexible-server microsoft-entra-admin create --resource-group <resourceGroupName> --server-name <serverName> --object-id <objectId> --display-name <displayName>",
"NativeIaC": "",
"Other": "1. In the Azure Portal, open Azure Database for PostgreSQL flexible server and select the target server.\n2. Under Security > Authentication, set Microsoft Entra ID authentication (or combined mode) to Enabled and save the change.\n3. Under Security > Microsoft Entra ID, add at least one administrator (user or group) linked to an Entra object ID and confirm the assignment.",
"Terraform": "```hcl\ndata \"azurerm_client_config\" \"current\" {}\n\nresource \"azurerm_postgresql_flexible_server\" \"example\" {\n name = \"pg-flex\"\n resource_group_name = azurerm_resource_group.example.name\n location = azurerm_resource_group.example.location\n sku_name = \"GP_Standard_D4s_v3\"\n administrator_login = \"pgadmin\"\n administrator_password = \"<complexPassword>\"\n storage_mb = 131072\n version = \"16\"\n\n authentication {\n active_directory_auth_enabled = true\n tenant_id = data.azurerm_client_config.current.tenant_id\n }\n}\n\nresource \"azurerm_postgresql_flexible_server_active_directory_administrator\" \"entra_admin\" {\n server_id = azurerm_postgresql_flexible_server.example.id\n object_id = var.entra_object_id\n principal_name = var.entra_principal_name\n principal_type = \"User\"\n tenant_id = data.azurerm_client_config.current.tenant_id\n}\n```"
},
"Recommendation": {
"Text": "Federate PostgreSQL Flexible Server access through **Microsoft Entra ID** so MFA, conditional access, and centralized RBAC govern logins. Maintain at least one delegated administrator group and rotate membership through identity governance processes.",
"Url": "https://hub.prowler.com/check/postgresql_flexible_server_entra_id_authentication_enabled"
}
},
"Categories": [
"identity-access"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check fails when Microsoft Entra ID authentication is disabled or no administrators are returned by the flexible server microsoft-entra-admin API."
}

View File

@@ -0,0 +1,43 @@
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_entra_id_authentication_enabled(Check):
def execute(self) -> Check_Report_Azure:
findings = []
for (
subscription,
flexible_servers,
) in postgresql_client.flexible_servers.items():
for server in flexible_servers:
report = Check_Report_Azure(metadata=self.metadata(), resource=server)
report.subscription = subscription
# Default to FAIL
report.status = "FAIL"
# Check if Entra ID authentication is enabled
# Note: active_directory_auth is already normalized to uppercase in service layer
if (
not server.active_directory_auth
or server.active_directory_auth != "ENABLED"
):
report.status_extended = f"Flexible Postgresql server {server.name} from subscription {subscription} has Microsoft Entra ID authentication disabled"
else:
# Authentication is enabled, now check for admins
admin_count = (
len(server.entra_id_admins) if server.entra_id_admins else 0
)
if admin_count == 0:
report.status_extended = f"Flexible Postgresql server {server.name} from subscription {subscription} has Microsoft Entra ID authentication enabled but no Entra ID administrators configured"
else:
report.status = "PASS"
admin_text = (
"administrator" if admin_count == 1 else "administrators"
)
report.status_extended = f"Flexible Postgresql server {server.name} from subscription {subscription} has Microsoft Entra ID authentication enabled with {admin_count} {admin_text} configured"
findings.append(report)
return findings

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from azure.mgmt.rdbms.postgresql_flexibleservers import PostgreSQLManagementClient
from azure.mgmt.postgresqlflexibleservers import PostgreSQLManagementClient
from prowler.lib.logger import logger
from prowler.providers.azure.azure_provider import AzureProvider
@@ -21,9 +21,19 @@ class PostgreSQL(AzureService):
flexible_servers_list = client.servers.list()
for postgresql_server in flexible_servers_list:
resource_group = self._get_resource_group(postgresql_server.id)
# Fetch full server object once to extract multiple properties
server_details = client.servers.get(
resource_group, postgresql_server.name
)
require_secure_transport = self._get_require_secure_transport(
subscription, resource_group, postgresql_server.name
)
active_directory_auth = self._extract_active_directory_auth(
server_details
)
entra_id_admins = self._get_entra_id_admins(
subscription, resource_group, postgresql_server.name
)
log_checkpoints = self._get_log_checkpoints(
subscription, resource_group, postgresql_server.name
)
@@ -42,22 +52,22 @@ class PostgreSQL(AzureService):
firewall = self._get_firewall(
subscription, resource_group, postgresql_server.name
)
location = self._get_location(
subscription, resource_group, postgresql_server.name
)
location = server_details.location
flexible_servers[subscription].append(
Server(
id=postgresql_server.id,
name=postgresql_server.name,
resource_group=resource_group,
location=location,
require_secure_transport=require_secure_transport,
active_directory_auth=active_directory_auth,
entra_id_admins=entra_id_admins,
log_checkpoints=log_checkpoints,
log_connections=log_connections,
log_disconnections=log_disconnections,
connection_throttling=connection_throttling,
log_retention_days=log_retention_days,
firewall=firewall,
location=location,
)
)
except Exception as error:
@@ -100,10 +110,47 @@ class PostgreSQL(AzureService):
)
return log_disconnections.value.upper()
def _get_location(self, subscription, resouce_group_name, server_name):
def _extract_active_directory_auth(self, server):
"""Extract active directory auth from a server object (no API call)."""
try:
auth_config = getattr(server, "auth_config", None)
active_directory_auth = (
getattr(auth_config, "active_directory_auth", None)
if auth_config is not None
else None
)
# Normalize enum/string to upper string
if hasattr(active_directory_auth, "value"):
return str(active_directory_auth.value).upper()
return (
str(active_directory_auth).upper()
if active_directory_auth is not None
else None
)
except Exception as e:
logger.error(f"Error extracting active directory auth: {e}")
return None
def _get_entra_id_admins(self, subscription, resource_group_name, server_name):
client = self.clients[subscription]
location = client.servers.get(resouce_group_name, server_name).location
return location
try:
admins = client.administrators.list_by_server(
resource_group_name, server_name
)
admin_list = []
for admin in admins:
admin_list.append(
EntraIdAdmin(
object_id=admin.object_id,
principal_name=admin.principal_name,
principal_type=admin.principal_type,
tenant_id=admin.tenant_id,
)
)
return admin_list
except Exception as e:
logger.error(f"Error getting Entra ID admins for {server_name}: {e}")
return []
def _get_connection_throttling(self, subscription, resouce_group_name, server_name):
client = self.clients[subscription]
@@ -147,16 +194,26 @@ class Firewall:
end_ip: str
@dataclass
class EntraIdAdmin:
object_id: str
principal_name: str
principal_type: str
tenant_id: str
@dataclass
class Server:
id: str
name: str
resource_group: str
location: str
require_secure_transport: str
active_directory_auth: str
entra_id_admins: list[EntraIdAdmin]
log_checkpoints: str
log_connections: str
log_disconnections: str
connection_throttling: str
log_retention_days: str
firewall: list[Firewall]
location: str

View File

@@ -26,6 +26,7 @@ dependencies = [
"azure-mgmt-monitor==6.0.2",
"azure-mgmt-network==28.1.0",
"azure-mgmt-rdbms==10.1.0",
"azure-mgmt-postgresqlflexibleservers==1.1.0",
"azure-mgmt-recoveryservices==3.1.0",
"azure-mgmt-recoveryservicesbackup==9.2.0",
"azure-mgmt-resource==23.3.0",

View File

@@ -50,14 +50,16 @@ class Test_postgresql_flexible_server_allow_access_services_disabled:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days="3",
firewall=[firewall],
location="location",
)
]
}
@@ -105,14 +107,16 @@ class Test_postgresql_flexible_server_allow_access_services_disabled:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days="3",
firewall=[firewall],
location="location",
)
]
}

View File

@@ -41,14 +41,16 @@ class Test_postgresql_flexible_server_connection_throttling_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days="3",
firewall=None,
location="location",
)
]
}
@@ -90,14 +92,16 @@ class Test_postgresql_flexible_server_connection_throttling_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}

View File

@@ -41,14 +41,16 @@ class Test_postgresql_flexible_server_enforce_ssl_enabled:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}
@@ -90,14 +92,16 @@ class Test_postgresql_flexible_server_enforce_ssl_enabled:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="ON",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}

View File

@@ -0,0 +1,195 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.postgresql.postgresql_service import (
EntraIdAdmin,
Server,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_postgresql_flexible_server_entra_id_authentication_enabled:
def test_no_postgresql_flexible_servers(self):
postgresql_client = mock.MagicMock
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_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_client",
new=postgresql_client,
),
):
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled import (
postgresql_flexible_server_entra_id_authentication_enabled,
)
check = postgresql_flexible_server_entra_id_authentication_enabled()
result = check.execute()
assert len(result) == 0
def test_flexible_servers_entra_id_auth_disabled(self):
postgresql_client = mock.MagicMock
postgresql_server_name = "Postgres Flexible Server Name"
postgresql_server_id = str(uuid4())
postgresql_client.flexible_servers = {
AZURE_SUBSCRIPTION_ID: [
Server(
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="ON",
active_directory_auth="DISABLED",
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=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_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_client",
new=postgresql_client,
),
):
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled import (
postgresql_flexible_server_entra_id_authentication_enabled,
)
check = postgresql_flexible_server_entra_id_authentication_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Flexible Postgresql server {postgresql_server_name} from subscription {AZURE_SUBSCRIPTION_ID} has Microsoft Entra ID authentication disabled"
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == postgresql_server_name
assert result[0].resource_id == postgresql_server_id
assert result[0].location == "location"
def test_flexible_servers_entra_id_auth_enabled_no_admins(self):
postgresql_client = mock.MagicMock
postgresql_server_name = "Postgres Flexible Server Name"
postgresql_server_id = str(uuid4())
postgresql_client.flexible_servers = {
AZURE_SUBSCRIPTION_ID: [
Server(
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
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=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_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_client",
new=postgresql_client,
),
):
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled import (
postgresql_flexible_server_entra_id_authentication_enabled,
)
check = postgresql_flexible_server_entra_id_authentication_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Flexible Postgresql server {postgresql_server_name} from subscription {AZURE_SUBSCRIPTION_ID} has Microsoft Entra ID authentication enabled but no Entra ID administrators configured"
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == postgresql_server_name
assert result[0].resource_id == postgresql_server_id
assert result[0].location == "location"
def test_flexible_servers_entra_id_auth_enabled(self):
postgresql_client = mock.MagicMock
postgresql_server_name = "Postgres Flexible Server Name"
postgresql_server_id = str(uuid4())
postgresql_client.flexible_servers = {
AZURE_SUBSCRIPTION_ID: [
Server(
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="ON",
active_directory_auth="ENABLED",
entra_id_admins=[
EntraIdAdmin(
object_id=str(uuid4()),
principal_name="Test Admin User",
principal_type="User",
tenant_id=str(uuid4()),
)
],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=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_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_client",
new=postgresql_client,
),
):
from prowler.providers.azure.services.postgresql.postgresql_flexible_server_entra_id_authentication_enabled.postgresql_flexible_server_entra_id_authentication_enabled import (
postgresql_flexible_server_entra_id_authentication_enabled,
)
check = postgresql_flexible_server_entra_id_authentication_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Flexible Postgresql server {postgresql_server_name} from subscription {AZURE_SUBSCRIPTION_ID} has Microsoft Entra ID authentication enabled with 1 administrator configured"
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == postgresql_server_name
assert result[0].resource_id == postgresql_server_id
assert result[0].location == "location"

View File

@@ -41,14 +41,16 @@ class Test_postgresql_flexible_server_log_checkpoints_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="ON",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}
@@ -90,14 +92,16 @@ class Test_postgresql_flexible_server_log_checkpoints_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="ON",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}

View File

@@ -41,14 +41,16 @@ class Test_postgresql_flexible_server_log_connections_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}
@@ -90,14 +92,16 @@ class Test_postgresql_flexible_server_log_connections_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}

View File

@@ -41,14 +41,16 @@ class Test_postgresql_flexible_server_log_disconnections_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days="3",
firewall=None,
location="location",
)
]
}
@@ -90,14 +92,16 @@ class Test_postgresql_flexible_server_log_disconnections_on:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=None,
location="location",
)
]
}

View File

@@ -41,14 +41,16 @@ class Test_postgresql_flexible_server_log_retention_days_greater_3:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days=None,
firewall=None,
location="location",
)
]
}
@@ -91,14 +93,16 @@ class Test_postgresql_flexible_server_log_retention_days_greater_3:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days=log_retention_days,
firewall=None,
location="location",
)
]
}
@@ -141,14 +145,16 @@ class Test_postgresql_flexible_server_log_retention_days_greater_3:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days=log_retention_days,
firewall=None,
location="location",
)
]
}
@@ -191,14 +197,16 @@ class Test_postgresql_flexible_server_log_retention_days_greater_3:
id=postgresql_server_id,
name=postgresql_server_name,
resource_group="resource_group",
location="location",
require_secure_transport="OFF",
active_directory_auth=None,
entra_id_admins=[],
log_checkpoints="OFF",
log_connections="OFF",
log_disconnections="OFF",
connection_throttling="OFF",
log_retention_days=log_retention_days,
firewall=None,
location="location",
)
]
}

View File

@@ -1,6 +1,7 @@
from unittest.mock import patch
from prowler.providers.azure.services.postgresql.postgresql_service import (
EntraIdAdmin,
Firewall,
PostgreSQL,
Server,
@@ -24,14 +25,23 @@ def mock_sqlserver_get_postgresql_flexible_servers(_):
id="id",
name="name",
resource_group="resource_group",
location="location",
require_secure_transport="ON",
active_directory_auth="ENABLED",
entra_id_admins=[
EntraIdAdmin(
object_id="11111111-1111-1111-1111-111111111111",
principal_name="Test Admin User",
principal_type="User",
tenant_id="22222222-2222-2222-2222-222222222222",
)
],
log_checkpoints="ON",
log_connections="ON",
log_disconnections="ON",
connection_throttling="ON",
log_retention_days="3",
firewall=[firewall],
location="location",
)
]
}
@@ -112,6 +122,22 @@ class Test_SqlServer_Service:
== "3"
)
def test_get_active_directory_auth(self):
postgresql = PostgreSQL(set_mocked_azure_provider())
assert (
postgresql.flexible_servers[AZURE_SUBSCRIPTION_ID][0].active_directory_auth
== "ENABLED"
)
def test_get_entra_id_admins(self):
postgresql = PostgreSQL(set_mocked_azure_provider())
admins = postgresql.flexible_servers[AZURE_SUBSCRIPTION_ID][0].entra_id_admins
assert isinstance(admins, list)
assert len(admins) == 1
assert isinstance(admins[0], EntraIdAdmin)
assert admins[0].principal_name == "Test Admin User"
assert admins[0].object_id == "11111111-1111-1111-1111-111111111111"
def test_get_firewall(self):
postgesql = PostgreSQL(set_mocked_azure_provider())
assert (