From 194b235f677e57be80e19b22fae7a11d5c70a2bf Mon Sep 17 00:00:00 2001 From: Bernie White Date: Fri, 11 Oct 2024 10:19:33 +1000 Subject: [PATCH] Fixed GetBicepParamResources exception when expanding Microsoft Graph #3062 (#3093) --- docs/CHANGELOG-v1.md | 6 ++ .../Common/JsonExtensions.cs | 17 ++++ .../Data/Template/TemplateVisitor.cs | 2 +- .../Tests.Bicep.1.bicep | 45 ++++++++++ .../ExtensibilityTestCases/Tests.Bicep.1.json | 85 +++++++++++++++++++ .../ExtensibilityTestCases/bicepconfig.json | 9 ++ .../PSRule.Rules.Azure.Tests.csproj | 3 + .../TemplateVisitorTests.cs | 15 ++++ 8 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.bicep create mode 100644 tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.json create mode 100644 tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/bicepconfig.json diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index ca43f624f32..d9e85683932 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -29,6 +29,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v1.39.0: + +- Bug fixes: + - Fixed `GetBicepParamResources` exception when expanding `Microsoft.Graph/groups` resource by @BernieWhite. + [#3062](https://github.com/Azure/PSRule.Rules.Azure/issues/3062) + ## v1.39.0 What's changed since pre-release v1.38.0: diff --git a/src/PSRule.Rules.Azure/Common/JsonExtensions.cs b/src/PSRule.Rules.Azure/Common/JsonExtensions.cs index 3a4907914a6..8082c38ba98 100644 --- a/src/PSRule.Rules.Azure/Common/JsonExtensions.cs +++ b/src/PSRule.Rules.Azure/Common/JsonExtensions.cs @@ -19,6 +19,7 @@ internal static class JsonExtensions private const string PROPERTY_TYPE = "type"; private const string PROPERTY_FIELD = "field"; private const string PROPERTY_EXISTING = "existing"; + private const string PROPERTY_IMPORT = "import"; private const string PROPERTY_SCOPE = "scope"; private const string PROPERTY_SUBSCRIPTION_ID = "subscriptionId"; private const string PROPERTY_RESOURCE_GROUP = "resourceGroup"; @@ -508,11 +509,27 @@ internal static bool IsEmpty(this JToken value) (value.Type == JTokenType.String && string.IsNullOrEmpty(value.Value())); } + /// + /// Resources with the existing property set to true are references to existing resources. + /// + /// The resource . + /// Returns true if the resource is a reference to an existing resource. internal static bool IsExisting(this JObject o) { return o != null && o.TryBoolProperty(PROPERTY_EXISTING, out var existing) && existing != null && existing.Value; } + /// + /// Resources with an import property set to a non-empty string refer to extensibility provided resources. + /// For example, Microsoft Graph. + /// + /// The resource . + /// Returns true if the resource is imported from an extensibility provider. + internal static bool IsImport(this JObject o) + { + return o != null && o.TryStringProperty(PROPERTY_IMPORT, out var s) && !string.IsNullOrEmpty(s); + } + internal static bool TryResourceType(this JObject o, out string type) { type = default; diff --git a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs index 2eb59ff91f4..1a23332d928 100644 --- a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs +++ b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs @@ -1231,7 +1231,7 @@ private void Resources(TemplateContext context, JObject resources) { Reference(context, p.Name, resource); } - else + else if (!resource.IsImport()) { r.AddRange(ResourceExpand(context, p.Name, resource)); } diff --git a/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.bicep b/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.bicep new file mode 100644 index 00000000000..4b77bfd51a1 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.bicep @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Test case for https://github.com/Azure/PSRule.Rules.Azure/issues/3062 + +extension microsoftGraphV1_0 + +var appRoleId = 'appRoleId' +var certKey = 'certKey' + +resource resourceApp 'Microsoft.Graph/applications@v1.0' = { + uniqueName: 'ExampleResourceApp' + displayName: 'Example Resource Application' + appRoles: [ + { + id: appRoleId + allowedMemberTypes: ['User', 'Application'] + description: 'Read access to resource app data' + displayName: 'ResourceAppData.Read.All' + value: 'ResourceAppData.Read.All' + isEnabled: true + } + ] +} + +resource resourceSp 'Microsoft.Graph/servicePrincipals@v1.0' = { + appId: resourceApp.appId +} + +resource clientApp 'Microsoft.Graph/applications@v1.0' = { + uniqueName: 'ExampleClientApp' + displayName: 'Example Client Application' + keyCredentials: [ + { + displayName: 'Example Client App Key Credential' + usage: 'Verify' + type: 'AsymmetricX509Cert' + key: certKey + } + ] +} + +resource clientSp 'Microsoft.Graph/servicePrincipals@v1.0' = { + appId: clientApp.appId +} diff --git a/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.json b/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.json new file mode 100644 index 00000000000..95503cb8df8 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/Tests.Bicep.1.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.1-experimental", + "contentVersion": "1.0.0.0", + "metadata": { + "_EXPERIMENTAL_WARNING": "This template uses ARM features that are experimental. Experimental features should be enabled for testing purposes only, as there are no guarantees about the quality or stability of these features. Do not enable these settings for any production usage, or your production environment may be subject to breaking.", + "_EXPERIMENTAL_FEATURES_ENABLED": [ + "Extensibility" + ], + "_generator": { + "name": "bicep", + "version": "0.30.23.60470", + "templateHash": "2001503425732761903" + } + }, + "variables": { + "appRoleId": "appRoleId", + "certKey": "certKey" + }, + "imports": { + "MicrosoftGraph": { + "provider": "MicrosoftGraph", + "version": "0.1.8-preview" + } + }, + "resources": { + "resourceApp": { + "import": "MicrosoftGraph", + "type": "Microsoft.Graph/applications@v1.0", + "properties": { + "uniqueName": "ExampleResourceApp", + "displayName": "Example Resource Application", + "appRoles": [ + { + "id": "[variables('appRoleId')]", + "allowedMemberTypes": [ + "User", + "Application" + ], + "description": "Read access to resource app data", + "displayName": "ResourceAppData.Read.All", + "value": "ResourceAppData.Read.All", + "isEnabled": true + } + ] + } + }, + "resourceSp": { + "import": "MicrosoftGraph", + "type": "Microsoft.Graph/servicePrincipals@v1.0", + "properties": { + "appId": "[reference('resourceApp').appId]" + }, + "dependsOn": [ + "resourceApp" + ] + }, + "clientApp": { + "import": "MicrosoftGraph", + "type": "Microsoft.Graph/applications@v1.0", + "properties": { + "uniqueName": "ExampleClientApp", + "displayName": "Example Client Application", + "keyCredentials": [ + { + "displayName": "Example Client App Key Credential", + "usage": "Verify", + "type": "AsymmetricX509Cert", + "key": "[variables('certKey')]" + } + ] + } + }, + "clientSp": { + "import": "MicrosoftGraph", + "type": "Microsoft.Graph/servicePrincipals@v1.0", + "properties": { + "appId": "[reference('clientApp').appId]" + }, + "dependsOn": [ + "clientApp" + ] + } + } +} \ No newline at end of file diff --git a/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/bicepconfig.json b/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/bicepconfig.json new file mode 100644 index 00000000000..2ea2a4bd12e --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Bicep/ExtensibilityTestCases/bicepconfig.json @@ -0,0 +1,9 @@ +{ + "experimentalFeaturesEnabled": { + "optionalModuleNames": true, + "extensibility": true + }, + "extensions": { + "microsoftGraphV1_0": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.8-preview" + } +} diff --git a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj index c5b9867796b..028d7e90933 100644 --- a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj +++ b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj @@ -287,6 +287,9 @@ PreserveNewest + + PreserveNewest + diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs index d9f08dd82e6..c6c390cb321 100644 --- a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs @@ -1351,6 +1351,21 @@ public void ProcessTemplate_WhenParented_ScopeIsExpected() Assert.Equal("/subscriptions/ffffffff-ffff-ffff-ffff-ffffffffffff/resourceGroups/ps-rule-test-rg/providers/Microsoft.Sql/servers/server01/databases/db02", actual["scope"].Value()); } + /// + /// Test case for https://github.com/Azure/PSRule.Rules.Azure/issues/3062 + /// + [Fact] + public void ProcessTemplate_WhenMicrosoftGraphType_ShouldIgnoreExtensibilityResources() + { + var resources = ProcessTemplate(GetSourcePath("Bicep/ExtensibilityTestCases/Tests.Bicep.1.json"), null, out _); + + Assert.Single(resources); + + var actual = resources[0]; + Assert.Equal("Microsoft.Resources/deployments", actual["type"].Value()); + Assert.Equal("ps-rule-test-deployment", actual["name"].Value()); + } + #region Helper methods private static string GetSourcePath(string fileName)