diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/README.md b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/README.md index 843443799..01533dfde 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/README.md +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/README.md @@ -40,7 +40,7 @@ Standard setup supports private network isolation through utilizing **Managed Vi 1. **Register Resource Providers** - Make sure you have an active Azure subscription that allows registering resource providers. For example, subnet delegation requires the Microsoft.App provider to be registered in your subscription. If it's not already registered, run the commands below: + Make sure you have an active Azure subscription that allows registering resource providers. If it's not already registered, run the commands below: ```bash az provider register --namespace 'Microsoft.KeyVault' @@ -48,7 +48,6 @@ Standard setup supports private network isolation through utilizing **Managed Vi az provider register --namespace 'Microsoft.Storage' az provider register --namespace 'Microsoft.Search' az provider register --namespace 'Microsoft.Network' - az provider register --namespace 'Microsoft.App' az provider register --namespace 'Microsoft.ContainerService' ``` @@ -62,11 +61,15 @@ Standard setup supports private network isolation through utilizing **Managed Vi ## Pre-Deployment Steps ### Limitations -1. Do not support Evaluations in Foundry currently, and only secure Agents service. -2. The private endpoints created for your CosmosDB account and Search resource must be done manually using the files in the "update-outbound-rules-cli" folder. Please run the commands as the "outbound-rule-cli.md" files outline for the additional resources you require private endpoints to in your managed vnet set-up. Keep in mind these are only the commands to create the Private endpoints. You will also need connections to those resources - these are covered in the template for CosmosDB, Search, and Storage but additional new resources will need both a connection created and a private endpoint created. -3. The managed VNET is supported for Agents v1 created and ingested through the Foundry classic experience. Agent v2 support is coming soon in GA. -4. We do not support private VNET support for Agent tools such as MCP yet. The tools must be public traffic facing for now. The support is coming soon in GA. -5. For any feedback, please directly email meerakurup@microsoft.com +1. A managed network Foundry resource is only deployable via the `main.bicep` template in the folder `18-managed-virtual-network-preview` in foundry-samples. +1. If you create FQDN outbound rules when the managed virtual network is in Allow Only Approved Outbound mode, a managed Azure Firewall is created which comes with associated Firewall costs. The FQDN outbound rules only support ports 80 and 443. +1. Managed virtual network isolation cannot be disabled after enabling. There is no upgrade path from custom virtual network set-up to managed virtual network. A Foundry resource redeployment is required. Deleting your Foundry resource deletes the managed virtual network. +1. Outbound rules from the managed network must be created through Azure CLI. For the end-to-end secured Agent service set-up with a managed virtual network, the template creates the managed private endpoint to the associated Storage account. Private endpoints are not created to CosmosDB or AI Search. Please use instructions in the `outbound rules CLI ` for information on how to create the managed private endpoints. +1. Support for managed virtual network is only in the following regions: **East US, East US2, Japan East, France Central, UAE North, Brazil South, Spain Central, Germany West Central, Italy North, South Central US, West Central US, Australia East, Sweden Central, Canada East, South Africa North, West Europe, West US, West US 3, South India, and UK South.** +1. If you require private access to on-premises resources for your Foundry resource, please use the to [Application Gatway](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/access-on-premises-resources?view=foundry-classic&viewFallbackFrom=foundry) to configure on-premises access. The same set-up with a private endpoint to Application Gateway and setting up backend pools is supported. Both L4 and L7 traffic are now supported with the Application Gateway in GA. +1. Supports only Standard BYO resources Agents v1 and the Foundry classic experience. Basic Agents do not require network isolation. Support in the new Agents v2 and the new Foundry UI is coming soon. +1. End-to-end network isolation for Agent MCP tools with managed virtual network is currently not supported. Please use public MCP tools with managed network isolation Foundry. +1. For any feedback, please directly email meerakurup@microsoft.com ### Template Customization diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json index cc58c92cf..57c847169 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "739301657298739367" + "templateHash": "15609984867232984221" } }, "parameters": { @@ -13,17 +13,42 @@ "type": "string", "defaultValue": "eastus2", "allowedValues": [ + "spaincentral", "westus", "eastus", - "eastus2", + "japaneast", "francecentral", + "eastus2", "uaenorth", - "canadaeast" + "brazilsouth", + "germanywestcentral", + "italynorth", + "southcentralus", + "westcentralus", + "australiaeast", + "swedencentral", + "canadaeast", + "southafricanorth", + "westeurope", + "westus3", + "southindia", + "uksouth" ], "metadata": { "description": "Location for all resources." } }, + "isolationMode": { + "type": "string", + "defaultValue": "AllowOnlyApprovedOutbound", + "allowedValues": [ + "AllowOnlyApprovedOutbound", + "AllowInternetOutbound" + ], + "metadata": { + "description": "The isolation mode for the managed network" + } + }, "aiServices": { "type": "string", "defaultValue": "aiservices", @@ -1181,6 +1206,9 @@ "accountName": { "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.value]" }, + "isolationMode": { + "value": "[parameters('isolationMode')]" + }, "storageName": { "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" }, @@ -1198,7 +1226,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "13583929851932803119" + "templateHash": "2247331089577562037" } }, "parameters": { @@ -1208,6 +1236,17 @@ "description": "The name of the AI Services account" } }, + "isolationMode": { + "type": "string", + "defaultValue": "AllowOnlyApprovedOutbound", + "allowedValues": [ + "AllowOnlyApprovedOutbound", + "AllowInternetOutbound" + ], + "metadata": { + "description": "The isolation mode for the managed network" + } + }, "storageName": { "type": "string", "metadata": { @@ -1234,7 +1273,7 @@ "name": "[format('{0}/{1}', parameters('accountName'), 'default')]", "properties": { "managedNetwork": { - "IsolationMode": "AllowOnlyApprovedOutbound", + "IsolationMode": "[parameters('isolationMode')]", "managedNetworkKind": "V2" } } diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.parameters.json b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.parameters.json index ee70af74d..f5dc15cfb 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.parameters.json +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.parameters.json @@ -1,54 +1,33 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "location": { "value": "eastus2" }, - "aiServices": { - "value": "" - }, - "modelName": { - "value": "" - }, - "modelFormat": { - "value": "" - }, - "modelVersion": { - "value": "" - }, - "modelSkuName": { - "value": "" - }, - "modelCapacity": { - "value": 0 + "isolationMode": { + "value": "AllowOnlyApprovedOutbound" }, - "deploymentTimestamp": { - "value": "" + "aiServices": { + "value": "aiservices" }, "firstProjectName": { - "value": "" + "value": "project" }, "projectDescription": { - "value": "" + "value": "A project for the AI Foundry account with managed network secured deployed Agent" }, "displayName": { - "value": "" - }, - "vnetName": { - "value": "" + "value": "project" }, "peSubnetName": { - "value": "" + "value": "pe-subnet" }, "existingVnetResourceId": { "value": "" }, - "vnetAddressPrefix": { - "value": "" - }, - "peSubnetPrefix": { - "value": "" + "vnetName": { + "value": "my-vnet" }, "aiSearchResourceId": { "value": "" @@ -59,9 +38,6 @@ "azureCosmosDBAccountResourceId": { "value": "" }, - "projectCapHost": { - "value": "" - }, "apiManagementResourceId": { "value": "" }, @@ -75,6 +51,23 @@ "privatelink.documents.azure.com": "", "privatelink.azure-api.net": "" } + }, + "dnsZoneNames": { + "value": [ + "privatelink.services.ai.azure.com", + "privatelink.openai.azure.com", + "privatelink.cognitiveservices.azure.com", + "privatelink.search.windows.net", + "privatelink.blob.core.windows.net", + "privatelink.documents.azure.com", + "privatelink.azure-api.net" + ] + }, + "vnetAddressPrefix": { + "value": "" + }, + "peSubnetPrefix": { + "value": "" } } -} +} \ No newline at end of file diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicep index 10263f43e..80b941e7c 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicep @@ -4,15 +4,36 @@ Standard Setup Managed Network Secured Steps for main.bicep */ @description('Location for all resources.') @allowed([ + 'spaincentral' 'westus' 'eastus' - 'eastus2' + 'japaneast' 'francecentral' + 'eastus2' 'uaenorth' + 'brazilsouth' + 'germanywestcentral' + 'italynorth' + 'southcentralus' + 'westcentralus' + 'australiaeast' + 'swedencentral' 'canadaeast' + 'southafricanorth' + 'westeurope' + 'westus3' + 'southindia' + 'uksouth' ]) param location string = 'eastus2' +@description('The isolation mode for the managed network') +@allowed([ + 'AllowOnlyApprovedOutbound' + 'AllowInternetOutbound' +]) +param isolationMode string = 'AllowOnlyApprovedOutbound' + @description('Name for your AI Services resource.') param aiServices string = 'aiservices' @@ -223,6 +244,7 @@ module managedNetwork 'modules-network-secured/managed-network.bicep' = { name: 'managed-network-${uniqueSuffix}-deployment' params: { accountName: aiAccount.outputs.accountName + isolationMode: isolationMode storageName: aiDependencies.outputs.azureStorageName storageResourceGroupName: azureStorageResourceGroupName storageSubscriptionId: azureStorageSubscriptionId diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicepparam b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicepparam index 4c1f1629f..0df098e5f 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicepparam +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/main.bicepparam @@ -1,6 +1,7 @@ using './main.bicep' param location = 'eastus2' +param isolationMode = 'AllowOnlyApprovedOutbound' param aiServices = 'aiservices' param firstProjectName = 'project' param projectDescription = 'A project for the AI Foundry account with managed network secured deployed Agent' diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/metadata.json b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/metadata.json index 4526441c6..e51f264f4 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/metadata.json +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/metadata.json @@ -5,7 +5,7 @@ "description": "This set of templates demonstrates how to set up Azure AI Agent Service with the managed network secured standard setup environment.", "summary": "This set of templates demonstrates how to use Azure AI Agent Service with a managed virtual network.", "githubUsername": "meerakurup", - "dateUpdated": "2025-06-24", + "dateUpdated": "2025-12-17", "environments": [ "AzureCloud" ] diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/managed-network.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/managed-network.bicep index 3053576ae..bbd387c58 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/managed-network.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/managed-network.bicep @@ -1,6 +1,13 @@ @description('The name of the AI Services account') param accountName string +@description('The isolation mode for the managed network') +@allowed([ + 'AllowOnlyApprovedOutbound' + 'AllowInternetOutbound' +]) +param isolationMode string = 'AllowOnlyApprovedOutbound' + @description('The name of the storage account to create outbound rules for') param storageName string @@ -10,7 +17,7 @@ param storageResourceGroupName string @description('The subscription ID where the storage account is located') param storageSubscriptionId string -@description('The name of the AI Search service to create outbound rules for') +// @description('The name of the AI Search service to create outbound rules for') // param aiSearchName string // @description('The resource group name where the AI Search service is located') @@ -40,8 +47,9 @@ resource managedNetwork 'Microsoft.CognitiveServices/accounts/managednetworks@20 name: 'default' properties: { managedNetwork: { - IsolationMode: 'AllowOnlyApprovedOutbound' + IsolationMode: isolationMode managedNetworkKind: 'V2' + //firewallSku: 'Standard' // Uncomment to enable firewall only when in AllowOnlyApprovedOutbound mode } } } diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/batch-outbound-rules.json b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/batch-outbound-rules.json new file mode 100644 index 000000000..f3ad55419 --- /dev/null +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/batch-outbound-rules.json @@ -0,0 +1,41 @@ +{ + "id": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/managedNetworks/default", + "name": "default", + "type": "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules", + "properties": { + "IsolationMode": "AllowOnlyApprovedOutbound", + "outboundRules": { + "storage-rule": { + "type": "PrivateEndpoint", + "destination": { + "serviceResourceId": "/subscriptions/{storageSubscriptionId}/resourceGroups/{storageResourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageName}", + "subresourceTarget": "blob", + "sparkEnabled": false, + "sparkStatus": "Inactive" + }, + "category": "UserDefined" + }, + "aisearch-rule": { + "type": "PrivateEndpoint", + "destination": { + "serviceResourceId": "/subscriptions/{aiSearchSubscriptionId}/resourceGroups/{aiSearchResourceGroupName}/providers/Microsoft.Search/searchServices/{aiSearchName}", + "subresourceTarget": "searchService", + "sparkEnabled": false, + "sparkStatus": "Inactive" + }, + "category": "UserDefined" + }, + "cosmosdb-rule": { + "type": "PrivateEndpoint", + "destination": { + "serviceResourceId": "/subscriptions/{cosmosDBSubscriptionId}/resourceGroups/{cosmosDBResourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/{cosmosDBName}", + "subresourceTarget": "Sql", + "sparkEnabled": false, + "sparkStatus": "Inactive" + }, + "category": "UserDefined" + } + }, + "managedNetworkKind": "V2" + } +} diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/outbound-rule-cli.md b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/outbound-rule-cli.md index a118cf73a..2080ce654 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/outbound-rule-cli.md +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/update-outbound-rules-cli/outbound-rule-cli.md @@ -56,4 +56,48 @@ az rest --method PUT --url 'https://management.azure.com/subscriptions/{sub-id}/ }, "category": "UserDefined" } -}' \ No newline at end of file +}' + +# Batch Outbound Rules CLI + +This folder contains the JSON payload for creating batch outbound rules via Azure REST API. This allows youto create all the outbound PE rules in one go instead of the individual PE rules one at a time. + +## Usage + +Replace the placeholders in `batch-outbound-rules.json` with your actual values: +- `{subscriptionId}` - Your Azure subscription ID +- `{resourceGroupName}` - Your resource group name +- `{accountName}` - Your AI Services account name +- `{storageSubscriptionId}` - Storage account subscription ID +- `{storageResourceGroupName}` - Storage account resource group +- `{storageName}` - Storage account name +- `{aiSearchSubscriptionId}` - AI Search subscription ID +- `{aiSearchResourceGroupName}` - AI Search resource group +- `{aiSearchName}` - AI Search service name +- `{cosmosDBSubscriptionId}` - Cosmos DB subscription ID +- `{cosmosDBResourceGroupName}` - Cosmos DB resource group +- `{cosmosDBName}` - Cosmos DB account name + +## REST API Call + +```bash +az rest --method POST \ + --uri "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/managedNetworks/default/batchOutboundRules?api-version=2025-10-01-preview" \ + --body @batch-outbound-rules.json +``` + +## PowerShell Example + +```powershell +$body = Get-Content -Path "batch-outbound-rules.json" -Raw +$uri = "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/managedNetworks/default/batchOutboundRules?api-version=2025-10-01-preview" + +az rest --method POST --uri $uri --body $body +``` + +## Notes + +- This is a POST action, not supported directly in Bicep +- Run this after the main Bicep deployment completes +- The managed network must already exist before running this command + diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/README.md b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/README.md new file mode 100644 index 000000000..a48c347d3 --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/README.md @@ -0,0 +1,357 @@ +# AI Foundry with Managed VNet - Terraform + +This Terraform configuration deploys an Azure AI Foundry environment with complete private networking, including: + +- Virtual Network with private endpoints subnet +- Private DNS Zones for complete private connectivity +- **Azure AI Foundry with Project-level Capability Host** for Agents workloads +- Storage Account, Cosmos DB, and AI Search with private endpoints +- **Managed Virtual Network with outbound rules** for secure agent connectivity +- Role assignments with RBAC and ABAC conditions for secure access + +## Recent Updates + +### December 2024 - Enhanced Capability Host Configuration +- ✅ **Project-level Capability Host**: Aligned with Bicep template architecture +- ✅ **Connection-based Configuration**: Explicit storage, vector store, and thread storage connections +- ✅ **Enhanced RBAC**: Complete role assignments matching Bicep configuration +- ✅ **Post-Deployment RBAC**: Storage Blob Data Owner with ABAC conditions +- ✅ **Cosmos DB Built-in Data Contributor**: Container-level access after capability host creation +- ✅ **VM and Bastion Removed**: Focused deployment on AI Foundry core infrastructure + +## Prerequisites + +- Azure subscription +- Terraform >= 1.0 +- Azure CLI installed and authenticated (`az login`) +- Appropriate Azure RBAC permissions to create resources + +## Configuration + +1. Copy the example variables file: + ```bash + cp terraform.tfvars.example terraform.tfvars + ``` + +2. Edit `terraform.tfvars` with your values: + - Resource group name (must already exist) + - Location + - Network configuration + - VM credentials (use secure values!) + +3. ReSubscription ID + - Resource group name + - Location + - Feature flags (enable_networking, enable_storage, enable_cosmos, enable_aisearch, enable_dns) + - Network configuration (if enabling networking +### Initial Deployment + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Review the planned changes:** + ```bash + terraform plan -out=tfplan + ``` + +3. **Apply the configuration:** + ```bash + terraform apply tfplan + ``` + + The deployment will crprivate endpoints subnet (if enabled) + - Private DNS zones (if enabled) + - Storage Account, Cosmos DB, AI Search (if enabled) + - AI Foundry account with managed network + - Private endpoints for all services (if networking enabled) + - **Project Capability Host** with proper role assignments + - All required RBAC role assignments with proper dependencie + - **Project Capability Host** with proper role assignments + +### Updating Existing Infrastructure + +If you already have infrastructure deployed and want to add the new features: + +1. **Review changes:** + ```bash + terraform plan -out=tfplan + ``` + +2. **Apply updates:** + ```bash + terraform apply tfplan + ``` + + **Expected Changes:** + - **New Resources**: + - Project Capability Host + - Network Connection Approver role assignment (AI Foundry account) + - Cosmos DB Account Reader role assignment + - Cosmos DB Operator role assignment + - Storage Blob Data Owner role (with ABAC condition) + - Cosmos DB Built-in Data Contributor role + +3. **Important Notes:** + - ✅ **Role Propagation**: Role assignments may take 5-10 minutes to take effect + - ✅ **Dependency Management**: Terraform handles proper sequencing of role assignments + - ⚠️ **Feature Flags**: Use variables to control which components are deployed + +## Post-Deployment Configuration + +### Verify Capability Host Configuration + +The deployment automatically configures the AI Foundry Project Capability Host with: +- **Storage Connections**: Links to Azure Storage Account +- **Thread Storage Connections**: Links to Cosmos DB for conversation history +- **Vector Store Connections**: Links to AI Search for vector storage + +Verify in Azure Portal: +1. Navigate to AI Foundry resource +2. Go to **Projects** > **firstProject** +3. Check **Capability Hosts** section +4. Verify connections are configured + +### Role Assignments Summary + +The following roles are automatically assigned: + +**Before Capability Host Creation:** +- ✅ Storage Blob Data Contributor (Project → Storage) +- ✅ Search Index Data Contributor (Project → AI Search) +- AI Foundry Account Identity:** +- ✅ **Contributor** (Account → Resource Group) - *Network connection approver role* +- ✅ **Storage Blob Data Contributor** (Account → Storage) - *If storage enabled* +- ✅ **Contributor** (Account → Storage) - *Storage management permissions* + +**Before Capability Host Creation:** +- ✅ **Storage Blob Data Contributor** (Project → Storage) +- ✅ **Search Index Data Contributor** (Project → AI Search) +- ✅ **Search Service Contributor** (Project → AI Search) +- ✅ **Cosmos DB Account Reader Role** (Project → Cosmos DB) +- ✅ **Cosmos DB Operator** (Project → Cosmos DB) - *Required for capability host* + +**After Capability Host Creation:** +- ✅ **Storage Blob Data Owner** (Project → Storage) - *With ABAC condition for agent containers* +- ✅ **Cosmos DB Built-in Data Contributor** (Project → Cosmos DB) - *For thread storage* +If you need additional managed VNet outbound rules (e.g., for Cosmos DB or AI Search), configure them using Azure CLI: + +**For Cosmos DB:** +```bash +COSMOS_ID=$(terraform output -raw cosmos_account_id) +AI_FOUNDRY_NAME=$(terraform output -raw ai_foundry_name) +RG_NAME=$(terraform output -raw resource_group_name) +SUBSCRIPTION_ID=$(az account show --query id -o tsv) + +az rest --method PUT \ + --uri "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RG_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_FOUNDRY_NAME}/managedNetworks/default/outboundRules/cosmos-pe-rule?api-version=2025-10-01-preview" \ + --body '{ + "properties": { + "type": "PrivateEndpoint", + "destination": { + "serviceResourceId": "'${COSMOS_ID}'", + "subresourceTarget": "Sql" + }, + "category": "UserDefined" + } + }' +``` + +**For AI Search:** +```bash +SEARCH_ID=$(terraform output -raw aisearch_id) + +az rest --method PUT \ + --uri "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RG_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_FOUNDRY_NAME}/managedNetworks/default/outboundRules/search-pe-rule?api-version=2025-10-01-preview" \ + --body '{ + "properties": { + "type": "PrivateEndpoint", + "destination": { + "serviceResourceId": "'${SEARCH_ID}'", + "subresourceTarget": "searchService" + }, + "category": "UserDefined" + } + }' +``` + +**Note:** The storage outbound rule is already created by Terraform. Additional rules are only needed for specific scenarios. + +## Connecting to the VM +``` +┌─────────────────────────────────────────────────────────┐ +│ Virtual Network │ +│ 10.0.0.0/16 │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ Private Endpoints Subnet (10.0.1.0/24) │ │ +│ │ - AI Foundry Private Endpoint │ │ +│ │ - Storage Blob Private Endpoint │ │ +│ │ - Storage File Private Endpoint │ │ +│ │ - Storage Table Private Endpoint │ │ +│ │ - Storage Queue Private Endpoint │ │ +│ │ - Cosmos DB Private Endpoint │ │ +│ │ - AI Search Private Endpoint │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ VM Subnet (10.0.2.0/24) │ │ +│ │ - Windows Server 2025 VM │ │ +│ │ + System Managed Identity │ │ +│ │ + AADLoginForWindows Extension │ │ +│ │ + No Public IP │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ Azure Bastion Subnet (10.0.3.0/26) │ │ +│ │ - Azure Bastion Standard │ │ +│ │ + Tunneling Enabled │ │ +│ │ + File Copy Enabled │ │ +│ │ + IP Connect Enabled │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────┘ + │ + │ Private Endpoints + ▼ + ┌───────────────────────────────┐ + │ Azure AI Foundry │ + │ + System Managed Identity │ + │ + Managed Network V2 │ + │ + Project: firstProject │ + │ - Capability Host │ + │ - Storage Connection │ + │ - Cosm(Optional) │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ Private Endpoints Subnet (10.0.1.0/24) │ │ +│ │ - AI Foundry Private Endpoint │ │ +│ │ - Storage Blob Private Endpoint │ │ +│ │ - Storage File Private Endpoint │ │ +│ │ - Storage Table Private Endpoint │ │ +│ │ - Storage Queue Private Endpoint │ │ +│ │ - Cosmos DB Private Endpoint │ │ +│ │ - AI Search Private Endpoint │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────┘ + │ + │ Private Endpoints + ▼ + ┌───────────────────────────────┐ + │ Azure AI Foundry │ + │ + System Managed Identity │ + │ + Managed Network V2 │ + │ + Project: firstProject │ + │ - Capability Host │ + │ - Storage Connection │ + │ - Cosmos DB Connection │ + │ - AI Search Connection │ + └───────────────────────────────┘ + │ + │ Managed Outbound (PE) + ▼ + ┌───────────────────────────────┐ + │ Storage Account │ + │ (Private Access Only) │ + │ + Blob, File, Table, Queue │ + └───────────────────────────────┘ + + ┌───────────────────────────────┐ + │ Cosmos DB for NoSQL │ + │ (Thread Storage) │ + └───────────────────────────────┘ + + ┌───────────────────────────────┐ + │ AI Search │ + │ (Vector Store) │ + └───────────────────────────────┘ +``` + +## Key Features + +| Feature | This Configuration | +|---------|-------------------| +| Capability Host Level | ✅ **Project** | +| Connection References | ✅ **Explicit** | +| AI Foundry Account RBAC | ✅ **Network Connection Approver + Storage Permissions** | +| Project Identity RBAC | ✅ **Complete role assignments** | +| Cosmos DB RBAC | ✅ **Account Reader + Operator + Built-in Contributor** | +| Storage RBAC | ✅ **Blob Contributor + Blob Owner (ABAC)** | +| Agent Workloads | ✅ **Ready** | +| Private Networking | ✅ **Optional (via feature flags)** | +| Modular Deployment | ✅ **Feature flags for all components** + az vm extension list --resource-group --vm-name + ``` +2. Check role assignment exists: + ```bash + az role assignment list --assignee --scope + ``` +3. Wait 5-10 minutes for role propagation + +### Capability Host Connection Issues +1. Verify connections exist in Azure Portal: AI Foundry → Projects → Connections +2. Check role assignments are complete: + ```bash + terraform output + az role assignment list --scope + ``` +3. Ensure Cosmos DB Operator role was assigned before capability host creation + +``` + +## Cleanup + +To destroy all resources: + +```bash +terraform destroy +``` + +Confirm by typing `yes` when prompted. + +## Security Considerations + +- All PaaS services have public network access disabled +- VM has no public IP address - access only via Bastion +- Storage account is only accessible via private endpoints +- AI Foundry uses managed network with controlled outbound rules +- All DNS resolution happens via private DNS zones +- Managed identity with RBAC for service-to-service authentication + +## Files + +- `providers.tf` - Provider configuration +- `variables.tf` - Input variables +- `main.tf` - Data sources and locals +- `network.tf` - VNet and private endpoints subnet +- `dns.tf` - Private DNS zones and VNet links +- `storage.tf` - Storage account and private endpoints (blob, file, table, queue) +- `cosmos.tf` - Cosmos DB account and private endpoint +- `aisearch.tf` - AI Search service and private endpoint +- `ai-foundry.tf` - AI Foundry account, project, capability host, connections, and complete + +## Notes +Uses project-level capability host matching Bicep template +- **Workspace ID Formatting**: Automatically extracts and formats project workspace ID for ABAC conditions +- **Role Assignment Timing**: + - Network Connection Approver assigned to AI Foundry account identity + - Cosmos DB Operator and Account Reader must be assigned **before** capability host creation + - Storage Blob Data Owner and Cosmos Built-in Contributor assigned **after** +- **ABAC Conditions**: Storage role includes condition for agent-specific containers +- **Role Propagation**: RBAC assignments may take 5-10 minutes to fully propagate +- **Managed Network**: Uses Managed Virtual Network V2 with AllowInternetOutbound isolation mode +- **Feature Flags**: All major components can be enabled/disabled via variables +- **Modular Design**: Deploy only what you need using feature flags +- Azure resources: Check Azure Portal for resource status +- Private connectivity: Verify DNS resolution from the VM using `nslookup` +- Managed network: Use Azure Portal or REST API to verify outbound rules + (when networking enabled) +- Storage account is only accessible via private endpoints (when networking enabled) +- AI Foundry uses managed network with controlled outbound rules +- All DNS resolution happens via private DNS zones (when DNS enabled) +- Managed identity with RBAC for service-to-service authentication +- ABAC conditions limit Storage Blob Data Owner to specific container patterns +- Network Connection Approver role enables AI Foundry to manage connections +- No VM or Bastion included - focused on AI Foundry infrastructur \ No newline at end of file diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/ai-foundry.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/ai-foundry.tf new file mode 100644 index 000000000..82e664ecf --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/ai-foundry.tf @@ -0,0 +1,409 @@ +# AI Foundry / Cognitive Services Account using AzAPI for preview features +resource "azapi_resource" "cognitive_account" { + type = "Microsoft.CognitiveServices/accounts@2025-10-01-preview" + name = local.foundry_name + location = azurerm_resource_group.main.location + parent_id = azurerm_resource_group.main.id + + schema_validation_enabled = false + + identity { + type = "SystemAssigned" + } + + body = { + sku = { + name = "S0" + } + kind = "AIServices" + properties = merge( + { + apiProperties = {} + customSubDomainName = local.foundry_name + disableLocalAuth = true + networkAcls = { + defaultAction = "Deny" + virtualNetworkRules = [] + ipRules = [] + } + allowProjectManagement = true + defaultProject = "firstProject" + associatedProjects = ["firstProject"] + networkInjections = [ + { + scenario = "agent" + subnetArmId = "" + useMicrosoftManagedNetwork = true + } + ] + publicNetworkAccess = "Disabled" + }, + var.enable_storage ? { + userOwnedStorage = [ + { + resourceId = azurerm_storage_account.main[0].id + } + ] + } : {}, + var.enable_cosmos ? { + userOwnedCosmosDB = [ + { + resourceId = azurerm_cosmosdb_account.main[0].id + } + ] + } : {}, + var.enable_aisearch ? { + userOwnedSearch = [ + { + resourceId = azurerm_search_service.main[0].id + } + ] + } : {} + ) + } + + tags = { + environment = "lab" + } + + lifecycle { + ignore_changes = [ + body["properties"]["restore"] + ] + } + + depends_on = [ + azurerm_storage_account.main, + azurerm_cosmosdb_account.main, + azurerm_search_service.main + ] +} + +# Role Assignment: Azure AI Network Connection Approver for AI Foundry Account Identity +# This role is required for the AI Foundry account to approve managed network connections +resource "azurerm_role_assignment" "foundry_network_connection_approver" { + scope = azurerm_resource_group.main.id + role_definition_name = "Contributor" + principal_id = azapi_resource.cognitive_account.identity[0].principal_id +} + +# Role Assignment: Storage Blob Data Contributor +resource "azurerm_role_assignment" "foundry_storage_blob" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage Blob Data Contributor" + principal_id = azapi_resource.cognitive_account.identity[0].principal_id +} + +# Role Assignment: Contributor on Storage Account +resource "azurerm_role_assignment" "foundry_storage_contributor" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Contributor" + principal_id = azapi_resource.cognitive_account.identity[0].principal_id +} + +# Private Endpoint for AI Foundry +resource "azurerm_private_endpoint" "cognitive_services" { + count = var.enable_networking ? 1 : 0 + name = "${local.foundry_name}-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${local.foundry_name}-psc" + private_connection_resource_id = azapi_resource.cognitive_account.id + is_manual_connection = false + subresource_names = ["account"] + } + + dynamic "private_dns_zone_group" { + for_each = var.enable_dns ? [1] : [] + content { + name = "cognitive-services-dns-zone-group" + private_dns_zone_ids = [ + azurerm_private_dns_zone.cognitive_services[0].id, + azurerm_private_dns_zone.openai[0].id, + azurerm_private_dns_zone.aifoundry_api[0].id, + azurerm_private_dns_zone.aifoundry_notebooks[0].id, + azurerm_private_dns_zone.aifoundry_services[0].id + ] + } + } +} + +# Managed Network Configuration +resource "azapi_resource" "managed_network" { + type = "Microsoft.CognitiveServices/accounts/managedNetworks@2025-10-01-preview" + name = "default" + parent_id = azapi_resource.cognitive_account.id + + schema_validation_enabled = false + + body = { + properties = { + managedNetwork = { + isolationMode = "AllowInternetOutbound" + managedNetworkKind = "V2" + } + } + } +} + +# Managed Network Outbound Rule for Storage Account +resource "azapi_resource" "storage_outbound_rule" { + count = var.enable_storage ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview" + name = "storage-blob-rule" + parent_id = azapi_resource.managed_network.id + + schema_validation_enabled = false + + body = { + properties = { + type = "PrivateEndpoint" + destination = { + serviceResourceId = azurerm_storage_account.main[0].id + subresourceTarget = "blob" + } + category = "UserDefined" + } + } + + depends_on = [ + azurerm_role_assignment.foundry_storage_blob, + azurerm_role_assignment.foundry_storage_contributor + ] +} + +# AI Foundry Project +resource "azapi_resource" "ai_foundry_project" { + type = "Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview" + name = "firstProject" + location = var.location + parent_id = azapi_resource.cognitive_account.id + + schema_validation_enabled = false + + identity { + type = "SystemAssigned" + } + + body = { + properties = { + description = "AI Foundry Project" + } + } + + depends_on = [ + azapi_resource.cognitive_account + ] +} + +# Wait for RBAC propagation +resource "time_sleep" "wait_rbac" { + create_duration = "60s" + + depends_on = [ + azurerm_role_assignment.foundry_storage_blob, + azurerm_role_assignment.foundry_storage_contributor + ] +} + +# Role Assignment: Project Identity - Storage Blob Data Contributor +resource "azurerm_role_assignment" "project_storage_blob" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage Blob Data Contributor" + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id +} + +# Role Assignment: Project Identity - AI Search Index Data Contributor +resource "azurerm_role_assignment" "project_search_index" { + count = var.enable_aisearch ? 1 : 0 + scope = azurerm_search_service.main[0].id + role_definition_name = "Search Index Data Contributor" + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id +} + +# Role Assignment: Project Identity - AI Search Service Contributor +resource "azurerm_role_assignment" "project_search_contributor" { + count = var.enable_aisearch ? 1 : 0 + scope = azurerm_search_service.main[0].id + role_definition_name = "Search Service Contributor" + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id +} + +# Role Assignment: Project Identity - Cosmos DB Account Reader Role +# This role allows the project to read metadata about the Cosmos DB account +resource "azurerm_role_assignment" "project_cosmos_account_reader" { + count = var.enable_cosmos ? 1 : 0 + scope = azurerm_cosmosdb_account.main[0].id + role_definition_name = "Cosmos DB Account Reader Role" + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id +} + +# Role Assignment: Project Identity - Cosmos DB Operator (required before capability host) +resource "azurerm_role_assignment" "project_cosmos_operator" { + count = var.enable_cosmos ? 1 : 0 + scope = azurerm_cosmosdb_account.main[0].id + role_definition_name = "Cosmos DB Operator" + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id +} + +# AI Foundry Project Capability Host (matches Bicep implementation) +# This configures the capability host at the project level with connection references +resource "azapi_resource" "project_capability_host" { + count = var.enable_storage && var.enable_cosmos && var.enable_aisearch ? 1 : 0 + + type = "Microsoft.CognitiveServices/accounts/projects/capabilityHosts@2025-04-01-preview" + name = "caphostproj" + parent_id = azapi_resource.ai_foundry_project.id + + schema_validation_enabled = false + + body = { + properties = { + capabilityHostKind = "Agents" + vectorStoreConnections = [azurerm_search_service.main[0].name] + storageConnections = [azurerm_storage_account.main[0].name] + threadStorageConnections = [azurerm_cosmosdb_account.main[0].name] + } + } + + depends_on = [ + azapi_resource.ai_foundry_project, + azapi_resource.conn_aisearch, + azapi_resource.conn_cosmosdb, + azapi_resource.conn_storage, + azurerm_role_assignment.project_cosmos_account_reader, + azurerm_role_assignment.project_cosmos_operator, + azurerm_role_assignment.project_storage_blob, + azurerm_role_assignment.project_search_index, + azurerm_role_assignment.project_search_contributor + ] +} + +# Connection: AI Search +resource "azapi_resource" "conn_aisearch" { + count = var.enable_aisearch ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview" + name = azurerm_search_service.main[0].name + parent_id = azapi_resource.ai_foundry_project.id + + schema_validation_enabled = false + + body = { + properties = { + category = "CognitiveSearch" + target = "https://${azurerm_search_service.main[0].name}.search.windows.net" + authType = "AAD" + metadata = { + ApiType = "Azure" + ResourceId = azurerm_search_service.main[0].id + location = azurerm_search_service.main[0].location + } + } + } +} + +# Connection: Cosmos DB +resource "azapi_resource" "conn_cosmosdb" { + count = var.enable_cosmos ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview" + name = azurerm_cosmosdb_account.main[0].name + parent_id = azapi_resource.ai_foundry_project.id + + schema_validation_enabled = false + + body = { + properties = { + category = "CosmosDb" + target = azurerm_cosmosdb_account.main[0].endpoint + authType = "AAD" + metadata = { + ApiType = "Azure" + ResourceId = azurerm_cosmosdb_account.main[0].id + location = azurerm_cosmosdb_account.main[0].location + } + } + } +} + +# Connection: Storage Account +resource "azapi_resource" "conn_storage" { + count = var.enable_storage ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview" + name = azurerm_storage_account.main[0].name + parent_id = azapi_resource.ai_foundry_project.id + + schema_validation_enabled = false + + body = { + properties = { + category = "AzureStorageAccount" + target = azurerm_storage_account.main[0].primary_blob_endpoint + authType = "AAD" + metadata = { + ApiType = "Azure" + ResourceId = azurerm_storage_account.main[0].id + location = azurerm_storage_account.main[0].location + } + } + } +} + +# Local variable to format project workspace ID as GUID +# The project.properties.internalId comes back as a 32-char hex string +# We need to format it as 8-4-4-4-12 GUID format +locals { + # Extract the workspace ID from the project output + project_workspace_id_raw = try(jsondecode(azapi_resource.ai_foundry_project.output).properties.internalId, "") + + # Format as GUID if we have a valid 32-character string + project_workspace_id_guid = length(local.project_workspace_id_raw) == 32 ? format( + "%s-%s-%s-%s-%s", + substr(local.project_workspace_id_raw, 0, 8), + substr(local.project_workspace_id_raw, 8, 4), + substr(local.project_workspace_id_raw, 12, 4), + substr(local.project_workspace_id_raw, 16, 4), + substr(local.project_workspace_id_raw, 20, 12) + ) : "" +} + +# Role Assignment: Storage Blob Data Owner with ABAC condition +# This must be assigned AFTER the capability host is created +# The condition restricts access to containers starting with workspace ID and ending with -azureml-agent +resource "azurerm_role_assignment" "project_storage_blob_owner_containers" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage Blob Data Owner" + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id + + # ABAC condition matching Bicep template + condition = "((!(ActionMatches{'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/read'}) AND !(ActionMatches{'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/filter/action'}) AND !(ActionMatches{'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write'})) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringStartsWithIgnoreCase '${local.project_workspace_id_guid}' AND @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringLikeIgnoreCase '*-azureml-agent'))" + condition_version = "2.0" + + depends_on = [ + azapi_resource.project_capability_host + ] +} + +# Role Assignment: Cosmos DB Built-in Data Contributor +# This must be assigned AFTER the capability host is created +resource "azurerm_cosmosdb_sql_role_assignment" "project_cosmos_builtin_contributor" { + count = var.enable_cosmos ? 1 : 0 + resource_group_name = azurerm_resource_group.main.name + account_name = azurerm_cosmosdb_account.main[0].name + + # Cosmos DB Built-in Data Contributor role + role_definition_id = "${azurerm_cosmosdb_account.main[0].id}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002" + + principal_id = azapi_resource.ai_foundry_project.identity[0].principal_id + scope = azurerm_cosmosdb_account.main[0].id + + depends_on = [ + azapi_resource.project_capability_host + ] +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/aisearch.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/aisearch.tf new file mode 100644 index 000000000..a02e4872e --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/aisearch.tf @@ -0,0 +1,61 @@ +# AI Search Service +resource "azurerm_search_service" "main" { + count = var.enable_aisearch ? 1 : 0 + name = local.aisearch_name + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + sku = "standard" + replica_count = 1 + partition_count = 1 + + public_network_access_enabled = true + + identity { + type = "SystemAssigned" + } + + tags = { + environment = "lab" + } +} + +# Private Endpoint for AI Search +resource "azurerm_private_endpoint" "aisearch" { + count = var.enable_aisearch && var.enable_networking ? 1 : 0 + name = "${azurerm_search_service.main[0].name}-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_search_service.main[0].name}-psc" + private_connection_resource_id = azurerm_search_service.main[0].id + is_manual_connection = false + subresource_names = ["searchService"] + } + + private_dns_zone_group { + name = "aisearch-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.aisearch[0].id] + } + + tags = { + environment = "lab" + } +} + +# Role Assignment: Current user needs Search Service Contributor +resource "azurerm_role_assignment" "current_user_search_contributor" { + count = var.enable_aisearch ? 1 : 0 + scope = azurerm_search_service.main[0].id + role_definition_name = "Search Service Contributor" + principal_id = data.azurerm_client_config.current.object_id +} + +# Role Assignment: Current user needs Search Index Data Contributor +resource "azurerm_role_assignment" "current_user_search_index" { + count = var.enable_aisearch ? 1 : 0 + scope = azurerm_search_service.main[0].id + role_definition_name = "Search Index Data Contributor" + principal_id = data.azurerm_client_config.current.object_id +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/cosmos.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/cosmos.tf new file mode 100644 index 000000000..326f7a2e1 --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/cosmos.tf @@ -0,0 +1,63 @@ +# Cosmos DB Account +resource "azurerm_cosmosdb_account" "main" { + count = var.enable_cosmos ? 1 : 0 + name = local.cosmos_name + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + offer_type = "Standard" + kind = "GlobalDocumentDB" + + consistency_policy { + consistency_level = "Session" + max_interval_in_seconds = 5 + max_staleness_prefix = 100 + } + + geo_location { + location = azurerm_resource_group.main.location + failover_priority = 0 + } + + public_network_access_enabled = false + network_acl_bypass_for_azure_services = false + local_authentication_disabled = true + + tags = { + environment = "lab" + } +} + +# Private Endpoint for Cosmos DB +resource "azurerm_private_endpoint" "cosmos" { + count = var.enable_cosmos && var.enable_networking ? 1 : 0 + name = "${azurerm_cosmosdb_account.main[0].name}-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_cosmosdb_account.main[0].name}-psc" + private_connection_resource_id = azurerm_cosmosdb_account.main[0].id + is_manual_connection = false + subresource_names = ["Sql"] + } + + private_dns_zone_group { + name = "cosmos-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.cosmos[0].id] + } + + tags = { + environment = "lab" + } +} + +# Role Assignment: Current user needs Cosmos DB Built-in Data Contributor +resource "azurerm_cosmosdb_sql_role_assignment" "current_user" { + count = var.enable_cosmos ? 1 : 0 + resource_group_name = azurerm_resource_group.main.name + account_name = azurerm_cosmosdb_account.main[0].name + role_definition_id = "${azurerm_cosmosdb_account.main[0].id}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002" + principal_id = data.azurerm_client_config.current.object_id + scope = azurerm_cosmosdb_account.main[0].id +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/dns.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/dns.tf new file mode 100644 index 000000000..65c8201ee --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/dns.tf @@ -0,0 +1,183 @@ +# Private DNS Zones +resource "azurerm_private_dns_zone" "cognitive_services" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.cognitiveservices.azure.com" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "storage_blob" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.blob.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "storage_file" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.file.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "storage_table" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.table.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "storage_queue" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.queue.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "key_vault" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.vaultcore.azure.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "container_registry" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.azurecr.io" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "openai" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.openai.azure.com" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "aifoundry_api" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.api.azureml.ms" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "aifoundry_notebooks" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.notebooks.azure.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "aifoundry_services" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.services.ai.azure.com" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "cosmos" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.documents.azure.com" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "aisearch" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.search.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +# VNet Links +resource "azurerm_private_dns_zone_virtual_network_link" "cognitive_services" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.cognitive_services[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "storage_blob" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-blob-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_blob[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "storage_file" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-file-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_file[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "storage_table" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-table-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_table[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "storage_queue" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-queue-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_queue[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "key_vault" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-keyvault-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.key_vault[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "container_registry" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-acr-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.container_registry[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "openai" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-openai-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.openai[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "aifoundry_api" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-aifoundryapi-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.aifoundry_api[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "aifoundry_notebooks" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-aifoundrynb-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.aifoundry_notebooks[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "aifoundry_services" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-aifoundrysvc-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.aifoundry_services[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "cosmos" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-cosmos-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.cosmos[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "aisearch" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-aisearch-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.aisearch[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/main.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/main.tf new file mode 100644 index 000000000..05988086d --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/main.tf @@ -0,0 +1,23 @@ +resource "random_id" "suffix" { + byte_length = 4 +} + +data "azurerm_client_config" "current" {} + +locals { + resource_suffix = random_id.suffix.hex + rg_name = "${var.resource_group_name}-${local.resource_suffix}" + foundry_name = "${var.foundry_identifier}-${local.resource_suffix}" + storage_name = "st${local.resource_suffix}" + aisearch_name = "srch-${local.resource_suffix}" + cosmos_name = "cosmos-${local.resource_suffix}" +} + +resource "azurerm_resource_group" "main" { + name = local.rg_name + location = var.location + + tags = { + environment = "lab" + } +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/network.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/network.tf new file mode 100644 index 000000000..331a6c2f6 --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/network.tf @@ -0,0 +1,18 @@ +# Virtual Network +resource "azurerm_virtual_network" "main" { + count = var.enable_networking ? 1 : 0 + name = var.vnet_name + location = var.location + resource_group_name = azurerm_resource_group.main.name + address_space = [var.vnet_address_prefix] +} + +# Subnets +resource "azurerm_subnet" "private_endpoints" { + count = var.enable_networking ? 1 : 0 + name = var.private_endpoints_subnet_name + resource_group_name = azurerm_resource_group.main.name + virtual_network_name = azurerm_virtual_network.main[0].name + address_prefixes = [var.private_endpoints_subnet_prefix] + default_outbound_access_enabled = true +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/outputs.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/outputs.tf new file mode 100644 index 000000000..a1170b0c9 --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/outputs.tf @@ -0,0 +1,106 @@ +# Outputs +output "resource_group_name" { + description = "The name of the resource group" + value = azurerm_resource_group.main.name +} + +output "resource_group_location" { + description = "The location of the resource group" + value = azurerm_resource_group.main.location +} + +output "vnet_id" { + description = "The ID of the virtual network" + value = var.enable_networking ? azurerm_virtual_network.main[0].id : null +} + +output "vnet_name" { + description = "The name of the virtual network" + value = var.enable_networking ? azurerm_virtual_network.main[0].name : null +} + +output "private_endpoints_subnet_id" { + description = "The ID of the private endpoints subnet" + value = var.enable_networking ? azurerm_subnet.private_endpoints[0].id : null +} + +output "storage_account_id" { + description = "The ID of the storage account" + value = var.enable_storage ? azurerm_storage_account.main[0].id : null +} + +output "storage_account_name" { + description = "The name of the storage account" + value = var.enable_storage ? azurerm_storage_account.main[0].name : null +} + +output "cosmos_account_id" { + description = "The ID of the Cosmos DB account" + value = var.enable_cosmos ? azurerm_cosmosdb_account.main[0].id : null +} + +output "cosmos_account_name" { + description = "The name of the Cosmos DB account" + value = var.enable_cosmos ? azurerm_cosmosdb_account.main[0].name : null +} + +output "cosmos_account_endpoint" { + description = "The endpoint of the Cosmos DB account" + value = var.enable_cosmos ? azurerm_cosmosdb_account.main[0].endpoint : null +} + +output "aisearch_id" { + description = "The ID of the AI Search service" + value = var.enable_aisearch ? azurerm_search_service.main[0].id : null +} + +output "aisearch_name" { + description = "The name of the AI Search service" + value = var.enable_aisearch ? azurerm_search_service.main[0].name : null +} + +output "aisearch_endpoint" { + description = "The endpoint of the AI Search service" + value = var.enable_aisearch ? "https://${azurerm_search_service.main[0].name}.search.windows.net" : null +} + +output "ai_foundry_id" { + description = "The ID of the AI Foundry / Cognitive Services account" + value = azapi_resource.cognitive_account.id +} + +output "ai_foundry_name" { + description = "The name of the AI Foundry / Cognitive Services account" + value = azapi_resource.cognitive_account.name +} + +output "ai_foundry_endpoint" { + description = "The endpoint of the AI Foundry / Cognitive Services account" + value = try(jsondecode(azapi_resource.cognitive_account.output).properties.endpoint, "") +} + +output "ai_foundry_principal_id" { + description = "The principal ID of the AI Foundry managed identity" + value = azapi_resource.cognitive_account.identity[0].principal_id + sensitive = true +} + +output "private_dns_zone_ids" { + description = "Map of private DNS zone IDs" + value = var.enable_dns ? { + cognitive_services = azurerm_private_dns_zone.cognitive_services[0].id + storage_blob = azurerm_private_dns_zone.storage_blob[0].id + storage_file = azurerm_private_dns_zone.storage_file[0].id + storage_table = azurerm_private_dns_zone.storage_table[0].id + storage_queue = azurerm_private_dns_zone.storage_queue[0].id + key_vault = azurerm_private_dns_zone.key_vault[0].id + container_registry = azurerm_private_dns_zone.container_registry[0].id + openai = azurerm_private_dns_zone.openai[0].id + aifoundry_api = azurerm_private_dns_zone.aifoundry_api[0].id + aifoundry_notebooks = azurerm_private_dns_zone.aifoundry_notebooks[0].id + aifoundry_services = azurerm_private_dns_zone.aifoundry_services[0].id + cosmos = azurerm_private_dns_zone.cosmos[0].id + aisearch = azurerm_private_dns_zone.aisearch[0].id + aifoundry_services = azurerm_private_dns_zone.aifoundry_services[0].id + } : {} +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/providers.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/providers.tf new file mode 100644 index 000000000..81b209f6b --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/providers.tf @@ -0,0 +1,29 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + azapi = { + source = "azure/azapi" + version = "~> 2.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + subscription_id = var.subscription_id + storage_use_azuread = true + + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/storage.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/storage.tf new file mode 100644 index 000000000..6a2534709 --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/storage.tf @@ -0,0 +1,144 @@ +# Storage Account +resource "azurerm_storage_account" "main" { + count = var.enable_storage ? 1 : 0 + name = local.storage_name + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "StorageV2" + shared_access_key_enabled = false + allow_nested_items_to_be_public = false + + # Disable public network access - only accessible via private endpoints + public_network_access_enabled = false + + # Ignore changes to queue/blob/file/table properties to avoid validation issues + lifecycle { + ignore_changes = [ + queue_properties, + blob_properties, + share_properties + ] + } + + tags = { + environment = "lab" + } +} + +# Private Endpoint for Blob +resource "azurerm_private_endpoint" "storage_blob" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-blob-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-blob-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["blob"] + } + + private_dns_zone_group { + name = "blob-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_blob[0].id] + } +} + +# Private Endpoint for File +resource "azurerm_private_endpoint" "storage_file" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-file-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-file-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["file"] + } + + private_dns_zone_group { + name = "file-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_file[0].id] + } +} + +# Private Endpoint for Table +resource "azurerm_private_endpoint" "storage_table" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-table-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-table-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["table"] + } + + private_dns_zone_group { + name = "table-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_table[0].id] + } +} + +# Private Endpoint for Queue +resource "azurerm_private_endpoint" "storage_queue" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-queue-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-queue-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["queue"] + } + + private_dns_zone_group { + name = "queue-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_queue[0].id] + } +} + +# Role Assignment: Current user needs Storage Blob Data Contributor for Terraform to manage storage +resource "azurerm_role_assignment" "current_user_storage_blob" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage Blob Data Contributor" + principal_id = data.azurerm_client_config.current.object_id +} + +# Role Assignment: Current user needs Storage Queue Data Contributor +resource "azurerm_role_assignment" "current_user_storage_queue" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage Queue Data Contributor" + principal_id = data.azurerm_client_config.current.object_id +} + +# Role Assignment: Current user needs Storage File Data SMB Share Contributor +resource "azurerm_role_assignment" "current_user_storage_file" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage File Data SMB Share Contributor" + principal_id = data.azurerm_client_config.current.object_id +} + +# Role Assignment: Current user needs Storage Table Data Contributor +resource "azurerm_role_assignment" "current_user_storage_table" { + count = var.enable_storage ? 1 : 0 + scope = azurerm_storage_account.main[0].id + role_definition_name = "Storage Table Data Contributor" + principal_id = data.azurerm_client_config.current.object_id +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/terraform.tfvars.example b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/terraform.tfvars.example new file mode 100644 index 000000000..ec6059f9a --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/terraform.tfvars.example @@ -0,0 +1,20 @@ +# Example Terraform Variables File +# Copy this file to terraform.tfvars and update with your values +subscription_id = "%subscription_id%" +resource_group_name = "foundry-mvnet-rg" +location = "eastus2" +foundry_identifier = "foundry-v1" # Avoid issues with soft delete, helps make the resource unique + +# Feature Flags - Enable/disable resources +enable_networking = true # Enable VNet and private endpoints +enable_storage = true # Enable Storage Account +enable_aisearch = true # Enable AI Search Service +enable_cosmos = true # Enable Cosmos DB +enable_dns = true # Enable Private DNS Zones + +# Network Configuration (only used if enable_networking = true) +vnet_name = "foundry-vnet" +vnet_address_prefix = "10.0.0.0/16" +private_endpoints_subnet_name = "private-endpoints-subnet" +private_endpoints_subnet_prefix = "10.0.1.0/24" + diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/variables.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/variables.tf new file mode 100644 index 000000000..3b2eb6696 --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview-tf/variables.tf @@ -0,0 +1,76 @@ +variable "subscription_id" { + description = "The Azure subscription ID" + type = string +} + +variable "resource_group_name" { + description = "The name of the resource group" + type = string +} + +variable "location" { + description = "Azure region for resources" + type = string + default = "uaenorth" +} + +variable "foundry_identifier" { + description = "Unique identifier for the AI Foundry account name (change this to recreate the foundry account)" + type = string + default = "foundry" +} + +# Feature flags for optional resources +variable "enable_networking" { + description = "Enable VNet, subnets, and network infrastructure" + type = bool + default = false +} + +variable "enable_storage" { + description = "Enable Storage Account and its private endpoints" + type = bool + default = false +} + +variable "enable_aisearch" { + description = "Enable AI Search Service and its private endpoint" + type = bool + default = false +} + +variable "enable_cosmos" { + description = "Enable Cosmos DB Account and its private endpoint" + type = bool + default = false +} + +variable "enable_dns" { + description = "Enable Private DNS Zones and VNet links" + type = bool + default = false +} + +variable "vnet_name" { + description = "Name of the virtual network" + type = string + default = "vnet-aifoundry" +} + +variable "vnet_address_prefix" { + description = "Address prefix for the virtual network" + type = string + default = "10.0.0.0/16" +} + +variable "private_endpoints_subnet_name" { + description = "Name of the private endpoints subnet" + type = string + default = "snet-privateendpoints" +} + +variable "private_endpoints_subnet_prefix" { + description = "Address prefix for private endpoints subnet" + type = string + default = "10.0.1.0/24" +}