feat(azure): add aks_cluster_auto_upgrade_enabled check (#11027)

Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
This commit is contained in:
s1ns3nz0
2026-06-16 13:56:40 +02:00
committed by GitHub
co-authored by Hugo P.Brito
parent d1ed1eddef
commit 6b4fb934f8
6 changed files with 221 additions and 1 deletions
+1
View File
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `identity_storage_service_level_admins_scoped` check for OCI provider CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions [(#11523)](https://github.com/prowler-cloud/prowler/pull/11523)
- `cosmosdb_account_automatic_failover_enabled` check for Azure provider [(#11031)](https://github.com/prowler-cloud/prowler/pull/11031)
- `cosmosdb_account_backup_policy_continuous` check for Azure provider [(#11032)](https://github.com/prowler-cloud/prowler/pull/11032)
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
- 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)
- TLS certificate verification in the `codepipeline_project_repo_private` check, which previously used an unverified SSL context, leaving the repository-visibility probe open to MITM tampering [(#11603)](https://github.com/prowler-cloud/prowler/pull/11603)
@@ -0,0 +1,38 @@
{
"Provider": "azure",
"CheckID": "aks_cluster_auto_upgrade_enabled",
"CheckTitle": "AKS cluster has automatic upgrade enabled",
"CheckType": [],
"ServiceName": "aks",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "microsoft.containerservice/managedclusters",
"ResourceGroup": "container",
"Description": "**AKS clusters** are evaluated for an **automatic upgrade channel** other than `none`. Automatic upgrades keep the Kubernetes control plane and node pools on supported patch/minor versions and reduce version drift across clusters.",
"Risk": "Without automatic upgrades, AKS clusters can remain on unsupported or vulnerable Kubernetes versions. Delayed patching increases exposure to **known CVEs**, control plane/node defects, and exploit chains that can affect workload **confidentiality**, **integrity**, and **availability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/aks/auto-upgrade-cluster",
"https://learn.microsoft.com/en-us/azure/aks/supported-kubernetes-versions"
],
"Remediation": {
"Code": {
"CLI": "az aks update --resource-group <RESOURCE_GROUP> --name <CLUSTER_NAME> --auto-upgrade-channel patch",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Configure an AKS automatic upgrade channel so clusters receive Kubernetes version updates and security patches without relying only on manual upgrade processes. Use `patch` or `stable` for conservative production upgrades, reserve faster channels such as `rapid` for environments that can absorb quicker version changes, and avoid `none` unless a documented exception and manual patching process exists.",
"Url": "https://hub.prowler.com/check/aks_cluster_auto_upgrade_enabled"
}
},
"Categories": [
"vulnerabilities",
"cluster-security"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Automatic upgrade channels can change cluster versions during Azure-managed upgrade windows. Select a channel that matches the workload change tolerance and use planned maintenance windows, staging validation, and documented rollback procedures for production clusters."
}
@@ -0,0 +1,28 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.aks.aks_client import aks_client
class aks_cluster_auto_upgrade_enabled(Check):
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
auto_upgrade_channel = (
(cluster.auto_upgrade_channel or "").strip().lower()
)
if auto_upgrade_channel and auto_upgrade_channel != "none":
report.status = "PASS"
report.status_extended = (
f"Cluster '{cluster.name}' has auto-upgrade channel."
)
else:
report.status = "FAIL"
report.status_extended = f"Cluster '{cluster.name}' does not have auto-upgrade configured."
findings.append(report)
return findings
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List
from typing import List, Optional
from azure.mgmt.containerservice import ContainerServiceClient
@@ -55,6 +55,56 @@ class AKS(AzureService):
)
],
rbac_enabled=getattr(cluster, "enable_rbac", False),
auto_upgrade_channel=getattr(
getattr(cluster, "auto_upgrade_profile", None),
"upgrade_channel",
None,
),
defender_enabled=bool(
getattr(
getattr(
getattr(
getattr(
cluster,
"security_profile",
None,
),
"defender",
None,
),
"security_monitoring",
None,
),
"enabled",
False,
)
),
azure_monitor_enabled=(
bool(
getattr(
getattr(
getattr(
cluster,
"azure_monitor_profile",
None,
),
"metrics",
None,
),
"enabled",
False,
)
)
if getattr(
cluster, "azure_monitor_profile", None
)
else False
),
local_accounts_disabled=bool(
getattr(
cluster, "disable_local_accounts", False
)
),
)
}
)
@@ -82,3 +132,7 @@ class Cluster:
agent_pool_profiles: List[ManagedClusterAgentPoolProfile]
rbac_enabled: bool
location: str
auto_upgrade_channel: Optional[str] = None
defender_enabled: bool = False
azure_monitor_enabled: bool = False
local_accounts_disabled: bool = False
@@ -0,0 +1,99 @@
from unittest import mock
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,
)
def build_cluster(auto_upgrade_channel):
return Cluster(
id="/sub/rg/cluster1",
name="test-cluster",
public_fqdn="test.azmk8s.io",
private_fqdn=None,
network_policy=None,
agent_pool_profiles=[],
rbac_enabled=True,
location="eastus",
auto_upgrade_channel=auto_upgrade_channel,
)
class Test_aks_cluster_auto_upgrade_enabled:
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(
"prowler.providers.azure.services.aks.aks_cluster_auto_upgrade_enabled.aks_cluster_auto_upgrade_enabled.aks_client",
new=aks_client,
),
):
from prowler.providers.azure.services.aks.aks_cluster_auto_upgrade_enabled.aks_cluster_auto_upgrade_enabled import (
aks_cluster_auto_upgrade_enabled,
)
aks_client.clusters = {}
check = aks_cluster_auto_upgrade_enabled()
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(
"prowler.providers.azure.services.aks.aks_cluster_auto_upgrade_enabled.aks_cluster_auto_upgrade_enabled.aks_client",
new=aks_client,
),
):
from prowler.providers.azure.services.aks.aks_cluster_auto_upgrade_enabled.aks_cluster_auto_upgrade_enabled import (
aks_cluster_auto_upgrade_enabled,
)
cluster = build_cluster(auto_upgrade_channel="stable")
aks_client.clusters = {AZURE_SUBSCRIPTION_ID: {cluster.id: cluster}}
check = aks_cluster_auto_upgrade_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
@pytest.mark.parametrize("auto_upgrade_channel", [None, "", "none", "None"])
def test_fail(self, auto_upgrade_channel):
aks_client = mock.MagicMock
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.aks.aks_cluster_auto_upgrade_enabled.aks_cluster_auto_upgrade_enabled.aks_client",
new=aks_client,
),
):
from prowler.providers.azure.services.aks.aks_cluster_auto_upgrade_enabled.aks_cluster_auto_upgrade_enabled import (
aks_cluster_auto_upgrade_enabled,
)
cluster = build_cluster(auto_upgrade_channel=auto_upgrade_channel)
aks_client.clusters = {AZURE_SUBSCRIPTION_ID: {cluster.id: cluster}}
check = aks_cluster_auto_upgrade_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"