feat(azure): add 22 new Azure checks across 9 services#10809
Open
s1ns3nz0 wants to merge 61 commits intoprowler-cloud:masterfrom
Open
feat(azure): add 22 new Azure checks across 9 services#10809s1ns3nz0 wants to merge 61 commits intoprowler-cloud:masterfrom
s1ns3nz0 wants to merge 61 commits intoprowler-cloud:masterfrom
Conversation
Add a new Azure Entra ID check that detects stale user accounts by evaluating the last sign-in date from Microsoft Graph signInActivity. - Extend User model with last_sign_in field (Optional[datetime]) - Fetch signInActivity in _get_users via Graph API select parameter - Check logic: FAIL if enabled user has not signed in within 90 days or has never signed in. Skip disabled users. - Metadata: medium severity, identity-access category, includes CLI and portal remediation steps - Tests: 6 test cases covering no tenants, disabled user, never signed in, stale (120 days), recent (10 days), boundary (90 days) Note: signInActivity requires Entra ID P1/P2 license.
Map the new stale users check to 12 requirements across 9 compliance frameworks: - CIS 2.0: 1.4 (access review) - CIS 5.0: 5.3.5 (inactive users — first check for this requirement) - ISO 27001: A.5.16 (identity management), A.5.18 (access rights) - CSA-CCM 4.0: IAM-07 (access revocation), IAM-08 (access review) - HIPAA: 164.308(a)(3)(ii)(C) (termination), 164.312(a)(1) (access control) - FedRAMP 20x: ksi-iam-07 (account lifecycle) - NIS2: 11.2.2.a (access control policies) - SOC2: CC5.2 (access authorization) - RBI Cyber Security: annex_i_7_1
Add a new Azure Entra ID check that detects expired, expiring, or non-expiring app registration credentials (secrets and certificates). Service changes: - Add _get_app_registrations() to entra_service.py — fetches app registrations with password_credentials and key_credentials via Microsoft Graph API - Add AppRegistration and AppCredential Pydantic models Check logic: - FAIL: credential already expired, expiring within 30 days, or has no expiration date set - PASS: credential valid for more than 30 days - Each credential reported individually; apps with no credentials skipped - Severity: high (expired creds cause outages, long-lived ones are security risks) Tests: 7 cases (no tenants, no credentials, expired, expiring soon, no expiration, valid, mixed credentials) Framework mappings (13 requirements across 9 frameworks): - CIS 2.0/3.0/5.0: credential management (were 0 checks — now 1) - ISO 27001: A.8.24 Use of Cryptography - CSA-CCM 4.0: CEK-08, CEK-12, IAM-14 - MITRE ATT&CK: T1078 Valid Accounts, T1552 Unsecured Credentials - SOC2: CC6.1, CC6.6 - NIS2: 9.2.c.i - SecNumCloud 3.2: 10.5
…rced Add a new Azure Entra ID check that verifies strong authentication methods are enabled and MFA registration enforcement is active. Service changes: - Add _get_authentication_methods_policy() to entra_service.py fetches auth methods policy via Graph API including registration campaign state and per-method configurations - Add AuthMethodsPolicy and AuthMethodConfig Pydantic models Check logic (produces 2 findings per tenant): 1. MFA registration campaign: PASS if enabled, FAIL if not 2. Strong auth methods: PASS if Microsoft Authenticator, FIDO2, or X.509 Certificate is enabled. FAIL if only weak methods (SMS/voice) This is the modern equivalent of password policy checks — Microsoft recommends strong auth over password complexity rules. Tests: 6 cases (no tenants, null policy, both pass, both fail, mixed results, multiple strong methods) Framework mappings (8 requirements across 8 frameworks): - ISO 27001: A.8.5 Secure Authentication - CSA-CCM: IAM-14 Strong Authentication - HIPAA: 164.312(a)(1) Access Control - SOC2: CC6.1 Logical Access Security - NIS2: 11.2.2.a Access Control Policies - FedRAMP: ksi-iam-07 Account Lifecycle - SecNumCloud 3.2: 9.5 Authentication Management
When all enabled users in a tenant have no sign-in activity data, this indicates the tenant lacks an Entra ID P1/P2 license (required for signInActivity). Instead of reporting N individual FAILs (mass false positives), report a single finding explaining the license requirement. Single users with null sign-in data are still reported individually (could be genuinely new accounts that haven't signed in yet).
Add defensive timezone handling for datetimes returned by the Microsoft Graph SDK. If the SDK returns a naive datetime (no tzinfo), assume UTC to prevent TypeError when comparing with timezone-aware datetime.now(). Graph API should always return UTC, but the SDK's JSON parser can produce naive datetimes if the response omits the timezone suffix.
Add 2 new Azure Recovery Services checks — first checks for a service that previously had 0: 1. recovery_vault_has_protected_items - FAIL if vault has no backup items configured - Detects provisioned-but-unused vaults leaving workloads unprotected 2. recovery_vault_backup_policy_retention_adequate - FAIL if daily retention < 30 days or not configured - Ensures sufficient recovery window for incident investigation Tests: 8 cases covering empty/populated vaults, adequate/short/missing retention, and edge cases. Framework mappings (7 across 5 frameworks): - ISO 27001: A.8.13 Information Backup (was 0 checks — now 2) - CSA-CCM 4.0: BCR-11 Backup - HIPAA: 164.308(a)(7)(ii)(A) Data Backup Plan - CIS 5.0: 7.6 VM Backups - NIS2: 11.2.2.e Business Continuity
Add 4 CosmosDB checks (3 → 7 total): - cosmosdb_account_automatic_failover_enabled — HA/DR - cosmosdb_account_backup_policy_continuous — PITR backup - cosmosdb_account_public_network_access_disabled — network isolation - cosmosdb_account_minimal_tls_version_12 — TLS enforcement Add 4 AKS checks (4 → 8 total): - aks_cluster_auto_upgrade_enabled — auto patch management - aks_cluster_defender_enabled — runtime threat detection - aks_cluster_azure_monitor_enabled — observability - aks_cluster_local_accounts_disabled — force Entra ID auth Service changes: - Extend CosmosDB Account dataclass with 4 new fields from SDK - Extend AKS Cluster dataclass with 4 new fields from SDK - No new API calls — all data was already fetched but not captured Tests: 24 new (3 per check: no subscriptions, pass, fail) Framework mappings: 13 across ISO 27001, CSA-CCM, CIS 5.0, HIPAA, SOC2 Notable: ISO 27001 A.8.8 (vulnerability management) and A.8.13 (information backup) had 0-1 Azure checks — now have coverage.
Add 2 MySQL checks (4 → 6 total): - mysql_flexible_server_geo_redundant_backup_enabled - mysql_flexible_server_high_availability_enabled Add 2 PostgreSQL checks (8 → 10 total): - postgresql_flexible_server_geo_redundant_backup_enabled - postgresql_flexible_server_high_availability_enabled Add 2 Databricks checks (2 → 4 total): - databricks_workspace_public_network_access_disabled - databricks_workspace_no_public_ip_enabled Service changes: - Extend MySQL FlexibleServer with backup/HA fields from SDK - Extend PostgreSQL Server with backup/HA fields from server_details - Extend DatabricksWorkspace with public_network_access and no_public_ip from workspace parameters Tests: 18 (3 per check) Framework mappings: 10 across ISO 27001, HIPAA, SOC2
- Fix aks_service.py defender_enabled: check security_monitoring.enabled
property instead of just object existence (was causing false PASSes)
- Rewrite all 8 CosmosDB/AKS checks to match existing Prowler patterns:
- Remove redundant `resource = account` aliasing
- Remove manual resource_name/resource_id/location setting
(framework handles via resource= parameter)
- Use `for cluster in clusters.values()` (not `.items()`)
- Match status_extended message style of existing checks
Add defender_ensure_defender_cspm_is_on — checks that Microsoft Defender Cloud Security Posture Management (CSPM) is set to Standard tier, enabling attack path analysis, agentless scanning, and security governance. No service changes needed — uses existing pricings data with "CloudPosture" key, following the exact same pattern as all other Defender pricing checks. Tests: 3 (no CSPM, free tier, standard tier) Framework mappings: - CIS 5.0: 8.1.1.1 (was 0 checks — now 1) - ISO 27001: A.8.16 Monitoring Activities - CSA-CCM: TVM-02
Add a new Azure Entra ID check that detects stale user accounts by evaluating the last sign-in date from Microsoft Graph signInActivity. - Extend User model with last_sign_in field (Optional[datetime]) - Fetch signInActivity in _get_users via Graph API select parameter - Check logic: FAIL if enabled user has not signed in within 90 days or has never signed in. Skip disabled users. - Metadata: medium severity, identity-access category, includes CLI and portal remediation steps - Tests: 6 test cases covering no tenants, disabled user, never signed in, stale (120 days), recent (10 days), boundary (90 days) Note: signInActivity requires Entra ID P1/P2 license.
Map the new stale users check to 12 requirements across 9 compliance frameworks: - CIS 2.0: 1.4 (access review) - CIS 5.0: 5.3.5 (inactive users — first check for this requirement) - ISO 27001: A.5.16 (identity management), A.5.18 (access rights) - CSA-CCM 4.0: IAM-07 (access revocation), IAM-08 (access review) - HIPAA: 164.308(a)(3)(ii)(C) (termination), 164.312(a)(1) (access control) - FedRAMP 20x: ksi-iam-07 (account lifecycle) - NIS2: 11.2.2.a (access control policies) - SOC2: CC5.2 (access authorization) - RBI Cyber Security: annex_i_7_1
Add a new Azure Entra ID check that detects expired, expiring, or non-expiring app registration credentials (secrets and certificates). Service changes: - Add _get_app_registrations() to entra_service.py — fetches app registrations with password_credentials and key_credentials via Microsoft Graph API - Add AppRegistration and AppCredential Pydantic models Check logic: - FAIL: credential already expired, expiring within 30 days, or has no expiration date set - PASS: credential valid for more than 30 days - Each credential reported individually; apps with no credentials skipped - Severity: high (expired creds cause outages, long-lived ones are security risks) Tests: 7 cases (no tenants, no credentials, expired, expiring soon, no expiration, valid, mixed credentials) Framework mappings (13 requirements across 9 frameworks): - CIS 2.0/3.0/5.0: credential management (were 0 checks — now 1) - ISO 27001: A.8.24 Use of Cryptography - CSA-CCM 4.0: CEK-08, CEK-12, IAM-14 - MITRE ATT&CK: T1078 Valid Accounts, T1552 Unsecured Credentials - SOC2: CC6.1, CC6.6 - NIS2: 9.2.c.i - SecNumCloud 3.2: 10.5
…rced Add a new Azure Entra ID check that verifies strong authentication methods are enabled and MFA registration enforcement is active. Service changes: - Add _get_authentication_methods_policy() to entra_service.py fetches auth methods policy via Graph API including registration campaign state and per-method configurations - Add AuthMethodsPolicy and AuthMethodConfig Pydantic models Check logic (produces 2 findings per tenant): 1. MFA registration campaign: PASS if enabled, FAIL if not 2. Strong auth methods: PASS if Microsoft Authenticator, FIDO2, or X.509 Certificate is enabled. FAIL if only weak methods (SMS/voice) This is the modern equivalent of password policy checks — Microsoft recommends strong auth over password complexity rules. Tests: 6 cases (no tenants, null policy, both pass, both fail, mixed results, multiple strong methods) Framework mappings (8 requirements across 8 frameworks): - ISO 27001: A.8.5 Secure Authentication - CSA-CCM: IAM-14 Strong Authentication - HIPAA: 164.312(a)(1) Access Control - SOC2: CC6.1 Logical Access Security - NIS2: 11.2.2.a Access Control Policies - FedRAMP: ksi-iam-07 Account Lifecycle - SecNumCloud 3.2: 9.5 Authentication Management
When all enabled users in a tenant have no sign-in activity data, this indicates the tenant lacks an Entra ID P1/P2 license (required for signInActivity). Instead of reporting N individual FAILs (mass false positives), report a single finding explaining the license requirement. Single users with null sign-in data are still reported individually (could be genuinely new accounts that haven't signed in yet).
Add defensive timezone handling for datetimes returned by the Microsoft Graph SDK. If the SDK returns a naive datetime (no tzinfo), assume UTC to prevent TypeError when comparing with timezone-aware datetime.now(). Graph API should always return UTC, but the SDK's JSON parser can produce naive datetimes if the response omits the timezone suffix.
Add 2 new Azure Recovery Services checks — first checks for a service that previously had 0: 1. recovery_vault_has_protected_items - FAIL if vault has no backup items configured - Detects provisioned-but-unused vaults leaving workloads unprotected 2. recovery_vault_backup_policy_retention_adequate - FAIL if daily retention < 30 days or not configured - Ensures sufficient recovery window for incident investigation Tests: 8 cases covering empty/populated vaults, adequate/short/missing retention, and edge cases. Framework mappings (7 across 5 frameworks): - ISO 27001: A.8.13 Information Backup (was 0 checks — now 2) - CSA-CCM 4.0: BCR-11 Backup - HIPAA: 164.308(a)(7)(ii)(A) Data Backup Plan - CIS 5.0: 7.6 VM Backups - NIS2: 11.2.2.e Business Continuity
Add 4 CosmosDB checks (3 → 7 total): - cosmosdb_account_automatic_failover_enabled — HA/DR - cosmosdb_account_backup_policy_continuous — PITR backup - cosmosdb_account_public_network_access_disabled — network isolation - cosmosdb_account_minimal_tls_version_12 — TLS enforcement Add 4 AKS checks (4 → 8 total): - aks_cluster_auto_upgrade_enabled — auto patch management - aks_cluster_defender_enabled — runtime threat detection - aks_cluster_azure_monitor_enabled — observability - aks_cluster_local_accounts_disabled — force Entra ID auth Service changes: - Extend CosmosDB Account dataclass with 4 new fields from SDK - Extend AKS Cluster dataclass with 4 new fields from SDK - No new API calls — all data was already fetched but not captured Tests: 24 new (3 per check: no subscriptions, pass, fail) Framework mappings: 13 across ISO 27001, CSA-CCM, CIS 5.0, HIPAA, SOC2 Notable: ISO 27001 A.8.8 (vulnerability management) and A.8.13 (information backup) had 0-1 Azure checks — now have coverage.
- Fix aks_service.py defender_enabled: check security_monitoring.enabled
property instead of just object existence (was causing false PASSes)
- Rewrite all 8 CosmosDB/AKS checks to match existing Prowler patterns:
- Remove redundant `resource = account` aliasing
- Remove manual resource_name/resource_id/location setting
(framework handles via resource= parameter)
- Use `for cluster in clusters.values()` (not `.items()`)
- Match status_extended message style of existing checks
Add 2 MySQL checks (4 → 6 total): - mysql_flexible_server_geo_redundant_backup_enabled - mysql_flexible_server_high_availability_enabled Add 2 PostgreSQL checks (8 → 10 total): - postgresql_flexible_server_geo_redundant_backup_enabled - postgresql_flexible_server_high_availability_enabled Add 2 Databricks checks (2 → 4 total): - databricks_workspace_public_network_access_disabled - databricks_workspace_no_public_ip_enabled Service changes: - Extend MySQL FlexibleServer with backup/HA fields from SDK - Extend PostgreSQL Server with backup/HA fields from server_details - Extend DatabricksWorkspace with public_network_access and no_public_ip from workspace parameters Tests: 18 (3 per check) Framework mappings: 10 across ISO 27001, HIPAA, SOC2
Co-authored-by: Shin-723 <72019064+Shin-723@users.noreply.github.com> - Remove `len > 1` guard: single-user tenants with no sign-in data now get telemetry gap warning (safer than false "never signed in") - Improve message: mention both licensing AND Graph permissions - Fix grammar: "1 enabled user" (singular) vs "N enabled users" - Rename duplicate test to test_entra_single_user_no_sign_in_reports_telemetry_gap - Add test: mixed tenant (active + never-signed-in) validates per-user reporting when telemetry IS available
Co-authored-by: Shin-723 <72019064+Shin-723@users.noreply.github.com> Previously vaults with no backup policies were silently skipped. Now reports FAIL — a vault with no policies is worse than short retention. Consistent with recovery_vault_has_protected_items which also FAILs empty vaults.
- Fix cosmosdb_account_minimal_tls_version_12: accept TLS 1.3 as PASS (was rejecting TLS 1.3 which is more secure than 1.2) - Fix categories: - automatic_failover: forensics-ready -> resilience - public_network_access: threat-detection -> internet-exposed - minimal_tls_version: threat-detection -> encryption
Add 2 new network checks (9 → 11 total):
1. network_vnet_ddos_protection_enabled
- FAIL if VNet does not have Azure DDoS Network Protection enabled
- Fills CIS 5.0 8.5 (was 0 checks)
2. network_subnet_nsg_associated
- FAIL if subnet has no Network Security Group associated
- Excludes Azure-managed subnets (GatewaySubnet, AzureFirewallSubnet,
AzureBastionSubnet, RouteServerSubnet)
- Fills CIS 5.0 7.11 (was 0 checks)
Service changes:
- Add _get_virtual_networks() to network_service.py — fetches VNets
with subnets, DDoS protection status, and NSG associations
- Add VirtualNetwork and VNetSubnet dataclasses
Tests: 8 (3 for DDoS, 5 for NSG including excluded subnet and mixed)
Framework mappings: 8 across CIS 5.0, CIS 2.0, ISO 27001, CSA-CCM, SOC2
# Conflicts: # prowler/compliance/azure/csa_ccm_4.0_azure.json # prowler/compliance/azure/hipaa_azure.json # prowler/compliance/azure/iso27001_2022_azure.json
…t/azure-new-checks # Conflicts: # prowler/compliance/azure/hipaa_azure.json # prowler/compliance/azure/iso27001_2022_azure.json
…-checks # Conflicts: # prowler/compliance/azure/iso27001_2022_azure.json # prowler/compliance/azure/soc2_azure.json
…n723' into feat/azure-new-checks # Conflicts: # prowler/compliance/azure/csa_ccm_4.0_azure.json # prowler/compliance/azure/hipaa_azure.json # prowler/compliance/azure/iso27001_2022_azure.json # prowler/compliance/azure/soc2_azure.json # prowler/providers/azure/services/aks/aks_cluster_auto_upgrade_enabled/aks_cluster_auto_upgrade_enabled.metadata.json # prowler/providers/azure/services/aks/aks_cluster_azure_monitor_enabled/aks_cluster_azure_monitor_enabled.metadata.json # prowler/providers/azure/services/aks/aks_cluster_defender_enabled/aks_cluster_defender_enabled.metadata.json # prowler/providers/azure/services/aks/aks_cluster_local_accounts_disabled/aks_cluster_local_accounts_disabled.metadata.json # prowler/providers/azure/services/cosmosdb/cosmosdb_account_automatic_failover_enabled/cosmosdb_account_automatic_failover_enabled.metadata.json # prowler/providers/azure/services/cosmosdb/cosmosdb_account_backup_policy_continuous/cosmosdb_account_backup_policy_continuous.metadata.json # prowler/providers/azure/services/cosmosdb/cosmosdb_account_minimal_tls_version_12/cosmosdb_account_minimal_tls_version_12.metadata.json # prowler/providers/azure/services/cosmosdb/cosmosdb_account_minimal_tls_version_12/cosmosdb_account_minimal_tls_version_12.py # prowler/providers/azure/services/cosmosdb/cosmosdb_account_public_network_access_disabled/cosmosdb_account_public_network_access_disabled.metadata.json # prowler/providers/azure/services/databricks/databricks_workspace_no_public_ip_enabled/databricks_workspace_no_public_ip_enabled.metadata.json # prowler/providers/azure/services/databricks/databricks_workspace_public_network_access_disabled/databricks_workspace_public_network_access_disabled.metadata.json # prowler/providers/azure/services/entra/entra_app_registration_credential_not_expired/entra_app_registration_credential_not_expired.metadata.json # prowler/providers/azure/services/entra/entra_authentication_methods_policy_strong_auth_enforced/entra_authentication_methods_policy_strong_auth_enforced.metadata.json # prowler/providers/azure/services/entra/entra_user_with_recent_sign_in/entra_user_with_recent_sign_in.py # prowler/providers/azure/services/mysql/mysql_flexible_server_geo_redundant_backup_enabled/mysql_flexible_server_geo_redundant_backup_enabled.metadata.json # prowler/providers/azure/services/mysql/mysql_flexible_server_high_availability_enabled/mysql_flexible_server_high_availability_enabled.metadata.json # prowler/providers/azure/services/postgresql/postgresql_flexible_server_geo_redundant_backup_enabled/postgresql_flexible_server_geo_redundant_backup_enabled.metadata.json # prowler/providers/azure/services/postgresql/postgresql_flexible_server_high_availability_enabled/postgresql_flexible_server_high_availability_enabled.metadata.json # prowler/providers/azure/services/recovery/recovery_vault_backup_policy_retention_adequate/recovery_vault_backup_policy_retention_adequate.metadata.json # prowler/providers/azure/services/recovery/recovery_vault_has_protected_items/recovery_vault_has_protected_items.metadata.json # tests/providers/azure/services/entra/entra_user_with_recent_sign_in/entra_user_with_recent_sign_in_test.py # tests/providers/azure/services/recovery/recovery_vault_backup_policy_retention_adequate/recovery_vault_backup_policy_retention_adequate_test.py
- Remove incorrect CIS 5.0 5.3.5 mapping (entra_user_with_recent_sign_in detects stale enabled accounts, not disabled accounts with RBAC roles) - Fix test assertion to match grammar fix (singular "1 enabled user")
Contributor
|
✅ Conflict Markers Resolved All conflict markers have been successfully resolved in this pull request. |
The recovery service directory was missing __init__.py, preventing Prowler from discovering the recovery vault checks. Co-authored-by: Shin-723 <72019064+Shin-723@users.noreply.github.com>
e576588 to
6a1807e
Compare
5 tasks
Contributor
|
Hope you don't mind me commenting, this is really interesting as I do think the Azure Prowler needs beefing up with extra checks. Just something I noticed, I think for checks relating to minimum TLS versions, you've sometimes used the word minimal, when I think you mean minimum (in file names and metadata). I know there are some existing minimum TLS checks for Azure already so it would align better to those. Minimal just means something else so not quite correct. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Expand Azure check coverage with 22 new security checks across 9 services: AKS, CosmosDB, Databricks, Defender, Entra, MySQL, Network, PostgreSQL, and Recovery.
Description
This PR adds 22 new Azure checks:
AKS (4): auto-upgrade, Azure Monitor, Defender, local accounts disabled
CosmosDB (4): automatic failover, continuous backup, TLS 1.2, public network access
Databricks (2): no public IP, public network access disabled
Defender (1): Defender CSPM enabled
Entra (3): stale user sign-in detection (with P1/P2 license awareness), app registration credential expiry, strong authentication methods enforcement
MySQL (2): geo-redundant backup, high availability
Network (2): VNet DDoS protection, subnet NSG association
PostgreSQL (2): geo-redundant backup, high availability
Recovery (2): vault backup policy retention, vault protected items
Also includes:
Steps to review
prowler/providers/azure/services/python3 -m pytest tests/providers/azure/services/{aks,cosmosdb,databricks,entra,mysql,network,postgresql,recovery}/ -vpython3 -m prowler azure --az-cli-auth --service entra --verboseto verify against live AzureChecklist
SDK/CLI
User.Read.AllandApplication.Read.AllMicrosoft Graph permissions (already standard for Azure provider). Recovery checks requireMicrosoft.RecoveryServices/vaults/read(standard Reader role).License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.