Skip to content

Commit 6dbc507

Browse files
authored
feat (infra): [sec] support multiple principals when creating role assignments (#77)
* create new module to support multiple rbac principals * Revert "create new module to support multiple rbac principals" This reverts commit 29d76d7. * Address PR Feedback: extract cosmos db operator role assignment to a module * Address PR Feedback: extract agent storage account role assignments to a module * Address PR Feedback: extract ai search role assignments to a module * Address PR Feedback: extract cosmos db sql role assignments to a module * Address PR Feedback: add conditionality when conditions strings are empty * Address PR Feedback: dont use aifoundry as existing resource but pass project id as arg instead
1 parent 54b5485 commit 6dbc507

File tree

5 files changed

+246
-67
lines changed

5 files changed

+246
-67
lines changed

infra-as-code/bicep/ai-foundry-project.bicep

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -47,47 +47,6 @@ resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-previ
4747
resource dataContributorRole 'sqlRoleDefinitions' existing = {
4848
name: '00000000-0000-0000-0000-000000000002'
4949
}
50-
51-
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
52-
resource projectUserThreadContainerWriter 'sqlRoleAssignments' = {
53-
name: guid(aiFoundry::project.id, cosmosDbAccount::dataContributorRole.id, 'enterprise_memory', 'user')
54-
properties: {
55-
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
56-
principalId: aiFoundry::project.identity.principalId
57-
scope: scopeUserContainerId
58-
}
59-
dependsOn: [
60-
aiFoundry::project::aiAgentService
61-
]
62-
}
63-
64-
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
65-
resource projectSystemThreadContainerWriter 'sqlRoleAssignments' = {
66-
name: guid(aiFoundry::project.id, cosmosDbAccount::dataContributorRole.id, 'enterprise_memory', 'system')
67-
properties: {
68-
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
69-
principalId: aiFoundry::project.identity.principalId
70-
scope: scopeSystemContainerId
71-
}
72-
dependsOn: [
73-
cosmosDbAccount::projectUserThreadContainerWriter // Single thread applying these permissions.
74-
aiFoundry::project::aiAgentService
75-
]
76-
}
77-
78-
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
79-
resource projectEntityContainerWriter 'sqlRoleAssignments' = {
80-
name: guid(aiFoundry::project.id, cosmosDbAccount::dataContributorRole.id, 'enterprise_memory', 'entities')
81-
properties: {
82-
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
83-
principalId: aiFoundry::project.identity.principalId
84-
scope: scopeEntityContainerId
85-
}
86-
dependsOn: [
87-
cosmosDbAccount::projectSystemThreadContainerWriter // Single thread applying these permissions.
88-
aiFoundry::project::aiAgentService
89-
]
90-
}
9150
}
9251

9352
resource agentStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = {
@@ -273,58 +232,118 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-06-01' existing =
273232

274233
// Role assignments
275234

276-
resource projectDbCosmosDbOperatorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
277-
name: guid(aiFoundry::project.id, cosmosDbOperatorRole.id, cosmosDbAccount.id)
278-
scope: cosmosDbAccount
279-
properties: {
235+
@description('Grant the AI Foundry Project managed identity Cosmos Db Db Operator user role permissions.')
236+
module projectDbCosmosDbOperatorAssignment './modules/cosmosdbRoleAssignment.bicep' = {
237+
name: 'projectDbCosmosDbOperatorAssignmentDeploy'
238+
params: {
280239
roleDefinitionId: cosmosDbOperatorRole.id
281240
principalId: aiFoundry::project.identity.principalId
282-
principalType: 'ServicePrincipal'
241+
existingAiFoundryProjectId: aiFoundry::project.id
242+
existingCosmosDbAccountName: existingCosmosDbAccountName
283243
}
284244
}
285245

286-
resource projectBlobDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
287-
name: guid(aiFoundry::project.id, storageBlobDataContributorRole.id, agentStorageAccount.id)
288-
scope: agentStorageAccount
289-
properties: {
246+
@description('Grant the AI Foundry Project managed identity Storage Account Blob Data Contributor user role permissions.')
247+
module projectBlobDataContributorAssignment './modules/storageAccountRoleAssignment.bicep' = {
248+
name: 'projectBlobDataContributorAssignmentDeploy'
249+
params: {
290250
roleDefinitionId: storageBlobDataContributorRole.id
291251
principalId: aiFoundry::project.identity.principalId
292-
principalType: 'ServicePrincipal'
252+
existingAiFoundryProjectId: aiFoundry::project.id
253+
existingStorageAccountName: existingStorageAccountName
293254
}
294255
}
295256

296-
resource projectBlobDataOwnerConditionalAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
297-
name: guid(aiFoundry::project.id, storageBlobDataOwnerRole.id, agentStorageAccount.id)
298-
scope: agentStorageAccount
299-
properties: {
300-
principalId: aiFoundry::project.identity.principalId
257+
@description('Grant the AI Foundry Project managed identity Storage Account Blob Data Owner user role permissions.')
258+
module projectBlobDataOwnerConditionalAssignment './modules/storageAccountRoleAssignment.bicep' = {
259+
name: 'projectBlobDataOwnerConditionalAssignmentDeploy'
260+
params: {
301261
roleDefinitionId: storageBlobDataOwnerRole.id
302-
principalType: 'ServicePrincipal'
262+
principalId: aiFoundry::project.identity.principalId
263+
existingAiFoundryProjectId: aiFoundry::project.id
264+
existingStorageAccountName: existingStorageAccountName
303265
conditionVersion: '2.0'
304266
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 \'${workspaceIdAsGuid}\'))'
305267
}
306268
}
307269

308-
resource projectAISearchContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
309-
name: guid(aiFoundry::project.id, azureAISearchServiceContributorRole.id, azureAISearchService.id)
310-
scope: azureAISearchService
311-
properties: {
270+
@description('Grant the AI Foundry Project managed identity AI Search Contributor user role permissions.')
271+
module projectAISearchContributorAssignment './modules/aiSearchRoleAssignment.bicep' = {
272+
name: 'projectAISearchContributorAssignmentDeploy'
273+
params: {
312274
roleDefinitionId: azureAISearchServiceContributorRole.id
313275
principalId: aiFoundry::project.identity.principalId
314-
principalType: 'ServicePrincipal'
276+
existingAiFoundryProjectId: aiFoundry::project.id
277+
existingAISearchAccountName: existingAISearchAccountName
315278
}
316279
}
317280

318-
resource projectAISearchIndexDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
319-
name: guid(aiFoundry::project.id, azureAISearchIndexDataContributorRole.id, azureAISearchService.id)
320-
scope: azureAISearchService
321-
properties: {
281+
@description('Grant the AI Foundry Project managed identity AI Search Data Contributor user role permissions.')
282+
module projectAISearchIndexDataContributorAssignment './modules/aiSearchRoleAssignment.bicep' = {
283+
name: 'projectAISearchIndexDataContributorAssignmentDeploy'
284+
params: {
322285
roleDefinitionId: azureAISearchIndexDataContributorRole.id
323286
principalId: aiFoundry::project.identity.principalId
324-
principalType: 'ServicePrincipal'
287+
existingAiFoundryProjectId: aiFoundry::project.id
288+
existingAISearchAccountName: existingAISearchAccountName
325289
}
326290
}
327291

292+
// Sql Role Assignments
293+
294+
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
295+
module projectUserThreadContainerWriterSqlAssignment './modules/cosmosdbSqlRoleAssignment.bicep' = {
296+
name: 'projectUserThreadContainerWriterSqlAssignmentDeploy'
297+
params: {
298+
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
299+
principalId: aiFoundry::project.identity.principalId
300+
existingAiFoundryProjectId: aiFoundry::project.id
301+
existingCosmosDbAccountName: existingCosmosDbAccountName
302+
existingCosmosDbName: 'enterprise_memory'
303+
existingCosmosCollectionTypeName: 'user'
304+
scopeUserContainerId: scopeUserContainerId
305+
}
306+
dependsOn: [
307+
aiFoundry::project::aiAgentService
308+
]
309+
}
310+
311+
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
312+
module projectSystemThreadContainerWriterSqlAssignment './modules/cosmosdbSqlRoleAssignment.bicep' = {
313+
name: 'projectSystemThreadContainerWriterSqlAssignmentDeploy'
314+
params: {
315+
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
316+
principalId: aiFoundry::project.identity.principalId
317+
existingAiFoundryProjectId: aiFoundry::project.id
318+
existingCosmosDbAccountName: existingCosmosDbAccountName
319+
existingCosmosDbName: 'enterprise_memory'
320+
existingCosmosCollectionTypeName: 'system'
321+
scopeUserContainerId: scopeSystemContainerId
322+
}
323+
dependsOn: [
324+
aiFoundry::project::aiAgentService
325+
projectUserThreadContainerWriterSqlAssignment // Single thread applying these permissions.
326+
]
327+
}
328+
329+
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
330+
module projectEntityContainerWriterSqlAssignment './modules/cosmosdbSqlRoleAssignment.bicep' = {
331+
name: 'projectEntityContainerWriterSqlAssignmentDeploy'
332+
params: {
333+
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
334+
principalId: aiFoundry::project.identity.principalId
335+
existingAiFoundryProjectId: aiFoundry::project.id
336+
existingCosmosDbAccountName: existingCosmosDbAccountName
337+
existingCosmosDbName: 'enterprise_memory'
338+
existingCosmosCollectionTypeName: 'entities'
339+
scopeUserContainerId: scopeEntityContainerId
340+
}
341+
dependsOn: [
342+
aiFoundry::project::aiAgentService
343+
projectSystemThreadContainerWriterSqlAssignment // Single thread applying these permissions.
344+
]
345+
}
346+
328347
// ---- Outputs ----
329348

330349
output aiAgentProjectName string = aiFoundry::project.name
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
This template creates a role assignment for a managed identity to access indexes in AI Search.
3+
4+
To ensure that each deployment has a unique role assignment ID, you can use the guid() function with a seed value that is based in part on the
5+
managed identity's principal ID. However, because Azure Resource Manager requires each resource's name to be available at the beginning of the deployment,
6+
you can't use this approach in the same Bicep file that defines the managed identity. This sample uses a Bicep module to work around this issue.
7+
*/
8+
@description('The Id of the role definition.')
9+
param roleDefinitionId string
10+
11+
@description('The principalId property of the managed identity.')
12+
param principalId string
13+
14+
@description('The existing Azure AI Foundry Project Id.')
15+
@minLength(2)
16+
param existingAiFoundryProjectId string
17+
18+
@description('The existing Azure AI Search account that is going to be used as the Azure AI Foundry Agent vector store (dependency).')
19+
@minLength(1)
20+
param existingAISearchAccountName string
21+
22+
// ---- Existing resources ----
23+
resource azureAISearchService 'Microsoft.Search/searchServices@2025-02-01-preview' existing = {
24+
name: existingAISearchAccountName
25+
}
26+
27+
// ---- Role assignment ----
28+
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
29+
name: guid(resourceGroup().id, existingAiFoundryProjectId, azureAISearchService.id, principalId, roleDefinitionId)
30+
scope: azureAISearchService
31+
properties: {
32+
roleDefinitionId: roleDefinitionId
33+
principalId: principalId
34+
principalType: 'ServicePrincipal'
35+
}
36+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
This template creates a role assignment for a managed identity to access dbs in Cosmos Db.
3+
4+
To ensure that each deployment has a unique role assignment ID, you can use the guid() function with a seed value that is based in part on the
5+
managed identity's principal ID. However, because Azure Resource Manager requires each resource's name to be available at the beginning of the deployment,
6+
you can't use this approach in the same Bicep file that defines the managed identity. This sample uses a Bicep module to work around this issue.
7+
*/
8+
@description('The Id of the role definition.')
9+
param roleDefinitionId string
10+
11+
@description('The principalId property of the managed identity.')
12+
param principalId string
13+
14+
@description('The existing Azure AI Foundry Project Id.')
15+
@minLength(2)
16+
param existingAiFoundryProjectId string
17+
18+
@description('The name of the existing Cosmos Db resource.')
19+
param existingCosmosDbAccountName string
20+
21+
// ---- Existing resources ----
22+
resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2025-05-01-preview' existing = {
23+
name: existingCosmosDbAccountName
24+
}
25+
26+
// ---- Role assignment ----
27+
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
28+
name: guid(resourceGroup().id, existingAiFoundryProjectId, cosmosDbAccount.id, principalId, roleDefinitionId)
29+
scope: cosmosDbAccount
30+
properties: {
31+
roleDefinitionId: roleDefinitionId
32+
principalId: principalId
33+
principalType: 'ServicePrincipal'
34+
}
35+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
This template creates a sql role assignment for a managed identity to access dbs and containers in Cosmos Db.
3+
4+
To ensure that each deployment has a unique role assignment ID, you can use the guid() function with a seed value that is based in part on the
5+
managed identity's principal ID. However, because Azure Resource Manager requires each resource's name to be available at the beginning of the deployment,
6+
you can't use this approach in the same Bicep file that defines the managed identity. This sample uses a Bicep module to work around this issue.
7+
*/
8+
@description('The Id of the role definition.')
9+
param roleDefinitionId string
10+
11+
@description('The principalId property of the managed identity.')
12+
param principalId string
13+
14+
@description('The existing Azure AI Foundry Project Id.')
15+
@minLength(2)
16+
param existingAiFoundryProjectId string
17+
18+
@description('The name of the existing Cosmos Db resource.')
19+
param existingCosmosDbAccountName string
20+
21+
@description('The Cosmos Db name of the sql role assignment.')
22+
param existingCosmosDbName string
23+
24+
@description('The Cosmos Db csontainer type name of the sql role assignment.')
25+
param existingCosmosCollectionTypeName string
26+
27+
@description('The Id of the Scope of the sql role assignment.')
28+
param scopeUserContainerId string
29+
30+
// ---- Existing resources ----
31+
resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2025-05-01-preview' existing = {
32+
name: existingCosmosDbAccountName
33+
}
34+
35+
// ---- Role assignment ----
36+
@description('Assign the project\'s managed identity the ability to read and write data in this collection within enterprise_memory database.')
37+
resource sqlRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2025-05-01-preview' = {
38+
name: guid(resourceGroup().id, existingAiFoundryProjectId, principalId, roleDefinitionId, existingCosmosDbName, existingCosmosCollectionTypeName)
39+
parent: cosmosDbAccount
40+
properties: {
41+
roleDefinitionId: roleDefinitionId
42+
principalId: principalId
43+
scope: scopeUserContainerId
44+
}
45+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
This template creates a role assignment for a managed identity to access blobs in Storage Account.
3+
4+
To ensure that each deployment has a unique role assignment ID, you can use the guid() function with a seed value that is based in part on the
5+
managed identity's principal ID. However, because Azure Resource Manager requires each resource's name to be available at the beginning of the deployment,
6+
you can't use this approach in the same Bicep file that defines the managed identity. This sample uses a Bicep module to work around this issue.
7+
*/
8+
@description('The Id of the role definition.')
9+
param roleDefinitionId string
10+
11+
@description('The principalId property of the managed identity.')
12+
param principalId string
13+
14+
@description('The existing Azure AI Foundry Project Id.')
15+
@minLength(2)
16+
param existingAiFoundryProjectId string
17+
18+
@description('The existing Azure Storage account that is going to be used as the Azure AI Foundry Agent blob store (dependency).')
19+
@minLength(3)
20+
param existingStorageAccountName string
21+
22+
@description('The Azure Storage account role assignment conditions version.')
23+
param conditionVersion string = ''
24+
25+
@description('The Azure Storage account role assignment conditions.')
26+
param condition string = ''
27+
28+
// ---- Existing resources ----
29+
resource agentStorageAccount 'Microsoft.Storage/storageAccounts@2025-01-01' existing = {
30+
name: existingStorageAccountName
31+
}
32+
33+
// ---- Role assignment ----
34+
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
35+
name: guid(resourceGroup().id, existingAiFoundryProjectId, agentStorageAccount.id, principalId, roleDefinitionId)
36+
scope: agentStorageAccount
37+
properties: {
38+
roleDefinitionId: roleDefinitionId
39+
principalId: principalId
40+
principalType: 'ServicePrincipal'
41+
conditionVersion: conditionVersion != '' ? conditionVersion : null
42+
condition: condition != '' ? condition : null
43+
}
44+
}

0 commit comments

Comments
 (0)