mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(azure): add aks_cluster_local_accounts_disabled check (#11030)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
@@ -27,6 +27,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `postgresql_flexible_server_geo_redundant_backup_enabled` check for Azure provider, verifying PostgreSQL Flexible Servers have geo-redundant backup enabled so backups are replicated to the paired region [(#11045)](https://github.com/prowler-cloud/prowler/pull/11045)
|
||||
- `postgresql_flexible_server_high_availability_enabled` check for Azure provider, verifying PostgreSQL Flexible Servers have high availability enabled for automatic failover to a standby replica [(#11046)](https://github.com/prowler-cloud/prowler/pull/11046)
|
||||
- `aks_cluster_azure_monitor_enabled` check for Azure provider, verifying AKS clusters have Azure Monitor (Container Insights) enabled for metrics, logs, and alerting [(#11029)](https://github.com/prowler-cloud/prowler/pull/11029)
|
||||
- `aks_cluster_local_accounts_disabled` check for Azure provider, verifying AKS clusters have local accounts disabled so authentication is forced through Microsoft Entra ID [(#11030)](https://github.com/prowler-cloud/prowler/pull/11030)
|
||||
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
|
||||
- Public `Provider.get_class()` method that resolves a provider class by name for both built-in and external (entry-point) providers [(#11398)](https://github.com/prowler-cloud/prowler/pull/11398)
|
||||
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
|
||||
|
||||
@@ -271,6 +271,7 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"aks_cluster_local_accounts_disabled",
|
||||
"defender_ensure_defender_for_containers_is_on",
|
||||
"defender_ensure_defender_for_cosmosdb_is_on",
|
||||
"defender_ensure_defender_for_databases_is_on",
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "aks_cluster_local_accounts_disabled",
|
||||
"CheckTitle": "AKS cluster has local accounts disabled",
|
||||
"CheckType": [],
|
||||
"ServiceName": "aks",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "microsoft.containerservice/managedclusters",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "**Azure Kubernetes Service** clusters are evaluated for **local account** status. Disabling local accounts forces all cluster authentication through Microsoft Entra ID, ensuring centralized identity management, MFA, and conditional access.",
|
||||
"Risk": "Local accounts bypass **Entra ID** authentication, MFA, and conditional access policies. Compromised local credentials (such as the static cluster-admin certificate) provide persistent cluster access without an identity-based audit trail or governance.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/azure/aks/manage-local-accounts-managed-azure-ad",
|
||||
"https://learn.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az-aks-update",
|
||||
"https://learn.microsoft.com/en-us/azure/templates/microsoft.containerservice/managedclusters"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "az aks update --resource-group <RESOURCE_GROUP> --name <CLUSTER_NAME> --disable-local-accounts",
|
||||
"NativeIaC": "```bicep\n// Bicep: AKS cluster with local accounts disabled\nresource aks 'Microsoft.ContainerService/managedClusters@2024-02-01' = {\n name: '<example_resource_name>'\n location: resourceGroup().location\n properties: {\n disableLocalAccounts: true // CRITICAL: forces authentication through Entra ID\n aadProfile: {\n managed: true\n enableAzureRbac: true\n }\n }\n}\n```",
|
||||
"Other": "1. Sign in to the Azure portal and open your AKS cluster\n2. Ensure Microsoft Entra ID (AAD) authentication is configured for the cluster\n3. Under Settings, select Cluster configuration (or Authentication and Authorization)\n4. Set Local accounts to Disabled\n5. Save the configuration",
|
||||
"Terraform": "```hcl\n# Terraform: AKS cluster with local accounts disabled\nresource \"azurerm_kubernetes_cluster\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n location = \"<region>\"\n resource_group_name = \"<example_resource_name>\"\n dns_prefix = \"<example_resource_name>\"\n local_account_disabled = true # CRITICAL: forces authentication through Entra ID\n\n azure_active_directory_role_based_access_control {\n azure_rbac_enabled = true\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Disable local accounts on AKS clusters and require **Microsoft Entra ID** authentication with Azure RBAC. Before disabling, confirm Entra ID integration and break-glass access are in place so administrators retain authenticated access, then enforce MFA and conditional access on cluster identities.",
|
||||
"Url": "https://hub.prowler.com/check/aks_cluster_local_accounts_disabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.services.aks.aks_client import aks_client
|
||||
|
||||
|
||||
class aks_cluster_local_accounts_disabled(Check):
|
||||
"""
|
||||
Ensure local accounts are disabled on AKS clusters.
|
||||
|
||||
This check evaluates whether each Azure Kubernetes Service cluster has local accounts disabled, forcing all authentication through Microsoft Entra ID.
|
||||
|
||||
- PASS: The cluster has local accounts disabled.
|
||||
- FAIL: The cluster has local accounts enabled.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[Check_Report_Azure]:
|
||||
findings = []
|
||||
|
||||
for subscription_name, clusters in aks_client.clusters.items():
|
||||
for cluster in clusters.values():
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=cluster)
|
||||
report.subscription = subscription_name
|
||||
|
||||
if cluster.local_accounts_disabled:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Cluster '{cluster.name}' has local accounts disabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Cluster '{cluster.name}' has local accounts enabled."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
from importlib import import_module
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from prowler.providers.azure.services.aks.aks_service import Cluster
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
CHECK_MODULE = (
|
||||
"prowler.providers.azure.services.aks.aks_cluster_local_accounts_disabled."
|
||||
"aks_cluster_local_accounts_disabled"
|
||||
)
|
||||
CHECK_CLIENT_PATCH = f"{CHECK_MODULE}.aks_client"
|
||||
|
||||
|
||||
def get_check_class():
|
||||
return import_module(CHECK_MODULE).aks_cluster_local_accounts_disabled
|
||||
|
||||
|
||||
def build_cluster(local_accounts_disabled):
|
||||
return Cluster(
|
||||
id=str(uuid4()),
|
||||
name="test-cluster",
|
||||
public_fqdn="test.azmk8s.io",
|
||||
private_fqdn=None,
|
||||
network_policy=None,
|
||||
agent_pool_profiles=[],
|
||||
rbac_enabled=True,
|
||||
location="eastus",
|
||||
local_accounts_disabled=local_accounts_disabled,
|
||||
)
|
||||
|
||||
|
||||
class Test_aks_cluster_local_accounts_disabled:
|
||||
def test_no_subscriptions(self):
|
||||
aks_client = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
CHECK_CLIENT_PATCH,
|
||||
new=aks_client,
|
||||
),
|
||||
):
|
||||
aks_cluster_local_accounts_disabled = get_check_class()
|
||||
|
||||
aks_client.clusters = {}
|
||||
|
||||
check = aks_cluster_local_accounts_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_pass(self):
|
||||
aks_client = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
CHECK_CLIENT_PATCH,
|
||||
new=aks_client,
|
||||
),
|
||||
):
|
||||
aks_cluster_local_accounts_disabled = get_check_class()
|
||||
|
||||
cluster = build_cluster(local_accounts_disabled=True)
|
||||
aks_client.clusters = {AZURE_SUBSCRIPTION_ID: {cluster.id: cluster}}
|
||||
|
||||
check = aks_cluster_local_accounts_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Cluster 'test-cluster' has local accounts disabled."
|
||||
)
|
||||
assert result[0].resource_name == "test-cluster"
|
||||
assert result[0].resource_id == cluster.id
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "eastus"
|
||||
|
||||
@pytest.mark.parametrize("local_accounts_disabled", [False, None])
|
||||
def test_fail(self, local_accounts_disabled):
|
||||
aks_client = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
CHECK_CLIENT_PATCH,
|
||||
new=aks_client,
|
||||
),
|
||||
):
|
||||
aks_cluster_local_accounts_disabled = get_check_class()
|
||||
|
||||
cluster = build_cluster(local_accounts_disabled=local_accounts_disabled)
|
||||
aks_client.clusters = {AZURE_SUBSCRIPTION_ID: {cluster.id: cluster}}
|
||||
|
||||
check = aks_cluster_local_accounts_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Cluster 'test-cluster' has local accounts enabled."
|
||||
)
|
||||
assert result[0].resource_name == "test-cluster"
|
||||
assert result[0].resource_id == cluster.id
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "eastus"
|
||||
Reference in New Issue
Block a user