diff --git a/.vscode/markdown.code-snippets b/.vscode/markdown.code-snippets
index 5d508583389..7fc4c3250b9 100644
--- a/.vscode/markdown.code-snippets
+++ b/.vscode/markdown.code-snippets
@@ -140,5 +140,15 @@
"",
"```"
]
+ },
+ "rule-azure-example-policy": {
+ "scope": "markdown",
+ "prefix": "rule-azure-example-policy",
+ "description": "Example for Azure Policy",
+ "body": [
+ "### Configure with Azure Policy",
+ "",
+ "To address this issue at runtime use the following policies:"
+ ]
}
}
diff --git a/.vscode/yaml.code-snippets b/.vscode/yaml.code-snippets
index 8f73ff02287..6d7482cdb7a 100644
--- a/.vscode/yaml.code-snippets
+++ b/.vscode/yaml.code-snippets
@@ -1,5 +1,6 @@
{
"Azure rule with type": {
+ "scope": "yaml",
"prefix": "rule-azure-with-type",
"description": "Rule definition for Azure",
"body": [
diff --git a/data/policy-ignore.json b/data/policy-ignore.json
index 731da75371e..e219be84574 100644
--- a/data/policy-ignore.json
+++ b/data/policy-ignore.json
@@ -165,6 +165,42 @@
"/providers/Microsoft.Authorization/policyDefinitions/cfdc5972-75b3-4418-8ae1-7f5c36839390"
],
"reason": "Duplicate",
- "value": "Azure.Defender.Storage.SensitiveData"
+ "value": "Azure.Defender.Storage.DataScan"
+ },
+ {
+ "policyDefinitionIds": [
+ "/providers/Microsoft.Authorization/policyDefinitions/0e80e269-43a4-4ae9-b5bc-178126b8a5cb"
+ ],
+ "reason": "Duplicate",
+ "value": "Azure.ContainerApp.Insecure"
+ },
+ {
+ "policyDefinitionIds": [
+ "/providers/Microsoft.Authorization/policyDefinitions/b874ab2d-72dd-47f1-8cb5-4a306478a4e7"
+ ],
+ "reason": "Duplicate",
+ "value": "Azure.ContainerApp.ManagedIdentity"
+ },
+ {
+ "policyDefinitionIds": [
+ "/providers/Microsoft.Authorization/policyDefinitions/13502221-8df0-4414-9937-de9c5c4e396b"
+ ],
+ "reason": "Duplicate",
+ "value": "Azure.Storage.BlobPublicAccess"
+ },
+ {
+ "policyDefinitionIds": [
+ "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9",
+ "/providers/Microsoft.Authorization/policyDefinitions/f81e3117-0093-4b17-8a60-82363134f0eb"
+ ],
+ "reason": "Duplicate",
+ "value": "Azure.Storage.SecureTransfer"
+ },
+ {
+ "policyDefinitionIds": [
+ "/providers/Microsoft.Authorization/policyDefinitions/fe83a0eb-a853-422d-aac2-1bffd182c5d0"
+ ],
+ "reason": "Duplicate",
+ "value": "Azure.Storage.MinTLS"
}
]
diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md
index b044784c35b..2c33ce19be1 100644
--- a/docs/CHANGELOG-v1.md
+++ b/docs/CHANGELOG-v1.md
@@ -48,9 +48,16 @@ What's changed since pre-release v1.34.0-B0047:
- Renamed `Azure.Storage.DefenderCloud.SensitiveData` to `Azure.Storage.Defender.DataScan`.
- Promoted `Azure.Storage.Defender.MalwareScan` to GA rule set by @BernieWhite.
[#2590](https://github.com/Azure/PSRule.Rules.Azure/pull/2590)
+- General improvements:
+ - Added duplicate policies to default ignore list by @BernieWhite.
+ [#1731](https://github.com/Azure/PSRule.Rules.Azure/issues/1731)
- Engineering:
- Updated resource providers and policy aliases.
[#2717](https://github.com/Azure/PSRule.Rules.Azure/pull/2717)
+- Bug fixes:
+ - Fixes for policy as rules by @BernieWhite.
+ [#181](https://github.com/Azure/PSRule.Rules.Azure/issues/181)
+ [#1323](https://github.com/Azure/PSRule.Rules.Azure/issues/1323)
## v1.34.0-B0047 (pre-release)
diff --git a/docs/en/rules/Azure.Cognitive.DisableLocalAuth.md b/docs/en/rules/Azure.Cognitive.DisableLocalAuth.md
index 025a0d0e3a8..cb159b47312 100644
--- a/docs/en/rules/Azure.Cognitive.DisableLocalAuth.md
+++ b/docs/en/rules/Azure.Cognitive.DisableLocalAuth.md
@@ -95,10 +95,10 @@ resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = {
To address this issue at runtime use the following policies:
-```text
-/providers/Microsoft.Authorization/policyDefinitions/71ef260a-8f18-47b7-abcb-62d0673d94dc
-/providers/Microsoft.Authorization/policyDefinitions/14de9e63-1b31-492e-a5a3-c3f7fd57f555
-```
+- [Azure AI Services resources should have key access disabled (disable local authentication)](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Azure%20Ai%20Services/CognitiveServices_DisableLocalAuth_Audit.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/71ef260a-8f18-47b7-abcb-62d0673d94dc`
+- [Configure Cognitive Services accounts to disable local authentication methods](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Cognitive%20Services/CognitiveServices_DisableLocalAuth_Modify.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/14de9e63-1b31-492e-a5a3-c3f7fd57f555`
## LINKS
diff --git a/docs/en/rules/Azure.ContainerApp.Insecure.md b/docs/en/rules/Azure.ContainerApp.Insecure.md
index 2012e543c22..a3fb28e6d1f 100644
--- a/docs/en/rules/Azure.ContainerApp.Insecure.md
+++ b/docs/en/rules/Azure.ContainerApp.Insecure.md
@@ -1,8 +1,8 @@
---
-reviewed: 2023-04-29
+reviewed: 2024-03-04
severity: Important
pillar: Security
-category: Design
+category: SE:07 Encryption
resource: Container App
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ContainerApp.Insecure/
---
@@ -98,9 +98,16 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
}
```
+### Configure with Azure Policy
+
+To address this issue at runtime use the following policies:
+
+- [Container Apps should only be accessible over HTTPS](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Container%20Apps/ContainerApps_EnableHTTPS_Audit.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/0e80e269-43a4-4ae9-b5bc-178126b8a5cb`
+
## LINKS
-- [Data encryption in Azure](https://learn.microsoft.com/azure/architecture/framework/security/design-storage-encryption#data-in-transit)
-- [Ingress in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/ingress-overview#configuration)
+- [SE:07 Encryption](https://learn.microsoft.com/azure/well-architected/security/encryption#data-in-transit)
+- [Ingress in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/ingress-overview)
- [Container Apps ARM template API specification](https://learn.microsoft.com/azure/container-apps/azure-resource-manager-api-spec?tabs=arm-template)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.app/containerapps)
diff --git a/docs/en/rules/Azure.ContainerApp.ManagedIdentity.md b/docs/en/rules/Azure.ContainerApp.ManagedIdentity.md
index fd9241351ee..f9539bca63e 100644
--- a/docs/en/rules/Azure.ContainerApp.ManagedIdentity.md
+++ b/docs/en/rules/Azure.ContainerApp.ManagedIdentity.md
@@ -1,7 +1,7 @@
---
severity: Important
pillar: Security
-category: Authentication
+category: SE:05 Identity and access management
resource: Container App
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ContainerApp.ManagedIdentity/
---
@@ -16,10 +16,13 @@ Ensure managed identity is used for authentication.
Using managed identities have the following benefits:
-- Your app connects to resources with the managed identity. You don't need to manage credentials in your container app.
+- Your app connects to resources with the managed identity.
+ You don't need to manage credentials in your container app.
- You can use role-based access control to grant specific permissions to a managed identity.
-- System-assigned identities are automatically created and managed. They're deleted when your container app is deleted.
-- You can add and delete user-assigned identities and assign them to multiple resources. They're independent of your container app's life cycle.
+- System-assigned identities are automatically created and managed.
+ They're deleted when your container app is deleted.
+- You can add and delete user-assigned identities and assign them to multiple resources.
+ They're independent of your container app's life cycle.
- You can use managed identity to authenticate with a private Azure Container Registry without a username and password to pull containers for your Container App.
- You can use managed identity to create connections for Dapr-enabled applications via Dapr components.
@@ -102,6 +105,13 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
}
```
+### Configure with Azure Policy
+
+To address this issue at runtime use the following policies:
+
+- [Managed Identity should be enabled for Container Apps](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Container%20Apps/ContainerApps_ManagedIdentity_Audit.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/b874ab2d-72dd-47f1-8cb5-4a306478a4e7`
+
## NOTES
Using managed identities in scale rules isn't supported.
@@ -109,6 +119,6 @@ Init containers can't access managed identities.
## LINKS
-- [Use identity-based authentication](https://learn.microsoft.com/azure/well-architected/security/design-identity-authentication#use-identity-based-authentication)
+- [SE:05 Identity and access management](https://learn.microsoft.com/azure/well-architected/security/identity-access)
- [Managed identities in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/managed-identity)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.app/containerapps#managedserviceidentity)
diff --git a/docs/en/rules/Azure.Storage.BlobAccessType.md b/docs/en/rules/Azure.Storage.BlobAccessType.md
index f3ac8e01539..cdb85d01433 100644
--- a/docs/en/rules/Azure.Storage.BlobAccessType.md
+++ b/docs/en/rules/Azure.Storage.BlobAccessType.md
@@ -1,8 +1,8 @@
---
-reviewed: 2022-01-20
+reviewed: 2024-03-04
severity: Important
pillar: Security
-category: Authentication
+category: SE:05 Identity and access management
resource: Storage Account
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Storage.BlobAccessType/
---
@@ -40,16 +40,16 @@ For example:
```json
{
- "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
- "apiVersion": "2021-06-01",
- "name": "[format('{0}/{1}/{2}', parameters('name'), 'default', variables('containerName'))]",
- "properties": {
- "publicAccess": "None"
- },
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('name'), 'default')]",
- "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"
- ]
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2021-06-01",
+ "name": "[format('{0}/{1}/{2}', parameters('name'), 'default', variables('containerName'))]",
+ "properties": {
+ "publicAccess": "None"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('name'), 'default')]",
+ "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"
+ ]
}
```
@@ -73,8 +73,10 @@ resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@20
## LINKS
-- [Authentication with Azure AD](https://learn.microsoft.com/azure/architecture/framework/security/design-identity-authentication)
-- [About anonymous public read access](https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-configure#about-anonymous-public-read-access)
-- [Use Azure Policy to enforce authorized access](https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-prevent#use-azure-policy-to-enforce-authorized-access)
-- [How a shared access signature works](https://docs.microsoft.com/azure/storage/common/storage-sas-overview#how-a-shared-access-signature-works)
+- [SE:05 Identity and access management](https://learn.microsoft.com/azure/well-architected/security/identity-access)
+- [Use Microsoft Entra ID for storage authentication](https://learn.microsoft.com/azure/security/fundamentals/identity-management-best-practices#use-microsoft-entra-id-for-storage-authentication)
+- [Configure anonymous read access for containers and blobs](https://learn.microsoft.com/azure/storage/blobs/anonymous-read-access-configure)
+- [Remediate anonymous read access to blob data](https://learn.microsoft.com/azure/storage/blobs/anonymous-read-access-prevent)
+- [How a shared access signature works](https://learn.microsoft.com/azure/storage/common/storage-sas-overview#how-a-shared-access-signature-works)
+- [Authorize access to blobs using Microsoft Entra ID](https://learn.microsoft.com/azure/storage/blobs/authorize-access-azure-active-directory)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.storage/storageaccounts)
diff --git a/docs/en/rules/Azure.Storage.BlobPublicAccess.md b/docs/en/rules/Azure.Storage.BlobPublicAccess.md
index 02b74e22535..a85e447ed2d 100644
--- a/docs/en/rules/Azure.Storage.BlobPublicAccess.md
+++ b/docs/en/rules/Azure.Storage.BlobPublicAccess.md
@@ -1,7 +1,7 @@
---
severity: Important
pillar: Security
-category: Authentication
+category: SE:05 Identity and access management
resource: Storage Account
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Storage.BlobPublicAccess/
---
@@ -89,11 +89,18 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
}
```
+### Configure with Azure Policy
+
+To address this issue at runtime use the following policies:
+
+- [Configure your Storage account public access to be disallowed](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Storage/StorageAccountDisablePublicBlobAccess_Modify.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/13502221-8df0-4414-9937-de9c5c4e396b`
+
## LINKS
-- [Use Azure AD for storage authentication](https://docs.microsoft.com/azure/security/fundamentals/identity-management-best-practices#use-azure-ad-for-storage-authentication)
-- [Allow or disallow public read access for a storage account](https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-configure#allow-or-disallow-public-read-access-for-a-storage-account)
-- [Remediate anonymous public access](https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-prevent#remediate-anonymous-public-access)
-- [Use Azure Policy to enforce authorized access](https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-prevent#use-azure-policy-to-enforce-authorized-access)
-- [Authorize access to blobs using Azure Active Directory](https://docs.microsoft.com/azure/storage/blobs/authorize-access-azure-active-directory)
+- [SE:05 Identity and access management](https://learn.microsoft.com/azure/well-architected/security/identity-access)
+- [Use Microsoft Entra ID for storage authentication](https://learn.microsoft.com/azure/security/fundamentals/identity-management-best-practices#use-microsoft-entra-id-for-storage-authentication)
+- [Configure anonymous read access for containers and blobs](https://learn.microsoft.com/azure/storage/blobs/anonymous-read-access-configure)
+- [Remediate anonymous read access to blob data](https://learn.microsoft.com/azure/storage/blobs/anonymous-read-access-prevent)
+- [Authorize access to blobs using Microsoft Entra ID](https://learn.microsoft.com/azure/storage/blobs/authorize-access-azure-active-directory)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.storage/storageaccounts)
diff --git a/docs/en/rules/Azure.Storage.MinTLS.md b/docs/en/rules/Azure.Storage.MinTLS.md
index cc5f14a1417..4bc99a43111 100644
--- a/docs/en/rules/Azure.Storage.MinTLS.md
+++ b/docs/en/rules/Azure.Storage.MinTLS.md
@@ -1,7 +1,8 @@
---
+reviewed: 2024-03-04
severity: Critical
pillar: Security
-category: Encryption
+category: SE:07 Encryption
resource: Storage Account
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Storage.MinTLS/
---
@@ -87,9 +88,16 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
}
```
+### Configure with Azure Policy
+
+To address this issue at runtime use the following policies:
+
+- [Storage accounts should have the specified minimum TLS version](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Storage/StorageAccountMinimumTLSVersion_Audit.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/fe83a0eb-a853-422d-aac2-1bffd182c5d0`
+
## LINKS
-- [Data encryption in Azure](https://learn.microsoft.com/azure/architecture/framework/security/design-storage-encryption#data-in-transit)
+- [SE:07 Encryption](https://learn.microsoft.com/azure/well-architected/security/encryption#data-in-transit)
- [TLS encryption in Azure](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview#tls-encryption-in-azure)
- [Enforce a minimum required version of Transport Layer Security (TLS) for requests to a storage account](https://learn.microsoft.com/azure/storage/common/transport-layer-security-configure-minimum-version)
- [DP-3: Encrypt sensitive data in transit](https://learn.microsoft.com/security/benchmark/azure/baselines/storage-security-baseline#dp-3-encrypt-sensitive-data-in-transit)
diff --git a/docs/en/rules/Azure.Storage.SecureTransfer.md b/docs/en/rules/Azure.Storage.SecureTransfer.md
index ed19e4749b0..c5e2480eed0 100644
--- a/docs/en/rules/Azure.Storage.SecureTransfer.md
+++ b/docs/en/rules/Azure.Storage.SecureTransfer.md
@@ -1,8 +1,8 @@
---
-reviewed: 2023-09-02
+reviewed: 2024-03-04
severity: Important
pillar: Security
-category: Encryption
+category: SE:07 Encryption
resource: Storage Account
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Storage.SecureTransfer/
ms-content-id: 539cb7b9-5510-4aa3-b422-41a049a10a88
@@ -101,9 +101,18 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
}
```
+### Configure with Azure Policy
+
+To address this issue at runtime use the following policies:
+
+- [Secure transfer to storage accounts should be enabled](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Storage/Storage_AuditForHTTPSEnabled_Audit.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9`
+- [Configure secure transfer of data on a storage account](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Storage/StorageAccountSecureTransfer_Modify.json)
+ `/providers/Microsoft.Authorization/policyDefinitions/f81e3117-0093-4b17-8a60-82363134f0eb`
+
## LINKS
-- [Data encryption in Azure](https://learn.microsoft.com/azure/architecture/framework/security/design-storage-encryption#data-in-transit)
+- [SE:07 Encryption](https://learn.microsoft.com/azure/well-architected/security/encryption#data-in-transit)
- [Require secure transfer in Azure Storage](https://learn.microsoft.com/azure/storage/common/storage-require-secure-transfer)
- [DP-3: Encrypt sensitive data in transit](https://learn.microsoft.com/security/benchmark/azure/baselines/storage-security-baseline#dp-3-encrypt-sensitive-data-in-transit)
- [Sample policy for ensuring https traffic](https://learn.microsoft.com/azure/governance/policy/samples/built-in-policies#storage)
diff --git a/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs b/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs
index 423c05a73b8..9429a29820e 100644
--- a/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs
+++ b/src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs
@@ -1001,7 +1001,7 @@ private static void VisitCondition(PolicyAssignmentContext context, PolicyDefini
{
if (condition.TryStringProperty(PROPERTY_VALUE, out var s) && s.IsExpressionString())
{
- VisitValueExpression(context, condition, s);
+ condition = VisitValueExpression(context, condition, s);
}
else if (condition.TryStringProperty(PROPERTY_FIELD, out var field))
{
@@ -1248,7 +1248,7 @@ private static void AddTypes(PolicyAssignmentContext context, PolicyDefinition p
}
}
- private static void VisitValueExpression(PolicyAssignmentContext context, JObject condition, string s)
+ private static JObject VisitValueExpression(PolicyAssignmentContext context, JObject condition, string s)
{
var tokens = ExpressionParser.Parse(s);
@@ -1288,7 +1288,24 @@ private static void VisitValueExpression(PolicyAssignmentContext context, JObjec
}
// Handle [field('string')]
- else if (tokens.ConsumeFunction(PROPERTY_FIELD) &&
+ else if (tokens.HasFieldTokens())
+ {
+ condition = VisitFieldTokens(context, condition, tokens);
+ }
+
+ // Handle runtime token
+ else if (tokens.HasPolicyRuntimeTokens())
+ {
+ var value = VisitRuntimeTokens(context, tokens);
+ if (value != null)
+ condition.ReplaceProperty(PROPERTY_VALUE, value);
+ }
+ return condition;
+ }
+
+ private static JObject VisitFieldTokens(PolicyAssignmentContext context, JObject condition, TokenStream tokens)
+ {
+ if (tokens.ConsumeFunction(PROPERTY_FIELD) &&
tokens.TryTokenType(ExpressionTokenType.GroupStart, out _) &&
tokens.ConsumeString(out var field) &&
tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _))
@@ -1302,17 +1319,68 @@ private static void VisitValueExpression(PolicyAssignmentContext context, JObjec
else
{
condition.Remove(PROPERTY_VALUE);
+
+ field = context.TryPolicyAliasPath(field, out var aliasPath) ? TrimFieldName(context, aliasPath) : field;
condition.Add(PROPERTY_FIELD, field);
}
}
- // Handle runtime token
- else if (tokens.HasPolicyRuntimeTokens())
+ else if (tokens.ConsumeFunction("if") &&
+ tokens.TryTokenType(ExpressionTokenType.GroupStart, out _))
{
- var value = VisitRuntimeTokens(context, tokens);
- if (value != null)
- condition.ReplaceProperty(PROPERTY_VALUE, value);
+ var orginal = condition;
+
+ // Condition
+ var leftCondition = VisitFieldTokens(context, new JObject(), tokens);
+ var rightCondition = ReverseCondition(Clone(leftCondition));
+
+ var leftEvaluation = VisitFieldTokens(context, Clone(orginal), tokens);
+ var rightEvaluation = VisitFieldTokens(context, Clone(orginal), tokens);
+
+ var left = new JObject
+ {
+ { PROPERTY_FIELD, DOT },
+ { PROPERTY_WHERE, leftCondition },
+ { PROPERTY_ALLOF, new JArray(new[] { leftEvaluation }) }
+ };
+
+ var right = new JObject
+ {
+ { PROPERTY_FIELD, DOT },
+ { PROPERTY_WHERE, rightCondition },
+ { PROPERTY_ALLOF, new JArray(new[] { rightEvaluation }) }
+ };
+
+ var result = OrCondition(left, right);
+ condition.Replace(result);
+ tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _);
+ return result;
}
+
+ else if (tokens.ConsumeFunction(PROPERTY_EQUALS) &&
+ tokens.TryTokenType(ExpressionTokenType.GroupStart, out _))
+ {
+ VisitFieldTokens(context, condition, tokens);
+ if (tokens.ConsumeString(out var s))
+ {
+ condition.Add(PROPERTY_EQUALS, s);
+ }
+
+ tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _);
+ }
+
+ else if (tokens.ConsumeFunction("empty") &&
+ tokens.TryTokenType(ExpressionTokenType.GroupStart, out _))
+ {
+ if (condition.TryBoolProperty(PROPERTY_EQUALS, out var emptyEquals))
+ {
+ condition.Remove(PROPERTY_EQUALS);
+ condition.Add("hasValue", !emptyEquals.Value);
+ }
+ VisitFieldTokens(context, condition, tokens);
+ tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _);
+ }
+ return condition;
}
private static JObject VisitRuntimeTokens(PolicyAssignmentContext context, TokenStream tokens)
@@ -1540,6 +1608,11 @@ private static JObject ReverseCondition(JObject condition)
return condition;
}
+ private static JObject Clone(JObject o)
+ {
+ return o.DeepClone() as JObject;
+ }
+
private static JObject AlwaysFail(string effect)
{
return new JObject
@@ -1564,7 +1637,7 @@ private static bool IsIfNotExistsEffect(PolicyAssignmentContext context, JObject
///
private static JObject DefaultEffectConditions(PolicyAssignmentContext context, JObject details)
{
- return AndNameCondition(details, TypeExpression(context, details));
+ return AndNameCondition(context, details, TypeExpression(context, details));
}
private static JObject TypeExpression(PolicyAssignmentContext context, JObject details)
@@ -1612,11 +1685,13 @@ private static JObject AndExistanceExpression(PolicyAssignmentContext context, J
return existenceCondition;
}
- private static JObject AndNameCondition(JObject details, JObject condition)
+ private static JObject AndNameCondition(PolicyAssignmentContext context, JObject details, JObject condition)
{
if (details == null || !details.TryStringProperty(PROPERTY_NAME, out var name))
return condition;
+ name = TemplateVisitor.ExpandString(context, name);
+
var nameCondition = new JObject {
{ PROPERTY_NAME, DOT },
{ PROPERTY_EQUALS, name }
@@ -1641,6 +1716,23 @@ private static JObject AndCondition(JObject left, JObject right)
return left == null || left.Count == 0 ? right : left;
}
+ private static JObject OrCondition(JObject left, JObject right)
+ {
+ if (left != null && left.Count > 0 && right != null && right.Count > 0)
+ {
+ var allOf = new JArray
+ {
+ left,
+ right
+ };
+ return new JObject
+ {
+ { PROPERTY_ANYOF, allOf }
+ };
+ }
+ return left == null || left.Count == 0 ? right : left;
+ }
+
private static bool TryPolicyRuleEffect(PolicyAssignmentContext context, JObject then, out string effect)
{
ResolveObject(context, then);
diff --git a/src/PSRule.Rules.Azure/Data/Template/TokenStream.cs b/src/PSRule.Rules.Azure/Data/Template/TokenStream.cs
index cf93ac7ee4b..8534ee0a02c 100644
--- a/src/PSRule.Rules.Azure/Data/Template/TokenStream.cs
+++ b/src/PSRule.Rules.Azure/Data/Template/TokenStream.cs
@@ -202,6 +202,14 @@ internal static bool ConsumeGroup(this TokenStream stream)
return true;
}
+ internal static bool HasFieldTokens(this TokenStream stream)
+ {
+ return stream.ToArray().Any(t =>
+ t.Type == ExpressionTokenType.Element &&
+ string.Equals("field", t.Content, StringComparison.OrdinalIgnoreCase)
+ );
+ }
+
internal static bool HasPolicyRuntimeTokens(this TokenStream stream)
{
return stream.ToArray().Any(t =>
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 c4484667f81..af0b4c54c15 100644
--- a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
+++ b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
@@ -41,6 +41,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/tests/PSRule.Rules.Azure.Tests/Policy.assignment.5.json b/tests/PSRule.Rules.Azure.Tests/Policy.assignment.5.json
new file mode 100644
index 00000000000..7ee4a33fa57
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/Policy.assignment.5.json
@@ -0,0 +1,273 @@
+[
+ {
+ "Identity": null,
+ "Location": null,
+ "Name": "assignment.5",
+ "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment.5",
+ "ResourceName": "assignment.5",
+ "ResourceGroupName": null,
+ "ResourceType": "Microsoft.Authorization/policyAssignments",
+ "SubscriptionId": "00000000-0000-0000-0000-000000000000",
+ "Sku": null,
+ "PolicyAssignmentId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment.5",
+ "Properties": {
+ "Scope": "/subscriptions/00000000-0000-0000-0000-000000000000",
+ "NotScopes": [],
+ "DisplayName": "Deploy Diagnostic Settings for Key Vault to Log Analytics workspace",
+ "Description": null,
+ "Metadata": {
+ "assignedBy": "",
+ "parameterScopes": {},
+ "createdBy": "",
+ "createdOn": "",
+ "updatedBy": null,
+ "updatedOn": null
+ },
+ "EnforcementMode": 1,
+ "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/bef3f64c-5290-43b7-85b0-9b254eef4c47",
+ "Parameters": {
+ "logAnalytics": {
+ "value": "test_workspace_id"
+ }
+ },
+ "NonComplianceMessages": []
+ },
+ "PolicyDefinitions": [
+ {
+ "Name": "bef3f64c-5290-43b7-85b0-9b254eef4c47",
+ "ResourceName": "bef3f64c-5290-43b7-85b0-9b254eef4c47",
+ "ResourceType": "Microsoft.Authorization/policyDefinitions",
+ "ResourceId": "/providers/Microsoft.Authorization/policyDefinitions/bef3f64c-5290-43b7-85b0-9b254eef4c47",
+ "SubscriptionId": null,
+ "Properties": {
+ "DisplayName": "Deploy Diagnostic Settings for Key Vault to Log Analytics workspace",
+ "PolicyType": 2,
+ "Mode": "Indexed",
+ "Description": "Deploys the diagnostic settings for Key Vault to stream to a regional Log Analytics workspace when any Key Vault which is missing this diagnostic settings is created or updated.",
+ "Metadata": {
+ "version": "3.0.0",
+ "category": "Monitoring"
+ },
+ "Version": "3.0.0",
+ "Parameters": {
+ "effect": {
+ "type": "String",
+ "metadata": {
+ "displayName": "Effect",
+ "description": "Enable or disable the execution of the policy"
+ },
+ "allowedValues": [
+ "DeployIfNotExists",
+ "Disabled"
+ ],
+ "defaultValue": "DeployIfNotExists"
+ },
+ "profileName": {
+ "type": "String",
+ "metadata": {
+ "displayName": "Profile name",
+ "description": "The diagnostic settings profile name"
+ },
+ "defaultValue": "setbypolicy_logAnalytics"
+ },
+ "logAnalytics": {
+ "type": "String",
+ "metadata": {
+ "displayName": "Log Analytics workspace",
+ "description": "Select Log Analytics workspace from dropdown list. If this workspace is outside of the scope of the assignment you must manually grant 'Log Analytics Contributor' permissions (or similar) to the policy assignment's principal ID.",
+ "strongType": "omsWorkspace",
+ "assignPermissions": true
+ }
+ },
+ "metricsEnabled": {
+ "type": "String",
+ "metadata": {
+ "displayName": "Enable metrics",
+ "description": "Whether to enable metrics stream to the Log Analytics workspace - True or False"
+ },
+ "allowedValues": [
+ "True",
+ "False"
+ ],
+ "defaultValue": "False"
+ },
+ "logsEnabled": {
+ "type": "String",
+ "metadata": {
+ "displayName": "Enable logs",
+ "description": "Whether to enable logs stream to the Log Analytics workspace - True or False"
+ },
+ "allowedValues": [
+ "True",
+ "False"
+ ],
+ "defaultValue": "True"
+ },
+ "matchWorkspace": {
+ "type": "Boolean",
+ "metadata": {
+ "displayName": "Workspace id must match",
+ "description": "Whether to require that the workspace of the diagnostic settings matches the workspace deployed by this policy"
+ },
+ "allowedValues": [
+ true,
+ false
+ ],
+ "defaultValue": false
+ }
+ },
+ "PolicyRule": {
+ "if": {
+ "field": "type",
+ "equals": "Microsoft.KeyVault/vaults"
+ },
+ "then": {
+ "effect": "[parameters('effect')]",
+ "details": {
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "name": "[parameters('profileName')]",
+ "evaluationDelay": "AfterProvisioning",
+ "existenceCondition": {
+ "allOf": [
+ {
+ "count": {
+ "field": "Microsoft.Insights/diagnosticSettings/logs[*]",
+ "where": {
+ "allOf": [
+ {
+ "field": "Microsoft.Insights/diagnosticSettings/logs[*].enabled",
+ "equals": "[parameters('logsEnabled')]"
+ },
+ {
+ "field": "Microsoft.Insights/diagnosticSettings/logs[*].category",
+ "equals": "AuditEvent"
+ }
+ ]
+ }
+ },
+ "greaterOrEquals": 1
+ },
+ {
+ "count": {
+ "field": "Microsoft.Insights/diagnosticSettings/metrics[*]",
+ "where": {
+ "field": "Microsoft.Insights/diagnosticSettings/metrics[*].enabled",
+ "equals": "[parameters('metricsEnabled')]"
+ }
+ },
+ "greaterOrEquals": 1
+ },
+ {
+ "anyOf": [
+ {
+ "value": "[parameters('matchWorkspace')]",
+ "equals": false
+ },
+ {
+ "field": "Microsoft.Insights/diagnosticSettings/workspaceId",
+ "equals": "[parameters('logAnalytics')]"
+ }
+ ]
+ }
+ ]
+ },
+ "roleDefinitionIds": [
+ "/providers/microsoft.authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa",
+ "/providers/microsoft.authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293"
+ ],
+ "deployment": {
+ "properties": {
+ "mode": "incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceName": {
+ "type": "string"
+ },
+ "location": {
+ "type": "string"
+ },
+ "logAnalytics": {
+ "type": "string"
+ },
+ "metricsEnabled": {
+ "type": "string"
+ },
+ "logsEnabled": {
+ "type": "string"
+ },
+ "profileName": {
+ "type": "string"
+ }
+ },
+ "variables": {},
+ "resources": [
+ {
+ "type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings",
+ "apiVersion": "2017-05-01-preview",
+ "name": "[concat(parameters('resourceName'), '/', 'Microsoft.Insights/', parameters('profileName'))]",
+ "location": "[parameters('location')]",
+ "dependsOn": [],
+ "properties": {
+ "workspaceId": "[parameters('logAnalytics')]",
+ "metrics": [
+ {
+ "category": "AllMetrics",
+ "enabled": "[parameters('metricsEnabled')]",
+ "retentionPolicy": {
+ "enabled": false,
+ "days": 0
+ }
+ }
+ ],
+ "logs": [
+ {
+ "category": "AuditEvent",
+ "enabled": "[parameters('logsEnabled')]"
+ },
+ {
+ "category": "AzurePolicyEvaluationDetails",
+ "enabled": "[parameters('logsEnabled')]"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": {}
+ },
+ "parameters": {
+ "location": {
+ "value": "[field('location')]"
+ },
+ "resourceName": {
+ "value": "[field('name')]"
+ },
+ "logAnalytics": {
+ "value": "[parameters('logAnalytics')]"
+ },
+ "metricsEnabled": {
+ "value": "[parameters('metricsEnabled')]"
+ },
+ "logsEnabled": {
+ "value": "[parameters('logsEnabled')]"
+ },
+ "profileName": {
+ "value": "[parameters('profileName')]"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "Versions": [
+ "3.0.0"
+ ]
+ },
+ "PolicyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/bef3f64c-5290-43b7-85b0-9b254eef4c47"
+ }
+ ],
+ "exemptions": []
+ }
+]
diff --git a/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs
index 4de51f8876a..4712dd5e278 100644
--- a/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs
+++ b/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs
@@ -35,7 +35,7 @@ public void GetPolicyDefinition()
var definitions = context.GetDefinitions();
Assert.NotNull(definitions);
- Assert.Equal(129, definitions.Length);
+ Assert.Equal(130, definitions.Length);
// Check category and version
var actual = definitions.FirstOrDefault(definition => definition.DefinitionId == "/providers/Microsoft.Authorization/policyDefinitions/34c877ad-507e-4c82-993e-3452a6e0ad3c");
@@ -228,6 +228,27 @@ public void GetParentAudit()
Assert.Equal(new string[] { "PSRule.Rules.Azure\\Azure.Policy.Indexed" }, actual.With);
}
+ [Fact]
+ public void ExpandDetailsName()
+ {
+ var context = new PolicyAssignmentContext(GetContext());
+ var visitor = new PolicyAssignmentDataExportVisitor();
+ foreach (var assignment in GetAssignmentData("Policy.assignment.5.json").Where(a => a["Name"].Value() == "assignment.5"))
+ visitor.Visit(context, assignment);
+
+ var definitions = context.GetDefinitions();
+ Assert.NotNull(definitions);
+ Assert.Single(definitions);
+
+ var actual = definitions.FirstOrDefault(definition => definition.DefinitionId == "/providers/Microsoft.Authorization/policyDefinitions/bef3f64c-5290-43b7-85b0-9b254eef4c47");
+ Assert.NotNull(actual);
+ Assert.Single(actual.Types);
+ Assert.Equal("Microsoft.KeyVault/vaults", actual.Types[0]);
+ Assert.Null(actual.Where);
+ Assert.Equal("{\"allOf\":[{\"type\":\".\",\"equals\":\"Microsoft.Insights/diagnosticSettings\"},{\"name\":\".\",\"equals\":\"setbypolicy_logAnalytics\"}]}", actual.Condition["where"].ToString(Formatting.None));
+ Assert.Equal(new string[] { "PSRule.Rules.Azure\\Azure.Policy.Indexed" }, actual.With);
+ }
+
#region Helper methods
private static PipelineContext GetContext(PSRuleOption option = null)
diff --git a/tests/PSRule.Rules.Azure.Tests/PolicyIgnoreDataTests.cs b/tests/PSRule.Rules.Azure.Tests/PolicyIgnoreDataTests.cs
index 3aa415dad5b..49b74af8d42 100644
--- a/tests/PSRule.Rules.Azure.Tests/PolicyIgnoreDataTests.cs
+++ b/tests/PSRule.Rules.Azure.Tests/PolicyIgnoreDataTests.cs
@@ -25,13 +25,13 @@ public void PolicyIgnoreData_contains_expected_values()
Assert.Equal(PolicyIgnoreReason.Duplicate, entry.Reason);
Assert.Contains("Azure.Defender.Storage.MalwareScan", entry.Value);
Assert.Contains("Azure.Defender.Storage", entry.Value);
- Assert.Contains("Azure.Defender.Storage.SensitiveData", entry.Value);
+ Assert.Contains("Azure.Defender.Storage.DataScan", entry.Value);
Assert.True(data.TryGetValue("/providers/Microsoft.Authorization/policyDefinitions/cfdc5972-75b3-4418-8ae1-7f5c36839390", out entry));
Assert.Equal(PolicyIgnoreReason.Duplicate, entry.Reason);
Assert.Contains("Azure.Defender.Storage.MalwareScan", entry.Value);
Assert.Contains("Azure.Defender.Storage", entry.Value);
- Assert.Contains("Azure.Defender.Storage.SensitiveData", entry.Value);
+ Assert.Contains("Azure.Defender.Storage.DataScan", entry.Value);
}
#region Helper methods