Skip to content
16 changes: 16 additions & 0 deletions docs/authoring-json-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,22 @@ Example:
}
```

#### **HasStableAKSVersion**
*Type: boolean*

The `HasStableAKSVersion` operator checks if an AKS cluster is using a stable Kubernetes version for its region. This operator fetches the list of stable AKS versions from the official AKS releases API. Checks if the cluster's `kubernetesVersion` is in the stable list for its `location`. Returns true if `hasStableAKSVersion: true` and the version is stable or if `hasStableAKSVersion: false` and the version is NOT stable.

Example:
```json
{
"resourceType": "Microsoft.ContainerService/managedClusters",
"path": "properties",
"hasStableAKSVersion": true
}
```



### Structured Operators
These operators build up a structure of child `Evaluation`s, and therefore contain additional operators inside them. These operators are not required to include a `path`. If `resourceType` or `path` are specified, that becomes the scope for all `Evaluation`s nested inside the operator. More information on [Scopes](#scopes) can be found below.

Expand Down
156 changes: 156 additions & 0 deletions src/Analyzer.Core.BuiltInRuleTests/Tests/TA-000039/AKSClusters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2022-03-01",
"name": "aks-cluster-good",
"location": "westeurope",
"properties": {
"kubernetesVersion": "1.32.5",
"dnsPrefix": "aksclusterone",
"agentPoolProfiles": [
{
"name": "nodepool1",
"count": 3,
"vmSize": "Standard_DS2_v2",
"osType": "Linux",
"mode": "System"
}
],
"networkProfile": {
"networkPlugin": "azure",
"loadBalancerSku": "standard"
}
}
},
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "storagenotaks",
"location": "invalid-region",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
},
{
"type": "Microsoft.CustomProviders/resourceProviders",
"apiVersion": "2018-09-01-preview",
"name": "custom-resource",
"location": "westeurope",
"properties": {
"kubernetesVersion": "1.28.5",
"description": "Custom resource simulating invalid version property"
}
},
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2022-03-01",
"name": "aks-cluster-invalid-version",
"location": "northeurope",
"properties": {
"kubernetesVersion": "1.28.5",
"dnsPrefix": "aksclustertwo",
"agentPoolProfiles": [
{
"name": "nodepool2",
"count": 2,
"vmSize": "Standard_B4ms",
"osType": "Linux",
"mode": "User"
}
],
"enableRBAC": true,
"networkProfile": {
"networkPlugin": "kubenet",
"outboundType": "loadBalancer"
}
}
},
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2022-03-01",
"name": "aks-cluster-invalid-location",
"location": "invalid-region",
"properties": {
"kubernetesVersion": "1.32.2",
"dnsPrefix": "aksclusterinvalid",
"agentPoolProfiles": [
{
"name": "nodepool3",
"count": 1,
"vmSize": "Standard_B2s",
"osType": "Linux",
"mode": "System"
}
],
"enableRBAC": true,
"networkProfile": {
"networkPlugin": "azure",
"outboundType": "loadBalancer"
}
}
},
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2022-03-01",
"name": "aks-cluster-old-version",
"location": "westeurope",
"properties": {
"kubernetesVersion": "1.18.0",
"dnsPrefix": "aksclusterold",
"agentPoolProfiles": [
{
"name": "nodepool4",
"count": 1,
"vmSize": "Standard_B2s",
"osType": "Linux",
"mode": "System"
}
],
"enableRBAC": true,
"networkProfile": {
"networkPlugin": "azure",
"outboundType": "loadBalancer"
}
}
},
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2022-03-01",
"name": "aks-cluster-nolocation-noversion",
"properties": {
"dnsPrefix": "aksnolocnoversion",
"agentPoolProfiles": [
{
"name": "nodepool5",
"count": 1,
"vmSize": "Standard_B2s",
"osType": "Linux",
"mode": "System"
}
]
}
},
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2022-03-01",
"name": "aks-cluster-noversion",
"location": "northeurope",
"properties": {
"dnsPrefix": "aksnoversion",
"agentPoolProfiles": [
{
"name": "nodepool6",
"count": 2,
"vmSize": "Standard_B4ms",
"osType": "Linux",
"mode": "User"
}
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[
{
"Template": "AKSClusters.json",
"ReportedFailures": [
{
"LineNumber": 46,
"Description": "AKS cluster using unstable Kubernetes version"
},
{
"LineNumber": 70,
"Description": "AKS cluster using invalid location"
},
{
"LineNumber": 94,
"Description": "AKS cluster using very old Kubernetes version"
},
{
"LineNumber": 118,
"Description": "AKS cluster missing Kubernetes version"
},
{
"LineNumber": 134,
"Description": "AKS cluster missing Kubernetes version"
}
],
"PassingSections": [
{
"ResourceName": "aks-cluster-good",
"Explanation": "AKS cluster using stable Kubernetes version 1.32.5 in supported region westeurope"
},
{
"ResourceName": "storagenotaks",
"Explanation": "Not an AKS resource, rule does not apply"
},
{
"ResourceName": "custom-resource",
"Explanation": "Not an AKS resource, rule does not apply"
}
]
}
]
14 changes: 14 additions & 0 deletions src/Analyzer.Core/Rules/BuiltInRules.json
Original file line number Diff line number Diff line change
Expand Up @@ -1096,5 +1096,19 @@
}
]
}
},
{
"id": "TA-000039",
"name": "AKS.UseStableKubernetesVersion",
"shortDescription": "AKS clusters should use stable Kubernetes versions",
"fullDescription": "AKS clusters should use stable Kubernetes versions for their region to ensure security and support.",
"recommendation": "Use a stable Kubernetes version for your AKS cluster",
"helpUri": "https://eng.ms/docs/microsoft-security/digital-security-and-resilience/azure-security/security-health-analytics/sha-intelligence/threat-vulnerability-management/remediate/actions/upgradeaksclusterversion",
"severity": 2,
"evaluation": {
"resourceType": "Microsoft.ContainerService/managedClusters",
"path": "properties",
"hasStableAKSVersion": true
}
}
]
12 changes: 12 additions & 0 deletions src/Analyzer.JsonRuleEngine.FunctionalTests/RuleParsingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Azure.Templates.Analyzer.RuleEngines.JsonEngine.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.Templates.Analyzer.RuleEngines.JsonEngine.FunctionalTests
{
Expand All @@ -29,6 +30,8 @@ public class RuleParsingTests
[DataTestMethod]
[DataRow("hasValue", false, typeof(HasValueOperator), DisplayName = "HasValue: false")]
[DataRow("exists", true, typeof(ExistsOperator), DisplayName = "Exists: true")]
[DataRow("hasStableAKSVersion", true, typeof(HasStableAksVersionOperator), DisplayName = "HasStableAKSVersion: true")]
[DataRow("hasStableAKSVersion", false, typeof(HasStableAksVersionOperator), DisplayName = "HasStableAKSVersion: false")]
[DataRow("greater", "2021-02-28", typeof(InequalityOperator), DisplayName = "Greater: 2021-02-28")]
[DataRow("greater", "2021-02-28T18:17:16Z", typeof(InequalityOperator), DisplayName = "Greater: 2021-02-28T18:17:16Z")]
[DataRow("greater", "2021-02-28T18:17:16+00:00", typeof(InequalityOperator), DisplayName = "Greater: 2021-02-28T18:17:16+00:00")]
Expand Down Expand Up @@ -107,6 +110,15 @@ private static void InequalityValidation(InequalityOperator inequalityOperator,
Assert.IsTrue(parsedDate.Second == 0 || parsedDate.Second == 16);
}

[OperatorSpecificValidator(typeof(HasStableAksVersionOperator))]
private static void HasStableAksVersionValidation(HasStableAksVersionOperator hasStableAksVersionOperator, bool operatorValue)
{
var actualValue = hasStableAksVersionOperator.SpecifiedValue.ToObject<bool>();
Assert.AreEqual(operatorValue, actualValue);
Assert.IsFalse(hasStableAksVersionOperator.IsNegative);
Assert.AreEqual("HasStableAksVersion", hasStableAksVersionOperator.Name);
}

private const string TestResourceType = "Namespace/ResourceType";
private const string TestPath = "json.path";
}
Expand Down
Loading