diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c0c87b2c9b..5f995cddd28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ When writing documentation in Markdown, please follow these formatting guideline Before improving rule recommendations familiarize yourself with writing [rule markdown documentation][4]. Rule documentation requires the following annotations for use with PSRule for Azure: -- `severity` - A subjective rating of the impact of a rule the solution or platform. +- `severity` - A subjective rating of the impact of a rule on the solution or platform. *NB* - the severity ratings reflect a productionised implementation, consideration should be applied for pre-production environments. Available severities are: diff --git a/docs/en/rules/Azure.ML.ComputeIdleShutdown.md b/docs/en/rules/Azure.ML.ComputeIdleShutdown.md new file mode 100644 index 00000000000..9988ec1dd52 --- /dev/null +++ b/docs/en/rules/Azure.ML.ComputeIdleShutdown.md @@ -0,0 +1,83 @@ +--- +reviewed: 2023-10-06 +severity: Critical +pillar: Cost Optimization +category: Provision +resource: ML +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ML.ComputeIdleShutdown/ +--- + +# ML Compute Idle Shutdown + +## SYNOPSIS + +Use ML - Compute Instances configured for idle shutdown. + +## DESCRIPTION + +Machine Learning uses compute instances as a training or inference compute for development and testing. It's similar to a virtual machine on the cloud. + +To avoid getting charged for a compute instance that is switched on but not being actively used, you can configure when to automatically shut down compute instances due to inactivity. + +## RECOMMENDATION + +Consider configuring ML - Compute Instances to automatically shutdown after a period of idle use as part of a broader cost optimization strategy. + +## EXAMPLES + +### Configure with Azure template + +To deploy an ML - compute instance that complies with this rule: + +- Provide a idle shutdown time as an ISO 8601 format string, i.e. 15mins = "PT15M" +- Define the "idleTimeBeforeShutdown" as this value + +For example: + +```json +{ + "type": "Microsoft.MachineLearningServices/workspaces/computes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', 'example-ws', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "managedResourceGroupId": "[subscriptionResourceId('Microsoft.Resources/resourceGroups', 'example-mg')]", + "computeType": "ComputeInstance", + "properties": { + "vmSize": "[parameters('vmSize')]", + "idleTimeBeforeShutdown": "[parameters('idleTimeBeforeShutdown')]" + } + } +} +``` + +### Configure with Bicep + +To deploy an ML - compute instance that complies with this rule: + +- Provide a idle shutdown time as an ISO 8601 format string, i.e. 15mins = 'PT15M' +- Define the "idleTimeBeforeShutdown" as this value + +For example: + +```bicep +resource aml_compute_instance 'Microsoft.MachineLearningServices/workspaces/computes@2023-04-01' ={ + name: '${mlWorkspace.name}/${name}' + location: location + properties:{ + managedResourceGroupId: managedRg.id + computeType: 'ComputeInstance' + properties: { + vmSize: vmSize + idleTimeBeforeShutdown: idleTimeBeforeShutdown // this must be a string in ISO 8601 format + } + } +} + +## LINKS + +- [Configure idle shutdown](https://learn.microsoft.com/azure/machine-learning/how-to-create-compute-instance?view=azureml-api-2&tabs=azure-cli#configure-idle-shutdown) +- [ML - Compute objects](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/workspaces/computes?pivots=deployment-language-bicep#compute-objects) +- [ML - Workspaces](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/2023-04-01/workspaces?pivots=deployment-language-bicep) +- [ML Compute](https://learn.microsoft.com/azure/machine-learning/azure-machine-learning-glossary?view=azureml-api-2#compute) +- [AI + Machine Learning cost estimates](https://learn.microsoft.com/azure/well-architected/cost/provision-ai-ml) diff --git a/docs/en/rules/Azure.ML.ComputeVnet.md b/docs/en/rules/Azure.ML.ComputeVnet.md new file mode 100644 index 00000000000..30792925c17 --- /dev/null +++ b/docs/en/rules/Azure.ML.ComputeVnet.md @@ -0,0 +1,91 @@ +--- +reviewed: 2023-10-10 +severity: Critical +pillar: Security +category: Networking +resource: ML +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ML.ComputeVnet/ +--- + +# ML Compute hosted in VNet + +## SYNOPSIS + +Azure Machine Learning Computes should be hosted in a virtual network (VNet). + +## DESCRIPTION + +Azure Virtual Networks (VNets) provide enhanced security and isolation for your Azure Machine Learning Compute Clusters and Instances, as well as subnets, access control policies, and other features to further restrict access. When a compute is configured with a virtual network, it is not publicly addressable and can only be accessed from virtual machines and applications within the virtual network. + +## RECOMMENDATION + +ML - Compute should be hosted in a virtual network (VNet) as part of a broader security strategy. + +## EXAMPLES + +### Configure with Azure template + +To deploy an ML - compute that complies with this rule: + +- update the compute properties to reference a specific subnet. + +For example: + +```json + +{ + "type": "Microsoft.MachineLearningServices/workspaces/computes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', 'example-ws', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "managedResourceGroupId": "[subscriptionResourceId('Microsoft.Resources/resourceGroups', 'example-rg')]", + "computeType": "[parameters('computeType')]", + "properties": { + "vmSize": "[parameters('vmSize')]", + "subnet": { + "id": "[parameters('subnetId')]" + } + } + } +} + +``` + +### Configure with Bicep + +To deploy an ML - compute that complies with this rule: + +- update the compute properties to reference a specific subnet. + +For example: + +```bicep + +resource aml_compute_instance 'Microsoft.MachineLearningServices/workspaces/computes@2023-04-01' ={ + name: '${mlWorkspace.name}/${name}' + location: location + + properties:{ + managedResourceGroupId: managedRg.id + computeType: ComputeType + properties: { + vmSize: vmSize + subnet:{ + id: subnet.id + } + } + } +} +``` + + +## LINKS + +- [Managed compute in a managed virtual network](https://learn.microsoft.com/azure/machine-learning/how-to-managed-network-compute?view=azureml-api-2&tabs=azure-cli) +- [ML - Network security and isolation](https://learn.microsoft.com/azure/machine-learning/concept-enterprise-security?view=azureml-api-2#network-security-and-isolation) +- [ML - Compute objects](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/workspaces/computes?pivots=deployment-language-bicep#resource-format) +- [ML - Workspaces](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/2023-04-01/workspaces?pivots=deployment-language-bicep) +- [ML Compute](https://learn.microsoft.com/azure/machine-learning/azure-machine-learning-glossary?view=azureml-api-2#compute) +- [WAF - Azure services for securing network connectivity](https://learn.microsoft.com/azure/well-architected/security/design-network-connectivity) + diff --git a/docs/en/rules/Azure.ML.DisableLocalAuth.md b/docs/en/rules/Azure.ML.DisableLocalAuth.md new file mode 100644 index 00000000000..f2e1da8cb1d --- /dev/null +++ b/docs/en/rules/Azure.ML.DisableLocalAuth.md @@ -0,0 +1,85 @@ +--- +reviewed: 2023-10-10 +severity: Critical +pillar: Security +category: Identity and Access Management +resource: ML +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ML.DisableLocalAuth/ +--- + +# ML Compute has local authentication disabled + +## SYNOPSIS + +Azure Machine Learning compute resources should have local authentication methods disabled. + +## DESCRIPTION + +Disabling local authentication methods improves security by ensuring that Machine Learning Computes require Azure Active Directory identities exclusively for authentication. + +## RECOMMENDATION + +ML - Compute should be configured with local authentication disabled as part of a broader security strategy. + +## EXAMPLES + +### Configure with Azure template + +To deploy an ML - compute that complies with this rule: + +- Set the `disableLocalAuth` property value to true. + +For example: + +```json + +{ + "type": "Microsoft.MachineLearningServices/workspaces/computes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', 'example-ws', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "managedResourceGroupId": "[subscriptionResourceId('Microsoft.Resources/resourceGroups', 'example-rg')]", + "computeType": "[parameters('computeType')]", + "disableLocalAuth": true, + "properties": { + "vmSize": "[parameters('vmSize')]", + } + } +} + +``` + +### Configure with Bicep + +To deploy an ML - compute that complies with this rule: + +- Set the `disableLocalAuth` property value to `true`. + +For example: + +```bicep + +resource aml_compute_instance 'Microsoft.MachineLearningServices/workspaces/computes@2023-04-01' ={ + name: '${mlWorkspace.name}/${name}' + location: location + + properties:{ + managedResourceGroupId: managedRg.id + computeType: ComputeType + disableLocalAuth: true + properties: { + vmSize: vmSize + } + } +} +``` + +## LINKS + +- [Disable local authentication](https://learn.microsoft.com/azure/machine-learning/how-to-integrate-azure-policy?view=azureml-api-2#disable-local-authentication) +- [ML - Compute objects](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/workspaces/computes?pivots=deployment-language-bicep#resource-format) +- [ML - Workspaces](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/2023-04-01/workspaces?pivots=deployment-language-bicep) +- [ML Compute](https://learn.microsoft.com/azure/machine-learning/azure-machine-learning-glossary?view=azureml-api-2#compute) +- [Azure Policy Regulatory Compliance controls for Azure Machine Learning](https://learn.microsoft.com/azure/machine-learning/security-controls-policy?view=azureml-api-2) +- [WAF - Authentication with Azure AD](https://learn.microsoft.com/azure/well-architected/security/design-identity-authentication) diff --git a/docs/en/rules/Azure.ML.WrkspPublicAccess.md b/docs/en/rules/Azure.ML.WrkspPublicAccess.md new file mode 100644 index 00000000000..e33b4715ced --- /dev/null +++ b/docs/en/rules/Azure.ML.WrkspPublicAccess.md @@ -0,0 +1,95 @@ +--- +reviewed: 2023-10-12 +severity: Critical +pillar: Security +category: Networking +resource: ML +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ML.WrkspPublicAccess/ +--- + +# ML Workspace has public access disabled + +## SYNOPSIS + +Disable public network access from a ML - Workspace. + +## DESCRIPTION + +Disabling public network access improves security by ensuring that the Machine Learning Workspaces aren't exposed on the public internet. You can control exposure of your workspaces by creating private endpoints instead. + +## RECOMMENDATION + +Consider setting the 'publicNetworkAccess' parameter of the Workspace properties to "Disabled", as part of a broader security strategy. + +## EXAMPLES + +### Configure with Azure template + +To deploy an ML - Workspace that complies with this rule: + +- update the 'publicNetworkAccess' parameter of the Workspace properties to "Disabled". + +For example: + +```json +{ + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "sku": { + "name": "basic", + "tier": "basic" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('name')]", + "keyVault": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]", + "storageAccount": "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]", + "applicationInsights": "[resourceId('Microsoft.Insights/components', parameters('AppInsightsName'))]", + "containerRegistry": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('ContainerRegistryName'))]", + "publicNetworkAccess": "Disabled" + } + +``` + +### Configure with Bicep + +To deploy an ML - Workspace that complies with this rule: + +- update the 'publicNetworkAccess' parameter of the Workspace properties to 'Disabled'. + +For example: + +```bicep +resource Ml_Workspace 'Microsoft.MachineLearningServices/workspaces@2023-04-01' = { + name: name + location: location + sku: { + name: 'basic' + tier: 'basic' + } + identity: { + type: 'SystemAssigned' + } + properties: { + friendlyName: friendlyName + keyVault: KeyVault.id + storageAccount: StorageAccount.id + applicationInsights: AppInsights.id + containerRegistry: ContainerRegistry.id + publicNetworkAccess: 'Disabled' + } +} + +``` + +## LINKS + +- [Configure a private endpoint for an Azure Machine Learning workspace](https://learn.microsoft.com/azure/machine-learning/how-to-configure-private-link?view=azureml-api-2&tabs=cli) +- [ML - Public access to Workspaces](https://learn.microsoft.com/azure/machine-learning/how-to-secure-workspace-vnet?view=azureml-api-2&tabs=required%2Cpe%2Ccli#public-access-to-workspace) +- [ML - Workspaces](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/workspaces?pivots=deployment-language-bicep#workspaceproperties) +- [Security and governance for ML](https://learn.microsoft.com/azure/machine-learning/concept-enterprise-security?view=azureml-api-2) +- [WAF - Azure services for securing network connectivity](https://learn.microsoft.com/azure/well-architected/security/design-network-connectivity) diff --git a/docs/en/rules/Azure.ML.WrkspUserMgId.md b/docs/en/rules/Azure.ML.WrkspUserMgId.md new file mode 100644 index 00000000000..57c21106dab --- /dev/null +++ b/docs/en/rules/Azure.ML.WrkspUserMgId.md @@ -0,0 +1,108 @@ +--- +reviewed: 2022-10-13 +severity: Important +pillar: Security +category: Identity and Access Management +resource: ML +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ML.WrkspUserMgId/ +--- + +# Azure Machine Learning workspaces should use user-assigned managed identity + +## SYNOPSIS + +ML workspaces should use user-assigned managed identity, rather than the default system-assigned managed identity. + +## DESCRIPTION + +Manange access to Azure ML workspace and associated resources, Azure Container Registry, KeyVault, Storage, and App Insights using user-assigned managed identity. By default, system-assigned managed identity is used by Azure ML workspace to access the associated resources. User-assigned managed identity allows you to create the identity as an Azure resource and maintain the life cycle of that identity. + +## RECOMMENDATION + +ML - Compute should be configured to use a user-assigned managed identity, as part of a broader security and lifecycle management strategy. + +## EXAMPLES + +### Configure with Azure template + +To deploy an ML - Workspace that complies with this rule: + +- Set the `identity` section; + - Change `Type` to "UserAssigned" + - Add in the 'userAssignedIdentities' field with the Managed Identity reference +- Set the `primaryUserAssignedIdentity` property value to the User Assigned Managed Identity + +For example: + +```json + +{ + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "sku": { + "name": "basic", + "tier": "basic" + }, + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": {"subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UserAssignedManagedIdentity": {} + } + }, + "properties": { + "friendlyName": "[parameters('name')]", + "keyVault": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]", + "storageAccount": "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]", + "applicationInsights": "[resourceId('Microsoft.Insights/components', parameters('AppInsightsName'))]", + "containerRegistry": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('ContainerRegistryName'))]", + "primaryUserAssignedIdentity": "[parameters('UserAssignedManagedIdentity')]" + } +} +``` + +### Configure with Bicep + +To deploy an ML - Workspace that complies with this rule: + +- Set the `identity` section; + - Change `Type` to "UserAssigned" + - Add in the 'userAssignedIdentities' field with the Managed Identity reference +- Set the `primaryUserAssignedIdentity` property value to the User Assigned Managed Identity + +For example: + +```bicep + +resource Ml_Workspace 'Microsoft.MachineLearningServices/workspaces@2023-04-01' = { + name: name + location: location + sku: { + name: 'basic' + tier: 'basic' + } + identity: { + type: 'UserAssigned', + "userAssignedIdentities": { + UserAssignedManagedIdentity.id: {} + } + }, + properties: { + friendlyName: friendlyName + keyVault: KeyVault.id + storageAccount: StorageAccount.id + applicationInsights: AppInsights.id + containerRegistry: ContainerRegistry.id + primaryUserAssignedIdentity: UserAssignedManagedIdentity + } +} +``` + + +## LINKS + +- [Set up authentication between Azure Machine Learning and other services](https://learn.microsoft.com/azure/machine-learning/how-to-identity-based-service-authentication?view=azureml-api-2&tabs=python) +- [ML - Workspaces](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/workspaces?pivots=deployment-language-bicep#workspaceproperties) +- [Azure Policy Regulatory Compliance controls for Azure Machine Learning](https://learn.microsoft.com/azure/machine-learning/security-controls-policy?view=azureml-api-2) +- [WAF - Authentication with Azure AD](https://learn.microsoft.com/azure/well-architected/security/design-identity-authentication) + diff --git a/docs/en/rules/Azure.ML.WrkspVnetPubAccess.md b/docs/en/rules/Azure.ML.WrkspVnetPubAccess.md new file mode 100644 index 00000000000..f180277f251 --- /dev/null +++ b/docs/en/rules/Azure.ML.WrkspVnetPubAccess.md @@ -0,0 +1,93 @@ +--- +reviewed: 2023-10-12 +severity: Critical +pillar: Security +category: Networking +resource: ML +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ML.WrkspVnetPubAccess/ +--- + +# ML Workspace has public access disabled behind a VNet + +## SYNOPSIS + +Disable public network access from a ML - Workspace when behind a VNet. + +## DESCRIPTION + +Disable public network access from a ML - Workspace when behind a VNet. + +## RECOMMENDATION + +Consider setting the 'allowPublicAccessWhenBehindVnet' parameter of the Workspace properties to false, as part of a broader security strategy. + +## EXAMPLES + +### Configure with Azure template + +To deploy an ML - Workspace that complies with this rule: + +- update the 'allowPublicAccessWhenBehindVnet' parameter of the Workspace properties to false. + +For example: + +```json +{ + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "sku": { + "name": "basic", + "tier": "basic" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('name')]", + "keyVault": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]", + "storageAccount": "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]", + "applicationInsights": "[resourceId('Microsoft.Insights/components', parameters('AppInsightsName'))]", + "containerRegistry": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('ContainerRegistryName'))]", + "allowPublicAccessWhenBehindVnet": false + } +} +``` + +### Configure with Bicep + +To deploy an ML - Workspace that complies with this rule: + +- update the 'allowPublicAccessWhenBehindVnet' parameter of the Workspace properties to false. + +For example: + +```bicep +resource Ml_Workspace 'Microsoft.MachineLearningServices/workspaces@2023-04-01' = { + name: name + location: location + sku: { + name: 'basic' + tier: 'basic' + } + identity: { + type: 'SystemAssigned' + } + properties: { + friendlyName: friendlyName + keyVault: KeyVault.id + storageAccount: StorageAccount.id + applicationInsights: AppInsights.id + containerRegistry: ContainerRegistry.id + allowPublicAccessWhenBehindVnet: false + } +} + +``` + +## LINKS +- [ML - Public access to Workspaces](https://learn.microsoft.com/azure/machine-learning/how-to-secure-workspace-vnet?view=azureml-api-2&tabs=required%2Cpe%2Ccli#public-access-to-workspace) +- [ML - Workspaces](https://learn.microsoft.com/azure/templates/microsoft.machinelearningservices/workspaces?pivots=deployment-language-bicep#workspaceproperties) +- [Security and governance for ML](https://learn.microsoft.com/azure/machine-learning/concept-enterprise-security?view=azureml-api-2) +- [WAF - Azure services for securing network connectivity](https://learn.microsoft.com/azure/well-architected/security/design-network-connectivity) diff --git a/docs/examples-ML.bicep b/docs/examples-ML.bicep new file mode 100644 index 00000000000..d46d457e776 --- /dev/null +++ b/docs/examples-ML.bicep @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Bicep documentation examples + +@description('The name of the ML - Compute Instance.') +param name string + +@description('The location resources will be deployed.') +param location string = resourceGroup().location + +@description('The VM SKU to be deployed.') +param vmSize string = 'STANDARD_D2_V2' + +@description('The Idle time before shutdown, as a string in ISO 8601 format.') +// this must be a string in ISO 8601 format +param idleTimeBeforeShutdown string = 'PT15M' + +resource managedRg 'Microsoft.Resources/resourceGroups@2022-09-01' existing = { + scope: subscription() + name: 'psrule' +} + +resource mlWorkspace 'Microsoft.MachineLearningServices/workspaces@2023-04-01' existing = { + name: 'example-ws' +} + +resource aml_compute_instance 'Microsoft.MachineLearningServices/workspaces/computes@2023-04-01' ={ + name: '${mlWorkspace.name}/${name}' + location: location + properties:{ + managedResourceGroupId: managedRg.id + computeType: 'ComputeInstance' + properties: { + vmSize: vmSize + idleTimeBeforeShutdown: idleTimeBeforeShutdown + } + } +} diff --git a/src/PSRule.Rules.Azure/rules/Azure.ML.Rule.yaml b/src/PSRule.Rules.Azure/rules/Azure.ML.Rule.yaml index 530a03f7ec7..6634fdc437a 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.ML.Rule.yaml +++ b/src/PSRule.Rules.Azure/rules/Azure.ML.Rule.yaml @@ -4,3 +4,119 @@ # # Validation rules for Azure Machine Learning # + +--- +# Synopsis: ML Compute Instances have idle shutdown enabled +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.ML.ComputeIdleShutdown + ref: AZR-000403 + tags: + release: GA + ruleSet: 2023_10 + Azure.WAF/pillar: 'Cost Optimization' +spec: + type: + - Microsoft.MachineLearningServices/workspaces/computes + condition: + allOf: + - field: properties.computeType + equals: ComputeInstance + - field: properties.properties.idleTimeBeforeShutdown + exists: true +--- +# Synopsis: ML Compute has local authentication disabled +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.ML.DisableLocalAuth + ref: AZR-000404 + tags: + release: GA + ruleSet: 2023_10 + Azure.WAF/pillar: Security +spec: + type: + - Microsoft.MachineLearningServices/workspaces/computes + condition: + field: properties.disableLocalAuth + equals: true + +--- +# Synopsis: ML Compute should be in a virtual network +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.ML.ComputeVnet + ref: AZR-000405 + tags: + release: GA + ruleSet: 2023_10 + Azure.WAF/pillar: Security +spec: + type: + - Microsoft.MachineLearningServices/workspaces/computes + condition: + field: properties.properties.subnet + exists: true + +--- +# Synopsis: Disable public network access from a ML Workspace when behind vnet +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.ML.WrkspVnetPubAccess + ref: AZR-000406 + tags: + release: GA + ruleSet: 2023_10 + Azure.WAF/pillar: Security +spec: + type: + - Microsoft.MachineLearningServices/workspaces + condition: + allOf: + - field: properties.allowPublicAccessWhenBehindVnet + exists: true + - field: properties.allowPublicAccessWhenBehindVnet + equals: false + +--- +# Synopsis: Disable public network access from a ML Workspace +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.ML.WrkspPublicAccess + ref: AZR-000407 + tags: + release: GA + ruleSet: 2023_10 + Azure.WAF/pillar: Security +spec: + type: + - Microsoft.MachineLearningServices/workspaces + condition: + field: properties.publicNetworkAccess + equals: "Disabled" + +--- +# Synopsis: ML Workspaces should use user-assigned managed identity +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.ML.WrkspUserMgId + ref: AZR-000408 + tags: + release: GA + ruleSet: 2023_10 + Azure.WAF/pillar: Security +spec: + type: + - Microsoft.MachineLearningServices/workspaces + condition: + allOf: + - field: properties.primaryUserAssignedIdentity + equals: "UserAssignedManagedIdentity" + - field: identity.userAssignedIdentities + exists: true diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.ML.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.ML.Tests.ps1 index 4ee95f2b342..c76e8ac2e59 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.ML.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.ML.Tests.ps1 @@ -2,5 +2,135 @@ # Licensed under the MIT License. # -# Unit tests for Azure Machine Learning rules +# Unit tests for Azure ML rules # + +[CmdletBinding()] +param () + +BeforeAll { + # Setup error handling + $ErrorActionPreference = 'Stop'; + Set-StrictMode -Version latest; + + if ($Env:SYSTEM_DEBUG -eq 'true') { + $VerbosePreference = 'Continue'; + } + + # Setup tests paths + $rootPath = $PWD; + Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Azure) -Force; + $here = (Resolve-Path $PSScriptRoot).Path; +} + +Describe 'Azure.ML' -Tag 'ML' { + Context 'Conditions' { + BeforeAll { + $invokeParams = @{ + Baseline = 'Azure.All' + Module = 'PSRule.Rules.Azure' + WarningAction = 'Ignore' + ErrorAction = 'Stop' + } + $dataPath = Join-Path -Path $here -ChildPath 'Resources.ML.json'; + $result = Invoke-PSRule @invokeParams -InputPath $dataPath; + } + + It 'Azure.ML.ComputeIdleShutdown' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ML.ComputeIdleShutdown' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'mlcomp-b'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'mlcomp-a'; + } + + It 'Azure.ML.DisableLocalAuth' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ML.DisableLocalAuth' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'mlcomp-b'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'mlcomp-a'; + } + + It 'Azure.ML.ComputeVnet' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ML.ComputeVnet' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'mlcomp-b'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'mlcomp-a'; + } + + It 'Azure.ML.WrkspVnetPubAccess' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ML.WrkspVnetPubAccess' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'ml-wks-b'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'ml-wks-a'; + } + + It 'Azure.ML.WrkspPublicAccess' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ML.WrkspPublicAccess' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'ml-wks-b'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'ml-wks-a'; + } + + It 'Azure.ML.WrkspUserMgId' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ML.WrkspUserMgId' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'ml-wks-b'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'ml-wks-a'; + } + + } +} diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.ML.json b/tests/PSRule.Rules.Azure.Tests/Resources.ML.json new file mode 100644 index 00000000000..99c23da529f --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Resources.ML.json @@ -0,0 +1,88 @@ +[ + { + "type": "Microsoft.MachineLearningServices/workspaces/computes", + "apiVersion": "2023-04-01", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.MachineLearningServices/workspaces/test-ws/computes/mlcomp-a", + "name": "mlcomp-a", + "location": "westus", + "tags": { + "application": "ML" + }, + "properties": { + "managedResourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg", + "computeType": "ComputeInstance", + "disableLocalAuth": true, + "properties": { + "vmSize": "STANDARD_D2_V2", + "idleTimeBeforeShutdown": "PT15M", + "subnet":{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/test-sub" + } + } + } + }, + { + "type": "Microsoft.MachineLearningServices/workspaces/computes", + "apiVersion": "2023-04-01", + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.MachineLearningServices/workspaces/test-ws/computes/mlcomp-b", + "name": "mlcomp-b", + "location": "westus", + "tags": { + "application": "ML" + }, + "properties": { + "managedResourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg", + "computeType": "ComputeInstance", + "properties": { + "vmSize": "STANDARD_D2_V2" + } + } + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-04-01", + "name": "ml-wks-a", + "location": "westus", + "tags": { + "application": "ML" + }, + "sku": { + "name": "Basic", + "tier": "Basic" + }, + "kind": "Default", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UserAssignedManagedIdentity": {} + } + }, + "properties": { + "friendlyName": "WorkspaceFriendlyName", + "allowPublicAccessWhenBehindVnet": false, + "primaryUserAssignedIdentity": "UserAssignedManagedIdentity", + "publicNetworkAccess": "Disabled" + } + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-04-01", + "name": "ml-wks-b", + "location": "westus", + "tags": { + "application": "ML" + }, + "sku": { + "name": "Basic", + "tier": "Basic" + }, + "kind": "Default", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "WorkspaceFriendlyName", + "publicNetworkAccess": "Enabled" + } + } +]