mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
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:
committed by
GitHub
parent
031548ca7e
commit
531ba5c31b
29
poetry.lock
generated
29
poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user