Skip to content

Commit

Permalink
Fixes policy deployment copy loop #2605 (#2613)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Dec 18, 2023
1 parent 4533f73 commit 44118b9
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ What's changed since v1.32.0:
- Bug fixes:
- Fixed quotes get incorrectly duplicated by @BernieWhite.
[#2593](https://github.com/Azure/PSRule.Rules.Azure/issues/2593)
- Fixed failure to expand copy loop in a Azure Policy deployment by @BernieWhite.
[#2605](https://github.com/Azure/PSRule.Rules.Azure/issues/2605)

## v1.32.0

Expand Down
14 changes: 5 additions & 9 deletions src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1764,10 +1764,9 @@ private static JToken ResolveToken(ITemplateContext context, JToken token)
/// </summary>
private static TemplateContext.CopyIndexState[] GetPropertyIterator(ITemplateContext context, JObject value)
{
if (value.ContainsKey(PROPERTY_COPY))
if (value.TryArrayProperty(PROPERTY_COPY, out var copyObjectArray))
{
var result = new List<TemplateContext.CopyIndexState>();
var copyObjectArray = value[PROPERTY_COPY].Value<JArray>();
for (var i = 0; i < copyObjectArray.Count; i++)
{
var copyObject = copyObjectArray[i] as JObject;
Expand All @@ -1792,13 +1791,12 @@ private static TemplateContext.CopyIndexState[] GetPropertyIterator(ITemplateCon
/// </summary>
private static TemplateContext.CopyIndexState[] GetOutputIterator(ITemplateContext context, JObject value)
{
if (value.ContainsKey(PROPERTY_COPY))
if (value.TryObjectProperty(PROPERTY_COPY, out var copyObject))
{
var result = new List<TemplateContext.CopyIndexState>();
var copyObject = value[PROPERTY_COPY].Value<JObject>();
var state = new TemplateContext.CopyIndexState
{
Name = "",
Name = string.Empty,
Input = copyObject[PROPERTY_INPUT],
Count = ExpandPropertyInt(context, copyObject, PROPERTY_COUNT)
};
Expand All @@ -1813,9 +1811,8 @@ private static TemplateContext.CopyIndexState[] GetOutputIterator(ITemplateConte

private static IEnumerable<TemplateContext.CopyIndexState> GetVariableIterator(ITemplateContext context, JObject value, bool pushToStack = true)
{
if (value.ContainsKey(PROPERTY_COPY))
if (value.TryArrayProperty(PROPERTY_COPY, out var copyObjectArray))
{
var copyObjectArray = value[PROPERTY_COPY].Value<JArray>();
for (var i = 0; i < copyObjectArray.Count; i++)
{
var copyObject = copyObjectArray[i] as JObject;
Expand Down Expand Up @@ -1848,9 +1845,8 @@ private static TemplateContext.CopyIndexState GetResourceIterator(TemplateContex
{
Input = value
};
if (value.ContainsKey(PROPERTY_COPY))
if (value.TryObjectProperty(PROPERTY_COPY, out var copyObject))
{
var copyObject = value[PROPERTY_COPY].Value<JObject>();
result.Name = ExpandProperty<string>(context, copyObject, PROPERTY_NAME);
result.Count = ExpandPropertyInt(context, copyObject, PROPERTY_COUNT);
context.CopyIndex.PushResourceType(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@
<None Update="Tests.Bicep.10.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Template.Policy.WithDeployment.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
228 changes: 228 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Template.Policy.WithDeployment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"policyName": {
"metadata": {
"description": "Policy name"
},
"type": "string",
"defaultValue": "Policy-example"
}
},
"resources": [
{
"name": "[parameters('policyName')]",
"type": "Microsoft.Authorization/policyDefinitions",
"apiVersion": "2021-06-01",
"properties": {
"policyType": "Custom",
"mode": "Indexed",
"displayName": "Deploy a route table with specific user defined routes",
"description": "Deploys a route table with specific user defined routes when one does not exist. The route table deployed by the policy must be manually associated to subnet(s)",
"metadata": {
"version": "1.0.0",
"category": "Network",
"source": "https://github.com/Azure/Enterprise-Scale/",
"alzCloudEnvironments": [
"AzureCloud",
"AzureChinaCloud",
"AzureUSGovernment"
]
},
"parameters": {
"effect": {
"type": "String",
"metadata": {
"displayName": "Effect",
"description": "Enable or disable the execution of the policy"
},
"allowedValues": [
"DeployIfNotExists",
"Disabled"
],
"defaultValue": "DeployIfNotExists"
},
"requiredRoutes": {
"type": "Array",
"metadata": {
"displayName": "requiredRoutes",
"description": "Routes that must exist in compliant route tables deployed by this policy"
}
},
"vnetRegion": {
"type": "String",
"metadata": {
"displayName": "vnetRegion",
"description": "Only VNets in this region will be evaluated against this policy"
}
},
"routeTableName": {
"type": "String",
"metadata": {
"displayName": "routeTableName",
"description": "Name of the route table automatically deployed by this policy"
}
},
"disableBgpPropagation": {
"type": "Boolean",
"metadata": {
"displayName": "DisableBgpPropagation",
"description": "Disable BGP Propagation"
},
"defaultValue": false
}
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Network/virtualNetworks"
},
{
"field": "location",
"equals": "[[parameters('vnetRegion')]"
}
]
},
"then": {
"effect": "[[parameters('effect')]",
"details": {
"type": "Microsoft.Network/routeTables",
"existenceCondition": {
"allOf": [
{
"field": "name",
"equals": "[[parameters('routeTableName')]"
},
{
"count": {
"field": "Microsoft.Network/routeTables/routes[*]",
"where": {
"value": "[[concat(current('Microsoft.Network/routeTables/routes[*].addressPrefix'), ';', current('Microsoft.Network/routeTables/routes[*].nextHopType'), if(equals(toLower(current('Microsoft.Network/routeTables/routes[*].nextHopType')),'virtualappliance'), concat(';', current('Microsoft.Network/routeTables/routes[*].nextHopIpAddress')), ''))]",
"in": "[[parameters('requiredRoutes')]"
}
},
"equals": "[[length(parameters('requiredRoutes'))]"
}
]
},
"roleDefinitionIds": [
"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/00000000-0000-0000-0000-000000000000"
],
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"routeTableName": {
"type": "string"
},
"vnetRegion": {
"type": "string"
},
"requiredRoutes": {
"type": "array"
},
"disableBgpPropagation": {
"type": "bool"
}
},
"variables": {
"copyLoop": [
{
"name": "routes",
"count": "[[[length(parameters('requiredRoutes'))]",
"input": {
"name": "[[[concat('route-',copyIndex('routes'))]",
"properties": {
"addressPrefix": "[[[split(parameters('requiredRoutes')[copyIndex('routes')], ';')[0]]",
"nextHopType": "[[[split(parameters('requiredRoutes')[copyIndex('routes')], ';')[1]]",
"nextHopIpAddress": "[[[if(equals(toLower(split(parameters('requiredRoutes')[copyIndex('routes')], ';')[1]),'virtualappliance'),split(parameters('requiredRoutes')[copyIndex('routes')], ';')[2], null())]"
}
}
}
]
},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "routeTableDepl",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"routeTableName": {
"type": "string"
},
"vnetRegion": {
"type": "string"
},
"requiredRoutes": {
"type": "array"
},
"disableBgpPropagation": {
"type": "bool"
}
},
"resources": [
{
"type": "Microsoft.Network/routeTables",
"apiVersion": "2021-02-01",
"name": "[[[parameters('routeTableName')]",
"location": "[[[parameters('vnetRegion')]",
"properties": {
"disableBgpRoutePropagation": "[[[parameters('disableBgpPropagation')]",
"copy": "[[variables('copyLoop')]"
}
}
]
},
"parameters": {
"routeTableName": {
"value": "[[parameters('routeTableName')]"
},
"vnetRegion": {
"value": "[[parameters('vnetRegion')]"
},
"requiredRoutes": {
"value": "[[parameters('requiredRoutes')]"
},
"disableBgpPropagation": {
"value": "[[parameters('disableBgpPropagation')]"
}
}
}
}
]
},
"parameters": {
"routeTableName": {
"value": "[[parameters('routeTableName')]"
},
"vnetRegion": {
"value": "[[parameters('vnetRegion')]"
},
"requiredRoutes": {
"value": "[[parameters('requiredRoutes')]"
},
"disableBgpPropagation": {
"value": "[[parameters('disableBgpPropagation')]"
}
}
}
}
}
}
}
}
}
],
"outputs": {}
}
7 changes: 7 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,13 @@ public void Quoting()
Assert.Equal(10, result["value"][0]["parameters"]["debugLength"].Value<int>());
}

[Fact]
public void PolicyCopyLoop()
{
var resources = ProcessTemplate(GetSourcePath("Template.Policy.WithDeployment.json"), null, out var templateContext);
Assert.Equal(2, resources.Length);
}

#region Helper methods

private static string GetSourcePath(string fileName)
Expand Down

0 comments on commit 44118b9

Please sign in to comment.