Skip to content

Commit f8b3053

Browse files
feat(new): Added Azure.AKS.AuditAdmin (Azure#2994)
* feat(new): Added Azure.AKS.AuditAdmin * Update src/PSRule.Rules.Azure/en/PSRule-rules.psd1 Co-authored-by: Bernie White <[email protected]> * Update src/PSRule.Rules.Azure/rules/Azure.AKS.Rule.ps1 Co-authored-by: Bernie White <[email protected]> * Update docs/en/rules/Azure.AKS.AuditAdmin.md Co-authored-by: Bernie White <[email protected]> * Delete docs/en/rules/json --------- Co-authored-by: Bernie White <[email protected]>
1 parent 721abad commit f8b3053

File tree

5 files changed

+219
-55
lines changed

5 files changed

+219
-55
lines changed

docs/CHANGELOG-v1.md

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
2929

3030
## Unreleased
3131

32+
- New rules:
33+
- Azure Kubernetes Service:
34+
- Verify that clusters have kube-audit logging disabled when not required by @BenjaminEngeset.
35+
[#2450](https://github.com/Azure/PSRule.Rules.Azure/issues/2450)
36+
3237
## v1.39.0-B0009 (pre-release)
3338

3439
What's changed since v1.38.0:

docs/en/rules/Azure.AKS.AuditAdmin.md

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
severity: Important
3+
pillar: Cost Optimization
4+
category: CO:07 Component costs
5+
resource: Azure Kubernetes Service
6+
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AKS.AuditAdmin/
7+
---
8+
9+
# kube-audit-admin
10+
11+
## SYNOPSIS
12+
13+
Use kube-audit-admin instead of kube-audit to capture administrative actions in AKS clusters.
14+
15+
## DESCRIPTION
16+
17+
Key components in a Kubernetes cluster regularly scan or check for updated Kubernetes resources against the API server.
18+
These _get_ and _list_ operations typically occur more frequently as a Kubernetes cluster grows.
19+
20+
Auditing within AKS writes log events for each operation that occur against the API server.
21+
As a result, collecting audit logs for _get_ and _list_ operations of a production AKS cluster can increase cost exponentially.
22+
23+
AKS provides two log categories for collecting audit logs, `kube-audit` and `kube-audit-admin`.
24+
25+
- `kube-audit` - Audit log data for every audit event including _get_, _list_, _create_, _update_, _delete_, _patch_, and _post_.
26+
- `kube-audit-admin` - Is a subset of the `kube-audit` log category that excludes _get_ and list_ audit events.
27+
28+
In other words, both `kube-audit` and `kube-audit-admin` contain the same data except `kube-audit-admin` does not contain _get_ and _list_ events.
29+
Changes to the cluster configuration are captured with _create_, _update_, _delete_, _patch_, and _post_ events.
30+
31+
By using `kube-audit-admin`, changes to resources in AKS are audited, however events relating to reading resources and configuration are not.
32+
This significantly reduces the number of logs and overall cost for collecting and storing AKS audit events.
33+
34+
## RECOMMENDATION
35+
36+
Consider using kube-audit-admin logging instead of kube-audit when detailed logging of every API request is not required.
37+
This approach helps in managing log volume and associated costs while still capturing essential administrative actions.
38+
39+
## EXAMPLES
40+
41+
### Configure with Azure template
42+
43+
To deploy AKS clusters that pass this rule:
44+
45+
- Deploy a diagnostic settings sub-resource.
46+
- Enable logging for the `kube-audit-admin` category and disable logging for the `kube-audit` category.
47+
48+
For example:
49+
50+
```json
51+
{
52+
"type": "Microsoft.Insights/diagnosticSettings",
53+
"apiVersion": "2021-05-01-preview",
54+
"scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('clusterName'))]",
55+
"name": "[parameters('name')]",
56+
"properties": {
57+
"logs": [
58+
{
59+
"category": "kube-audit-admin",
60+
"enabled": true,
61+
"retentionPolicy": {
62+
"days": 0,
63+
"enabled": false
64+
}
65+
},
66+
{
67+
"category": "kube-audit",
68+
"enabled": false,
69+
"retentionPolicy": {
70+
"days": 0,
71+
"enabled": false
72+
}
73+
}
74+
],
75+
"workspaceId": "[parameters('workspaceId')]",
76+
"logAnalyticsDestinationType": "Dedicated"
77+
},
78+
"dependsOn": [
79+
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName'))]"
80+
]
81+
}
82+
```
83+
84+
### Configure with Bicep
85+
86+
To deploy AKS clusters that pass this rule:
87+
88+
- Deploy a diagnostic settings sub-resource.
89+
- Enable logging for the `kube-audit-admin` category and disable logging for the `kube-audit` category.
90+
91+
For example:
92+
93+
```bicep
94+
resource diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
95+
name: name
96+
scope: aks
97+
properties: {
98+
logs: [
99+
{
100+
category: 'kube-audit-admin'
101+
enabled: true
102+
retentionPolicy: {
103+
days: 0
104+
enabled: false
105+
}
106+
}
107+
{
108+
category: 'kube-audit'
109+
enabled: false
110+
retentionPolicy: {
111+
days: 0
112+
enabled: false
113+
}
114+
}
115+
]
116+
workspaceId: workspaceId
117+
logAnalyticsDestinationType: 'Dedicated'
118+
}
119+
}
120+
```
121+
122+
## LINKS
123+
124+
- [CO:07 Component costs](https://learn.microsoft.com/azure/well-architected/cost-optimization/optimize-component-costs)
125+
- [Monitor AKS](https://learn.microsoft.com/azure/aks/monitor-aks)
126+
- [AKS control plane/resource logs](https://learn.microsoft.com/azure/aks/monitor-aks#aks-control-planeresource-logs)
127+
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.insights/diagnosticsettings)

src/PSRule.Rules.Azure/en/PSRule-rules.psd1

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
AKSAvailabilityZone = "The agent pool ({0}) deployed to region ({1}) should use following availability zones [{2}]."
1616
AKSAuditLogs = "The diagnostic setting ({0}) logs should enable at least one of (kube-audit, kube-audit-admin) and guard."
1717
AKSPlatformLogs = "The diagnostic setting ({0}) logs should enable ({1})."
18+
AKSAuditAdmin = "The diagnostic setting ({0}) should use 'kube-audit-admin' instead of the 'kube-audit' log category."
1819
AKSEphemeralOSDiskNotConfigured = "The OS disk type 'Managed' should be of type 'Ephemeral'."
1920
SubnetNSGNotConfigured = "The subnet ({0}) has no NSG associated."
2021
ServiceUrlNotHttps = "The service URL for '{0}' is not a HTTPS endpoint."

src/PSRule.Rules.Azure/rules/Azure.AKS.Rule.ps1

+69-55
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Rule 'Azure.AKS.PoolVersion' -Ref 'AZR-000016' -Type 'Microsoft.ContainerService
3737
}
3838
foreach ($agentPool in $agentPools) {
3939
$Assert.HasDefaultValue($agentPool, 'orchestratorVersion', $clusterVersion).
40-
Reason($LocalizedData.AKSNodePoolVersion, $agentPool.name, $agentPool.orchestratorVersion);
40+
Reason($LocalizedData.AKSNodePoolVersion, $agentPool.name, $agentPool.orchestratorVersion);
4141
}
4242
}
4343

@@ -50,7 +50,7 @@ Rule 'Azure.AKS.PoolScaleSet' -Ref 'AZR-000017' -Type 'Microsoft.ContainerServic
5050
}
5151
foreach ($agentPool in $agentPools) {
5252
$Assert.HasFieldValue($agentPool, 'type', 'VirtualMachineScaleSets').
53-
Reason($LocalizedData.AKSNodePoolType, $agentPool.name);
53+
Reason($LocalizedData.AKSNodePoolType, $agentPool.name);
5454
}
5555
}
5656

@@ -99,11 +99,11 @@ Rule 'Azure.AKS.CNISubnetSize' -Ref 'AZR-000020' -If { IsExport } -With 'Azure.A
9999
$subnetAddressPrefixSize = [int]$subnet.Properties.addressPrefix.Split('/')[-1];
100100

101101
$Assert.LessOrEqual($subnetAddressPrefixSize, '.', $configurationMinimumSubnetSize).
102-
Reason(
103-
$LocalizedData.AKSAzureCNI,
104-
$subnet.Name,
105-
$configurationMinimumSubnetSize
106-
);
102+
Reason(
103+
$LocalizedData.AKSAzureCNI,
104+
$subnet.Name,
105+
$configurationMinimumSubnetSize
106+
);
107107
}
108108
} -Configure @{ AZURE_AKS_CNI_MINIMUM_CLUSTER_SUBNET_SIZE = 23 }
109109

@@ -134,7 +134,7 @@ Rule 'Azure.AKS.AvailabilityZone' -Ref 'AZR-000021' -Type 'Microsoft.ContainerSe
134134
# Availability zones only available on virtual machine scale sets
135135
if ($Assert.HasFieldValue($agentPool, 'type', 'VirtualMachineScaleSets').Result) {
136136
$Assert.HasFieldValue($agentPool, 'availabilityZones').
137-
Reason($LocalizedData.AKSAvailabilityZone, $agentPool.name, $TargetObject.Location, $joinedZoneString);
137+
Reason($LocalizedData.AKSAvailabilityZone, $agentPool.name, $TargetObject.Location, $joinedZoneString);
138138
}
139139
else {
140140
$Assert.Pass();
@@ -150,15 +150,15 @@ Rule 'Azure.AKS.AuditLogs' -Ref 'AZR-000022' -Type 'Microsoft.ContainerService/m
150150

151151
foreach ($setting in $diagnosticLogs) {
152152
$kubeAuditEnabledLog = @($setting.Properties.logs | Where-Object {
153-
$_.category -in 'kube-audit', 'kube-audit-admin' -and $_.enabled
154-
});
153+
$_.category -in 'kube-audit', 'kube-audit-admin' -and $_.enabled
154+
});
155155

156156
$guardEnabledLog = @($setting.Properties.logs | Where-Object {
157-
$_.category -eq 'guard' -and $_.enabled
158-
});
157+
$_.category -eq 'guard' -and $_.enabled
158+
});
159159

160160
$auditLogsEnabled = $Assert.Greater($kubeAuditEnabledLog, '.', 0).Result -and
161-
$Assert.Greater($guardEnabledLog, '.', 0).Result;
161+
$Assert.Greater($guardEnabledLog, '.', 0).Result;
162162

163163
$Assert.Create($auditLogsEnabled, $LocalizedData.AKSAuditLogs, $setting.name);
164164
}
@@ -177,7 +177,7 @@ Rule 'Azure.AKS.PlatformLogs' -Ref 'AZR-000023' -Type 'Microsoft.ContainerServic
177177
$Assert.Greater($diagnosticLogs, '.', 0).Reason($LocalizedData.DiagnosticSettingsNotConfigured, $TargetObject.name);
178178

179179
$availableLogCategories = @{
180-
Logs = @(
180+
Logs = @(
181181
'cluster-autoscaler',
182182
'kube-apiserver',
183183
'kube-controller-manager',
@@ -189,12 +189,12 @@ Rule 'Azure.AKS.PlatformLogs' -Ref 'AZR-000023' -Type 'Microsoft.ContainerServic
189189
}
190190

191191
$configurationLogCategories = @($configurationLogCategoriesList | Where-Object {
192-
$_ -in $availableLogCategories.Logs
193-
});
192+
$_ -in $availableLogCategories.Logs
193+
});
194194

195195
$configurationMetricCategories = @($configurationLogCategoriesList | Where-Object {
196-
$_ -in $availableLogCategories.Metrics
197-
});
196+
$_ -in $availableLogCategories.Metrics
197+
});
198198

199199
$logCategoriesNeeded = [System.Math]::Min(
200200
$configurationLogCategories.Length,
@@ -210,19 +210,19 @@ Rule 'Azure.AKS.PlatformLogs' -Ref 'AZR-000023' -Type 'Microsoft.ContainerServic
210210

211211
foreach ($setting in $diagnosticLogs) {
212212
$platformLogs = @($setting.Properties.logs | Where-Object {
213-
$_.enabled -and
214-
$_.category -in $configurationLogCategories -and
215-
$_.category -in $availableLogCategories.Logs
216-
});
213+
$_.enabled -and
214+
$_.category -in $configurationLogCategories -and
215+
$_.category -in $availableLogCategories.Logs
216+
});
217217

218218
$metricLogs = @($setting.Properties.metrics | Where-Object {
219-
$_.enabled -and
220-
$_.category -in $configurationMetricCategories -and
221-
$_.category -in $availableLogCategories.Metrics
222-
});
219+
$_.enabled -and
220+
$_.category -in $configurationMetricCategories -and
221+
$_.category -in $availableLogCategories.Metrics
222+
});
223223

224224
$platformLogsEnabled = $Assert.HasFieldValue($platformLogs, 'Length', $logCategoriesNeeded).Result -and
225-
$Assert.HasFieldValue($metricLogs, 'Length', $metricCategoriesNeeded).Result
225+
$Assert.HasFieldValue($metricLogs, 'Length', $metricCategoriesNeeded).Result
226226

227227
$Assert.Create(
228228
$platformLogsEnabled,
@@ -262,8 +262,8 @@ Rule 'Azure.AKS.MinNodeCount' -Ref 'AZR-000024' -Type 'Microsoft.ContainerServic
262262
Rule 'Azure.AKS.MinUserPoolNodes' -Ref 'AZR-000412' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2024_03'; 'Azure.WAF/pillar' = 'Reliability' } {
263263
$excludedPools = $Configuration.GetStringValues('AZURE_AKS_CLUSTER_USER_POOL_EXCLUDED_FROM_MINIMUM_NODES');
264264
$agentPools = @(GetAgentPoolProfiles | Where-Object {
265-
$_.mode -eq 'user' -and $_.name -notin $excludedPools -and $_.scaleSetPriority -ne 'Spot'
266-
})
265+
$_.mode -eq 'user' -and $_.name -notin $excludedPools -and $_.scaleSetPriority -ne 'Spot'
266+
})
267267

268268
if ($agentPools.Length -eq 0) {
269269
return $Assert.Pass();
@@ -305,6 +305,20 @@ Rule 'Azure.AKS.EphemeralOSDisk' -Ref 'AZR-000287' -Level Warning -Type 'Microso
305305
}
306306
}
307307

308+
# Synopsis: Use kube-audit-admin instead of kube-audit to capture administrative actions in AKS clusters.
309+
Rule 'Azure.AKS.AuditAdmin' -Ref 'AZR-000445' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2024_09'; 'Azure.WAF/pillar' = 'Cost Optimization'; } {
310+
$kubeAuditLogs = @(GetSubResources -ResourceType 'Microsoft.Insights/diagnosticSettings' |
311+
Where-Object { $_.properties.logs | Where-Object { $_.category -eq 'kube-audit' -and $_.enabled } } )
312+
313+
if ($kubeAuditLogs.Count -eq 0) {
314+
return $Assert.Pass()
315+
}
316+
317+
foreach ($kubeAuditLog in $kubeAuditLogs) {
318+
$Assert.Fail().Reason($LocalizedData.AKSAuditAdmin, $kubeAuditLog.name)
319+
}
320+
}
321+
308322
#region Helper functions
309323

310324
function global:GetAgentPoolProfiles {
@@ -315,36 +329,36 @@ function global:GetAgentPoolProfiles {
315329
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
316330
$TargetObject.Properties.agentPoolProfiles;
317331
@(GetSubResources -ResourceType 'Microsoft.ContainerService/managedClusters/agentPools' | ForEach-Object {
318-
[PSCustomObject]@{
319-
name = $_.name
320-
type = $_.properties.type
321-
mode = $_.properties.mode
322-
maxPods = $_.properties.maxPods
323-
orchestratorVersion = $_.properties.orchestratorVersion
324-
enableAutoScaling = $_.properties.enableAutoScaling
325-
availabilityZones = $_.properties.availabilityZones
326-
osDiskType = $_.properties.osDiskType
327-
count = [int]$_.properties.count
328-
minCount = [int]$_.properties.minCount
329-
maxCount = [int]$_.properties.maxCount
330-
scaleSetPriority = $_.properties.scaleSetPriority
331-
}
332-
});
332+
[PSCustomObject]@{
333+
name = $_.name
334+
type = $_.properties.type
335+
mode = $_.properties.mode
336+
maxPods = $_.properties.maxPods
337+
orchestratorVersion = $_.properties.orchestratorVersion
338+
enableAutoScaling = $_.properties.enableAutoScaling
339+
availabilityZones = $_.properties.availabilityZones
340+
osDiskType = $_.properties.osDiskType
341+
count = [int]$_.properties.count
342+
minCount = [int]$_.properties.minCount
343+
maxCount = [int]$_.properties.maxCount
344+
scaleSetPriority = $_.properties.scaleSetPriority
345+
}
346+
});
333347
}
334348
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
335349
[PSCustomObject]@{
336-
name = $TargetObject.name
337-
type = $TargetObject.properties.type
338-
mode = $TargetObject.properties.mode
339-
maxPods = $TargetObject.properties.maxPods
350+
name = $TargetObject.name
351+
type = $TargetObject.properties.type
352+
mode = $TargetObject.properties.mode
353+
maxPods = $TargetObject.properties.maxPods
340354
orchestratorVersion = $TargetObject.properties.orchestratorVersion
341-
enableAutoScaling = $TargetObject.properties.enableAutoScaling
342-
availabilityZones = $TargetObject.properties.availabilityZones
343-
osDiskType = $TargetObject.properties.osDiskType
344-
count = [int]$TargetObject.properties.count
345-
minCount = [int]$TargetObject.properties.minCount
346-
maxCount = [int]$TargetObject.properties.maxCount
347-
scaleSetPriority = $TargetObject.properties.scaleSetPriority
355+
enableAutoScaling = $TargetObject.properties.enableAutoScaling
356+
availabilityZones = $TargetObject.properties.availabilityZones
357+
osDiskType = $TargetObject.properties.osDiskType
358+
count = [int]$TargetObject.properties.count
359+
minCount = [int]$TargetObject.properties.minCount
360+
maxCount = [int]$TargetObject.properties.maxCount
361+
scaleSetPriority = $TargetObject.properties.scaleSetPriority
348362
}
349363
}
350364
}

tests/PSRule.Rules.Azure.Tests/Azure.AKS.Tests.ps1

+17
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,23 @@ Describe 'Azure.AKS' -Tag AKS {
526526
$ruleResult.Length | Should -Be 1;
527527
$ruleResult.TargetName | Should -BeIn 'cluster-L';
528528
}
529+
530+
It 'Azure.AKS.AuditAdmin' {
531+
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.AKS.AuditAdmin' };
532+
533+
# Fail
534+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
535+
$ruleResult.Length | Should -Be 2;
536+
$ruleResult.TargetName | Should -Be 'cluster-I', 'cluster-J';
537+
538+
$ruleResult[0].Reason | Should -Be "The diagnostic setting (metrics) should use 'kube-audit-admin' instead of the 'kube-audit' log category.";
539+
$ruleResult[1].Reason | Should -Be "The diagnostic setting (metrics) should use 'kube-audit-admin' instead of the 'kube-audit' log category.";
540+
541+
# Pass
542+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
543+
$ruleResult.Length | Should -Be 9;
544+
$ruleResult.TargetName | Should -Be 'cluster-A', 'cluster-B', 'cluster-C', 'cluster-D', 'cluster-F', 'cluster-G', 'cluster-H', 'cluster-K', 'cluster-L';
545+
}
529546
}
530547

531548
Context 'Resource name' {

0 commit comments

Comments
 (0)