diff --git a/src/Analyzer.Cli.FunctionalTests/Tests/TriggersOnlyNonSecurityRules.json b/src/Analyzer.Cli.FunctionalTests/Tests/TriggersOnlyNonSecurityRules.json
index 73b80652..32e698d8 100644
--- a/src/Analyzer.Cli.FunctionalTests/Tests/TriggersOnlyNonSecurityRules.json
+++ b/src/Analyzer.Cli.FunctionalTests/Tests/TriggersOnlyNonSecurityRules.json
@@ -10,7 +10,16 @@
"name": "aWorkspace",
"apiVersion": "2016-12-01",
"location": "",
- "properties": {}
+ "identity": {
+ "type": "UserAssigned",
+ "userAssignedIdentities": {
+ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'example'))]": {}
+ }
+ },
+ "properties": {
+ "primaryUserAssignedIdentity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'example')]",
+ "publicNetworkAccess": "Disabled"
+ }
}
],
"outputs": {}
diff --git a/src/Analyzer.Core.UnitTests/TemplateAnalyzerTests.cs b/src/Analyzer.Core.UnitTests/TemplateAnalyzerTests.cs
index 4804538c..ae1a10e8 100644
--- a/src/Analyzer.Core.UnitTests/TemplateAnalyzerTests.cs
+++ b/src/Analyzer.Core.UnitTests/TemplateAnalyzerTests.cs
@@ -28,7 +28,7 @@ public static void AssemblyInitialize(TestContext context)
[DataTestMethod]
[DataRow(@"{ ""azureActiveDirectory"": { ""tenantId"": ""tenantId"" } }", "Microsoft.ServiceFabric/clusters", 1, 1, DisplayName = "1 matching resource with 1 passing evaluation")]
[DataRow(@"{ ""azureActiveDirectory"": { ""someProperty"": ""propertyValue"" } }", "Microsoft.ServiceFabric/clusters", 1, 0, DisplayName = "1 matching resource with 1 failing evaluation")]
- [DataRow(@"{ ""property1"": { ""someProperty"": ""propertyValue"" } }", "Microsoft.MachineLearningServices/workspaces", 0, 0, DisplayName = "0 matching resources with no results")]
+ [DataRow(@"{ ""property1"": { ""someProperty"": ""propertyValue"" } }", "Microsoft.MachineLearningServices/workspaces", 2, 0, DisplayName = "0 matching resources with no results")]
[DataRow(@"{ ""azureActiveDirectory"": { ""tenantId"": ""tenantId"" } }", "Microsoft.ServiceFabric/clusters", 2, 1, @"{ ""azureActiveDirectory"": { ""someProperty"": ""propertyValue"" } }", DisplayName = "2 matching resources with 1 passing evaluation")]
public void AnalyzeTemplate_ValidInputValues_ReturnCorrectEvaluations(string resource1Properties, string resourceType, int expectedEvaluationCount, int expectedEvaluationPassCount, string resource2Properties = null)
{
diff --git a/src/Analyzer.PowerShellRuleEngine.UnitTests/Analyzer.PowerShellRuleEngine.UnitTests.csproj b/src/Analyzer.PowerShellRuleEngine.UnitTests/Analyzer.PowerShellRuleEngine.UnitTests.csproj
index 6aae6da5..bf27b42f 100644
--- a/src/Analyzer.PowerShellRuleEngine.UnitTests/Analyzer.PowerShellRuleEngine.UnitTests.csproj
+++ b/src/Analyzer.PowerShellRuleEngine.UnitTests/Analyzer.PowerShellRuleEngine.UnitTests.csproj
@@ -24,4 +24,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Analyzer.PowerShellRuleEngine.UnitTests/PowerShellRuleEngineTests.cs b/src/Analyzer.PowerShellRuleEngine.UnitTests/PowerShellRuleEngineTests.cs
index c5482bfa..67f7bd76 100644
--- a/src/Analyzer.PowerShellRuleEngine.UnitTests/PowerShellRuleEngineTests.cs
+++ b/src/Analyzer.PowerShellRuleEngine.UnitTests/PowerShellRuleEngineTests.cs
@@ -16,6 +16,23 @@ namespace Microsoft.Azure.Templates.Analyzer.RuleEngines.PowerShellEngine.UnitTe
[TestClass]
public class PowerShellRuleEngineTests
{
+ private const string EmptyBaseline = @"
+ [
+ {
+ ""kind"": ""Baseline"",
+ ""metadata"": {
+ ""name"": ""RepeatedRulesBaseline""
+ },
+ ""apiVersion"": ""github.com/microsoft/PSRule/v1"",
+ ""spec"": {
+ ""rule"": {
+ ""exclude"": [
+ ]
+ }
+ }
+ }
+ ]";
+
private readonly string templatesFolder = @"templates";
private static PowerShellRuleEngine powerShellRuleEngineAllRules;
private static PowerShellRuleEngine powerShellRuleEngineSecurityRules;
@@ -29,8 +46,8 @@ public static void AssemblyInitialize(TestContext context)
[DataTestMethod]
// PSRule detects errors in two analysis stages: when looking at the whole file (through the file path), and when looking at each resource (pipeline.Process(resource)):
- [DataRow("template_and_resource_level_results.json", true, 13, new int[] { 1, 1, 1, 1, 8, 14, 17, 1, 17, 17, 1, 17, 1 }, DisplayName = "Running all the rules against a template with errors reported in both analysis stages")]
- [DataRow("template_and_resource_level_results.json", false, 4, new int[] { 17, 17, 17, 17 }, DisplayName = "Running only the security rules against a template with errors reported in both analysis stages")]
+ [DataRow("template_and_resource_level_results.json", true, 14, new int[] { 1, 1, 1, 1, 8, 14, 17, 1, 17, 17, 1, 17, 1, 11 }, DisplayName = "Running all the rules against a template with errors reported in both analysis stages")]
+ [DataRow("template_and_resource_level_results.json", false, 5, new int[] { 17, 17, 17, 17, 11 }, DisplayName = "Running only the security rules against a template with errors reported in both analysis stages")]
// TODO add test case for error, warning (rule with severity level of warning?) and informational (also rule with that severity level?)
public void AnalyzeTemplate_ValidTemplate_ReturnsExpectedEvaluations(string templateFileName, bool runsAllRules, int expectedErrorCount, dynamic expectedLineNumbers)
{
@@ -80,7 +97,7 @@ public void AnalyzeTemplate_ValidTemplate_ReturnsExpectedEvaluations(string temp
Assert.AreEqual(expectedErrorCount, failedEvaluations.Count);
// PSRule evaluations can change order depending on the OS:
- foreach(var expectedLineNumber in expectedLineNumbers)
+ foreach (var expectedLineNumber in expectedLineNumbers)
{
var matchingEvaluation = failedEvaluations.Find(evaluation => evaluation.Result.SourceLocation.LineNumber == expectedLineNumber);
failedEvaluations.Remove(matchingEvaluation);
@@ -90,7 +107,7 @@ public void AnalyzeTemplate_ValidTemplate_ReturnsExpectedEvaluations(string temp
[DataTestMethod]
[DataRow(true, DisplayName = "Repeated rules are excluded when running all the rules")]
- [DataRow(true, DisplayName = "Repeated rules are excluded when running only the security rules")]
+ [DataRow(false, DisplayName = "Repeated rules are excluded when running only the security rules")]
public void AnalyzeTemplate_ValidTemplate_ExcludesRepeatedRules(bool runsAllRules)
{
var templateFilePath = Path.Combine(templatesFolder, "triggers_excluded_rules.json");
@@ -115,7 +132,7 @@ public void AnalyzeTemplate_ValidTemplate_ExcludesRepeatedRules(bool runsAllRule
// The RepeatedRulesBaseline will only be used when all rules are run
// Otherwise SecurityBaseline is used, those rules are not in the "include" array of the baseline so they won't be executed either
- // Next we validate that when RepeatedRulesBaseline is an empty file then the test file does indeed trigger the excluded rule:
+ // Next we validate that when RepeatedRulesBaseline has no exclusions then the test file does indeed trigger the excluded rule:
if (runsAllRules)
{
var baselineLocation = Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory), "baselines", "RepeatedRulesBaseline.Rule.json");
@@ -123,11 +140,10 @@ public void AnalyzeTemplate_ValidTemplate_ExcludesRepeatedRules(bool runsAllRule
try
{
File.Move(baselineLocation, newBaselineLocation);
- var emptyBaseline = File.Create(baselineLocation);
- emptyBaseline.Close();
-
+ File.WriteAllText(baselineLocation, EmptyBaseline);
+
evaluations = powerShellRuleEngineAllRules.AnalyzeTemplate(templateContext);
-
+
Assert.IsTrue(evaluations.Any(evaluation => evaluation.RuleId == "AZR-000081"));
}
finally
@@ -138,6 +154,39 @@ public void AnalyzeTemplate_ValidTemplate_ExcludesRepeatedRules(bool runsAllRule
}
}
+ // Sanity checks for using hardcoded AZURE_RESOURCE_ALLOWED_LOCATIONS in the rules
+ [TestMethod]
+ [DataRow("templateWithDefaultLocation.json", DisplayName = "Template with default location")]
+ [DataRow("templateWithHardcodedLocation.json", DisplayName = "Template with hardcoded location")]
+ public void AnalyzeTemplate_ValidTemplate_SpecifiedLocations(string templateFileName)
+ {
+ var templateFilePath = Path.Combine(templatesFolder, templateFileName);
+
+ var template = File.ReadAllText(templateFilePath);
+ var armTemplateProcessor = new ArmTemplateProcessor(template);
+ var templatejObject = armTemplateProcessor.ProcessTemplate();
+
+ var templateContext = new TemplateContext
+ {
+ OriginalTemplate = JObject.Parse(template),
+ ExpandedTemplate = templatejObject,
+ ResourceMappings = armTemplateProcessor.ResourceMappings,
+ TemplateIdentifier = templateFilePath
+ };
+
+ var evaluations = powerShellRuleEngineSecurityRules.AnalyzeTemplate(templateContext);
+
+ var failedEvaluations = new List();
+
+ foreach (PowerShellRuleEvaluation evaluation in evaluations)
+ {
+ if (!evaluation.Passed)
+ {
+ failedEvaluations.Add(evaluation);
+ }
+ }
+ }
+
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void AnalyzeTemplate_NullTemplateContext_ThrowsException()
diff --git a/src/Analyzer.PowerShellRuleEngine.UnitTests/templates/templateWithDefaultLocation.json b/src/Analyzer.PowerShellRuleEngine.UnitTests/templates/templateWithDefaultLocation.json
new file mode 100644
index 00000000..96111e9c
--- /dev/null
+++ b/src/Analyzer.PowerShellRuleEngine.UnitTests/templates/templateWithDefaultLocation.json
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "location": {
+ "type": "string",
+ "defaultValue": "southcentralus",
+ "metadata": {
+ "description": "Location for all resources."
+ }
+ },
+ "managedIdentityName": {
+ "type": "string",
+ "metadata": {
+ "description": "Specifies managed identity name"
+ }
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2019-08-01",
+ "type": "Microsoft.Web/sites",
+ "kind": "functionapp",
+ "name": "functionAppKind",
+ "location": "[parameters('location')]",
+ "properties": {
+ "httpsOnly": true,
+ "siteConfig": {
+ "detailedErrorLoggingEnabled": false,
+ "ftpsState": "Disabled",
+ "httpLoggingEnabled": false,
+ "minTlsVersion": "1.2",
+ "requestTracingEnabled": false
+ }
+ },
+ "identity": {
+ "type": "UserAssigned",
+ "userAssignedIdentities": {
+ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('managedIdentityName'))]": {}
+ }
+ }
+ },
+ {
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
+ "apiVersion": "2018-11-30",
+ "name": "[parameters('managedIdentityName')]",
+ "location": "[parameters('location')]"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Analyzer.PowerShellRuleEngine.UnitTests/templates/templateWithHardcodedLocation.json b/src/Analyzer.PowerShellRuleEngine.UnitTests/templates/templateWithHardcodedLocation.json
new file mode 100644
index 00000000..3c47374f
--- /dev/null
+++ b/src/Analyzer.PowerShellRuleEngine.UnitTests/templates/templateWithHardcodedLocation.json
@@ -0,0 +1,43 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "managedIdentityName": {
+ "type": "string",
+ "metadata": {
+ "description": "Specifies managed identity name"
+ }
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2019-08-01",
+ "type": "Microsoft.Web/sites",
+ "kind": "functionapp",
+ "name": "functionAppKind",
+ "location": "southcentralus",
+ "properties": {
+ "httpsOnly": true,
+ "siteConfig": {
+ "detailedErrorLoggingEnabled": false,
+ "ftpsState": "Disabled",
+ "httpLoggingEnabled": false,
+ "minTlsVersion": "1.2",
+ "requestTracingEnabled": false
+ }
+ },
+ "identity": {
+ "type": "UserAssigned",
+ "userAssignedIdentities": {
+ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('managedIdentityName'))]": {}
+ }
+ }
+ },
+ {
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
+ "apiVersion": "2018-11-30",
+ "name": "[parameters('managedIdentityName')]",
+ "location": "southcentralus"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Analyzer.PowerShellRuleEngine/Analyzer.PowerShellRuleEngine.csproj b/src/Analyzer.PowerShellRuleEngine/Analyzer.PowerShellRuleEngine.csproj
index 93c09331..824372dd 100644
--- a/src/Analyzer.PowerShellRuleEngine/Analyzer.PowerShellRuleEngine.csproj
+++ b/src/Analyzer.PowerShellRuleEngine/Analyzer.PowerShellRuleEngine.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/src/Analyzer.PowerShellRuleEngine/PowerShellRuleEngine.cs b/src/Analyzer.PowerShellRuleEngine/PowerShellRuleEngine.cs
index 1d18f1fa..cc62887f 100644
--- a/src/Analyzer.PowerShellRuleEngine/PowerShellRuleEngine.cs
+++ b/src/Analyzer.PowerShellRuleEngine/PowerShellRuleEngine.cs
@@ -68,8 +68,6 @@ public PowerShellRuleEngine(bool includeNonSecurityRules, ILogger logger = null)
/// The s of the PowerShell rules against the template.
public IEnumerable AnalyzeTemplate(TemplateContext templateContext)
{
- string templString = templateContext.ExpandedTemplate.ToString();
-
if (templateContext?.TemplateIdentifier == null)
{
throw new ArgumentException($"{nameof(TemplateContext.TemplateIdentifier)} must not be null.", nameof(templateContext));
@@ -96,11 +94,6 @@ public IEnumerable AnalyzeTemplate(TemplateContext templateContext)
{
var fileOptions = PSRuleOption.FromFileOrEmpty();
- if (fileOptions.Configuration.ContainsKey("AZURE_RESOURCE_ALLOWED_LOCATIONS"))
- {
- // TODO: support external config for PSRule
- }
-
hostContext = new PSRuleHostContext(templateContext, logger);
var modules = new string[] { PSRuleModuleName };
var optionsForFileAnalysis = new PSRuleOption