Skip to content

Commit cf5b5b9

Browse files
authored
Fixed parent not exists effect Azure#2608 (Azure#2685)
1 parent dfbad68 commit cf5b5b9

File tree

5 files changed

+171
-15
lines changed

5 files changed

+171
-15
lines changed

docs/CHANGELOG-v1.md

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ What's changed since v1.33.0:
3737
- Bug fixes:
3838
- Fixed `Azure.AKS.AuthorizedIPs` is not valid for a private cluster by @BernieWhite.
3939
[#2677](https://github.com/Azure/PSRule.Rules.Azure/issues/2677)
40+
- Fixed generating rule for VM extensions from policy is incorrect by @BernieWhite.
41+
[#2608](https://github.com/Azure/PSRule.Rules.Azure/issues/2608)
4042

4143
## v1.33.0
4244

src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs

+41-15
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
using PSRule.Rules.Azure.Data.Template;
1616
using PSRule.Rules.Azure.Pipeline;
1717
using PSRule.Rules.Azure.Resources;
18-
using YamlDotNet.Core.Tokens;
1918

2019
namespace PSRule.Rules.Azure.Data.Policy
2120
{
@@ -1471,9 +1470,22 @@ private static void EffectConditions(PolicyAssignmentContext context, PolicyDefi
14711470
!then.TryObjectProperty(PROPERTY_DETAILS, out var details))
14721471
return;
14731472

1474-
if (IsIfNotExistsEffect(context, then))
1473+
// Handle not exist effect for a parent type.
1474+
if (IsIfNotExistsEffect(context, then) && details.TryStringProperty(PROPERTY_TYPE, out var type) && ChildOf(policyDefinition, type))
1475+
{
1476+
// Replace with parent type.
1477+
policyDefinition.Types.Clear();
1478+
policyDefinition.Types.Add(type);
1479+
context.SetDefaultResourceType(type);
1480+
1481+
// Clean up condition that applies to child.
1482+
policyDefinition.Where = null;
1483+
1484+
policyDefinition.Condition = AndExistanceExpression(context, details, null, applyToChildren: false);
1485+
}
1486+
// Handle not exist effect.
1487+
else if (IsIfNotExistsEffect(context, then))
14751488
{
1476-
//policyDefinition.Where = policyDefinition.Condition;
14771489
policyDefinition.Condition = AndExistanceExpression(context, details, DefaultEffectConditions(context, details));
14781490
}
14791491
else
@@ -1482,6 +1494,15 @@ private static void EffectConditions(PolicyAssignmentContext context, PolicyDefi
14821494
}
14831495
}
14841496

1497+
private static bool ChildOf(PolicyDefinition policyDefinition, string type)
1498+
{
1499+
if (policyDefinition == null || policyDefinition.Types == null || policyDefinition.Types.Count != 1 || string.IsNullOrEmpty(type))
1500+
return false;
1501+
1502+
var currentType = policyDefinition.Types[0];
1503+
return type.Length < currentType.Length && currentType.StartsWith($"{type}/", StringComparison.OrdinalIgnoreCase);
1504+
}
1505+
14851506
private static void CompleteCondition(PolicyAssignmentContext context, PolicyDefinition policyDefinition, string effect)
14861507
{
14871508
if (policyDefinition.Condition != null)
@@ -1555,7 +1576,7 @@ private static JObject TypeExpression(PolicyAssignmentContext context, JObject d
15551576
};
15561577
}
15571578

1558-
private static JObject AndExistanceExpression(PolicyAssignmentContext context, JObject details, JObject subselector)
1579+
private static JObject AndExistanceExpression(PolicyAssignmentContext context, JObject details, JObject subselector, bool applyToChildren = true)
15591580
{
15601581
if (details == null || !details.TryObjectProperty(PROPERTY_EXISTENCECONDITION, out var existenceCondition))
15611582
{
@@ -1568,19 +1589,24 @@ private static JObject AndExistanceExpression(PolicyAssignmentContext context, J
15681589

15691590
VisitCondition(context, null, existenceCondition);
15701591

1571-
var allOf = new JArray
1572-
{
1573-
existenceCondition
1574-
};
1575-
var existenceExpression = new JObject
1592+
if (applyToChildren)
15761593
{
1577-
{ PROPERTY_FIELD, PROPERTY_RESOURCES },
1578-
{ PROPERTY_ALLOF, allOf }
1579-
};
1580-
if (subselector != null && subselector.Count > 0)
1581-
existenceExpression[PROPERTY_WHERE] = subselector;
1594+
var allOf = new JArray
1595+
{
1596+
existenceCondition
1597+
};
1598+
var existenceExpression = new JObject
1599+
{
1600+
{ PROPERTY_FIELD, PROPERTY_RESOURCES },
1601+
{ PROPERTY_ALLOF, allOf }
1602+
};
15821603

1583-
return existenceExpression;
1604+
if (subselector != null && subselector.Count > 0)
1605+
existenceExpression[PROPERTY_WHERE] = subselector;
1606+
1607+
existenceCondition = existenceExpression;
1608+
}
1609+
return existenceCondition;
15841610
}
15851611

15861612
private static JObject AndNameCondition(JObject details, JObject condition)

tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
<None Update="Policy.assignment.3.json">
3939
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4040
</None>
41+
<None Update="Policy.assignment.4.json">
42+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
43+
</None>
4144
<None Update="Policy.assignment.json">
4245
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4346
</None>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
[
2+
{
3+
"Identity": null,
4+
"Location": null,
5+
"Name": "assignment.4",
6+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment.4",
7+
"ResourceName": "assignment.4",
8+
"ResourceGroupName": null,
9+
"ResourceType": "Microsoft.Authorization/policyAssignments",
10+
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
11+
"Sku": null,
12+
"PolicyAssignmentId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment.4",
13+
"Properties": {
14+
"Scope": "/subscriptions/00000000-0000-0000-0000-000000000000",
15+
"NotScopes": [],
16+
"DisplayName": "Virtual machines' Guest Configuration extension should be deployed with system-assigned managed identity",
17+
"Description": null,
18+
"Metadata": {
19+
"assignedBy": "",
20+
"parameterScopes": {},
21+
"createdBy": "",
22+
"createdOn": "",
23+
"updatedBy": null,
24+
"updatedOn": null
25+
},
26+
"EnforcementMode": 1,
27+
"PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/d26f7642-7545-4e18-9b75-8c9bbdee3a9a",
28+
"Parameters": {
29+
"tagName": {
30+
"value": "env"
31+
}
32+
},
33+
"NonComplianceMessages": [
34+
{
35+
"Message": "Virtual machines' Guest Configuration extension should be deployed with system-assigned managed identity",
36+
"PolicyDefinitionReferenceId": null
37+
}
38+
]
39+
},
40+
"PolicyDefinitions": [
41+
{
42+
"Name": "d26f7642-7545-4e18-9b75-8c9bbdee3a9a",
43+
"ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/d26f7642-7545-4e18-9b75-8c9bbdee3a9a",
44+
"ResourceName": "d26f7642-7545-4e18-9b75-8c9bbdee3a9a",
45+
"ResourceType": "Microsoft.Authorization/policyDefinitions",
46+
"SubscriptionId": null,
47+
"Properties": {
48+
"Description": "The Guest Configuration extension requires a system assigned managed identity. Azure virtual machines in the scope of this policy will be non-compliant when they have the Guest Configuration extension installed but do not have a system assigned managed identity. Learn more at https://aka.ms/gcpol",
49+
"DisplayName": "Virtual machines' Guest Configuration extension should be deployed with system-assigned managed identity",
50+
"Metadata": {
51+
"version": "1.0.1",
52+
"category": "Security Center"
53+
},
54+
"Mode": "Indexed",
55+
"Parameters": {
56+
"effect": {
57+
"type": "String",
58+
"metadata": {
59+
"displayName": "Effect",
60+
"description": "Enable or disable the execution of the policy"
61+
},
62+
"allowedValues": [
63+
"AuditIfNotExists",
64+
"Disabled"
65+
],
66+
"defaultValue": "AuditIfNotExists"
67+
}
68+
},
69+
"PolicyRule": {
70+
"if": {
71+
"allOf": [
72+
{
73+
"field": "type",
74+
"equals": "Microsoft.Compute/virtualMachines/extensions"
75+
},
76+
{
77+
"field": "Microsoft.Compute/virtualMachines/extensions/publisher",
78+
"equals": "Microsoft.GuestConfiguration"
79+
}
80+
]
81+
},
82+
"then": {
83+
"effect": "[parameters('effect')]",
84+
"details": {
85+
"type": "Microsoft.Compute/virtualMachines",
86+
"name": "[first(split(field('fullName'), '/'))]",
87+
"existenceCondition": {
88+
"field": "identity.type",
89+
"contains": "SystemAssigned"
90+
}
91+
}
92+
}
93+
},
94+
"PolicyType": 2
95+
},
96+
"PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/d26f7642-7545-4e18-9b75-8c9bbdee3a9a"
97+
}
98+
],
99+
"exemptions": []
100+
}
101+
]

tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs

+24
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,30 @@ public void GetPolicyBaseline()
204204
Assert.Equal(new string[] { "Azure.Policy.b8a4e2d03e09", "PSRule.Rules.Azure\\Azure.KeyVault.SoftDelete" }, baseline.Include);
205205
}
206206

207+
/// <summary>
208+
/// This tests for a reverse cases where the child extension is audit if a setting on the parent resource is set.
209+
/// </summary>
210+
[Fact]
211+
public void GetParentAudit()
212+
{
213+
var context = new PolicyAssignmentContext(GetContext());
214+
var visitor = new PolicyAssignmentDataExportVisitor();
215+
foreach (var assignment in GetAssignmentData("Policy.assignment.4.json").Where(a => a["Name"].Value<string>() == "assignment.4"))
216+
visitor.Visit(context, assignment);
217+
218+
var definitions = context.GetDefinitions();
219+
Assert.NotNull(definitions);
220+
Assert.Single(definitions);
221+
222+
var actual = definitions.FirstOrDefault(definition => definition.DefinitionId == "/providers/Microsoft.Authorization/policyDefinitions/d26f7642-7545-4e18-9b75-8c9bbdee3a9a");
223+
Assert.NotNull(actual);
224+
Assert.Single(actual.Types);
225+
Assert.Equal("Microsoft.Compute/virtualMachines", actual.Types[0]);
226+
Assert.Null(actual.Where);
227+
Assert.Equal("{\"field\":\"identity.type\",\"contains\":\"SystemAssigned\"}", actual.Condition.ToString(Formatting.None));
228+
Assert.Equal(new string[] { "PSRule.Rules.Azure\\Azure.Policy.Indexed" }, actual.With);
229+
}
230+
207231
#region Helper methods
208232

209233
private static PipelineContext GetContext(PSRuleOption option = null)

0 commit comments

Comments
 (0)