diff --git a/.vscode/markdown.code-snippets b/.vscode/markdown.code-snippets index 72da48c55e..96d5cd2610 100644 --- a/.vscode/markdown.code-snippets +++ b/.vscode/markdown.code-snippets @@ -5,7 +5,7 @@ "description": "Azure rule documentation", "body": [ "---", - "reviewed: 2022-mm-dd", + "reviewed: 2023-mm-dd", "severity: ${4:severity}", "pillar: ${5:pillar}", "category: ${6:category}", diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 9965f8a276..43ebf0b5e6 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -31,6 +31,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since v1.30.3: - New rules: + - Deployment: + - Check parameters potentially containing secure values by @BernieWhite. + [#1476](https://github.com/Azure/PSRule.Rules.Azure/issues/1476) - Machine Learning: - Check compute instances are configured for an idle shutdown by @batemansogq. [#2484](https://github.com/Azure/PSRule.Rules.Azure/issues/2484) diff --git a/docs/en/rules/Azure.Deployment.SecureParameter.md b/docs/en/rules/Azure.Deployment.SecureParameter.md new file mode 100644 index 0000000000..10e1d070ec --- /dev/null +++ b/docs/en/rules/Azure.Deployment.SecureParameter.md @@ -0,0 +1,94 @@ +--- +reviewed: 2023-10-25 +severity: Critical +pillar: Security +category: Infrastructure provisioning +resource: Deployment +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Deployment.SecureParameter/ +--- + +# Use secure parameters for sensitive information + +## SYNOPSIS + +Use secure parameters for any parameter that contains sensitive information. + +## DESCRIPTION + +Azure Bicep and Azure Resource Manager (ARM) templates can be used to deploy resources to Azure. +When deploying Azure resources, sensitive values such as passwords, certificates, and keys should be passed as secure parameters. +Secure parameters use the `secureString` or `secureObject` type. + +Parameters that do not use secure types are recorded in logs and deployment history. +These values can be retrieved by anyone with access to the deployment history. + +## RECOMMENDATION + +Consider using secure parameters for parameters that contain sensitive information. + +## EXAMPLES + +### Configure with Azure template + +To configure deployments that pass this rule: + +- Set the type of sensitive parameters to `secureString` or `secureObject`. + +For example: + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "secret": { + "type": "secureString" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "keyvault/good", + "properties": { + "value": "[parameters('secret')]" + } + } + ] +} +``` + +### Configure with Bicep + +To configure deployments that pass this rule: + +- Add the `@secure()` attribute on sensitive parameters. + +For example: + +```bicep +@secure() +param secret string + +resource goodSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: vault + name: 'good' + properties: { + value: secret + } +} +``` + +## NOTES + +This rule uses a heuristics to determine if a parameter should use a secure type: + +- Any parameter with a name containing `password`, `secret`, or `token` will be considered sensitive. +- Any parameter with a name ending in `key` or `keys` will be considered sensitive. +- Any parameter with a name ending in `publickey` or `publickeys` will not be considered sensitive. + +## LINKS + +- [Infrastructure provisioning considerations in Azure](https://learn.microsoft.com/azure/architecture/framework/security/deploy-infrastructure) +- [Use Azure Key Vault to pass secure parameter value during Bicep deployment](https://learn.microsoft.com/azure/azure-resource-manager/bicep/key-vault-parameter) +- [Integrate Azure Key Vault in your ARM template deployment](https://learn.microsoft.com/azure/azure-resource-manager/templates/template-tutorial-use-key-vault#edit-the-parameters-file) diff --git a/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 b/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 index aa344f5159..69969a23c4 100644 --- a/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 +++ b/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 @@ -106,4 +106,5 @@ SubStorageSensitiveDataThreatDetection = "The Microsoft Defender for Storage plan should have sensitive data threat detection configured." ResStorageSensitiveDataThreatDetection = "The storage account '{0}' should have sensitive data threat detection in Microsoft Defender for Storage configured." ResAPIDefender = "The API '{0}' should be onboarded to Microsoft Defender for APIs." + InsecureParameterType = "The parameter '{0}' with type '{1}' is not secure." } diff --git a/src/PSRule.Rules.Azure/rules/Azure.Deployment.Rule.ps1 b/src/PSRule.Rules.Azure/rules/Azure.Deployment.Rule.ps1 index c47a0ac251..09c0c85b51 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.Deployment.Rule.ps1 +++ b/src/PSRule.Rules.Azure/rules/Azure.Deployment.Rule.ps1 @@ -17,6 +17,11 @@ Rule 'Azure.Deployment.AdminUsername' -Ref 'AZR-000284' -Type 'Microsoft.Resourc RecurseDeploymentSensitive -Deployment $TargetObject } +# Synopsis: Use secure parameters for any parameter that contains sensitive information. +Rule 'Azure.Deployment.SecureParameter' -Ref 'AZR-000408' -Type 'Microsoft.Resources/deployments' -Tag @{ release = 'GA'; ruleSet = '2023_12'; 'Azure.WAF/pillar' = 'Security'; } { + GetSecureParameter -Deployment $TargetObject +} + # Synopsis: Use secure parameters for setting properties of resources that contain sensitive information. Rule 'Azure.Deployment.SecureValue' -Ref 'AZR-000316' -Type 'Microsoft.Resources/deployments' -Tag @{ release = 'GA'; ruleSet = '2022_12'; 'Azure.WAF/pillar' = 'Security'; } { RecurseSecureValue -Deployment $TargetObject @@ -51,6 +56,38 @@ Rule 'Azure.Deployment.OuterSecret' -Ref 'AZR-000331' -Type 'Microsoft.Resources #region Helpers +function global:GetSecureParameter { + param ( + [Parameter(Mandatory = $True)] + [PSObject]$Deployment + ) + process { + $count = 0; + if ($Null -ne $Deployment.properties.template.parameters.PSObject.properties) { + foreach ($parameter in $Deployment.properties.template.parameters.PSObject.properties.GetEnumerator()) { + if ( + $Assert.Like($parameter, 'Name', @( + '*password*' + '*secret*' + '*token*' + '*key' + '*keys' + )).Result -and + $parameter.Name -notlike '*publickey' -and + $parameter.Name -notlike '*publickeys' -and + $Null -ne $parameter.Value.type + ) { + $count++ + $Assert.In($parameter.Value.type, '.', @('secureString', 'secureObject')).ReasonFrom($parameter.Name, $LocalizedData.InsecureParameterType, $parameter.Name, $parameter.Value.type); + } + } + } + if ($count -eq 0) { + return $Assert.Pass(); + } + } +} + function global:RecurseDeploymentSensitive { param ( [Parameter(Mandatory = $True)] @@ -285,6 +322,7 @@ function global:RecurseSecureValue { 'Microsoft.Network/Connections' { CheckPropertyUsesSecureParameter -Resource $resource -SecureParameters $secureParameters -PropertyPath "properties.virtualNetworkGateway1.properties.vpnClientConfiguration.radiusServerSecret" CheckPropertyUsesSecureParameter -Resource $resource -SecureParameters $secureParameters -PropertyPath "properties.virtualNetworkGateway2.properties.vpnClientConfiguration.radiusServerSecret" + CheckPropertyUsesSecureParameter -Resource $resource -SecureParameters $secureParameters -PropertyPath "properties.sharedKey" } 'Microsoft.Network/VirtualNetworkGateways' { CheckPropertyUsesSecureParameter -Resource $resource -SecureParameters $secureParameters -PropertyPath "properties.vpnClientConfiguration.radiusServerSecret" diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.Deployment.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.Deployment.Tests.ps1 index 8b22f10e6d..fade7b564a 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.Deployment.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.Deployment.Tests.ps1 @@ -78,6 +78,36 @@ Describe 'Azure.Deployment' -Tag 'Deployment' { } } +Describe 'Azure.Deployment' -Tag 'Deployment' { + Context 'Conditions' { + BeforeAll { + $invokeParams = @{ + Baseline = 'Azure.All' + Module = 'PSRule.Rules.Azure' + WarningAction = 'SilentlyContinue' + ErrorAction = 'Stop' + } + } + + It 'Azure.Deployment.SecureParameter' { + $sourcePath = Join-Path -Path $here -ChildPath 'Resources.Deployments.json'; + $result = Invoke-PSRule @invokeParams -InputPath $sourcePath -Name 'Azure.Deployment.SecureParameter'; + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.Deployment.SecureParameter' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + # $ruleResult.Length | Should -Be 2; + $ruleResult.TargetName | Should -BeIn 'nestedDeployment-I'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + # $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -BeIn 'nestedDeployment-A', 'nestedDeployment-B', 'nestedDeployment-C', 'nestedDeployment-D', 'nestedDeployment-E', 'nestedDeployment-F', 'nestedDeployment-G', 'nestedDeployment-H', 'nestedDeployment-J'; + } + } +} Describe 'Azure.Deployment.AdminUsername' -Tag 'Deployment' { Context 'Conditions' { @@ -104,8 +134,8 @@ Describe 'Azure.Deployment.AdminUsername' -Tag 'Deployment' { # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 5; - $ruleResult.TargetName | Should -BeIn 'nestedDeployment-B', 'nestedDeployment-C', 'nestedDeployment-F', 'nestedDeployment-G', 'nestedDeployment-H'; + $ruleResult.Length | Should -Be 7; + $ruleResult.TargetName | Should -BeIn 'nestedDeployment-B', 'nestedDeployment-C', 'nestedDeployment-F', 'nestedDeployment-G', 'nestedDeployment-H', 'nestedDeployment-I', 'nestedDeployment-J'; } } diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.Deployments.json b/tests/PSRule.Rules.Azure.Tests/Resources.Deployments.json index 171e93b5e2..e05a1c86a0 100644 --- a/tests/PSRule.Rules.Azure.Tests/Resources.Deployments.json +++ b/tests/PSRule.Rules.Azure.Tests/Resources.Deployments.json @@ -1,240 +1,304 @@ [ - { - "name": "nestedDeployment-A", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.Sql/servers", - "apiVersion": "2022-02-01-preview", - "name": "sql-example", - "location": "australiaeast", - "properties": { - "administratorLogin": "admin" - } - } - ] + { + "name": "nestedDeployment-A", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-02-01-preview", + "name": "sql-example", + "location": "australiaeast", + "properties": { + "administratorLogin": "admin" } - } - }, - { - "name": "nestedDeployment-B", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUser": { - "type": "SecureString" - } - }, - "variables": {}, - "resources": [ - { - "type": "Microsoft.Sql/servers", - "apiVersion": "2022-02-01-preview", - "name": "sql-example", - "location": "australiaeast", - "properties": { - "administratorLogin": "[parameters('adminUser')]" - } - } - ] - } - } - }, - { - "name": "nestedDeployment-C", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUser": { - "type": "SecureString" - } - }, - "variables": {}, - "resources": [ - { - "apiVersion": "2019-12-01", - "type": "Microsoft.Compute/virtualMachines", - "name": "vm-example", - "location": "australiaeast", - "properties": { - "osProfile": { - "computerName": "vm-example", - "adminUsername": "[parameters('adminUser')]" - } - } - } - ] - } - } - }, - { - "name": "nestedDeployment-D", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUser": { - "type": "SecureString" - } - }, - "variables": {}, - "resources": [ - { - "apiVersion": "2019-12-01", - "type": "Microsoft.Compute/virtualMachines", - "name": "vm-example", - "location": "australiaeast", - "properties": { - "osProfile": { - "computerName": "vm-example", - "adminUsername": "username" - } - } - } - ] + } + ] + } + } + }, + { + "name": "nestedDeployment-B", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminUser": { + "type": "SecureString" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-02-01-preview", + "name": "sql-example", + "location": "australiaeast", + "properties": { + "administratorLogin": "[parameters('adminUser')]" } - } - }, - { - "name": "nestedDeployment-E", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": { - "adminUsername": "sensitive" - }, - "resources": [ - { - "apiVersion": "2019-12-01", - "type": "Microsoft.Compute/virtualMachines", - "name": "vm-example", - "location": "australiaeast", - "properties": { - "osProfile": { - "computerName": "vm-example", - "adminUsername": "[variables('adminUsername')]" - } - } - } - ] + } + ] + } + } + }, + { + "name": "nestedDeployment-C", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminUser": { + "type": "SecureString" + } + }, + "variables": {}, + "resources": [ + { + "apiVersion": "2019-12-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "vm-example", + "location": "australiaeast", + "properties": { + "osProfile": { + "computerName": "vm-example", + "adminUsername": "[parameters('adminUser')]" + } } - } - }, - { - "name": "nestedDeployment-F", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [] + } + ] + } + } + }, + { + "name": "nestedDeployment-D", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminUser": { + "type": "SecureString" + } + }, + "variables": {}, + "resources": [ + { + "apiVersion": "2019-12-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "vm-example", + "location": "australiaeast", + "properties": { + "osProfile": { + "computerName": "vm-example", + "adminUsername": "username" + } } - } - }, - { - "name": "nestedDeployment-G", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "apiVersion": "2019-12-01", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "name": "vm-example", - "location": "australiaeast", - "properties": {} - } - ] + } + ] + } + } + }, + { + "name": "nestedDeployment-E", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": { + "adminUsername": "sensitive" + }, + "resources": [ + { + "apiVersion": "2019-12-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "vm-example", + "location": "australiaeast", + "properties": { + "osProfile": { + "computerName": "vm-example", + "adminUsername": "[variables('adminUsername')]" + } } - } - }, - { - "name": "nestedDeployment-H", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "apiVersion": "2019-12-01", - "type": "Microsoft.Compute/virtualMachines", - "name": "vm-example", - "location": "australiaeast", - "properties": { - "osProfile": { - "computerName": "vm-example", - "adminUsername": "[if(true(), null(), parameters('adminUser'))]" - } - } - } - ] + } + ] + } + } + }, + { + "name": "nestedDeployment-F", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [] + } + } + }, + { + "name": "nestedDeployment-G", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "apiVersion": "2019-12-01", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "vm-example", + "location": "australiaeast", + "properties": {} + } + ] + } + } + }, + { + "name": "nestedDeployment-H", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "apiVersion": "2019-12-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "vm-example", + "location": "australiaeast", + "properties": { + "osProfile": { + "computerName": "vm-example", + "adminUsername": "[if(true(), null(), parameters('adminUser'))]" + } } - } + } + ] + } + } + }, + { + "name": "nestedDeployment-I", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "adminUser": { + "type": "string" + }, + "secretValue": { + "type": "string" + } + }, + "variables": {}, + "resources": [] + }, + "parameters": {} + } + }, + { + "name": "nestedDeployment-J", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "SecureString" + }, + "privateKey": { + "type": "SecureString" + }, + "adminUser": { + "type": "SecureString" + }, + "secretValue": { + "type": "SecureString" + } + }, + "variables": {}, + "resources": [] + }, + "parameters": {} } + } ]