Skip to content

Commit 2b31eb0

Browse files
committed
Add AI Foundry module with network ACLs and role assignments
1 parent 0ded935 commit 2b31eb0

File tree

2 files changed

+337
-1
lines changed

2 files changed

+337
-1
lines changed

infra/main.bicep

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,14 @@ module virtualNetwork 'br/public:avm/res/network/virtual-network:0.6.1' = if (vi
590590
addressPrefix: '10.0.0.64/26'
591591
networkSecurityGroupResourceId: networkSecurityGroupBastion.outputs.resourceId
592592
}
593+
{
594+
// Subnet IP address limitation: only class B and C are supported
595+
// https://learn.microsoft.com/en-us/azure/ai-services/agents/how-to/virtual-networks
596+
name: 'ai-foundry-agents' //This exact name is required for Azure Bastion
597+
addressPrefix: '10.0.1.0/24'
598+
//networkSecurityGroupResourceId: networkSecurityGroupBastion.outputs.resourceId
599+
delegation: 'Microsoft.App/environments'
600+
}
593601
{
594602
// If you use your own vnw, you need to provide a subnet that is dedicated exclusively to the Container App environment you deploy. This subnet isn't available to other services
595603
// https://learn.microsoft.com/en-us/azure/container-apps/networking?tabs=workload-profiles-env%2Cazure-cli#custom-vnw-configuration
@@ -729,7 +737,7 @@ var aiFoundryAiServicesModelDeployment = {
729737
version: '2024-08-06'
730738
sku: {
731739
name: 'GlobalStandard'
732-
//Curently the capacity is set to 140 for opinanal performance.
740+
//Currently the capacity is set to 140 for optimal performance.
733741
capacity: aiFoundryAiServicesConfiguration.?modelCapcity ?? 140
734742
}
735743
raiPolicyName: 'Microsoft.Default'
@@ -750,6 +758,11 @@ module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.10.2'
750758
apiProperties: {
751759
//staticsEnabled: false
752760
}
761+
networkAcls: {
762+
defaultAction: 'Allow'
763+
virtualNetworkRules: []
764+
ipRules: []
765+
}
753766
//publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled'
754767
//publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled'
755768
publicNetworkAccess: 'Enabled' //TODO: connection via private endpoint is not working from containers network. Change this when fixed

infra/modules/ai-services.bicep

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
//NOTE: this code is based on AVM module plus missing required capbilities we need for this GSA 'br/public:avm/res/cognitive-services/account:0.10.2'
2+
param name string
3+
param tags object = {}
4+
param location string
5+
param kind string
6+
param sku string
7+
import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
8+
param diagnosticSettings diagnosticSettingFullType[]
9+
param publicNetworkAccess string
10+
param customSubDomainName string
11+
import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
12+
param privateEndpoints privateEndpointSingleServiceType[]
13+
param networkAcls object?
14+
import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
15+
@description('Optional. The managed identity definition for this resource.')
16+
param managedIdentities managedIdentityAllType
17+
param allowedFqdnList array?
18+
param apiProperties object
19+
param disableLocalAuth bool = true
20+
import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
21+
param roleAssignments roleAssignmentType[]?
22+
23+
var formattedUserAssignedIdentities = reduce(
24+
map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }),
25+
{},
26+
(cur, next) => union(cur, next)
27+
) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} }
28+
var identity = !empty(managedIdentities)
29+
? {
30+
type: (managedIdentities.?systemAssigned ?? false)
31+
? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned')
32+
: (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null)
33+
userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null
34+
}
35+
: null
36+
37+
var builtInRoleNames = {
38+
'Cognitive Services Contributor': subscriptionResourceId(
39+
'Microsoft.Authorization/roleDefinitions',
40+
'25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68'
41+
)
42+
'Cognitive Services Custom Vision Contributor': subscriptionResourceId(
43+
'Microsoft.Authorization/roleDefinitions',
44+
'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3'
45+
)
46+
'Cognitive Services Custom Vision Deployment': subscriptionResourceId(
47+
'Microsoft.Authorization/roleDefinitions',
48+
'5c4089e1-6d96-4d2f-b296-c1bc7137275f'
49+
)
50+
'Cognitive Services Custom Vision Labeler': subscriptionResourceId(
51+
'Microsoft.Authorization/roleDefinitions',
52+
'88424f51-ebe7-446f-bc41-7fa16989e96c'
53+
)
54+
'Cognitive Services Custom Vision Reader': subscriptionResourceId(
55+
'Microsoft.Authorization/roleDefinitions',
56+
'93586559-c37d-4a6b-ba08-b9f0940c2d73'
57+
)
58+
'Cognitive Services Custom Vision Trainer': subscriptionResourceId(
59+
'Microsoft.Authorization/roleDefinitions',
60+
'0a5ae4ab-0d65-4eeb-be61-29fc9b54394b'
61+
)
62+
'Cognitive Services Data Reader (Preview)': subscriptionResourceId(
63+
'Microsoft.Authorization/roleDefinitions',
64+
'b59867f0-fa02-499b-be73-45a86b5b3e1c'
65+
)
66+
'Cognitive Services Face Recognizer': subscriptionResourceId(
67+
'Microsoft.Authorization/roleDefinitions',
68+
'9894cab4-e18a-44aa-828b-cb588cd6f2d7'
69+
)
70+
'Cognitive Services Immersive Reader User': subscriptionResourceId(
71+
'Microsoft.Authorization/roleDefinitions',
72+
'b2de6794-95db-4659-8781-7e080d3f2b9d'
73+
)
74+
'Cognitive Services Language Owner': subscriptionResourceId(
75+
'Microsoft.Authorization/roleDefinitions',
76+
'f07febfe-79bc-46b1-8b37-790e26e6e498'
77+
)
78+
'Cognitive Services Language Reader': subscriptionResourceId(
79+
'Microsoft.Authorization/roleDefinitions',
80+
'7628b7b8-a8b2-4cdc-b46f-e9b35248918e'
81+
)
82+
'Cognitive Services Language Writer': subscriptionResourceId(
83+
'Microsoft.Authorization/roleDefinitions',
84+
'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8'
85+
)
86+
'Cognitive Services LUIS Owner': subscriptionResourceId(
87+
'Microsoft.Authorization/roleDefinitions',
88+
'f72c8140-2111-481c-87ff-72b910f6e3f8'
89+
)
90+
'Cognitive Services LUIS Reader': subscriptionResourceId(
91+
'Microsoft.Authorization/roleDefinitions',
92+
'18e81cdc-4e98-4e29-a639-e7d10c5a6226'
93+
)
94+
'Cognitive Services LUIS Writer': subscriptionResourceId(
95+
'Microsoft.Authorization/roleDefinitions',
96+
'6322a993-d5c9-4bed-b113-e49bbea25b27'
97+
)
98+
'Cognitive Services Metrics Advisor Administrator': subscriptionResourceId(
99+
'Microsoft.Authorization/roleDefinitions',
100+
'cb43c632-a144-4ec5-977c-e80c4affc34a'
101+
)
102+
'Cognitive Services Metrics Advisor User': subscriptionResourceId(
103+
'Microsoft.Authorization/roleDefinitions',
104+
'3b20f47b-3825-43cb-8114-4bd2201156a8'
105+
)
106+
'Cognitive Services OpenAI Contributor': subscriptionResourceId(
107+
'Microsoft.Authorization/roleDefinitions',
108+
'a001fd3d-188f-4b5d-821b-7da978bf7442'
109+
)
110+
'Cognitive Services OpenAI User': subscriptionResourceId(
111+
'Microsoft.Authorization/roleDefinitions',
112+
'5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'
113+
)
114+
'Cognitive Services QnA Maker Editor': subscriptionResourceId(
115+
'Microsoft.Authorization/roleDefinitions',
116+
'f4cc2bf9-21be-47a1-bdf1-5c5804381025'
117+
)
118+
'Cognitive Services QnA Maker Reader': subscriptionResourceId(
119+
'Microsoft.Authorization/roleDefinitions',
120+
'466ccd10-b268-4a11-b098-b4849f024126'
121+
)
122+
'Cognitive Services Speech Contributor': subscriptionResourceId(
123+
'Microsoft.Authorization/roleDefinitions',
124+
'0e75ca1e-0464-4b4d-8b93-68208a576181'
125+
)
126+
'Cognitive Services Speech User': subscriptionResourceId(
127+
'Microsoft.Authorization/roleDefinitions',
128+
'f2dc8367-1007-4938-bd23-fe263f013447'
129+
)
130+
'Cognitive Services User': subscriptionResourceId(
131+
'Microsoft.Authorization/roleDefinitions',
132+
'a97b65f3-24c7-4388-baec-2e87135dc908'
133+
)
134+
Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
135+
Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
136+
Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
137+
'Role Based Access Control Administrator': subscriptionResourceId(
138+
'Microsoft.Authorization/roleDefinitions',
139+
'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
140+
)
141+
'User Access Administrator': subscriptionResourceId(
142+
'Microsoft.Authorization/roleDefinitions',
143+
'18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'
144+
)
145+
}
146+
147+
var formattedRoleAssignments = [
148+
for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, {
149+
roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains(
150+
roleAssignment.roleDefinitionIdOrName,
151+
'/providers/Microsoft.Authorization/roleDefinitions/'
152+
)
153+
? roleAssignment.roleDefinitionIdOrName
154+
: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName))
155+
})
156+
]
157+
158+
resource cognitiveService 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
159+
name: name
160+
kind: kind
161+
identity: identity
162+
location: location
163+
tags: tags
164+
sku: {
165+
name: sku
166+
}
167+
properties: {
168+
customSubDomainName: customSubDomainName
169+
networkAcls: !empty(networkAcls ?? {})
170+
? {
171+
defaultAction: networkAcls.?defaultAction
172+
virtualNetworkRules: networkAcls.?virtualNetworkRules ?? []
173+
ipRules: networkAcls.?ipRules ?? []
174+
}
175+
: null
176+
publicNetworkAccess: publicNetworkAccess != null
177+
? publicNetworkAccess
178+
: (!empty(networkAcls) ? 'Enabled' : 'Disabled')
179+
allowedFqdnList: allowedFqdnList
180+
apiProperties: apiProperties
181+
disableLocalAuth: disableLocalAuth
182+
encryption: !empty(customerManagedKey)
183+
? {
184+
keySource: 'Microsoft.KeyVault'
185+
keyVaultProperties: {
186+
identityClientId: !empty(customerManagedKey.?userAssignedIdentityResourceId ?? '')
187+
? cMKUserAssignedIdentity.properties.clientId
188+
: null
189+
keyVaultUri: cMKKeyVault.properties.vaultUri
190+
keyName: customerManagedKey!.keyName
191+
keyVersion: !empty(customerManagedKey.?keyVersion ?? '')
192+
? customerManagedKey!.?keyVersion
193+
: last(split(cMKKeyVault::cMKKey.properties.keyUriWithVersion, '/'))
194+
}
195+
}
196+
: null
197+
migrationToken: migrationToken
198+
restore: restore
199+
restrictOutboundNetworkAccess: restrictOutboundNetworkAccess
200+
userOwnedStorage: userOwnedStorage
201+
dynamicThrottlingEnabled: dynamicThrottlingEnabled
202+
}
203+
}
204+
205+
@batchSize(1)
206+
resource cognitiveService_deployments 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [
207+
for (deployment, index) in (deployments ?? []): {
208+
parent: cognitiveService
209+
name: deployment.?name ?? '${name}-deployments'
210+
properties: {
211+
model: deployment.model
212+
raiPolicyName: deployment.?raiPolicyName
213+
versionUpgradeOption: deployment.?versionUpgradeOption
214+
}
215+
sku: deployment.?sku ?? {
216+
name: sku
217+
capacity: sku.?capacity
218+
tier: sku.?tier
219+
size: sku.?size
220+
family: sku.?family
221+
}
222+
}
223+
]
224+
225+
resource cognitiveService_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [
226+
for (diagnosticSetting, index) in (diagnosticSettings ?? []): {
227+
name: diagnosticSetting.?name ?? '${name}-diagnosticSettings'
228+
properties: {
229+
storageAccountId: diagnosticSetting.?storageAccountResourceId
230+
workspaceId: diagnosticSetting.?workspaceResourceId
231+
eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId
232+
eventHubName: diagnosticSetting.?eventHubName
233+
metrics: [
234+
for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): {
235+
category: group.category
236+
enabled: group.?enabled ?? true
237+
timeGrain: null
238+
}
239+
]
240+
logs: [
241+
for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): {
242+
categoryGroup: group.?categoryGroup
243+
category: group.?category
244+
enabled: group.?enabled ?? true
245+
}
246+
]
247+
marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId
248+
logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType
249+
}
250+
scope: cognitiveService
251+
}
252+
]
253+
254+
module cognitiveService_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.10.1' = [
255+
for (privateEndpoint, index) in (privateEndpoints ?? []): {
256+
name: '${uniqueString(deployment().name, location)}-cognitiveService-PrivateEndpoint-${index}'
257+
scope: resourceGroup(
258+
split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2],
259+
split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4]
260+
)
261+
params: {
262+
name: privateEndpoint.?name ?? 'pep-${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}'
263+
privateLinkServiceConnections: privateEndpoint.?isManualConnection != true
264+
? [
265+
{
266+
name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}'
267+
properties: {
268+
privateLinkServiceId: cognitiveService.id
269+
groupIds: [
270+
privateEndpoint.?service ?? 'account'
271+
]
272+
}
273+
}
274+
]
275+
: null
276+
manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true
277+
? [
278+
{
279+
name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}'
280+
properties: {
281+
privateLinkServiceId: cognitiveService.id
282+
groupIds: [
283+
privateEndpoint.?service ?? 'account'
284+
]
285+
requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.'
286+
}
287+
}
288+
]
289+
: null
290+
subnetResourceId: privateEndpoint.subnetResourceId
291+
enableTelemetry: enableReferencedModulesTelemetry
292+
location: privateEndpoint.?location ?? reference(
293+
split(privateEndpoint.subnetResourceId, '/subnets/')[0],
294+
'2020-06-01',
295+
'Full'
296+
).location
297+
lock: privateEndpoint.?lock ?? lock
298+
privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup
299+
roleAssignments: privateEndpoint.?roleAssignments
300+
tags: privateEndpoint.?tags ?? tags
301+
customDnsConfigs: privateEndpoint.?customDnsConfigs
302+
ipConfigurations: privateEndpoint.?ipConfigurations
303+
applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds
304+
customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName
305+
}
306+
}
307+
]
308+
309+
resource cognitiveService_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [
310+
for (roleAssignment, index) in (formattedRoleAssignments ?? []): {
311+
name: roleAssignment.?name ?? guid(cognitiveService.id, roleAssignment.principalId, roleAssignment.roleDefinitionId)
312+
properties: {
313+
roleDefinitionId: roleAssignment.roleDefinitionId
314+
principalId: roleAssignment.principalId
315+
description: roleAssignment.?description
316+
principalType: roleAssignment.?principalType
317+
condition: roleAssignment.?condition
318+
conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set
319+
delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId
320+
}
321+
scope: cognitiveService
322+
}
323+
]

0 commit comments

Comments
 (0)