Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for release 0.6.0 -> release 0.7.0 #358

Merged
merged 10 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Analyzer.BicepProcessor/Analyzer.BicepProcessor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

<ItemGroup>
<PackageReference Include="Azure.Bicep.Core" Version="0.24.24" />
<!--Az.Id version forced here due to CG alert. After net8 upgrade, will be able
to update Bicep.Core to version with safe Az.Id dependency and can remove below -->
<PackageReference Include="Azure.Identity" Version="1.10.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
Expand Down
11 changes: 7 additions & 4 deletions src/Analyzer.Core.NuGet/Analyzer.Core.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@

<dependencies>
<group targetFramework="net7.0">
nonik0 marked this conversation as resolved.
Show resolved Hide resolved
<dependency id="Azure.Bicep.Core" version="0.22.6" />
<dependency id="Azure.Deployments.Core" version="1.0.1040" />
<dependency id="Azure.Deployments.Expression" version="1.0.1040" />
<dependency id="Azure.Deployments.Templates" version="1.0.1040" />
<dependency id="Azure.Bicep.Core" version="0.24.24" />
<!--Az.Id version forced here due to CG alert. After net8 upgrade, will be able
to update Bicep.Core to version with safe Az.Id dependency and can remove below -->
<dependency id="Azure.Identity" Version="1.10.4" />
<dependency id="Azure.Deployments.Core" version="1.0.1103" />
<dependency id="Azure.Deployments.Expression" version="1.0.1103" />
<dependency id="Azure.Deployments.Templates" version="1.0.1103" />
<dependency id="Newtonsoft.Json" version="13.0.3" />
<dependency id="Microsoft.Extensions.Logging.Abstractions" version="6.0.1" />
<dependency id="Microsoft.PowerShell.SDK" version="7.2.12" />
Expand Down
2 changes: 1 addition & 1 deletion src/Analyzer.Core.UnitTests/TemplateAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "2 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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, 11, 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, 5, new int[] { 11, 17, 17, 17, 17 }, 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)
{
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand All @@ -115,19 +132,18 @@ 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");
var newBaselineLocation = baselineLocation + ".moved";
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
Expand All @@ -138,6 +154,32 @@ public void AnalyzeTemplate_ValidTemplate_ExcludesRepeatedRules(bool runsAllRule
}
}

// Sanity checks for using hardcoded AZURE_RESOURCE_ALLOWED_LOCATIONS to match the placeholder region in the template processor
// locations used are different from the placeholder region westus2
[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);

Assert.IsTrue(evaluations.All(evaluation => evaluation.Passed));
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void AnalyzeTemplate_NullTemplateContext_ThrowsException()
Expand Down
Original file line number Diff line number Diff line change
@@ -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')]"
}
]
}
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.12" />
<PackageReference Include="Microsoft.PSRule.Rules.Azure" Version="1.26.0-B0011" />
<PackageReference Include="Microsoft.PSRule.SDK" Version="2.8.0" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.3.11" />
<PackageReference Include="Microsoft.PSRule.Rules.Azure" Version="1.33.2" />
<PackageReference Include="Microsoft.PSRule.SDK" Version="2.9.0" />
</ItemGroup>

<ItemGroup>
Expand Down
12 changes: 12 additions & 0 deletions src/Analyzer.PowerShellRuleEngine/PSRuleHostContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public PSRuleHostContext(TemplateContext templateContext, ILogger logger = null)
: new JsonSourceLocationResolver(templateContext);
}

/// <inheritdoc/>
public override ActionPreference GetPreferenceVariable(string variableName)
{
return string.Equals(variableName, "VerbosePreference", System.StringComparison.OrdinalIgnoreCase) ? ActionPreference.Continue : base.GetPreferenceVariable(variableName);
}

/// <inheritdoc/>
public override bool ShouldProcess(string target, string action)
{
Expand Down Expand Up @@ -68,6 +74,12 @@ public override void Information(InformationRecord informationRecord)
}
}

/// <inheritdoc/>
public override void Verbose(string text)
{
logger?.LogDebug(text);
}

/// <inheritdoc/>
public override void Record(IResultRecord record)
{
Expand Down
12 changes: 9 additions & 3 deletions src/Analyzer.PowerShellRuleEngine/PowerShellRuleEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Azure.Templates.Analyzer.Types;
using Microsoft.Azure.Templates.Analyzer.Utilities;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -91,6 +92,8 @@ public IEnumerable<IEvaluation> AnalyzeTemplate(TemplateContext templateContext)

try
{
var fileOptions = PSRuleOption.FromFileOrEmpty();

hostContext = new PSRuleHostContext(templateContext, logger);
var modules = new string[] { PSRuleModuleName };
var optionsForFileAnalysis = new PSRuleOption
Expand All @@ -115,13 +118,16 @@ public IEnumerable<IEvaluation> AnalyzeTemplate(TemplateContext templateContext)
},
Execution = new ExecutionOption
{
NotProcessedWarning = false,
nonik0 marked this conversation as resolved.
Show resolved Hide resolved

UnprocessedObject = ExecutionActionPreference.Ignore,
// PSRule internally creates a PowerShell initial state with InitialSessionState.CreateDefault().
// SessionState.Minimal causes PSRule to use CreateDefault2 instead of CreateDefault.
InitialSessionState = PSRule.Configuration.SessionState.Minimal
InitialSessionState = SessionState.Minimal
}
};

// placeholder value for location is westus2
optionsForFileAnalysis.Configuration["AZURE_RESOURCE_ALLOWED_LOCATIONS"] = new[] { "westus2" };
nonik0 marked this conversation as resolved.
Show resolved Hide resolved

var resources = templateContext.ExpandedTemplate.InsensitiveToken("resources").Values<JObject>();

var builder = CommandLineBuilder.Invoke(modules, optionsForFileAnalysis, hostContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
// Synopsis: Excludes the rules that are already in TemplateAnalyzer
"_comment": "Synopsis: Excludes the rules that are already in TemplateAnalyzer",
"kind": "Baseline",
"metadata": {
"name": "RepeatedRulesBaseline"
Expand Down
Loading
Loading