Skip to content

Commit d4a6806

Browse files
author
Seth
committed
App - added secrets to keyvault. doc updates
1 parent 5e20f67 commit d4a6806

File tree

4 files changed

+89
-52
lines changed

4 files changed

+89
-52
lines changed

docs/sample_app_setup.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The client ID and client secret are required for authenticating your application
2020

2121
## Deployment Steps
2222

23+
### Setup Environment Variables
24+
2325
In order to have the sample application infrastructure deployed, certain parameter requirements must be met. Set specific environment variables listed in the below AZD command block prior to running `azd up` to properly deploy the sample application.
2426

2527
```sh
@@ -33,6 +35,15 @@ azd env set AZURE_AUTH_CLIENT_SECRET <your-client-secret>
3335

3436
Replace `<your-app-id>`, `<your-client-id>`, and `<your-client-secret>` with your actual Azure credentials.
3537

38+
### AI Models Parameter Requirements
39+
40+
Also, the `aiModelDeployments` parameter in the [main.parameters.json](/infra/main.parameters.json) must contain two AI model deployments in this specific order (Note: the default setup meets these requirements):
41+
42+
1. Text Embedding model (e.g., `text-embedding-ada-002`, `text-embedding-3-small`, `text-embedding-3-large`)
43+
2. Chat Completion model (e.g., `gpt-4`, `gpt-4o`, `gpt-4o-mini`)
44+
45+
### Deploy
46+
3647
Follow the [standard deployment guide](./local_environment_steps.md).
3748

3849
## Post-Deployment Steps

infra/main.bicep

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ var allTags = union(defaultTags, tags)
100100
var resourceToken = substring(uniqueString(subscription().id, location, name), 0, 5)
101101
var servicesUsername = take(replace(vmAdminUsername,'.', ''), 20)
102102

103-
var deploySampleApp = appSampleEnabled && cosmosDbEnabled && searchEnabled && !empty(authClientId) && !empty(authClientSecret) && !empty(cosmosDatabases) && !empty(aiModelDeployments)
103+
var deploySampleApp = appSampleEnabled && cosmosDbEnabled && searchEnabled && !empty(authClientId) && !empty(authClientSecret) && !empty(cosmosDatabases) && !empty(aiModelDeployments) && length(aiModelDeployments) >= 2
104+
var authClientSecretName = 'auth-client-secret'
104105

105106
module appIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = if (deploySampleApp) {
106107
name: take('${name}-identity-deployment', 64)
@@ -152,7 +153,25 @@ module keyvault 'modules/keyvault.bicep' = {
152153
virtualNetworkResourceId: networkIsolation ? network.outputs.resourceId : ''
153154
virtualNetworkSubnetResourceId: networkIsolation ? network.outputs.defaultSubnetResourceId : ''
154155
logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.resourceId
155-
userObjectId: userObjectId
156+
roleAssignments: concat(empty(userObjectId) ? [] : [
157+
{
158+
principalId: userObjectId
159+
principalType: 'User'
160+
roleDefinitionIdOrName: 'Key Vault Secrets User'
161+
}
162+
], deploySampleApp ? [
163+
{
164+
principalId: appIdentity.outputs.principalId
165+
principalType: 'ServicePrincipal'
166+
roleDefinitionIdOrName: 'Key Vault Secrets User'
167+
}
168+
] : [])
169+
secrets: deploySampleApp ? [
170+
{
171+
name: authClientSecretName
172+
value: authClientSecret ?? ''
173+
}
174+
] : []
156175
tags: allTags
157176
}
158177
}
@@ -286,37 +305,6 @@ module virtualMachine './modules/virtualMachine.bicep' = if (networkIsolation)
286305
dependsOn: networkIsolation ? [storageAccount] : []
287306
}
288307

289-
var hubOutputRules = searchEnabled ? {
290-
cog_services_pep: {
291-
category: 'UserDefined'
292-
destination: {
293-
serviceResourceId: cognitiveServices.outputs.aiServicesResourceId
294-
sparkEnabled: true
295-
subresourceTarget: 'account'
296-
}
297-
type: 'PrivateEndpoint'
298-
}
299-
search_pep: {
300-
category: 'UserDefined'
301-
destination: {
302-
serviceResourceId: aiSearch.outputs.resourceId
303-
sparkEnabled: true
304-
subresourceTarget: 'searchService'
305-
}
306-
type: 'PrivateEndpoint'
307-
}
308-
} : {
309-
cog_services_pep: {
310-
category: 'UserDefined'
311-
destination: {
312-
serviceResourceId: cognitiveServices.outputs.aiServicesResourceId
313-
sparkEnabled: true
314-
subresourceTarget: 'account'
315-
}
316-
type: 'PrivateEndpoint'
317-
}
318-
}
319-
320308
module aiHub 'modules/ai-foundry/hub.bicep' = {
321309
name: take('${name}-ai-hub-deployment', 64)
322310
params: {
@@ -332,7 +320,36 @@ module aiHub 'modules/ai-foundry/hub.bicep' = {
332320
storageAccountResourceId: storageAccount.outputs.resourceId
333321
managedNetworkSettings: {
334322
isolationMode: networkIsolation ? 'AllowInternetOutbound' : 'Disabled'
335-
outboundRules: hubOutputRules
323+
outboundRules: searchEnabled ? {
324+
cog_services_pep: {
325+
category: 'UserDefined'
326+
destination: {
327+
serviceResourceId: cognitiveServices.outputs.aiServicesResourceId
328+
sparkEnabled: true
329+
subresourceTarget: 'account'
330+
}
331+
type: 'PrivateEndpoint'
332+
}
333+
search_pep: {
334+
category: 'UserDefined'
335+
destination: {
336+
serviceResourceId: aiSearch.outputs.resourceId
337+
sparkEnabled: true
338+
subresourceTarget: 'searchService'
339+
}
340+
type: 'PrivateEndpoint'
341+
}
342+
} : {
343+
cog_services_pep: {
344+
category: 'UserDefined'
345+
destination: {
346+
serviceResourceId: cognitiveServices.outputs.aiServicesResourceId
347+
sparkEnabled: true
348+
subresourceTarget: 'account'
349+
}
350+
type: 'PrivateEndpoint'
351+
}
352+
}
336353
}
337354
roleAssignments:empty(userObjectId) ? [] : [
338355
{
@@ -442,6 +459,7 @@ module appService 'modules/appservice.bicep' = if (deploySampleApp) {
442459
location: location
443460
userAssignedIdentityName: appIdentity.outputs.name
444461
appInsightsName: applicationInsights.outputs.name
462+
keyVaultName: keyvault.outputs.name
445463
logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.resourceId
446464
skuName: 'B3'
447465
skuCapacity: 1
@@ -450,7 +468,7 @@ module appService 'modules/appservice.bicep' = if (deploySampleApp) {
450468
virtualNetworkSubnetId: network.outputs.appSubnetResourceId
451469
authProvider: {
452470
clientId: authClientId ?? ''
453-
clientSecret: authClientSecret ?? ''
471+
clientSecretName: authClientSecretName
454472
openIdIssuer: '${environment().authentication.loginEndpoint}${tenant().tenantId}/v2.0'
455473
}
456474
searchServiceConfiguration: {
@@ -465,9 +483,9 @@ module appService 'modules/appservice.bicep' = if (deploySampleApp) {
465483
openAIConfiguration: {
466484
name: cognitiveServices.outputs.aiServicesName
467485
endpoint: cognitiveServices.outputs.aiServicesEndpoint
468-
gptModelName: aiModelDeployments[1].model.name
469-
gptModelDeploymentName: aiModelDeployments[1].?name ?? aiModelDeployments[1].model.name
470-
embeddingModelDeploymentName: aiModelDeployments[0].?name ?? aiModelDeployments[0].model.name
486+
gptModelName: aiModelDeployments[1].model.name // GPT model is second item in array from parameters
487+
gptModelDeploymentName: aiModelDeployments[1].?name ?? aiModelDeployments[1].model.name // GPT model is second item in array from parameters
488+
embeddingModelDeploymentName: aiModelDeployments[0].?name ?? aiModelDeployments[0].model.name // Embedding model is first item in array from parameters
471489
}
472490
}
473491
}

infra/modules/appservice.bicep

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ param logAnalyticsWorkspaceResourceId string
2727
@description('Name of an existing Application Insights resource for the App Service.')
2828
param appInsightsName string
2929

30+
@description('Name of existing Key Vault to read secrets.')
31+
param keyVaultName string
32+
3033
@description('Full path to container image.')
3134
param imagePath string
3235

@@ -53,6 +56,10 @@ resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@
5356
name: userAssignedIdentityName
5457
}
5558

59+
resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' existing = {
60+
name: keyVaultName
61+
}
62+
5663
var nameFormatted = take(toLower(name), 55)
5764

5865
module appServicePlan 'br/public:avm/res/web/serverfarm:0.4.1' = {
@@ -90,6 +97,7 @@ module appService 'br/public:avm/res/web/site:0.15.1' = {
9097
serverFarmResourceId: appServicePlan.outputs.resourceId
9198
appInsightResourceId: appInsights.id
9299
virtualNetworkSubnetId: virtualNetworkSubnetId
100+
keyVaultAccessIdentityResourceId: userAssignedIdentity.id
93101
managedIdentities: {
94102
userAssignedResourceIds: [userAssignedIdentity.id]
95103
}
@@ -159,7 +167,7 @@ module appService 'br/public:avm/res/web/site:0.15.1' = {
159167
APPLICATIONINSIGHTS_CONFIGURATION_CONTENT: ''
160168
APPLICATIONINSIGHTS_CONNECTION_STRING: appInsights.properties.ConnectionString
161169
ApplicationInsightsAgent_EXTENSION_VERSION: '~3'
162-
AUTH_CLIENT_SECRET: authProvider.clientSecret
170+
AUTH_CLIENT_SECRET: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=${authProvider.clientSecretName})' // NOTE: This secret should be created in Key Vault with the name provided in authProvider.clientSecretName.
163171
AZURE_CLIENT_ID: userAssignedIdentity.properties.clientId // NOTE: This is the client ID of the managed identity, not the Entra application, and is needed for the App Service to access the Cosmos DB account.
164172
AZURE_COSMOSDB_ACCOUNT: cosmosDbConfiguration.account
165173
AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: cosmosDbConfiguration.container
@@ -258,9 +266,8 @@ type authIdentityProvider = {
258266
@description('Required. The client/application ID of the Entra application.')
259267
clientId: string
260268

261-
@description('Required. The resource ID of the Azure Active Directory application secret.')
262-
@secure()
263-
clientSecret: string
269+
@description('Required. The secret name of the Azure Active Directory application secret stored in Key Vault.')
270+
clientSecretName: string
264271

265272
@description('Required. The OpenID issuer of the Entra application.')
266273
openIdIssuer: string

infra/modules/keyvault.bicep

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ param logAnalyticsWorkspaceResourceId string
1919
@description('Specifies whether network isolation is enabled. This will create a private endpoint for the Key Vault and link the private DNS zone.')
2020
param networkIsolation bool = true
2121

22-
@description('Specifies the object id of a Microsoft Entra ID user. In general, this the object id of the system administrator who deploys the Azure resources. This defaults to the deploying user.')
23-
param userObjectId string
22+
@description('Optional. Array of role assignments to create.')
23+
param roleAssignments roleAssignmentType[]?
24+
25+
@description('Optional. Array of secrets to create in the Key Vault.')
26+
param secrets secretType[]?
2427

2528
module privateDnsZone 'br/public:avm/res/network/private-dns-zone:0.7.0' = if (networkIsolation) {
2629
name: 'private-dns-keyvault-deployment'
@@ -37,7 +40,7 @@ module privateDnsZone 'br/public:avm/res/network/private-dns-zone:0.7.0' = if (n
3740

3841
var nameFormatted = take(toLower(name), 24)
3942

40-
module keyvault 'br/public:avm/res/key-vault/vault:0.11.0' = {
43+
module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = {
4144
name: take('${nameFormatted}-keyvault-deployment', 64)
4245
dependsOn: [privateDnsZone] // required due to optional flags that could change dependency
4346
params: {
@@ -73,15 +76,13 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.11.0' = {
7376
subnetResourceId: virtualNetworkSubnetResourceId
7477
}
7578
] : []
76-
roleAssignments: empty(userObjectId) ? [] : [
77-
{
78-
principalId: userObjectId
79-
principalType: 'User'
80-
roleDefinitionIdOrName: 'Key Vault Secrets User'
81-
}
82-
]
79+
roleAssignments: roleAssignments
80+
secrets: secrets
8381
}
8482
}
8583

84+
import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
85+
import { secretType } from 'br/public:avm/res/key-vault/vault:0.12.1'
86+
8687
output resourceId string = keyvault.outputs.resourceId
8788
output name string = keyvault.outputs.name

0 commit comments

Comments
 (0)