diff --git a/infra/POLICIES.md b/infra/POLICIES.md new file mode 100644 index 0000000..7eae594 --- /dev/null +++ b/infra/POLICIES.md @@ -0,0 +1,182 @@ +# Azure Policy Governance for AI Resources + +This document describes the Azure Policy controls implemented to govern Azure Machine Learning and AI Foundry (Cognitive Services) resources. + +## Overview + +The AI governance policies enforce security and compliance standards across your AI infrastructure. Policies are organized into five control categories: + +| Category | Description | Policies | +|----------|-------------|----------| +| **Network Security** | Ensure private connectivity and network isolation | 4 | +| **Identity Management** | Enforce Azure AD authentication, disable local auth | 2 | +| **Data Protection** | Require customer-managed key encryption | 2 | +| **Model Governance** | Control which AI models can be deployed | 2 | +| **Logging & Monitoring** | Ensure diagnostic logging is enabled | 1 | + +--- + +## Policy Controls + +### Network Security + +| Policy | Description | Effect | Built-in ID | +|--------|-------------|--------|-------------| +| **Azure ML Workspaces should disable public network access** | Ensures ML workspaces are not exposed on the public internet | Audit/Deny | `438c38d2-3772-465a-a9cc-7a6666a275ce` | +| **Azure ML Workspaces should use private link** | Requires private endpoint connections for ML workspaces | Audit/Deny | `45e05259-1eb5-4f70-9574-baf73e9d219b` | +| **Azure ML Computes should be in a virtual network** | Ensures compute resources are deployed within a VNet | Audit/Deny | `7804b5c7-01dc-4723-969b-ae300cc07ff1` | +| **Azure AI Services should restrict network access** | Requires network rules to limit access to AI Services | Audit/Deny | `037eea7a-bd0a-46c5-9a66-03aea78705d3` | + +**Why it matters:** Network isolation prevents unauthorized access and data exfiltration. Private endpoints ensure traffic stays within your virtual network. + +--- + +### Identity Management + +| Policy | Description | Effect | Built-in ID | +|--------|-------------|--------|-------------| +| **Azure ML Computes should have local authentication disabled** | Requires Azure AD for ML compute authentication | Audit/Deny | `e96a9a5f-07ca-471b-9bc5-6a0f33cbd68f` | +| **Azure AI Services should have key access disabled** | Disables API key authentication, requiring Entra ID | Audit/Deny | `71ef260a-8f18-47b7-abcb-62d0673d94dc` | + +**Why it matters:** Azure AD/Entra ID provides centralized identity management, conditional access, MFA, and audit trails. API keys are shared secrets that are harder to manage and rotate. + +> ⚠️ **Note:** Disabling local auth on AI Services will prevent Azure OpenAI Studio from working in development mode. Consider using `Audit` in dev/test environments. + +--- + +### Data Protection + +| Policy | Description | Effect | Built-in ID | +|--------|-------------|--------|-------------| +| **Azure ML Workspaces should be encrypted with CMK** | Requires customer-managed keys for workspace encryption | Audit/Deny | `ba769a63-b8cc-4b2d-abf6-ac33c7204be8` | +| **Azure AI Services should encrypt data at rest with CMK** | Requires customer-managed keys for AI Services encryption | Audit/Deny | `67121cc7-ff39-4ab8-b7e3-95b84dab487d` | + +**Why it matters:** Customer-managed keys (CMK) provide full control over encryption key lifecycle, including rotation and revocation. Required for many regulatory compliance standards. + +> ℹ️ **Note:** CMK encryption requires Azure Key Vault setup and is typically only required for production workloads with specific compliance requirements. Disabled by default. + +--- + +### Model Governance + +| Policy | Description | Effect | Built-in ID | +|--------|-------------|--------|-------------| +| **Azure ML Deployments should only use approved Registry Models** | Restricts ML model deployments to an approved list | Audit/Deny | `12e5dd16-d201-47ff-849b-8454061c293d` | +| **Cognitive Services Deployments should only use approved Registry Models** | Restricts AI Services model deployments to an approved list | Audit/Deny | `aafe3651-cb78-4f68-9f81-e7e41509110f` | + +**Why it matters:** Model governance ensures only vetted and approved AI models are deployed, preventing use of untested or potentially harmful models. + +**Example allowed models:** +``` +azureml://registries/azure-openai/models/gpt-4o/versions/1 +azureml://registries/azure-openai/models/gpt-35-turbo/versions/3 +azureml://registries/azure-openai/models/text-embedding-ada-002/versions/2 +``` + +> ℹ️ **Note:** Model governance is disabled by default. Enable when you need to restrict which models can be deployed in your organization. + +--- + +### Logging & Monitoring + +| Policy | Description | Effect | Built-in ID | +|--------|-------------|--------|-------------| +| **Diagnostic logs in Azure AI services should be enabled** | Audits AI Services for diagnostic logging configuration | AuditIfNotExists | `1b4d1c4e-934c-4703-944c-27c82c06bebb` | + +**Why it matters:** Diagnostic logs are essential for security investigations, troubleshooting, and compliance auditing. + +--- + +## Deployment + +### Deploy to Subscription + +```bash +# Deploy with default settings (Audit mode) +az deployment sub create \ + --location canadaeast \ + --template-file ai-governance.bicep \ + --parameters ai-governance.bicepparam + +# Deploy with specific environment +az deployment sub create \ + --location canadaeast \ + --template-file ai-governance.bicep \ + --parameters ai-governance.bicepparam \ + --parameters environment=prod +``` + +### View Compliance Status + +```bash +# Summary of policy compliance +az policy state summarize --policy-set-definition ai-governance-dev + +# List non-compliant resources +az policy state list \ + --policy-set-definition ai-governance-dev \ + --filter "complianceState eq 'NonCompliant'" +``` + +--- + +## Configuration + +### Policy Effects + +Configure policy effects in `ai-governance.bicepparam`: + +| Effect | Behavior | Recommended For | +|--------|----------|-----------------| +| `Audit` | Report non-compliance without blocking | Dev/Test environments | +| `Deny` | Block non-compliant deployments | Production environments | +| `Disabled` | Turn off the policy | Policies not applicable | + +### Environment-Specific Settings + +| Parameter | Dev/Test | Production | +|-----------|----------|------------| +| `networkPolicyEffect` | Audit | Deny | +| `authPolicyEffect` | Audit | Deny | +| `cmkPolicyEffect` | Disabled | Audit or Deny | +| `modelGovernanceEffect` | Disabled | Audit or Deny | +| `enforcementMode` | DoNotEnforce | Default (Enforce) | + +### Enforcement Mode + +The deployment automatically sets enforcement mode based on environment: +- **dev/test**: `DoNotEnforce` - Policies are evaluated but not enforced +- **prod**: `Default` - Policies are fully enforced + +--- + +## Files + +| File | Description | +|------|-------------| +| `ai-governance.bicep` | Main deployment file (subscription scope) | +| `ai-governance.bicepparam` | Parameters file for customization | +| `modules/ai-policy-initiative.bicep` | Policy initiative (policy set) definition | +| `modules/ai-policies.bicep` | Individual policy assignments (resource group scope) | + +--- + +## Compliance Mapping + +These policies align with common compliance frameworks: + +| Framework | Controls Addressed | +|-----------|-------------------| +| **Microsoft Cloud Security Benchmark** | NS-2 (Network Security), IM-1 (Identity Management), DP-5 (Data Protection) | +| **NIST 800-53** | AC-3 (Access Enforcement), SC-7 (Boundary Protection), SC-28 (Data at Rest) | +| **ISO 27001** | A.9 (Access Control), A.13 (Communications Security), A.10 (Cryptography) | +| **SOC 2** | CC6.1 (Logical Access), CC6.6 (System Boundaries), CC6.7 (Data Transmission) | + +--- + +## References + +- [Azure Policy built-in definitions for Azure Machine Learning](https://learn.microsoft.com/azure/machine-learning/policy-reference) +- [Azure Policy built-in definitions for Azure AI Services](https://learn.microsoft.com/azure/ai-services/policy-reference) +- [Azure Policy regulatory compliance](https://learn.microsoft.com/azure/governance/policy/concepts/regulatory-compliance) +- [Microsoft cloud security benchmark](https://learn.microsoft.com/security/benchmark/azure/introduction) diff --git a/infra/README.md b/infra/README.md index 78fb429..62d5ad6 100644 --- a/infra/README.md +++ b/infra/README.md @@ -2,6 +2,41 @@ ## What's New +### Azure Policy Governance for AI Resources +New policy modules to enforce security and compliance for AI resources: + +- **`ai-governance.bicep`**: Main deployment file for AI governance policies +- **`ai-governance.bicepparam`**: Parameters file for customizing policy effects +- **`modules/ai-policies.bicep`**: Individual policy assignments for resource groups +- **`modules/ai-policy-initiative.bicep`**: Policy initiative (policy set) definition + +**Policies included:** +| Category | Policies | +|----------|----------| +| **Network Security** | Disable public access, require private endpoints, VNet integration | +| **Authentication** | Disable local auth, require Azure AD/Entra ID | +| **Data Protection** | Customer-managed key (CMK) encryption | +| **Model Governance** | Restrict deployments to approved AI models | +| **Logging** | Enable diagnostic logging for AI services | + +**Deployment:** +```bash +# Deploy at subscription level +az deployment sub create \ + --location canadaeast \ + --template-file ai-governance.bicep \ + --parameters ai-governance.bicepparam +``` + +**Policy Effects:** +- `Audit` - Report non-compliant resources (recommended for dev/test) +- `Deny` - Block non-compliant deployments (recommended for production) +- `Disabled` - Turn off the policy + +See the parameters file for detailed configuration options. + +--- + ### Automated Deployment Script (PowerShell) We've added comprehensive PowerShell-based deployment automation to simplify infrastructure deployment: diff --git a/infra/ai-governance.bicep b/infra/ai-governance.bicep new file mode 100644 index 0000000..267bf05 --- /dev/null +++ b/infra/ai-governance.bicep @@ -0,0 +1,148 @@ +/* + AI Governance Policies Deployment + + This file deploys the AI governance policy initiative to a subscription + and assigns it to the specified scope (subscription or resource group). + + Usage: + # Deploy initiative at subscription level + az deployment sub create --location --template-file ai-governance.bicep --parameters ai-governance.bicepparam + + # Or with PowerShell + New-AzSubscriptionDeployment -Location -TemplateFile ai-governance.bicep -TemplateParameterFile ai-governance.bicepparam +*/ + +targetScope = 'subscription' + +// ============================================================================ +// PARAMETERS +// ============================================================================ + +@description('Location for policy assignment resources') +param location string + +@description('Environment name for naming resources') +@allowed(['dev', 'test', 'prod']) +param environment string = 'dev' + +// Policy Effect Parameters +@description('Effect for network security policies') +@allowed(['Audit', 'Deny', 'Disabled']) +param networkPolicyEffect string = 'Audit' + +@description('Effect for authentication policies') +@allowed(['Audit', 'Deny', 'Disabled']) +param authPolicyEffect string = 'Audit' + +@description('Effect for CMK encryption policies') +@allowed(['Audit', 'Deny', 'Disabled']) +param cmkPolicyEffect string = 'Disabled' + +@description('Effect for model governance policies') +@allowed(['Audit', 'Deny', 'Disabled']) +param modelGovernanceEffect string = 'Disabled' + +// Model Governance Parameters +@description('List of allowed AI model asset IDs') +param allowedAssetIds array = [] + +// ============================================================================ +// VARIABLES +// ============================================================================ + +var initiativeName = 'ai-governance-${environment}' +var assignmentName = 'ai-governance-assignment-${environment}' + +// ============================================================================ +// POLICY INITIATIVE +// ============================================================================ + +module policyInitiative 'modules/ai-policy-initiative.bicep' = { + name: 'deploy-ai-policy-initiative' + params: { + initiativeName: initiativeName + initiativeDisplayName: 'AI Resources Governance - ${toUpper(environment)}' + initiativeDescription: 'Enforces security and compliance policies for Azure ML and AI Foundry resources in ${environment} environment.' + category: 'AI Governance' + } +} + +// ============================================================================ +// POLICY ASSIGNMENT +// ============================================================================ + +resource policyAssignment 'Microsoft.Authorization/policyAssignments@2025-01-01' = { + name: assignmentName + location: location + identity: { + type: 'SystemAssigned' + } + properties: { + displayName: 'AI Governance Policies - ${toUpper(environment)}' + description: 'Assignment of AI governance initiative for ${environment} environment' + policyDefinitionId: policyInitiative.outputs.initiativeId + enforcementMode: environment == 'prod' ? 'Default' : 'DoNotEnforce' + parameters: { + networkPolicyEffect: { + value: networkPolicyEffect + } + authPolicyEffect: { + value: authPolicyEffect + } + cmkPolicyEffect: { + value: cmkPolicyEffect + } + modelGovernanceEffect: { + value: modelGovernanceEffect + } + allowedAssetIds: { + value: allowedAssetIds + } + } + nonComplianceMessages: [ + { + message: 'This resource violates AI governance policies. Please review the policy requirements and update your configuration.' + } + { + policyDefinitionReferenceId: 'amlDisablePublicAccess' + message: 'Azure Machine Learning workspaces must have public network access disabled.' + } + { + policyDefinitionReferenceId: 'aiServicesRestrictNetwork' + message: 'Azure AI Services must have network access restricted.' + } + { + policyDefinitionReferenceId: 'amlDisableLocalAuth' + message: 'Azure Machine Learning resources must use Azure AD authentication only.' + } + { + policyDefinitionReferenceId: 'aiServicesDisableLocalAuth' + message: 'Azure AI Services must use Azure AD authentication only. Key-based auth is not allowed.' + } + ] + } +} + +// ============================================================================ +// OUTPUTS +// ============================================================================ + +@description('The ID of the policy initiative') +output initiativeId string = policyInitiative.outputs.initiativeId + +@description('The ID of the policy assignment') +output assignmentId string = policyAssignment.id + +@description('The principal ID of the policy assignment managed identity') +output assignmentPrincipalId string = policyAssignment.identity.principalId + +@description('Summary of policy configuration') +output policyConfiguration object = { + environment: environment + enforcementMode: environment == 'prod' ? 'Enforcing' : 'AuditOnly' + networkPolicies: networkPolicyEffect + authPolicies: authPolicyEffect + cmkPolicies: cmkPolicyEffect + modelGovernance: modelGovernanceEffect + scope: 'Subscription' +} diff --git a/infra/ai-governance.bicepparam b/infra/ai-governance.bicepparam new file mode 100644 index 0000000..22b23cb --- /dev/null +++ b/infra/ai-governance.bicepparam @@ -0,0 +1,49 @@ +/* + AI Governance Policies Parameters + + Configure these parameters based on your environment and compliance requirements. +*/ + +using 'ai-governance.bicep' + +// Deployment location +param location = 'canadaeast' + +// Environment configuration +param environment = 'dev' + +// ============================================================================= +// POLICY EFFECTS +// ============================================================================= +// Options: 'Audit' (report only), 'Deny' (block), 'Disabled' (off) +// +// Recommendation: +// - dev/test: Use 'Audit' to understand compliance without blocking deployments +// - prod: Use 'Deny' for critical security policies + +// Network Security: Ensure private endpoints and VNet integration +param networkPolicyEffect = 'Audit' + +// Authentication: Require Azure AD, disable local/key auth +param authPolicyEffect = 'Audit' + +// Data Protection: Require customer-managed keys (CMK) +// Note: CMK requires additional Key Vault setup +param cmkPolicyEffect = 'Disabled' + +// Model Governance: Restrict which AI models can be deployed +param modelGovernanceEffect = 'Disabled' + +// ============================================================================= +// MODEL GOVERNANCE CONFIGURATION +// ============================================================================= +// Only used when modelGovernanceEffect is 'Audit' or 'Deny' + +// Allowed model asset IDs - specific models that can be deployed +// Example: 'azureml://registries/azure-openai/models/gpt-4o/versions/1' +param allowedAssetIds = [ + // Uncomment and modify as needed: + // 'azureml://registries/azure-openai/models/gpt-4o/versions/1' + // 'azureml://registries/azure-openai/models/gpt-35-turbo/versions/3' + // 'azureml://registries/azure-openai/models/text-embedding-ada-002/versions/2' +] diff --git a/infra/config.json b/infra/config.json index 4b488c5..b0354d5 100644 --- a/infra/config.json +++ b/infra/config.json @@ -14,8 +14,8 @@ "amlDescription": "This is an example SAIL deployment using Azure ML.", "prefix": "sailtest2", - "foundryName": "foundry-test-nodns", + "foundryName": "foundry-sail-lz2-dev", "foundryLocation": "canadaeast", - "foundryProjectName": "foundry-test-nodns-proj", + "foundryProjectName": "foundry-sail-lz2-dev-proj", "createPrivateDnsZones": false } diff --git a/infra/modules/ai-policies.bicep b/infra/modules/ai-policies.bicep new file mode 100644 index 0000000..e118465 --- /dev/null +++ b/infra/modules/ai-policies.bicep @@ -0,0 +1,334 @@ +/* + Azure Policy Assignments for AI Resource Governance + + This module creates policy assignments to enforce security and compliance + for Azure Machine Learning and AI Foundry resources. + + Policies included: + - Network security (private endpoints, public access) + - Authentication (disable local auth, require Entra ID) + - Data protection (encryption with CMK) + - Model governance (allowed models) + - Diagnostic logging +*/ + +// Parameters +@description('The effect for audit policies. Use Audit for visibility, Deny for enforcement.') +@allowed(['Audit', 'Deny', 'Disabled']) +param auditOrDenyEffect string = 'Audit' + +@description('Whether to enforce CMK encryption on AI resources.') +param enforceCMKEncryption bool = false + +@description('Whether to enforce private endpoints (strict network isolation).') +param enforcePrivateEndpoints bool = true + +@description('Whether to restrict AI model deployments to approved models.') +param restrictModelDeployments bool = false + +@description('List of allowed model asset IDs for AI deployments.') +param allowedModelAssetIds array = [] + +@description('Location for policy assignment resources.') +param location string = resourceGroup().location + +// Built-in Policy Definition IDs +var policyDefinitions = { + // Azure Machine Learning policies + amlDisablePublicAccess: '/providers/Microsoft.Authorization/policyDefinitions/438c38d2-3772-465a-a9cc-7a6666a275ce' + amlRequirePrivateLink: '/providers/Microsoft.Authorization/policyDefinitions/45e05259-1eb5-4f70-9574-baf73e9d219b' + amlDisableLocalAuth: '/providers/Microsoft.Authorization/policyDefinitions/e96a9a5f-07ca-471b-9bc5-6a0f33cbd68f' + amlRequireCMK: '/providers/Microsoft.Authorization/policyDefinitions/ba769a63-b8cc-4b2d-abf6-ac33c7204be8' + amlRequireVnet: '/providers/Microsoft.Authorization/policyDefinitions/7804b5c7-01dc-4723-969b-ae300cc07ff1' + + // Azure AI Services / Cognitive Services / AI Foundry policies + aiServicesDisableLocalAuth: '/providers/Microsoft.Authorization/policyDefinitions/71ef260a-8f18-47b7-abcb-62d0673d94dc' + aiServicesRestrictNetwork: '/providers/Microsoft.Authorization/policyDefinitions/037eea7a-bd0a-46c5-9a66-03aea78705d3' + aiServicesRequireCMK: '/providers/Microsoft.Authorization/policyDefinitions/67121cc7-ff39-4ab8-b7e3-95b84dab487d' + aiServicesEnableDiagnostics: '/providers/Microsoft.Authorization/policyDefinitions/1b4d1c4e-934c-4703-944c-27c82c06bebb' + + // Configure policies (DINE) + configureAiServicesDisableLocalAuth: '/providers/Microsoft.Authorization/policyDefinitions/55eff01b-f2bd-4c32-9203-db285f709d30' + configureCognitiveServicesDisablePublicAccess: '/providers/Microsoft.Authorization/policyDefinitions/47ba1dd7-28d9-4b07-a8d5-9813bed64e0c' + + // Model governance + amlAllowedModels: '/providers/Microsoft.Authorization/policyDefinitions/12e5dd16-d201-47ff-849b-8454061c293d' + cognitiveServicesAllowedModels: '/providers/Microsoft.Authorization/policyDefinitions/aafe3651-cb78-4f68-9f81-e7e41509110f' +} + +// ============================================================================ +// NETWORK SECURITY POLICIES +// ============================================================================ + +// Policy: Azure ML Workspaces should disable public network access +resource amlDisablePublicAccessAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (enforcePrivateEndpoints) { + name: 'aml-disable-public-access' + location: location + properties: { + displayName: 'Azure ML Workspaces should disable public network access' + description: 'Disabling public network access improves security by ensuring workspaces are not exposed on the public internet.' + policyDefinitionId: policyDefinitions.amlDisablePublicAccess + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure Machine Learning workspaces must have public network access disabled. Use private endpoints for connectivity.' + } + ] + } +} + +// Policy: Azure ML Workspaces should use private link +resource amlRequirePrivateLinkAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (enforcePrivateEndpoints) { + name: 'aml-require-private-link' + location: location + properties: { + displayName: 'Azure ML Workspaces should use private link' + description: 'Ensure Azure Machine Learning workspaces are connected via private endpoints.' + policyDefinitionId: policyDefinitions.amlRequirePrivateLink + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure Machine Learning workspaces must be configured with private link connections.' + } + ] + } +} + +// Policy: Azure AI Services resources should restrict network access +resource aiServicesRestrictNetworkAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (enforcePrivateEndpoints) { + name: 'ai-services-restrict-network' + location: location + properties: { + displayName: 'Azure AI Services resources should restrict network access' + description: 'Restrict network access to AI Services by configuring network rules.' + policyDefinitionId: policyDefinitions.aiServicesRestrictNetwork + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure AI Services resources must have network access restricted to allowed networks only.' + } + ] + } +} + +// Policy: Azure ML Computes should be in a virtual network +resource amlRequireVnetAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (enforcePrivateEndpoints) { + name: 'aml-require-vnet' + location: location + properties: { + displayName: 'Azure ML Computes should be in a virtual network' + description: 'Ensure Azure Machine Learning compute resources are deployed within a virtual network.' + policyDefinitionId: policyDefinitions.amlRequireVnet + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure Machine Learning compute resources must be deployed within a virtual network.' + } + ] + } +} + +// ============================================================================ +// AUTHENTICATION POLICIES +// ============================================================================ + +// Policy: Azure ML Computes should have local authentication disabled +resource amlDisableLocalAuthAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: 'aml-disable-local-auth' + location: location + properties: { + displayName: 'Azure ML Computes should have local authentication disabled' + description: 'Disabling local authentication improves security by ensuring ML Computes require Azure AD identities exclusively.' + policyDefinitionId: policyDefinitions.amlDisableLocalAuth + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure Machine Learning compute resources must use Azure AD authentication. Local authentication is not allowed.' + } + ] + } +} + +// Policy: Azure AI Services should have key access disabled (disable local auth) +resource aiServicesDisableLocalAuthAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: 'ai-services-disable-local-auth' + location: location + properties: { + displayName: 'Azure AI Services should have key access disabled' + description: 'Key access (local authentication) should be disabled. Microsoft Entra ID becomes the only access method.' + policyDefinitionId: policyDefinitions.aiServicesDisableLocalAuth + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure AI Services resources must disable key-based authentication. Use Microsoft Entra ID for authentication.' + } + ] + } +} + +// ============================================================================ +// DATA PROTECTION POLICIES (CMK Encryption) +// ============================================================================ + +// Policy: Azure ML Workspaces should be encrypted with CMK +resource amlRequireCMKAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (enforceCMKEncryption) { + name: 'aml-require-cmk' + location: location + properties: { + displayName: 'Azure ML Workspaces should be encrypted with customer-managed key' + description: 'Manage encryption at rest of Azure Machine Learning workspace data with customer-managed keys.' + policyDefinitionId: policyDefinitions.amlRequireCMK + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure Machine Learning workspaces must be encrypted with a customer-managed key (CMK).' + } + ] + } +} + +// Policy: Azure AI Services should encrypt data at rest with CMK +resource aiServicesRequireCMKAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (enforceCMKEncryption) { + name: 'ai-services-require-cmk' + location: location + properties: { + displayName: 'Azure AI Services should encrypt data at rest with CMK' + description: 'Using customer-managed keys to encrypt data at rest provides more control over the key lifecycle.' + policyDefinitionId: policyDefinitions.aiServicesRequireCMK + parameters: { + effect: { + value: auditOrDenyEffect + } + } + nonComplianceMessages: [ + { + message: 'Azure AI Services resources must be encrypted with a customer-managed key (CMK).' + } + ] + } +} + +// ============================================================================ +// MODEL GOVERNANCE POLICIES +// ============================================================================ + +// Policy: Azure ML Deployments should only use approved Registry Models +resource amlAllowedModelsAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (restrictModelDeployments && length(allowedModelAssetIds) > 0) { + name: 'aml-allowed-models' + location: location + properties: { + displayName: 'Azure ML Deployments should only use approved Registry Models' + description: 'Restrict the deployment of Registry models to control externally created models used within your organization.' + policyDefinitionId: policyDefinitions.amlAllowedModels + parameters: { + effect: { + value: auditOrDenyEffect + } + allowedAssetIds: { + value: allowedModelAssetIds + } + } + nonComplianceMessages: [ + { + message: 'Only approved AI models from the allowed list can be deployed. Contact your administrator for model approval.' + } + ] + } +} + +// Policy: Cognitive Services Deployments should only use approved Registry Models +resource cognitiveServicesAllowedModelsAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = if (restrictModelDeployments && length(allowedModelAssetIds) > 0) { + name: 'cognitive-allowed-models' + location: location + properties: { + displayName: 'Cognitive Services Deployments should only use approved Registry Models' + description: 'Restrict the deployment of Registry models to control externally created models used within your organization.' + policyDefinitionId: policyDefinitions.cognitiveServicesAllowedModels + parameters: { + effect: { + value: auditOrDenyEffect + } + allowedAssetIds: { + value: allowedModelAssetIds + } + } + nonComplianceMessages: [ + { + message: 'Only approved AI models from the allowed list can be deployed. Contact your administrator for model approval.' + } + ] + } +} + +// ============================================================================ +// DIAGNOSTIC LOGGING POLICIES +// ============================================================================ + +// Policy: Diagnostic logs in Azure AI services should be enabled +resource aiServicesEnableDiagnosticsAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: 'ai-services-enable-diagnostics' + location: location + properties: { + displayName: 'Diagnostic logs in Azure AI services should be enabled' + description: 'Enable logs for Azure AI services resources for investigation purposes when a security incident occurs.' + policyDefinitionId: policyDefinitions.aiServicesEnableDiagnostics + parameters: { + effect: { + value: 'AuditIfNotExists' + } + } + nonComplianceMessages: [ + { + message: 'Azure AI Services resources must have diagnostic logging enabled.' + } + ] + } +} + +// ============================================================================ +// OUTPUTS +// ============================================================================ + +@description('Array of policy assignment IDs created') +output policyAssignmentIds array = [ + amlDisableLocalAuthAssignment.id + aiServicesDisableLocalAuthAssignment.id + aiServicesEnableDiagnosticsAssignment.id +] + +@description('Summary of policies applied') +output policySummary object = { + networkSecurityPolicies: enforcePrivateEndpoints ? 4 : 0 + authenticationPolicies: 2 + dataProtectionPolicies: enforceCMKEncryption ? 2 : 0 + modelGovernancePolicies: restrictModelDeployments && length(allowedModelAssetIds) > 0 ? 2 : 0 + diagnosticPolicies: 1 +} diff --git a/infra/modules/ai-policy-initiative.bicep b/infra/modules/ai-policy-initiative.bicep new file mode 100644 index 0000000..f473f98 --- /dev/null +++ b/infra/modules/ai-policy-initiative.bicep @@ -0,0 +1,304 @@ +/* + Azure Policy Initiative (Policy Set) for AI Governance + + This module creates a custom policy initiative that groups all AI governance + policies together for easier assignment and management. + + Deploy at subscription or management group scope. +*/ + +targetScope = 'subscription' + +// Parameters +@description('Display name for the policy initiative') +param initiativeName string = 'AI-Governance-Initiative' + +@description('Display name shown in Azure Portal') +param initiativeDisplayName string = 'AI Resources Governance Initiative' + +@description('Description of the policy initiative') +param initiativeDescription string = 'This initiative enforces security and compliance policies for Azure Machine Learning and AI Foundry resources.' + +@description('Category for organizing in Azure Portal') +param category string = 'AI Governance' + +// Built-in Policy Definition IDs +var policyDefinitionIds = { + // Network Security + amlDisablePublicAccess: '/providers/Microsoft.Authorization/policyDefinitions/438c38d2-3772-465a-a9cc-7a6666a275ce' + amlRequirePrivateLink: '/providers/Microsoft.Authorization/policyDefinitions/45e05259-1eb5-4f70-9574-baf73e9d219b' + amlRequireVnet: '/providers/Microsoft.Authorization/policyDefinitions/7804b5c7-01dc-4723-969b-ae300cc07ff1' + aiServicesRestrictNetwork: '/providers/Microsoft.Authorization/policyDefinitions/037eea7a-bd0a-46c5-9a66-03aea78705d3' + + // Authentication + amlDisableLocalAuth: '/providers/Microsoft.Authorization/policyDefinitions/e96a9a5f-07ca-471b-9bc5-6a0f33cbd68f' + aiServicesDisableLocalAuth: '/providers/Microsoft.Authorization/policyDefinitions/71ef260a-8f18-47b7-abcb-62d0673d94dc' + + // Data Protection + amlRequireCMK: '/providers/Microsoft.Authorization/policyDefinitions/ba769a63-b8cc-4b2d-abf6-ac33c7204be8' + aiServicesRequireCMK: '/providers/Microsoft.Authorization/policyDefinitions/67121cc7-ff39-4ab8-b7e3-95b84dab487d' + + // Model Governance + amlAllowedModels: '/providers/Microsoft.Authorization/policyDefinitions/12e5dd16-d201-47ff-849b-8454061c293d' + cognitiveServicesAllowedModels: '/providers/Microsoft.Authorization/policyDefinitions/aafe3651-cb78-4f68-9f81-e7e41509110f' + + // Diagnostics + aiServicesEnableDiagnostics: '/providers/Microsoft.Authorization/policyDefinitions/1b4d1c4e-934c-4703-944c-27c82c06bebb' +} + +// Policy Initiative Definition +resource aiGovernanceInitiative 'Microsoft.Authorization/policySetDefinitions@2025-01-01' = { + name: initiativeName + properties: { + displayName: initiativeDisplayName + description: initiativeDescription + policyType: 'Custom' + metadata: { + category: category + version: '1.0.0' + } + parameters: { + // Effect parameters + networkPolicyEffect: { + type: 'String' + metadata: { + displayName: 'Effect for network policies' + description: 'The effect for network security policies' + } + allowedValues: [ + 'Audit' + 'Deny' + 'Disabled' + ] + defaultValue: 'Audit' + } + authPolicyEffect: { + type: 'String' + metadata: { + displayName: 'Effect for authentication policies' + description: 'The effect for authentication policies' + } + allowedValues: [ + 'Audit' + 'Deny' + 'Disabled' + ] + defaultValue: 'Audit' + } + cmkPolicyEffect: { + type: 'String' + metadata: { + displayName: 'Effect for CMK encryption policies' + description: 'The effect for customer-managed key encryption policies' + } + allowedValues: [ + 'Audit' + 'Deny' + 'Disabled' + ] + defaultValue: 'Disabled' + } + modelGovernanceEffect: { + type: 'String' + metadata: { + displayName: 'Effect for model governance policies' + description: 'The effect for AI model deployment governance policies' + } + allowedValues: [ + 'Audit' + 'Deny' + 'Disabled' + ] + defaultValue: 'Disabled' + } + // Model governance parameters + allowedAssetIds: { + type: 'Array' + metadata: { + displayName: 'Allowed model asset IDs' + description: 'List of allowed model asset IDs for deployments' + } + defaultValue: [] + } + } + policyDefinitions: [ + // ========== NETWORK SECURITY POLICIES ========== + { + policyDefinitionId: policyDefinitionIds.amlDisablePublicAccess + policyDefinitionReferenceId: 'amlDisablePublicAccess' + parameters: { + effect: { + value: '[parameters(\'networkPolicyEffect\')]' + } + } + groupNames: [ + 'NetworkSecurity' + ] + } + { + policyDefinitionId: policyDefinitionIds.amlRequirePrivateLink + policyDefinitionReferenceId: 'amlRequirePrivateLink' + parameters: { + effect: { + value: '[parameters(\'networkPolicyEffect\')]' + } + } + groupNames: [ + 'NetworkSecurity' + ] + } + { + policyDefinitionId: policyDefinitionIds.amlRequireVnet + policyDefinitionReferenceId: 'amlRequireVnet' + parameters: { + effect: { + value: '[parameters(\'networkPolicyEffect\')]' + } + } + groupNames: [ + 'NetworkSecurity' + ] + } + { + policyDefinitionId: policyDefinitionIds.aiServicesRestrictNetwork + policyDefinitionReferenceId: 'aiServicesRestrictNetwork' + parameters: { + effect: { + value: '[parameters(\'networkPolicyEffect\')]' + } + } + groupNames: [ + 'NetworkSecurity' + ] + } + // ========== AUTHENTICATION POLICIES ========== + { + policyDefinitionId: policyDefinitionIds.amlDisableLocalAuth + policyDefinitionReferenceId: 'amlDisableLocalAuth' + parameters: { + effect: { + value: '[parameters(\'authPolicyEffect\')]' + } + } + groupNames: [ + 'IdentityManagement' + ] + } + { + policyDefinitionId: policyDefinitionIds.aiServicesDisableLocalAuth + policyDefinitionReferenceId: 'aiServicesDisableLocalAuth' + parameters: { + effect: { + value: '[parameters(\'authPolicyEffect\')]' + } + } + groupNames: [ + 'IdentityManagement' + ] + } + // ========== DATA PROTECTION POLICIES ========== + { + policyDefinitionId: policyDefinitionIds.amlRequireCMK + policyDefinitionReferenceId: 'amlRequireCMK' + parameters: { + effect: { + value: '[parameters(\'cmkPolicyEffect\')]' + } + } + groupNames: [ + 'DataProtection' + ] + } + { + policyDefinitionId: policyDefinitionIds.aiServicesRequireCMK + policyDefinitionReferenceId: 'aiServicesRequireCMK' + parameters: { + effect: { + value: '[parameters(\'cmkPolicyEffect\')]' + } + } + groupNames: [ + 'DataProtection' + ] + } + // ========== MODEL GOVERNANCE POLICIES ========== + { + policyDefinitionId: policyDefinitionIds.amlAllowedModels + policyDefinitionReferenceId: 'amlAllowedModels' + parameters: { + effect: { + value: '[parameters(\'modelGovernanceEffect\')]' + } + allowedAssetIds: { + value: '[parameters(\'allowedAssetIds\')]' + } + } + groupNames: [ + 'ModelGovernance' + ] + } + { + policyDefinitionId: policyDefinitionIds.cognitiveServicesAllowedModels + policyDefinitionReferenceId: 'cognitiveServicesAllowedModels' + parameters: { + effect: { + value: '[parameters(\'modelGovernanceEffect\')]' + } + allowedAssetIds: { + value: '[parameters(\'allowedAssetIds\')]' + } + } + groupNames: [ + 'ModelGovernance' + ] + } + // ========== DIAGNOSTIC POLICIES ========== + { + policyDefinitionId: policyDefinitionIds.aiServicesEnableDiagnostics + policyDefinitionReferenceId: 'aiServicesEnableDiagnostics' + parameters: { + effect: { + value: 'AuditIfNotExists' + } + } + groupNames: [ + 'Logging' + ] + } + ] + policyDefinitionGroups: [ + { + name: 'NetworkSecurity' + displayName: 'Network Security' + description: 'Policies to ensure AI resources are properly network isolated' + } + { + name: 'IdentityManagement' + displayName: 'Identity Management' + description: 'Policies to enforce Azure AD authentication and disable local auth' + } + { + name: 'DataProtection' + displayName: 'Data Protection' + description: 'Policies to ensure data encryption with customer-managed keys' + } + { + name: 'ModelGovernance' + displayName: 'Model Governance' + description: 'Policies to control which AI models can be deployed' + } + { + name: 'Logging' + displayName: 'Logging & Monitoring' + description: 'Policies to ensure proper diagnostic logging is enabled' + } + ] + } +} + +// Outputs +@description('The resource ID of the policy initiative') +output initiativeId string = aiGovernanceInitiative.id + +@description('The name of the policy initiative') +output initiativeName string = aiGovernanceInitiative.name diff --git a/infra/vnet.bicep b/infra/vnet.bicep index 9ec8981..4f0c581 100644 --- a/infra/vnet.bicep +++ b/infra/vnet.bicep @@ -2,8 +2,8 @@ Generic virtual network and subnet Description: - - Virtual network - - Subnet + - Virtual network with optional DDoS protection + - Subnet with Network Security Group (required for landing zone compliance) */ @description('Name of the virtual network') @@ -12,30 +12,98 @@ param vnetName string = 'private-vnet' @description('Name of the private endpoint subnet') param peSubnetName string = 'pe-subnet' +@description('Address space for the virtual network') +param addressPrefix string = '192.168.0.0/16' + +@description('Address prefix for the private endpoint subnet') +param subnetPrefix string = '192.168.0.0/24' + +@description('Enable DDoS protection (required in some landing zones)') +param enableDdosProtection bool = false + +@description('Resource ID of existing DDoS protection plan (required if enableDdosProtection is true and createDdosPlan is false)') +param ddosProtectionPlanId string = '' + +@description('Create a new DDoS protection plan') +param createDdosPlan bool = false + +// Enforce that when DDoS protection is enabled without creating a new plan, +// an existing ddosProtectionPlanId must be provided. +var requireExistingDdosPlanId = enableDdosProtection && !createDdosPlan + +assert ddosPlanIdProvided = !requireExistingDdosPlanId || !empty(ddosProtectionPlanId) +// DDoS Protection Plan (optional - only create if specified) +resource ddosPlan 'Microsoft.Network/ddosProtectionPlans@2024-05-01' = if (createDdosPlan) { + name: '${vnetName}-ddos-plan' + location: resourceGroup().location + properties: {} +} + +// Network Security Group for the subnet (required by Azure Landing Zone policies) +resource nsg 'Microsoft.Network/networkSecurityGroups@2024-05-01' = { + name: '${peSubnetName}-nsg' + location: resourceGroup().location + properties: { + securityRules: [ + // Allow inbound HTTPS for private endpoint traffic + { + name: 'AllowHttpsInbound' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + destinationAddressPrefix: subnetPrefix + destinationPortRange: '443' + } + } + // Deny all other inbound traffic from internet + { + name: 'DenyInternetInbound' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourceAddressPrefix: 'Internet' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '*' + } + } + ] + } +} + resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { name: vnetName location: resourceGroup().location properties: { addressSpace: { addressPrefixes: [ - '192.168.0.0/16' + addressPrefix ] } + enableDdosProtection: enableDdosProtection + if (enableDdosProtection) ddosProtectionPlan: { + id: createDdosPlan ? ddosPlan.id : ddosProtectionPlanId + } subnets: [ { name: peSubnetName properties: { - addressPrefix: '192.168.0.0/24' + addressPrefix: subnetPrefix + networkSecurityGroup: { + id: nsg.id + } } } ] } } -resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' = { - parent: virtualNetwork - name: peSubnetName - properties: { - addressPrefix: '192.168.0.0/24' - } -} +output vnetId string = virtualNetwork.id +output subnetId string = virtualNetwork.properties.subnets[0].id +output nsgId string = nsg.id