diff --git a/.vscode/policy-ignore.schema.json b/.vscode/policy-ignore.schema.json new file mode 100644 index 00000000000..fcab0efc822 --- /dev/null +++ b/.vscode/policy-ignore.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "Policy as Code Ignore", + "description": "Policy ignore identifies policies that will be ignored by PSRule for generating rules during export of policies.", + "type": "array", + "items": { + "properties": { + "policyDefinitionIds": { + "type": "array", + "title": "Policy definition IDs", + "description": "The resource IDs of built-in policies to ignore.", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "reason": { + "type": "string", + "title": "The reason why the policy definition is ignored.", + "enum": [ + "Duplicate", + "NotApplicable" + ], + "default": "Duplicate" + }, + "value": { + "type": "string", + "title": "Value", + "description": "An additional relating to the reason the policy definition was ignored." + } + }, + "additionalProperties": false, + "examples": [ + { + "policyDefinitionIds": [], + "reason": "Duplicate", + "value": "" + } + ] + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json index ae21178b8cc..a16d4e147e1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -133,6 +133,14 @@ "yaml", "yml" ], + "json.schemas": [ + { + "fileMatch": [ + "**/data/policy-ignore.json" + ], + "url": "./.vscode/policy-ignore.schema.json" + } + ], "omnisharp.organizeImportsOnFormat": true, "omnisharp.enableEditorConfigSupport": true, "omnisharp.enableRoslynAnalyzers": true, diff --git a/data/policy-ignore.json b/data/policy-ignore.json index 40f8439d88d..2c0cbbca619 100644 --- a/data/policy-ignore.json +++ b/data/policy-ignore.json @@ -1,36 +1,130 @@ [ - // Azure.ACR.AdminUser - "/providers/Microsoft.Authorization/policyDefinitions/dc921057-6b28-4fbe-9b83-f7bec05db6c2", - "/providers/Microsoft.Authorization/policyDefinitions/79fdfe03-ffcb-4e55-b4d0-b925b8241759", - // Azure.SQL.AAD - "/providers/Microsoft.Authorization/policyDefinitions/1f314764-cb73-4fc9-b863-8eca98ac36e9", - // Azure.ServiceFabric.AAD - "/providers/Microsoft.Authorization/policyDefinitions/b54ed75b-3e1a-44ac-a333-05ba39b99ff0", - // Azure.Redis.NonSslPort - "/providers/Microsoft.Authorization/policyDefinitions/22bee202-a82f-4305-9a2a-6d7f44d4dedb", - // Azure.Automation.EncryptVariables - "/providers/Microsoft.Authorization/policyDefinitions/3657f5a0-770e-44a3-b44e-9431ba1e9735", - // Azure.AKS.UseRBAC - "/providers/Microsoft.Authorization/policyDefinitions/ac4a19c2-fa67-49b4-8ae5-0b2e78c49457", - // Azure.AKS.AzurePolicyAddOn - "/providers/Microsoft.Authorization/policyDefinitions/0a15ec92-a229-4763-bb14-0ea34a568f8d", - // Azure.Storage.BlobPublicAccess - "/providers/Microsoft.Authorization/policyDefinitions/4fa4b6c0-31ca-4c0d-b10d-24b96f62a751", - // Azure.PostgreSQL.UseSSL - "/providers/Microsoft.Authorization/policyDefinitions/d158790f-bfb0-486c-8631-2dc6b4e8e6af", - // Azure.MySQL.UseSSL - "/providers/Microsoft.Authorization/policyDefinitions/e802a67a-daf5-4436-9ea6-f6d821dd0c5d", - // Azure.KeyVault.SoftDelete - "/providers/Microsoft.Authorization/policyDefinitions/0b60c0b2-2dc2-4e1c-b5c9-abbed971de53", - // Checking for Network Watcher in a resource group is not enforcable by code. - "/providers/Microsoft.Authorization/policyDefinitions/b6e2945c-0b7b-40f5-9233-7a5323b5cdc6", - // Azure.AKS.LocalAccounts - "/providers/Microsoft.Authorization/policyDefinitions/993c2fcd-2b29-49d2-9eb0-df2c3a730c32", - // Azure.Cognitive.DisableLocalAuth - "/providers/Microsoft.Authorization/policyDefinitions/71ef260a-8f18-47b7-abcb-62d0673d94dc", - "/providers/Microsoft.Authorization/policyDefinitions/14de9e63-1b31-492e-a5a3-c3f7fd57f555", - // Azure.Cognitive.ManagedIdentity - "/providers/Microsoft.Authorization/policyDefinitions/fe3fd216-4f83-4fc1-8984-2bbec80a3418", - // Azure.VM.UseManagedDisks - "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d" + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/dc921057-6b28-4fbe-9b83-f7bec05db6c2", + "/providers/Microsoft.Authorization/policyDefinitions/79fdfe03-ffcb-4e55-b4d0-b925b8241759" + ], + "reason": "Duplicate", + "value": "Azure.ACR.AdminUser" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/1f314764-cb73-4fc9-b863-8eca98ac36e9" + ], + "reason": "Duplicate", + "value": "Azure.SQL.AAD" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/b54ed75b-3e1a-44ac-a333-05ba39b99ff0" + ], + "reason": "Duplicate", + "value": "Azure.ServiceFabric.AAD" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/22bee202-a82f-4305-9a2a-6d7f44d4dedb" + ], + "reason": "Duplicate", + "value": "Azure.Redis.NonSslPort" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/3657f5a0-770e-44a3-b44e-9431ba1e9735" + ], + "reason": "Duplicate", + "value": "Azure.Automation.EncryptVariables" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/ac4a19c2-fa67-49b4-8ae5-0b2e78c49457" + ], + "reason": "Duplicate", + "value": "Azure.AKS.UseRBAC" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/0a15ec92-a229-4763-bb14-0ea34a568f8d" + ], + "reason": "Duplicate", + "value": "Azure.AKS.AzurePolicyAddOn" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/4fa4b6c0-31ca-4c0d-b10d-24b96f62a751" + ], + "reason": "Duplicate", + "value": "Azure.Storage.BlobPublicAccess" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/d158790f-bfb0-486c-8631-2dc6b4e8e6af" + ], + "reason": "Duplicate", + "value": "Azure.PostgreSQL.UseSSL" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/e802a67a-daf5-4436-9ea6-f6d821dd0c5d" + ], + "reason": "Duplicate", + "value": "Azure.MySQL.UseSSL" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/0b60c0b2-2dc2-4e1c-b5c9-abbed971de53" + ], + "reason": "Duplicate", + "value": "Azure.KeyVault.PurgeProtect" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/b6e2945c-0b7b-40f5-9233-7a5323b5cdc6" + ], + "reason": "NotApplicable", + "value": "Checking for Network Watcher in a resource group is not enforcable by code." + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/993c2fcd-2b29-49d2-9eb0-df2c3a730c32" + ], + "reason": "Duplicate", + "value": "Azure.AKS.LocalAccounts" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/71ef260a-8f18-47b7-abcb-62d0673d94dc", + "/providers/Microsoft.Authorization/policyDefinitions/14de9e63-1b31-492e-a5a3-c3f7fd57f555" + ], + "reason": "Duplicate", + "value": "Azure.Cognitive.DisableLocalAuth" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/fe3fd216-4f83-4fc1-8984-2bbec80a3418" + ], + "reason": "Duplicate", + "value": "Azure.Cognitive.ManagedIdentity" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d" + ], + "reason": "Duplicate", + "value": "Azure.VM.UseManagedDisks" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/1e66c121-a66a-4b1f-9b83-0fd99bf0fc2d" + ], + "reason": "Duplicate", + "value": "Azure.KeyVault.SoftDelete" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/12d4fa5e-1f9f-4c21-97a9-b99b3c6611b5" + ], + "reason": "Duplicate", + "value": "Azure.KeyVault.RBAC" + } ] diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 38b2c3596f0..8def1b2c756 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -32,6 +32,24 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v1.33.0-B0126: + +- New features: + - Exporting policy as rules also generates a baseline by @BernieWhite. + [#2482](https://github.com/Azure/PSRule.Rules.Azure/issues/2482) + - A baseline is automatically generated that includes for all rules exported. + If a policy rule has been replaced by a built-in rule, the baseline will include the built-in rule instead. + - The baseline is named `.PolicyBaseline.All`. i.e. `Azure.PolicyBaseline.All` by default. + - For details see [Policy as rules](./concepts/policy-as-rules.md#generated-baseline). +- General improvements: + - Rules that are ignored during exporting policy as rules are now generate a verbose logs by @BernieWhite. + [#2482](https://github.com/Azure/PSRule.Rules.Azure/issues/2482) + - This is to improve transparency of why rules are not exported. + - To see details on why a rule is ignored, enable verbose logging with `-Verbose`. + - Policies that duplicate built-in rules can now be exported by using the `-KeepDuplicates` parameter by @BernieWhite. + [#2482](https://github.com/Azure/PSRule.Rules.Azure/issues/2482) + - For details see [Policy as rules](./concepts/policy-as-rules.md#duplicate-policies). + ## v1.33.0-B0126 (pre-release) What's changed since pre-release v1.33.0-B0088: diff --git a/docs/commands/Export-AzPolicyAssignmentRuleData.md b/docs/commands/Export-AzPolicyAssignmentRuleData.md index faf42dcacd6..460878f3cc0 100644 --- a/docs/commands/Export-AzPolicyAssignmentRuleData.md +++ b/docs/commands/Export-AzPolicyAssignmentRuleData.md @@ -16,7 +16,7 @@ Export JSON based rules from policy assignment data. ```text Export-AzPolicyAssignmentRuleData [-Name ] -AssignmentFile [-ResourceGroup ] [-Subscription ] [-OutputPath ] - [-RulePrefix ] [-PassThru] [] + [-RulePrefix ] [-PassThru] [-KeepDuplicates] [] ``` ## DESCRIPTION @@ -200,7 +200,26 @@ Aliases: Required: False Position: Named -Default value: None +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -KeepDuplicates + +Determines if Azure policy definitions that duplicate existing built-in rules are exported. +By default, duplicates are not exported. + +This only applies to built-in policy definitions. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` diff --git a/docs/concepts/about_PSRule_Azure_Configuration.md b/docs/concepts/about_PSRule_Azure_Configuration.md index 515083a29fe..1dcc4b9746f 100644 --- a/docs/concepts/about_PSRule_Azure_Configuration.md +++ b/docs/concepts/about_PSRule_Azure_Configuration.md @@ -320,6 +320,7 @@ Example: ```yaml # YAML: Add a custom policy definition to ignore +configuration: AZURE_POLICY_IGNORE_LIST: - '/providers/Microsoft.Authorization/policyDefinitions/1f314764-cb73-4fc9-b863-8eca98ac36e9' - '/providers/Microsoft.Authorization/policyDefinitions/b54ed75b-3e1a-44ac-a333-05ba39b99ff0' diff --git a/docs/concepts/policy-as-rules.md b/docs/concepts/policy-as-rules.md index 4a41a3e167b..f3ed2df3b1d 100644 --- a/docs/concepts/policy-as-rules.md +++ b/docs/concepts/policy-as-rules.md @@ -34,8 +34,6 @@ This feature does not support: - **Policies that check for assessment status** — Some policies use additional detection tools to check for compliance. Policies that check for assessment status are ignored. - **Importing rules** — Rules generated from policy assignments cannot be imported back into Azure Policy. -- **Duplicate rules** — Currently, built-in Azure Policies that are duplicates of existing rules are ignored. - [#2482](https://github.com/Azure/PSRule.Rules.Azure/issues/2482) ## Using policy as rules @@ -60,3 +58,61 @@ Run `Export-AzPolicyAssignmentRuleData` to convert assignments to rules. To run this command an `-AssignmentFile` parameter with the path to the assignment JSON file generated in the previous step. After the command completes a new file `*.Rule.jsonc` should be generated containing generated rules. + +## Customizing the generated rules + +PSRule for Azure allows you to: + +- **Set a name prefix** — to help identify generated rules. + By default, generated rules and baselines are prefixed with `Azure`. + To change the prefix: + - Use the `-RulePrefix` parameter when running `Export-AzPolicyAssignmentRuleData`. _OR_ + - Set the `AZURE_POLICY_RULE_PREFIX` configuration option in `ps-rule.yaml`. +- **Exclude specific policies** — by setting the `AZURE_POLICY_IGNORE_LIST` configuration option in `ps-rule.yaml`. + This option allows you to prevent specific policies from being exported as rules. + +For example: + +```yaml title="ps-rule.yaml" +configuration: + AZURE_POLICY_RULE_PREFIX: MyOrg + AZURE_POLICY_IGNORE_LIST: + - /providers/Microsoft.Authorization/policyDefinitions/1f314764-cb73-4fc9-b863-8eca98ac36e9 + - /providers/Microsoft.Authorization/policyDefinitions/b54ed75b-3e1a-44ac-a333-05ba39b99ff0 +``` + +## Generated baseline + + + +When exporting policies, PSRule for Azure will automatically generate a baseline including any generated rules. +By default, this baseline is called `Azure.PolicyBaseline.All`. +If you change the prefix of generated rules the baseline will be named `.PolicyBaseline.All`. + +See [Using baselines](../working-with-baselines.md#using-baselines) for examples on how to use a baseline in a run. + +## Duplicate policies + + + +When exporting policies, you may encounter definitions that are duplicates of existing rules shipped with PSRule for Azure. +By default, built-in Azure policies that are duplicates of existing rules are ignored. +Additionally, PSRule for Azure will automatically switch in existing rules into the generated baseline. + +!!! Note + This only applies to built-in Azure policies that are duplicates of existing rules. + Custom policies are not effected. + + The list of built-in policies that are duplicates can be viewed [here][3]. + If you believe a policy is missing from this list, please [open an issue][4]. + + [3]: https://github.com/Azure/PSRule.Rules.Azure/blob/main/data/policy-ignore.json + [4]: https://github.com/Azure/PSRule.Rules.Azure/issues/new/choose + +This allows you to: + +- Focus on policies that are unique to your environment and not already covered by PSRule for Azure. +- Benefit from the additional references and examples provided by PSRule for Azure. +- Reduce noise reporting the same issue multiple times. + +To override this behavior use the `-KeepDuplicates` parameter switch when running `Export-AzPolicyAssignmentRuleData`. diff --git a/src/PSRule.Rules.Azure.BuildTool/Models.cs b/src/PSRule.Rules.Azure.BuildTool/Models.cs index 5ac3e18b022..6903633b2e1 100644 --- a/src/PSRule.Rules.Azure.BuildTool/Models.cs +++ b/src/PSRule.Rules.Azure.BuildTool/Models.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace PSRule.Rules.Azure.BuildTool { @@ -16,6 +17,9 @@ internal sealed class IndexEntry public int Index { get; set; } } + /// + /// This is the full class for deserializing un-minified resource type data. + /// internal sealed class ResourceTypeEntry { public ResourceTypeEntry() @@ -56,9 +60,9 @@ internal sealed class ResourceTypeMin { private readonly ResourceTypeEntry _Expanded; - public ResourceTypeMin(ResourceTypeEntry resourceType) + public ResourceTypeMin(ResourceTypeEntry entry) { - _Expanded = resourceType; + _Expanded = entry; } [JsonProperty(PropertyName = "a")] @@ -94,4 +98,54 @@ internal sealed class AvailabilityZoneMappingMin [JsonProperty(PropertyName = "z")] public string[] Zones { get; set; } } + + internal enum PolicyIgnoreReason + { + Duplicate = 1, + + NotApplicable = 2, + } + + internal sealed class PolicyIgnoreEntry + { + public PolicyIgnoreEntry() + { + Min = new PolicyIgnoreMin(this); + } + + [JsonProperty(PropertyName = "policyDefinitionIds")] + public string[] PolicyDefinitionIds { get; set; } + + [JsonProperty(PropertyName = "reason")] + [JsonConverter(typeof(StringEnumConverter))] + public PolicyIgnoreReason Reason { get; set; } + + [JsonProperty(PropertyName = "value")] + public string Value { get; set; } + + [JsonIgnore] + public PolicyIgnoreMin Min { get; } + } + + /// + /// A wrapper class for JSON minification. + /// + internal sealed class PolicyIgnoreMin + { + private readonly PolicyIgnoreEntry _Expanded; + + public PolicyIgnoreMin(PolicyIgnoreEntry entry) + { + _Expanded = entry; + } + + [JsonProperty("i")] + public string[] PolicyDefinitionIds => _Expanded.PolicyDefinitionIds; + + [JsonProperty("r")] + public PolicyIgnoreReason Reason => _Expanded.Reason; + + [JsonProperty("v", NullValueHandling = NullValueHandling.Ignore)] + public string Value => _Expanded.Reason == PolicyIgnoreReason.Duplicate ? _Expanded.Value : null; + } } diff --git a/src/PSRule.Rules.Azure.BuildTool/ProviderResource.cs b/src/PSRule.Rules.Azure.BuildTool/ProviderResource.cs index bb3816eb6c9..e79af3211d9 100644 --- a/src/PSRule.Rules.Azure.BuildTool/ProviderResource.cs +++ b/src/PSRule.Rules.Azure.BuildTool/ProviderResource.cs @@ -47,8 +47,8 @@ private static void MinifyEnvironments(ProviderResourceOption options) private static void MinifyPolicyIgnore(ProviderResourceOption options) { Console.WriteLine("BuildTool -- Minify policy-ignore"); - var policyIgnore = ReadFile(GetSourcePath("./data/policy-ignore.json")); - WriteFile(GetSourcePath("./data/policy-ignore.min.json"), policyIgnore); + var entries = ReadFile(GetSourcePath("./data/policy-ignore.json")); + WriteFile(GetSourcePath("./data/policy-ignore.min.json"), entries.Select(e => e.Min)); } private static void MinifyTypes(ProviderResourceOption options) @@ -58,7 +58,7 @@ private static void MinifyTypes(ProviderResourceOption options) foreach (var provider in GetProviders(GetSourcePath("./data/providers"))) { var entries = ReadFile(provider); - WriteMinified(provider, entries.Select(e => e.Min)); + WriteMinifiedResourceType(provider, entries.Select(e => e.Min)); count++; } Console.WriteLine($"BuildTool -- {count} providers processed"); @@ -88,7 +88,7 @@ private static void BuildIndex(ProviderResourceOption options) Console.WriteLine($"BuildTool -- {count} providers processed"); } - private static void WriteMinified(string provider, IEnumerable entries) + private static void WriteMinifiedResourceType(string provider, IEnumerable entries) { var file = provider.Replace("types.json", "types.min.json"); WriteFile(file, entries); @@ -124,7 +124,7 @@ private static T ReadFile(string path) var d = new JsonSerializer(); return d.Deserialize(reader); } - catch (Exception ex) + catch (Exception) { Console.WriteLine($"ERROR - Failed to read file: {path}"); throw; diff --git a/src/PSRule.Rules.Azure/Common/JsonConverters.cs b/src/PSRule.Rules.Azure/Common/JsonConverters.cs index 782e9ccdbcf..9a56e65fbd7 100644 --- a/src/PSRule.Rules.Azure/Common/JsonConverters.cs +++ b/src/PSRule.Rules.Azure/Common/JsonConverters.cs @@ -244,4 +244,83 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist throw new NotImplementedException(); } } + + internal sealed class PolicyBaselineConverter : JsonConverter + { + private const string SYNOPSIS_COMMENT = "Synopsis: "; + private const string PROPERTY_APIVERSION = "apiVersion"; + private const string APIVERSION_VALUE = "github.com/microsoft/PSRule/v1"; + private const string PROPERTY_KIND = "kind"; + private const string KIND_VALUE = "Baseline"; + private const string PROPERTY_METADATA = "metadata"; + private const string PROPERTY_NAME = "name"; + private const string PROPERTY_SPEC = "spec"; + private const string PROPERTY_RULE = "rule"; + private const string PROPERTY_INCLUDE = "include"; + + public override bool CanConvert(Type objectType) + { + throw new NotImplementedException(); + } + + public override bool CanWrite => true; + + public override bool CanRead => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + Map(writer, serializer, value as PolicyBaseline); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + private static void Map(JsonWriter writer, JsonSerializer serializer, PolicyBaseline baseline) + { + writer.WriteStartObject(); + + // Synopsis + writer.WriteComment(string.Concat(SYNOPSIS_COMMENT, baseline.Description)); + + // Api Version + writer.WritePropertyName(PROPERTY_APIVERSION); + writer.WriteValue(APIVERSION_VALUE); + + // Kind + writer.WritePropertyName(PROPERTY_KIND); + writer.WriteValue(KIND_VALUE); + + // Metadata + writer.WritePropertyName(PROPERTY_METADATA); + writer.WriteStartObject(); + writer.WritePropertyName(PROPERTY_NAME); + writer.WriteValue(baseline.Name); + writer.WriteEndObject(); + + // Spec + writer.WritePropertyName(PROPERTY_SPEC); + writer.WriteStartObject(); + WriteRule(writer, serializer, baseline); + + writer.WriteEndObject(); + } + + private static void WriteRule(JsonWriter writer, JsonSerializer serializer, PolicyBaseline baseline) + { + if (baseline.Include == null || baseline.Include.Length == 0) + return; + + var types = new HashSet(baseline.Include, StringComparer.OrdinalIgnoreCase); + writer.WritePropertyName(PROPERTY_RULE); + writer.WriteStartObject(); + + writer.WritePropertyName(PROPERTY_INCLUDE); + serializer.Serialize(writer, types); + writer.WriteEndObject(); + + writer.WriteEndObject(); + } + } } diff --git a/src/PSRule.Rules.Azure/Data/Policy/LoggerExtensions.cs b/src/PSRule.Rules.Azure/Data/Policy/LoggerExtensions.cs new file mode 100644 index 00000000000..c69d4ea303c --- /dev/null +++ b/src/PSRule.Rules.Azure/Data/Policy/LoggerExtensions.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Rules.Azure.Pipeline; +using PSRule.Rules.Azure.Resources; + +namespace PSRule.Rules.Azure.Data.Policy +{ + internal static class LoggerExtensions + { + /// + /// Policy definition has been ignored based on configuration: {0} + /// + /// A logger instance. + /// The policy definition ID. + public static void VerbosePolicyIgnoreConfigured(this ILogger logger, string definitionId) + { + logger.WriteVerbose(PSRuleResources.PolicyIgnoreConfigured, definitionId); + } + + /// + /// Policy definition has been ignored because it is not applicable to Infrastructure as Code: {0} + /// + /// A logger instance. + /// The policy definition ID. + public static void VerbosePolicyIgnoreNotApplicable(this ILogger logger, string definitionId) + { + logger.WriteVerbose(PSRuleResources.PolicyIgnoreNotApplicable, definitionId); + } + + /// + /// Policy definition has been ignored because a similar built-in rule already exists ({1}): {0} + /// + /// A logger instance. + /// The policy definition ID. + /// The name of the built-in rule. + public static void VerbosePolicyIgnoreDuplicate(this ILogger logger, string definitionId, string ruleName) + { + logger.WriteVerbose(PSRuleResources.PolicyIgnoreDuplicate, definitionId, ruleName); + } + + /// + /// Policy definition has been ignored because it is disabled: {0} + /// + /// A logger instance. + /// The policy definition ID. + public static void VerbosePolicyIgnoreDisabled(this ILogger logger, string definitionId) + { + logger.WriteVerbose(PSRuleResources.PolicyIgnoreDisabled, definitionId); + } + } +} diff --git a/src/PSRule.Rules.Azure/Data/Policy/Models.cs b/src/PSRule.Rules.Azure/Data/Policy/Models.cs index ffb791df7e5..849d4bd4ebb 100644 --- a/src/PSRule.Rules.Azure/Data/Policy/Models.cs +++ b/src/PSRule.Rules.Azure/Data/Policy/Models.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -176,4 +177,26 @@ public object GetValue(PolicyAssignmentVisitor.PolicyAssignmentContext context) return _Value; } } + + [JsonConverter(typeof(PolicyBaselineConverter))] + internal sealed class PolicyBaseline + { + public PolicyBaseline(string name, string description, IEnumerable definitionRuleNames, IEnumerable replacedRuleNames) + { + Name = name; + Description = description; + Include = Union(definitionRuleNames ?? Array.Empty(), replacedRuleNames ?? Array.Empty()); + } + + private static string[] Union(IEnumerable definitionRuleNames, IEnumerable replacedRuleNames) + { + return definitionRuleNames.Union(replacedRuleNames).ToArray(); + } + + public string Name { get; } + + public string Description { get; } + + public string[] Include { get; } + } } diff --git a/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentHelper.cs b/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentHelper.cs index 2f9c2c857e9..c4a2932e95f 100644 --- a/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentHelper.cs +++ b/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentHelper.cs @@ -17,13 +17,18 @@ namespace PSRule.Rules.Azure.Data.Policy internal sealed class PolicyAssignmentHelper { private readonly PipelineContext _PipelineContext; + private readonly bool _KeepDuplicates; - public PolicyAssignmentHelper(PipelineContext pipelineContext) + public PolicyAssignmentHelper(PipelineContext pipelineContext, bool keepDuplicates) { _PipelineContext = pipelineContext; + _KeepDuplicates = keepDuplicates; + Context = new PolicyAssignmentVisitor.PolicyAssignmentContext(_PipelineContext, _KeepDuplicates); } - internal PolicyDefinition[] ProcessAssignment(string assignmentFile, out PolicyAssignmentVisitor.PolicyAssignmentContext context) + public PolicyAssignmentVisitor.PolicyAssignmentContext Context { get; } + + internal void ProcessAssignment(string assignmentFile) { var rootedAssignmentFile = PSRuleOption.GetRootedPath(assignmentFile); if (!File.Exists(rootedAssignmentFile)) @@ -35,8 +40,7 @@ internal PolicyDefinition[] ProcessAssignment(string assignmentFile, out PolicyA rootedAssignmentFile); var visitor = new PolicyAssignmentDataExportVisitor(); - context = new PolicyAssignmentVisitor.PolicyAssignmentContext(_PipelineContext); - context.SetSource(assignmentFile); + Context.SetSource(assignmentFile); try { @@ -47,7 +51,7 @@ internal PolicyDefinition[] ProcessAssignment(string assignmentFile, out PolicyA { try { - visitor.Visit(context, assignment); + visitor.Visit(Context, assignment); } catch (Exception inner) { @@ -66,7 +70,6 @@ internal PolicyDefinition[] ProcessAssignment(string assignmentFile, out PolicyA inner, rootedAssignmentFile); } - return context.GetDefinitions(); } private static JObject[] ReadFileArray(string path) diff --git a/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs b/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs index 93e04b5381f..357ecd4fc7a 100644 --- a/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs +++ b/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs @@ -15,6 +15,7 @@ using PSRule.Rules.Azure.Data.Template; using PSRule.Rules.Azure.Pipeline; using PSRule.Rules.Azure.Resources; +using YamlDotNet.Core.Tokens; namespace PSRule.Rules.Azure.Data.Policy { @@ -114,10 +115,13 @@ public sealed class PolicyAssignmentContext : ITemplateContext private readonly Stack _FieldPrefix; private readonly TemplateValidator _Validator; private readonly IDictionary _ParameterAssignments; - private readonly HashSet _PolicyIgnore; + private readonly bool _KeepDuplicates; + private readonly Dictionary _PolicyIgnore; + private readonly List _ReplacementRules; - internal PolicyAssignmentContext(PipelineContext context) + internal PolicyAssignmentContext(PipelineContext context, bool keepDuplicates = false) { + _KeepDuplicates = keepDuplicates; _ExpressionFactory = new ExpressionFactory(policy: true); _ExpressionBuilder = new ExpressionBuilder(_ExpressionFactory); _PolicyAliasProviderHelper = new PolicyAliasProviderHelper(); @@ -149,8 +153,18 @@ internal PolicyAssignmentContext(PipelineContext context) PolicyRulePrefix = context?.Option?.Configuration?.PolicyRulePrefix; _PolicyIgnore = new PolicyIgnoreData().GetIndex(); - if (context?.Option?.Configuration?.PolicyIgnoreList != null) - _PolicyIgnore.UnionWith(context?.Option?.Configuration?.PolicyIgnoreList); + if (context?.Option?.Configuration?.PolicyIgnoreList != null && + context.Option.Configuration.PolicyIgnoreList.Length > 0) + { + foreach (var id in context.Option.Configuration.PolicyIgnoreList) + { + _PolicyIgnore[id] = new PolicyIgnoreResult + { + Reason = PolicyIgnoreReason.Configured + }; + } + } + _ReplacementRules = new List(); } public TemplateVisitor.TemplateContext.CopyIndexStore CopyIndex { get; } @@ -241,11 +255,6 @@ internal void SetDefaultResourceType(string type) } } - internal void ClearDefaultResourceType() - { - _PolicyAliasProviderHelper.ClearDefaultResourceType(); - } - internal void SetDefinitionParameterAssignment(PolicyDefinition definition, JProperty parameter) { var type = GetParameterType(parameter.Value); @@ -585,6 +594,17 @@ public PolicyDefinition[] GetDefinitions() return _Definitions.ToArray(); } + public PolicyBaseline GenerateBaseline() + { + return new PolicyBaseline + ( + name: string.Concat(PolicyRulePrefix, ".PolicyBaseline.All"), + description: "Generated automatically when exporting Azure Policy rules.", + definitionRuleNames: _Definitions.Select(d => d.Name), + replacedRuleNames: _ReplacementRules + ); + } + internal bool TryPolicyAliasPath(string aliasName, out string aliasPath) { aliasPath = null; @@ -683,7 +703,29 @@ internal void SetPolicyDefinitionId(string definitionId) /// internal bool ShouldIgnorePolicyDefinition(string definitionId) { - return _PolicyIgnore.Contains(definitionId); + if (!_PolicyIgnore.TryGetValue(definitionId, out var value)) + return false; + + if (value.Reason == PolicyIgnoreReason.Configured) + { + // Policy definition has been ignored based on configuration: {0} + Pipeline?.Writer?.VerbosePolicyIgnoreConfigured(definitionId); + return true; + } + else if (value.Reason == PolicyIgnoreReason.NotApplicable) + { + // Policy definition has been ignored because it is not applicable to Infrastructure as Code: {0} + Pipeline?.Writer?.VerbosePolicyIgnoreNotApplicable(definitionId); + return true; + } + else if (value.Reason == PolicyIgnoreReason.Duplicate && !_KeepDuplicates) + { + // Policy definition has been ignored because a similar built-in rule already exists ({1}): {0} + Pipeline?.Writer?.VerbosePolicyIgnoreDuplicate(definitionId, value.Value); + _ReplacementRules.Add(string.Concat("PSRule.Rules.Azure\\", value.Value)); + return true; + } + return false; } /// @@ -845,9 +887,11 @@ protected virtual bool TryPolicyDefinition(PolicyAssignmentContext context, JObj !policyRule.TryObjectProperty(PROPERTY_THEN, out var then)) return false; - if (!properties.TryStringProperty(PROPERTY_MODE, out var mode) || - !IsPolicyMode(mode, out var policyMode)) + if (!properties.TryStringProperty(PROPERTY_MODE, out var mode) || !IsPolicyMode(mode, out var policyMode)) + { + context.Pipeline?.Writer?.VerbosePolicyIgnoreNotApplicable(policyDefinitionId); return false; + } properties.TryStringProperty(PROPERTY_DISPLAYNAME, out var displayName); properties.TryStringProperty(PROPERTY_DESCRIPTION, out var description); @@ -892,8 +936,7 @@ protected virtual bool TryPolicyDefinition(PolicyAssignmentContext context, JObj context.DefinitionParameterMap[policyDefinitionId] = result.Parameters; } - if (!TryPolicyRuleEffect(context, then, out var effect) || - ShouldFilterRule(then, effect)) + if (!TryPolicyRuleEffect(context, then, out var effect) || ShouldFilterRule(context, policyDefinitionId, then, effect)) return false; // Modify policy rule @@ -1592,23 +1635,32 @@ private static void TrimPolicyRule(JObject policyRule) /// /// Determines if the policy definition should be skipped and not generate a rule. /// - private static bool ShouldFilterRule(JObject then, string effect) + private static bool ShouldFilterRule(PolicyAssignmentContext context, string policyDefinitionId, JObject then, string effect) { if (effect.Equals(EFFECT_DISABLED, StringComparison.OrdinalIgnoreCase)) + { + context?.Pipeline?.Writer.VerbosePolicyIgnoreDisabled(policyDefinitionId); return true; + } // Check if AuditIfNotExists type is a runtime type. return then.TryObjectProperty(PROPERTY_DETAILS, out var details) && details.TryStringProperty(PROPERTY_TYPE, out var type) && effect.Equals(EFFECT_AUDITIFNOTEXISTS, StringComparison.OrdinalIgnoreCase) && - IsRuntimeType(type); + IsRuntimeType(context, policyDefinitionId, type); } - private static bool IsRuntimeType(string type) + private static bool IsRuntimeType(PolicyAssignmentContext context, string policyDefinitionId, string type) { - return type.Equals(TYPE_SECURITYASSESSMENTS, StringComparison.OrdinalIgnoreCase) || + var isRuntimeType = type.Equals(TYPE_SECURITYASSESSMENTS, StringComparison.OrdinalIgnoreCase) || type.Equals(TYPE_GUESTCONFIGURATIONASSIGNMENTS, StringComparison.OrdinalIgnoreCase) || type.Equals(TYPE_BACKUPPROTECTEDITEMS, StringComparison.OrdinalIgnoreCase); + + if (isRuntimeType) + { + context?.Pipeline?.Writer.VerbosePolicyIgnoreNotApplicable(policyDefinitionId); + } + return isRuntimeType; } private static bool IsPolicyMode(string mode, out PolicyMode policyMode) diff --git a/src/PSRule.Rules.Azure/Data/PolicyIgnore.cs b/src/PSRule.Rules.Azure/Data/PolicyIgnore.cs new file mode 100644 index 00000000000..8f031199802 --- /dev/null +++ b/src/PSRule.Rules.Azure/Data/PolicyIgnore.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace PSRule.Rules.Azure.Data +{ + internal sealed class PolicyIgnoreResult + { + public PolicyIgnoreReason Reason { get; set; } + + public string Value { get; set; } + } + + internal sealed class PolicyIgnoreEntry + { + [JsonProperty("i")] + public string[] PolicyDefinitionIds { get; set; } + + [JsonProperty("r")] + public PolicyIgnoreReason Reason { get; set; } + + [JsonProperty("v", NullValueHandling = NullValueHandling.Ignore)] + public string Value { get; set; } + } + + internal enum PolicyIgnoreReason + { + /// + /// The policy is excluded because it was duplicated with an existing rule. + /// + Duplicate = 1, + + /// + /// The policy is excluded because it is not testable or not applicable for IaC. + /// + NotApplicable = 2, + + /// + /// An exclusion configured by the user. + /// + Configured = 3 + } + + internal sealed class PolicyIgnoreResultConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Dictionary); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartArray) + return null; + + reader.Read(); + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + while (reader.TokenType != JsonToken.EndArray) + { + var entry = serializer.Deserialize(reader); + for (var i = 0; i < entry.PolicyDefinitionIds.Length; i++) + { + result[entry.PolicyDefinitionIds[i]] = new PolicyIgnoreResult + { + Reason = entry.Reason, + Value = entry.Value, + }; + } + reader.Read(); + } + return result; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/PSRule.Rules.Azure/Data/PolicyIgnoreData.cs b/src/PSRule.Rules.Azure/Data/PolicyIgnoreData.cs index 4d8bf115553..b787c1e57ef 100644 --- a/src/PSRule.Rules.Azure/Data/PolicyIgnoreData.cs +++ b/src/PSRule.Rules.Azure/Data/PolicyIgnoreData.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using Newtonsoft.Json; using PSRule.Rules.Azure.Resources; @@ -19,20 +18,20 @@ internal sealed class PolicyIgnoreData : ResourceLoader { Converters = new List { - new HashSetConverter(StringComparer.OrdinalIgnoreCase), + new PolicyIgnoreResultConverter(), } }; - private HashSet _Index; + private Dictionary _Index; - internal HashSet GetIndex() + internal Dictionary GetIndex() { _Index ??= ReadIndex(GetContent(RESOURCE_PATH)); return _Index; } - private static HashSet ReadIndex(string content) + private static Dictionary ReadIndex(string content) { - return JsonConvert.DeserializeObject>(content, _Settings) ?? throw new JsonException(PSRuleResources.PolicyIgnoreInvalid); + return JsonConvert.DeserializeObject>(content, _Settings) ?? throw new JsonException(PSRuleResources.PolicyIgnoreInvalid); } } } diff --git a/src/PSRule.Rules.Azure/Data/Template/ExpressionStream.cs b/src/PSRule.Rules.Azure/Data/Template/ExpressionStream.cs index 035a99f1574..4867fbf83cc 100644 --- a/src/PSRule.Rules.Azure/Data/Template/ExpressionStream.cs +++ b/src/PSRule.Rules.Azure/Data/Template/ExpressionStream.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.ComponentModel.Design; using System.Diagnostics; using System.Linq; using System.Threading; diff --git a/src/PSRule.Rules.Azure/PSRule.Rules.Azure.psm1 b/src/PSRule.Rules.Azure/PSRule.Rules.Azure.psm1 index 16e17018014..2714ba97050 100644 --- a/src/PSRule.Rules.Azure/PSRule.Rules.Azure.psm1 +++ b/src/PSRule.Rules.Azure/PSRule.Rules.Azure.psm1 @@ -459,7 +459,10 @@ function Export-AzPolicyAssignmentRuleData { [String]$RulePrefix, [Parameter(Mandatory = $False)] - [Switch]$PassThru = $False + [Switch]$PassThru = $False, + + [Parameter(Mandatory = $False)] + [Switch]$KeepDuplicates = $False ) begin { Write-Verbose -Message '[Export-AzPolicyAssignmentRuleData] BEGIN::'; @@ -475,6 +478,7 @@ function Export-AzPolicyAssignmentRuleData { $builder = [PSRule.Rules.Azure.Pipeline.PipelineBuilder]::Assignment($option); $builder.Assignment($Name); $builder.PassThru($PassThru); + $builder.KeepDuplicates($KeepDuplicates); # Bind to subscription context if ($PSBoundParameters.ContainsKey('Subscription')) { diff --git a/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipeline.cs b/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipeline.cs index c9fb638f74e..034767d06c6 100644 --- a/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipeline.cs +++ b/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipeline.cs @@ -10,10 +10,10 @@ internal sealed class PolicyAssignmentPipeline : PipelineBase { private readonly PolicyAssignmentHelper _PolicyAssignmentHelper; - internal PolicyAssignmentPipeline(PipelineContext context) + internal PolicyAssignmentPipeline(PipelineContext context, bool keepDuplicates) : base(context) { - _PolicyAssignmentHelper = new PolicyAssignmentHelper(context); + _PolicyAssignmentHelper = new PolicyAssignmentHelper(context, keepDuplicates); } /// @@ -25,11 +25,18 @@ public override void Process(PSObject sourceObject) ProcessCatch(source.AssignmentFile); } + public override void End() + { + Context.Writer.WriteObject(_PolicyAssignmentHelper.Context.GetDefinitions(), true); + Context.Writer.WriteObject(_PolicyAssignmentHelper.Context.GenerateBaseline(), false); + base.End(); + } + private void ProcessCatch(string assignmentFile) { try { - Context.Writer.WriteObject(ProcessAssignment(assignmentFile), true); + ProcessAssignment(assignmentFile); } catch (PipelineException ex) { @@ -45,9 +52,9 @@ private void ProcessCatch(string assignmentFile) } } - internal PolicyDefinition[] ProcessAssignment(string assignmentFile) + private void ProcessAssignment(string assignmentFile) { - return _PolicyAssignmentHelper.ProcessAssignment(assignmentFile, out _); + _PolicyAssignmentHelper.ProcessAssignment(assignmentFile); } } } diff --git a/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipelineBuilder.cs b/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipelineBuilder.cs index 9cea2d8283b..068ea460d70 100644 --- a/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipelineBuilder.cs +++ b/src/PSRule.Rules.Azure/Pipeline/PolicyAssignmentPipelineBuilder.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - using System; using PSRule.Rules.Azure.Configuration; using PSRule.Rules.Azure.Pipeline.Output; @@ -18,11 +17,18 @@ public interface IPolicyAssignmentPipelineBuilder : IPipelineBuilder /// /// Enable pass-through. void PassThru(bool passThru); + + /// + /// Determines if policy definitions that duplicate existing built-in rules are exported. + /// + /// Enable exporting duplicates. + void KeepDuplicates(bool keepDuplicates); } internal sealed class PolicyAssignmentPipelineBuilder : PipelineBuilderBase, IPolicyAssignmentPipelineBuilder { private bool _PassThru; + private bool _KeepDuplicates; private const string OUTPUTFILE_PREFIX = "definitions-"; private const string OUTPUTFILE_EXTENSION = ".Rule.jsonc"; private const string ASSIGNMENTNAME_PREFIX = "export-"; @@ -64,6 +70,12 @@ public void PassThru(bool passThru) _PassThru = passThru; } + /// + public void KeepDuplicates(bool keepDuplicates) + { + _KeepDuplicates = keepDuplicates; + } + protected override PipelineWriter GetOutput() { // Redirect to file instead @@ -87,7 +99,7 @@ protected override PipelineWriter PrepareWriter() /// public override IPipeline Build() { - return new PolicyAssignmentPipeline(PrepareContext()); + return new PolicyAssignmentPipeline(PrepareContext(), _KeepDuplicates); } } } diff --git a/src/PSRule.Rules.Azure/Resources/PSRuleResources.Designer.cs b/src/PSRule.Rules.Azure/Resources/PSRuleResources.Designer.cs index 533b9ba0347..d3080776a01 100644 --- a/src/PSRule.Rules.Azure/Resources/PSRuleResources.Designer.cs +++ b/src/PSRule.Rules.Azure/Resources/PSRuleResources.Designer.cs @@ -366,6 +366,33 @@ internal static string PathTraversal { } } + /// + /// Looks up a localized string similar to Policy definition has been ignored based on configuration: {0}. + /// + internal static string PolicyIgnoreConfigured { + get { + return ResourceManager.GetString("PolicyIgnoreConfigured", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Policy definition has been ignored because it is disabled: {0}. + /// + internal static string PolicyIgnoreDisabled { + get { + return ResourceManager.GetString("PolicyIgnoreDisabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Policy definition has been ignored because a similar built-in rule already exists ({1}): {0}. + /// + internal static string PolicyIgnoreDuplicate { + get { + return ResourceManager.GetString("PolicyIgnoreDuplicate", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to the policy ignore content.. /// @@ -375,6 +402,15 @@ internal static string PolicyIgnoreInvalid { } } + /// + /// Looks up a localized string similar to Policy definition has been ignored because it is not applicable to Infrastructure as Code: {0}. + /// + internal static string PolicyIgnoreNotApplicable { + get { + return ResourceManager.GetString("PolicyIgnoreNotApplicable", resourceCulture); + } + } + /// /// Looks up a localized string similar to The language expression property '{0}' doesn't exist.. /// diff --git a/src/PSRule.Rules.Azure/Resources/PSRuleResources.resx b/src/PSRule.Rules.Azure/Resources/PSRuleResources.resx index 00fcead3fef..28b16ed3a8b 100644 --- a/src/PSRule.Rules.Azure/Resources/PSRuleResources.resx +++ b/src/PSRule.Rules.Azure/Resources/PSRuleResources.resx @@ -226,9 +226,21 @@ The template file '{0}' must be within the current working directory. Occurs when the template file is outside of the current working path. + + Policy definition has been ignored based on configuration: {0} + + + Policy definition has been ignored because it is disabled: {0} + + + Policy definition has been ignored because a similar built-in rule already exists ({1}): {0} + Failed to the policy ignore content. + + Policy definition has been ignored because it is not applicable to Infrastructure as Code: {0} + The language expression property '{0}' doesn't exist. Occurs when a property that don't exist is referenced. diff --git a/tests/PSRule.Rules.Azure.Tests/Cmdlet.Common.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Cmdlet.Common.Tests.ps1 index 8945ea474b5..558200b8132 100644 --- a/tests/PSRule.Rules.Azure.Tests/Cmdlet.Common.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Cmdlet.Common.Tests.ps1 @@ -573,7 +573,7 @@ Describe 'Export-AzPolicyAssignmentRuleData' -Tag 'Cmdlet', 'Export-AzPolicyAssi $filename = Split-Path -Path $result.FullName -Leaf; $filename | Should -BeExactly "definitions-$Name.Rule.jsonc"; $resultJson = ((Get-Content -Path $result.FullName) -replace '^\s*//.*') | ConvertFrom-Json; - $compressedResult = $resultJson | ConvertTo-Json -Depth 100 -Compress; + $compressedResult = $resultJson[0] | ConvertTo-Json -Depth 100 -Compress; $compressedExpected = $jsonRulesData[$Index] | ConvertTo-Json -Depth 100 -Compress; $compressedResult | Should -BeExactly $compressedExpected; } @@ -586,7 +586,7 @@ Describe 'Export-AzPolicyAssignmentRuleData' -Tag 'Cmdlet', 'Export-AzPolicyAssi $filename = Split-Path -Path $result.FullName -Leaf; $filename | Should -BeExactly "definitions-test12.Rule.jsonc"; $resultJson = ((Get-Content -Path $result.FullName) -replace '^\s*//.*') | ConvertFrom-Json; - $compressedResult = $resultJson | ConvertTo-Json -Depth 100 -Compress; + $compressedResult = $resultJson[0] | ConvertTo-Json -Depth 100 -Compress; $compressedExpected = $jsonRulesPrefixData | ConvertTo-Json -Depth 100 -Compress; $compressedResult | Should -BeExactly $compressedExpected; } diff --git a/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs index 5678b6f927f..65b0e197ecf 100644 --- a/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs @@ -19,7 +19,7 @@ public sealed class PolicyAssignmentVisitorTests [Fact] public void GetPolicyDefinition() { - var context = new PolicyAssignmentContext(GetContext()); + var context = new PolicyAssignmentContext(GetContext(), keepDuplicates: true); var visitor = new PolicyAssignmentDataExportVisitor(); foreach (var assignment in GetAssignmentData()) { @@ -35,7 +35,7 @@ public void GetPolicyDefinition() var definitions = context.GetDefinitions(); Assert.NotNull(definitions); - Assert.Equal(119, definitions.Length); + Assert.Equal(129, definitions.Length); // Check category and version var actual = definitions.FirstOrDefault(definition => definition.DefinitionId == "/providers/Microsoft.Authorization/policyDefinitions/34c877ad-507e-4c82-993e-3452a6e0ad3c"); @@ -95,7 +95,7 @@ public void GetPolicyDefinitionWithIgnore() var definitions = context.GetDefinitions(); Assert.NotNull(definitions); - Assert.Equal(118, definitions.Length); + Assert.Equal(117, definitions.Length); // Check category and version var actual = definitions.FirstOrDefault(definition => definition.DefinitionId == "/providers/Microsoft.Authorization/policyDefinitions/34c877ad-507e-4c82-993e-3452a6e0ad3c"); @@ -105,7 +105,7 @@ public void GetPolicyDefinitionWithIgnore() [Fact] public void GetAssignmentWithSameParameters() { - var context = new PolicyAssignmentContext(GetContext()); + var context = new PolicyAssignmentContext(GetContext(), keepDuplicates: true); var visitor = new PolicyAssignmentDataExportVisitor(); foreach (var assignment in GetAssignmentData().Where(a => a["Name"].Value().StartsWith("CustomAllowedLocations-"))) { @@ -127,7 +127,7 @@ public void GetAssignmentWithSameParameters() [Fact] public void ConvertRequestContext() { - var context = new PolicyAssignmentContext(GetContext()); + var context = new PolicyAssignmentContext(GetContext(), keepDuplicates: true); var visitor = new PolicyAssignmentDataExportVisitor(); foreach (var assignment in GetAssignmentData().Where(a => a["Name"].Value() == "RequestContext")) visitor.Visit(context, assignment); @@ -140,7 +140,7 @@ public void ConvertRequestContext() [Fact] public void GetResourceGroupLocation() { - var context = new PolicyAssignmentContext(GetContext()); + var context = new PolicyAssignmentContext(GetContext(), keepDuplicates: true); var visitor = new PolicyAssignmentDataExportVisitor(); foreach (var assignment in GetAssignmentData("Policy.assignment.2.json").Where(a => a["Name"].Value() == "8a3e449a009c485b930b36f2")) visitor.Visit(context, assignment); @@ -166,7 +166,7 @@ public void GetResourceGroupLocation() [Fact] public void GetFieldConcat() { - var context = new PolicyAssignmentContext(GetContext()); + var context = new PolicyAssignmentContext(GetContext(), keepDuplicates: true); var visitor = new PolicyAssignmentDataExportVisitor(); foreach (var assignment in GetAssignmentData("Policy.assignment.3.json").Where(a => a["Name"].Value() == "eba0bb3d870549789539e7d2")) visitor.Visit(context, assignment); @@ -189,6 +189,21 @@ public void GetFieldConcat() Assert.Equal(new string[] { "PSRule.Rules.Azure\\Azure.Policy.All" }, actual.With); } + [Fact] + public void GetPolicyBaseline() + { + var context = new PolicyAssignmentContext(GetContext()); + var visitor = new PolicyAssignmentDataExportVisitor(); + foreach (var assignment in GetAssignmentData().Where(a => a["Name"].Value() == "RequestContext" || a["Name"].Value() == "CustomAllowedLocations-australiaeast")) + visitor.Visit(context, assignment); + + var baseline = context.GenerateBaseline(); + + Assert.Equal("Azure.PolicyBaseline.All", baseline.Name); + Assert.Equal("Generated automatically when exporting Azure Policy rules.", baseline.Description); + Assert.Equal(new string[] { "Azure.Policy.b8a4e2d03e09", "PSRule.Rules.Azure\\Azure.KeyVault.SoftDelete" }, baseline.Include); + } + #region Helper methods private static PipelineContext GetContext(PSRuleOption option = null) diff --git a/tests/PSRule.Rules.Azure.Tests/PolicyBaselineConverterTests.cs b/tests/PSRule.Rules.Azure.Tests/PolicyBaselineConverterTests.cs new file mode 100644 index 00000000000..32bb2998229 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/PolicyBaselineConverterTests.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using PSRule.Rules.Azure.Data.Policy; +using Xunit; + +namespace PSRule.Rules.Azure +{ + public sealed class PolicyBaselineConverterTests + { + [Fact] + public void WriteJson() + { + var baseline = new PolicyBaseline("Baseline1", "Generated automatically when exporting Azure Policy rules.", new string[] { "Policy1" }, new string[] { "Rule1" }); + var json = JsonConvert.SerializeObject(baseline); + Assert.Equal(@"{/*Synopsis: Generated automatically when exporting Azure Policy rules.*/""apiVersion"":""github.com/microsoft/PSRule/v1"",""kind"":""Baseline"",""metadata"":{""name"":""Baseline1""},""spec"":{""rule"":{""include"":[""Policy1"",""Rule1""]}}}", json); + } + } +} diff --git a/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesData.jsonc b/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesData.jsonc index e969a72274a..3eadc3fc529 100644 --- a/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesData.jsonc +++ b/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesData.jsonc @@ -33,13 +33,13 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.d49c9ce3b804", + "name": "Azure.Policy.dda024fdd4b3", "displayName": "Deploy Log Analytics extension for Linux VMs", "tags": { "Azure.Policy/category": "Monitoring" }, "annotations": { - "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", + "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000002", "Azure.Policy/version": "2.0.1" } }, @@ -332,13 +332,13 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.eeb7e34479ef", + "name": "Azure.Policy.d56db8a16bcd", "displayName": "FunctionAppPullFromSpecifiedRegistry", "tags": { "Azure.Policy/category": "App Service" }, "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/0000000000-0000-0000-0000-000000000000", + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/0000000000-0000-0000-0000-000000000003", "Azure.Policy/version": "1.0.0" } }, @@ -390,10 +390,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.48eab4644919", + "name": "Azure.Policy.5236dec1a570", "displayName": "DisableLBRuleSNAT", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000004" } }, "spec": { @@ -421,10 +421,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.dd003b6a0803", + "name": "Azure.Policy.956f4a31128a", "displayName": "EnsureAtleastOneLBRule", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000005" } }, "spec": { @@ -447,10 +447,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.6ce831b6b744", + "name": "Azure.Policy.f95588f22a92", "displayName": "UniqueDescriptionNSG", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000006" } }, "spec": { @@ -478,10 +478,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.09175cf78e7c", + "name": "Azure.Policy.9aefa39d26cc", "displayName": "DenyNSGRDPInboundPort", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000007" } }, "spec": { @@ -517,10 +517,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.9b51258eff45", + "name": "Azure.Policy.2d46ae1952d5", "displayName": "DenyPortsNSG", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000008" } }, "spec": { @@ -603,10 +603,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.b54abd1594e6", + "name": "Azure.Policy.eac8effcedc0", "displayName": "PreventSubnetsWithoutNSG", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000009" } }, "spec": { @@ -677,10 +677,10 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.fe294bf42b80", + "name": "Azure.Policy.6e7a9b50e524", "displayName": "DenyPrivateEndpointSpecificSubnet", "annotations": { - "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "Azure.Policy/id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000010" } }, "spec": { @@ -723,13 +723,13 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "Azure.Policy.237646e7bc12", + "name": "Azure.Policy.55eedf5a41fd", "displayName": "View and configure system diagnostic data", "tags": { "Azure.Policy/category": "Regulatory Compliance" }, "annotations": { - "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", + "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000011-A", "Azure.Policy/version": "1.0.0" } }, @@ -746,5 +746,60 @@ "equals": false } } + }, + { + // Synopsis: View and configure system diagnostic data + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Rule", + "metadata": { + "name": "Azure.Policy.1cc127cf387a", + "displayName": "View and configure system diagnostic data", + "tags": { + "Azure.Policy/category": "Regulatory Compliance" + }, + "annotations": { + "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000011-B", + "Azure.Policy/version": "1.0.0" + } + }, + "spec": { + "recommend": "View and configure system diagnostic data", + "type": [ + "Microsoft.Resources/subscriptions" + ], + "with": [ + "PSRule.Rules.Azure\\Azure.Policy.All" + ], + "condition": { + "value": "Manual", + "equals": false + } + } + }, + { + // Synopsis: Generated automatically when exporting Azure Policy rules. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Baseline", + "metadata": { + "name": "Azure.PolicyBaseline.All" + }, + "spec": { + "rule": { + "include": [ + "Azure.Policy.65db1c629a22", + "Azure.Policy.dda024fdd4b3", + "Azure.Policy.d56db8a16bcd", + "Azure.Policy.5236dec1a570", + "Azure.Policy.956f4a31128a", + "Azure.Policy.f95588f22a92", + "Azure.Policy.9aefa39d26cc", + "Azure.Policy.2d46ae1952d5", + "Azure.Policy.eac8effcedc0", + "Azure.Policy.6e7a9b50e524", + "Azure.Policy.55eedf5a41fd", + "Azure.Policy.1cc127cf387a" + ] + } + } } ] diff --git a/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesPrefixData.jsonc b/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesPrefixData.jsonc index 44ecb7649a3..fe24b622e2a 100644 --- a/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesPrefixData.jsonc +++ b/tests/PSRule.Rules.Azure.Tests/emittedJsonRulesPrefixData.jsonc @@ -4,13 +4,13 @@ "apiVersion": "github.com/microsoft/PSRule/v1", "kind": "Rule", "metadata": { - "name": "AzureCustomPrefix.Policy.61bca30d0de3", + "name": "AzureCustomPrefix.Policy.6546bdb3e190", "displayName": "Allowed locations", "tags": { "Azure.Policy/category": "General" }, "annotations": { - "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", + "Azure.Policy/id": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000012", "Azure.Policy/version": "1.0.0" } }, diff --git a/tests/PSRule.Rules.Azure.Tests/test.assignment.json b/tests/PSRule.Rules.Azure.Tests/test.assignment.json index f208541a317..236861f8ee0 100644 --- a/tests/PSRule.Rules.Azure.Tests/test.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test.assignment.json @@ -1,102 +1,101 @@ [ - { - "Identity": null, - "Location": null, - "Name": "000000000000000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/PolicyRG/providers/Microsoft.Authorization/policyAssignments/000000000000000000000000", - "ResourceName": "000000000000000000000000", - "ResourceGroupName": "PolicyRG", - "ResourceType": "Microsoft.Authorization/policyAssignments", - "SubscriptionId": "00000000-0000-0000-0000-000000000000", - "Sku": null, - "PolicyAssignmentId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/PolicyRG/providers/Microsoft.Authorization/policyAssignments/000000000000000000000000", - "Properties": { - "Scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/PolicyRG", - "NotScopes": [], - "DisplayName": "Deny Storage Account Not Using Minumum TLS version", - "Description": null, - "Metadata": { - "assignedBy": "Armaan Dhaliwal-McLeod", - "parameterScopes": {}, - "createdBy": "00000000-0000-0000-0000-000000000000", - "createdOn": "2022-03-22T12:58:35.4594114Z", - "updatedBy": null, - "updatedOn": null - }, - "EnforcementMode": 0, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/Deny-Storage-Account-Not-Using-Minimum-TLS-Version", - "Parameters": {}, - "NonComplianceMessages": [] + { + "Identity": null, + "Location": null, + "Name": "000000000000000000000000", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/PolicyRG/providers/Microsoft.Authorization/policyAssignments/000000000000000000000000", + "ResourceName": "000000000000000000000000", + "ResourceGroupName": "PolicyRG", + "ResourceType": "Microsoft.Authorization/policyAssignments", + "SubscriptionId": "00000000-0000-0000-0000-000000000000", + "Sku": null, + "PolicyAssignmentId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/PolicyRG/providers/Microsoft.Authorization/policyAssignments/000000000000000000000000", + "Properties": { + "Scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/PolicyRG", + "NotScopes": [], + "DisplayName": "Deny Storage Account Not Using Minumum TLS version", + "Description": null, + "Metadata": { + "assignedBy": "Armaan Dhaliwal-McLeod", + "parameterScopes": {}, + "createdBy": "00000000-0000-0000-0000-000000000000", + "createdOn": "2022-03-22T12:58:35.4594114Z", + "updatedBy": null, + "updatedOn": null }, - "PolicyDefinitions": [ - { - "Name": "Deny-Storage-Account-Not-Using-TLS1-2", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/Deny-Storage-Account-Not-Using-Minimum-TLS-Version", - "ResourceName": "Deny-Storage-Account-Not-Using-TLS1-2", - "ResourceType": "Microsoft.Authorization/policyDefinitions", - "SubscriptionId": "00000000-0000-0000-0000-000000000000", - "Properties": { - "Description": "Minmum TLS version must be used on Storage accounts", - "DisplayName": "Deny Storage Account Not Using Minumum TLS version", - "Metadata": { - "version": "1.0.0", - "category": "Storage", - "createdBy": "00000000-0000-0000-0000-000000000000", - "createdOn": "2021-06-07T03:21:16.6955366Z", - "updatedBy": "00000000-0000-0000-0000-000000000000", - "updatedOn": "2022-03-22T12:58:04.8841964Z" - }, - "Mode": "Indexed", - "Parameters": { - "effect": { - "type": "String", - "metadata": { - "displayName": "Effect", - "description": "Enable or disable the execution of the policy" - }, - "allowedValues": [ - "Audit", - "Deny", - "Disabled" - ], - "defaultValue": "Deny" + "EnforcementMode": 0, + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/Deny-Storage-Account-Not-Using-Minimum-TLS-Version", + "Parameters": {}, + "NonComplianceMessages": [] + }, + "PolicyDefinitions": [ + { + "Name": "Deny-Storage-Account-Not-Using-TLS1-2", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/Deny-Storage-Account-Not-Using-Minimum-TLS-Version", + "ResourceName": "Deny-Storage-Account-Not-Using-TLS1-2", + "ResourceType": "Microsoft.Authorization/policyDefinitions", + "SubscriptionId": "00000000-0000-0000-0000-000000000000", + "Properties": { + "Description": "Minmum TLS version must be used on Storage accounts", + "DisplayName": "Deny Storage Account Not Using Minumum TLS version", + "Metadata": { + "version": "1.0.0", + "category": "Storage", + "createdBy": "00000000-0000-0000-0000-000000000000", + "createdOn": "2021-06-07T03:21:16.6955366Z", + "updatedBy": "00000000-0000-0000-0000-000000000000", + "updatedOn": "2022-03-22T12:58:04.8841964Z" + }, + "Mode": "Indexed", + "Parameters": { + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" }, - "minimumTlsVersion": { - "type": "String", - "metadata": { - "displayName": "TLS Version", - "description": "Minimum TLS Version Required for Storage Accounts" - }, - "allowedValues": [ - "TLS1_0", - "TLS1_1", - "TLS1_2" - ], - "defaultValue": "TLS1_2" - } + "allowedValues": [ + "Audit", + "Deny", + "Disabled" + ], + "defaultValue": "Deny" }, - "PolicyRule": { - "if": { - "allOf": [ - { - "field": "type", - "equals": "Microsoft.Storage/storageAccounts" - }, - { - "field": "Microsoft.Storage/storageAccounts/minimumTlsVersion", - "notEquals": "[parameters('minimumTlsVersion')]" - } - ] + "minimumTlsVersion": { + "type": "String", + "metadata": { + "displayName": "TLS Version", + "description": "Minimum TLS Version Required for Storage Accounts" }, - "then": { - "effect": "[parameters('effect')]" - } + "allowedValues": [ + "TLS1_0", + "TLS1_1", + "TLS1_2" + ], + "defaultValue": "TLS1_2" + } + }, + "PolicyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Storage/storageAccounts" + }, + { + "field": "Microsoft.Storage/storageAccounts/minimumTlsVersion", + "notEquals": "[parameters('minimumTlsVersion')]" + } + ] }, - "PolicyType": 1 + "then": { + "effect": "[parameters('effect')]" + } }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/Deny-Storage-Account-Not-Using-Minimum-TLS-Version" - } - ] - } - ] - \ No newline at end of file + "PolicyType": 1 + }, + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/Deny-Storage-Account-Not-Using-Minimum-TLS-Version" + } + ] + } +] diff --git a/tests/PSRule.Rules.Azure.Tests/test10.assignment.json b/tests/PSRule.Rules.Azure.Tests/test10.assignment.json index 1fefc9b8e88..061ef7a3493 100644 --- a/tests/PSRule.Rules.Azure.Tests/test10.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test10.assignment.json @@ -40,9 +40,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000010", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000010", + "ResourceName": "00000000-0000-0000-0000-000000000010", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -100,8 +100,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000010" } ] } -] \ No newline at end of file +] diff --git a/tests/PSRule.Rules.Azure.Tests/test11.assignment.json b/tests/PSRule.Rules.Azure.Tests/test11.assignment.json index 73cbef0f319..1e91229487a 100644 --- a/tests/PSRule.Rules.Azure.Tests/test11.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test11.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000011-A", + "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000011-A", + "ResourceName": "00000000-0000-0000-0000-000000000011-A", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": null, "Properties": { @@ -71,12 +71,12 @@ }, "PolicyType": 2 }, - "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000011-A" }, { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000011-B", + "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000011-B", + "ResourceName": "00000000-0000-0000-0000-000000000011-B", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": null, "Properties": { @@ -115,7 +115,7 @@ }, "PolicyType": 2 }, - "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000011-B" } ] } diff --git a/tests/PSRule.Rules.Azure.Tests/test12.assignment.json b/tests/PSRule.Rules.Azure.Tests/test12.assignment.json index d0f3ccf4629..dde96aba250 100644 --- a/tests/PSRule.Rules.Azure.Tests/test12.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test12.assignment.json @@ -39,9 +39,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000012", + "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000012", + "ResourceName": "00000000-0000-0000-0000-000000000012", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": null, "Properties": { @@ -85,7 +85,7 @@ }, "PolicyType": 2 }, - "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000012" } ] } diff --git a/tests/PSRule.Rules.Azure.Tests/test2.assignment.json b/tests/PSRule.Rules.Azure.Tests/test2.assignment.json index 30d613a2f27..2d7f3d3dd3f 100644 --- a/tests/PSRule.Rules.Azure.Tests/test2.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test2.assignment.json @@ -46,9 +46,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000002", + "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000002", + "ResourceName": "00000000-0000-0000-0000-000000000002", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": null, "Properties": { @@ -426,7 +426,7 @@ }, "PolicyType": 2 }, - "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000002" } ] } diff --git a/tests/PSRule.Rules.Azure.Tests/test3.assignment.json b/tests/PSRule.Rules.Azure.Tests/test3.assignment.json index df36c0362a7..f0f6f2f48e9 100644 --- a/tests/PSRule.Rules.Azure.Tests/test3.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test3.assignment.json @@ -39,9 +39,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/0000000000-0000-0000-0000-000000000000", - "ResourceName": "0000000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000003", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/0000000000-0000-0000-0000-000000000003", + "ResourceName": "0000000000-0000-0000-0000-000000000003", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -174,8 +174,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/0000000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/0000000000-0000-0000-0000-000000000003" } ] } -] \ No newline at end of file +] diff --git a/tests/PSRule.Rules.Azure.Tests/test4.assignment.json b/tests/PSRule.Rules.Azure.Tests/test4.assignment.json index b8982622ba7..58aa84825b3 100644 --- a/tests/PSRule.Rules.Azure.Tests/test4.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test4.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000004", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000004", + "ResourceName": "00000000-0000-0000-0000-000000000004", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -71,8 +71,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000004" } ] } -] \ No newline at end of file +] diff --git a/tests/PSRule.Rules.Azure.Tests/test5.assignment.json b/tests/PSRule.Rules.Azure.Tests/test5.assignment.json index 4111c7ca1f8..5d024d65efd 100644 --- a/tests/PSRule.Rules.Azure.Tests/test5.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test5.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000005", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000005", + "ResourceName": "00000000-0000-0000-0000-000000000005", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -67,7 +67,7 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000005" } ] } diff --git a/tests/PSRule.Rules.Azure.Tests/test6.assignment.json b/tests/PSRule.Rules.Azure.Tests/test6.assignment.json index 870c0552369..e9814453bde 100644 --- a/tests/PSRule.Rules.Azure.Tests/test6.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test6.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000006", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000006", + "ResourceName": "00000000-0000-0000-0000-000000000006", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -71,8 +71,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000006" } ] } -] \ No newline at end of file +] diff --git a/tests/PSRule.Rules.Azure.Tests/test7.assignment.json b/tests/PSRule.Rules.Azure.Tests/test7.assignment.json index 8c492c09687..fa5c1ad2f99 100644 --- a/tests/PSRule.Rules.Azure.Tests/test7.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test7.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000007", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000007", + "ResourceName": "00000000-0000-0000-0000-000000000007", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -92,8 +92,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000007" } ] } -] \ No newline at end of file +] diff --git a/tests/PSRule.Rules.Azure.Tests/test8.assignment.json b/tests/PSRule.Rules.Azure.Tests/test8.assignment.json index c244ed78e25..7b17b4afa71 100644 --- a/tests/PSRule.Rules.Azure.Tests/test8.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test8.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000008", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000008", + "ResourceName": "00000000-0000-0000-0000-000000000008", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -117,8 +117,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000008" } ] } -] \ No newline at end of file +] diff --git a/tests/PSRule.Rules.Azure.Tests/test9.assignment.json b/tests/PSRule.Rules.Azure.Tests/test9.assignment.json index 2611d99e440..5e6abd31d10 100644 --- a/tests/PSRule.Rules.Azure.Tests/test9.assignment.json +++ b/tests/PSRule.Rules.Azure.Tests/test9.assignment.json @@ -30,9 +30,9 @@ }, "PolicyDefinitions": [ { - "Name": "00000000-0000-0000-0000-000000000000", - "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000", - "ResourceName": "00000000-0000-0000-0000-000000000000", + "Name": "00000000-0000-0000-0000-000000000009", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000009", + "ResourceName": "00000000-0000-0000-0000-000000000009", "ResourceType": "Microsoft.Authorization/policyDefinitions", "SubscriptionId": "00000000-0000-0000-0000-000000000000", "Properties": { @@ -105,8 +105,8 @@ }, "PolicyType": 1 }, - "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000000" + "PolicyDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyDefinitions/00000000-0000-0000-0000-000000000009" } ] } -] \ No newline at end of file +]