@aifabrix/builder 2.56.0 → 2.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/lib/agent-kit/session-report.js +76 -0
  2. package/lib/agent-kit/start.js +124 -57
  3. package/lib/agent-kit/tmux-sessions.js +163 -0
  4. package/lib/api/environments.api.js +38 -0
  5. package/lib/api/marketplace-bootstrap.api.js +31 -0
  6. package/lib/cli/setup-environment.js +76 -19
  7. package/lib/commands/agent-kit-help.js +32 -15
  8. package/lib/commands/agent-kit.js +90 -8
  9. package/lib/deployment/installation/azure-infra-stage.js +160 -0
  10. package/lib/deployment/installation/azure-preflight.js +102 -0
  11. package/lib/deployment/installation/azure-readiness.js +71 -0
  12. package/lib/deployment/installation/docker-register.js +112 -0
  13. package/lib/deployment/installation/environment-auth.js +76 -0
  14. package/lib/deployment/installation/environment-manifest.js +165 -0
  15. package/lib/deployment/installation/environment-stage.js +191 -0
  16. package/lib/deployment/installation/index.js +39 -0
  17. package/lib/deployment/installation/infra-catalog.js +165 -0
  18. package/lib/deployment/installation/infra-manifest.js +161 -0
  19. package/lib/deployment/installation/infra-projection.js +86 -0
  20. package/lib/deployment/installation/public-ip.js +65 -0
  21. package/lib/deployment/installation/stage-input.js +169 -0
  22. package/lib/deployment/installation/stage-output.js +53 -0
  23. package/lib/deployment/installation/workspace-env.js +76 -0
  24. package/lib/deployment/installation/workspace-files.js +178 -0
  25. package/lib/deployment/installation/workspace-schema.js +40 -0
  26. package/lib/deployment/installation/workspace-secrets.js +66 -0
  27. package/lib/deployment/installation/workspace.js +128 -0
  28. package/lib/deployment/marketplace-environment.js +305 -0
  29. package/lib/generator/builders.js +4 -0
  30. package/lib/generator/helpers.js +55 -56
  31. package/lib/generator/split-variables.js +11 -5
  32. package/lib/internal/fs-real-sync.js +29 -0
  33. package/lib/role-assistant/package-validate.js +4 -0
  34. package/lib/role-assistant/test-runner-clarification-resubmit.js +149 -0
  35. package/lib/role-assistant/test-runner.js +14 -17
  36. package/lib/schema/application-schema.json +20 -2
  37. package/lib/schema/infra-parameter.schema.json +7 -0
  38. package/lib/schema/infra.parameter.yaml +26 -36
  39. package/lib/schema/infrastructure-schema.json +1480 -399
  40. package/lib/schema/installation-environment.schema.json +601 -0
  41. package/lib/utils/variable-transformer.js +3 -0
  42. package/lib/validation/required-variables.js +41 -0
  43. package/lib/validation/validator.js +8 -5
  44. package/package.json +5 -1
  45. package/templates/README.md +4 -0
  46. package/templates/agent-kit/instructions/AGENTKIT.md +6 -6
  47. package/templates/agent-kit/skills/shared/hosts.md +10 -10
  48. package/templates/applications/dataplane/application.yaml +1 -1
  49. package/templates/applications/dataplane/env.template +1 -1
  50. package/templates/applications/miso-controller/application.yaml +3 -2
  51. package/templates/applications/miso-controller/env.template +15 -6
  52. package/templates/marketplace/createUiDefinition.json +77 -0
  53. package/templates/marketplace/install.parameters.example.json +9 -0
  54. package/templates/marketplace/main.json +4087 -0
@@ -0,0 +1,4087 @@
1
+ {
2
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3
+ "contentVersion": "1.0.0.0",
4
+ "metadata": {
5
+ "_generator": {
6
+ "name": "bicep",
7
+ "version": "0.47.16.16243",
8
+ "templateHash": "12207364571048202485"
9
+ }
10
+ },
11
+ "parameters": {
12
+ "infrastructureNameOverride": {
13
+ "type": "string",
14
+ "defaultValue": "",
15
+ "metadata": {
16
+ "description": "Non-Marketplace override only. Empty → resourceGroup().name (managed RG SSOT). Do not map from createUiDefinition — Marketplace resolves template params in customer RG context."
17
+ }
18
+ },
19
+ "location": {
20
+ "type": "string",
21
+ "defaultValue": "[resourceGroup().location]",
22
+ "metadata": {
23
+ "description": "Azure region for deployment. Default is the managed resource group location; portal may override via createUiDefinition."
24
+ }
25
+ },
26
+ "tags": {
27
+ "type": "object",
28
+ "defaultValue": {
29
+ "CreatedBy": "AI Fabrix Miso",
30
+ "Environment": "miso",
31
+ "ServiceName": "aifabrix-miso",
32
+ "Application": "AI Fabrix",
33
+ "Tier": "Infrastructure",
34
+ "CostCenter": "Platform",
35
+ "Owner": "Platform Team"
36
+ },
37
+ "metadata": {
38
+ "description": "Resource tags to apply to all resources"
39
+ }
40
+ },
41
+ "enableAppInsights": {
42
+ "type": "bool",
43
+ "defaultValue": false,
44
+ "metadata": {
45
+ "description": "Create Application Insights for Keycloak. Default false — no ai_*_managed companion RG."
46
+ }
47
+ },
48
+ "enableAzureOpenAI": {
49
+ "type": "bool",
50
+ "defaultValue": true,
51
+ "metadata": {
52
+ "description": "When true, create Azure OpenAI in Phase-1 (same name TA uses) so Complete mode does not delete it."
53
+ }
54
+ },
55
+ "enablePartnerCenterAttribution": {
56
+ "type": "bool",
57
+ "defaultValue": true,
58
+ "metadata": {
59
+ "description": "Partner Center customer usage attribution (PID). Default true for MP; Service Catalog build sets false."
60
+ }
61
+ },
62
+ "enablePostgresPublicAccess": {
63
+ "type": "bool",
64
+ "defaultValue": true,
65
+ "metadata": {
66
+ "description": "Enable public network access for PostgreSQL (developer / evaluation default; use private+VNet in hardened deployments)"
67
+ }
68
+ },
69
+ "allowedIPs": {
70
+ "type": "array",
71
+ "defaultValue": [],
72
+ "metadata": {
73
+ "description": "PostgreSQL firewall rules when enablePostgresPublicAccess is true (CI/CD deploy passes via @file)."
74
+ }
75
+ },
76
+ "adminPassword": {
77
+ "type": "securestring",
78
+ "metadata": {
79
+ "description": "Initial AI Fabrix platform administrator password (user admin). Not used for PostgreSQL, Keycloak internal admin, or encryption."
80
+ }
81
+ },
82
+ "deploymentEnvironment": {
83
+ "type": "string",
84
+ "defaultValue": "evaluation",
85
+ "allowedValues": ["prod", "nonprod", "evaluation"],
86
+ "metadata": {
87
+ "description": "Deployment environment used for app runtime defaults. MP UI hardcodes evaluation for now."
88
+ }
89
+ },
90
+ "keycloakVerifyAudience": {
91
+ "type": "bool",
92
+ "defaultValue": false,
93
+ "metadata": {
94
+ "description": "Enable Keycloak access-token audience validation in controller"
95
+ }
96
+ },
97
+ "encryptionKey": {
98
+ "type": "securestring",
99
+ "defaultValue": "",
100
+ "metadata": {
101
+ "description": "Optional encryption key override. Empty → install-unique generated key in Key Vault (no PLACEHOLDER)."
102
+ }
103
+ },
104
+ "acrLoginServer": {
105
+ "type": "string",
106
+ "defaultValue": "",
107
+ "metadata": {
108
+ "description": "Optional registry login server. Empty (default) derives it from acrResourceId (<registry><cloud acrLoginServer suffix>)."
109
+ }
110
+ },
111
+ "acrAdminUsername": {
112
+ "type": "string",
113
+ "defaultValue": "",
114
+ "metadata": {
115
+ "description": "Optional registry username. Used only together with a non-empty acrAdminPassword (legacy/local registry credential); empty derives the registry name from acrResourceId. Azure image pull uses managed identity."
116
+ }
117
+ },
118
+ "acrAdminPassword": {
119
+ "type": "securestring",
120
+ "defaultValue": "",
121
+ "metadata": {
122
+ "description": "Optional registry pull password/token. Empty (default) → no DOCKER_REGISTRY_SERVER_PASSWORD setting and no Key Vault registry secret; Azure image pull uses the web app managed identity (AcrPull)."
123
+ }
124
+ },
125
+ "acrResourceId": {
126
+ "type": "string",
127
+ "minLength": 1,
128
+ "metadata": {
129
+ "description": "Required full ARM resource ID of the container registry that serves the Miso and Keycloak images. Both web app managed identities receive AcrPull on it through an ARM role assignment in the resource group of the registry; the deploying principal needs roleAssignments/write on the registry. No default: the deployer (Builder or the Marketplace package) supplies it."
130
+ }
131
+ },
132
+ "deployRoleAssignments": {
133
+ "type": "bool",
134
+ "defaultValue": true,
135
+ "metadata": {
136
+ "description": "When false, skip ARM role assignments (CI/CD redeploy without roleAssignments/write). RBAC must already exist."
137
+ }
138
+ },
139
+ "moriBaseUrl": {
140
+ "type": "string",
141
+ "defaultValue": "https://aifabrix-pro-miso-mori.azurewebsites.net",
142
+ "metadata": {
143
+ "description": "Mori base URL for controller tenant activation and Entra handoff (no trailing slash)"
144
+ }
145
+ },
146
+ "moriCallbackApiKey": {
147
+ "type": "securestring",
148
+ "defaultValue": "",
149
+ "metadata": {
150
+ "description": "Optional shared secret for Mori → controller callback. Configure through the Miso installation/application contract."
151
+ }
152
+ },
153
+ "entraCallbackStateSecret": {
154
+ "type": "securestring",
155
+ "defaultValue": "",
156
+ "metadata": {
157
+ "description": "Optional shared HMAC secret for Entra consent OAuth state. Configure through the Miso installation contract."
158
+ }
159
+ },
160
+ "entraIdClientId": {
161
+ "type": "string",
162
+ "defaultValue": "481251f2-a318-47ba-9ad8-40c36f790df1",
163
+ "metadata": {
164
+ "description": "Vendor Entra multi-tenant app client ID. Default matches vendor miso-controller .env (not in createUiDefinition)."
165
+ }
166
+ },
167
+ "entraIdClientSecret": {
168
+ "type": "securestring",
169
+ "defaultValue": "",
170
+ "metadata": {
171
+ "description": "Optional vendor Entra app client secret. Configure through the Miso installation contract."
172
+ }
173
+ },
174
+ "entraIdTenantId": {
175
+ "type": "string",
176
+ "defaultValue": "09aff594-ba37-4af2-bf40-b773b654c563",
177
+ "metadata": {
178
+ "description": "Publisher/home tenant for vendor Entra app (ENTRA_ID_TENANT_ID). TENANT_ID uses subscription().tenantId at deploy. Default matches vendor miso-controller .env (not in createUiDefinition)."
179
+ }
180
+ },
181
+ "misoImageTag": {
182
+ "type": "string",
183
+ "defaultValue": "latest",
184
+ "metadata": {
185
+ "description": "MISO controller image tag on publisher ACR (not in createUiDefinition). MP/managed-app default is latest (Sweden cicd retags on pro promote); Service Catalog publish may override."
186
+ }
187
+ },
188
+ "keycloakImageTag": {
189
+ "type": "string",
190
+ "defaultValue": "latest",
191
+ "metadata": {
192
+ "description": "Keycloak image tag on publisher ACR (not in createUiDefinition). MP/managed-app default is latest (Sweden cicd retags on pro promote); Service Catalog publish may override."
193
+ }
194
+ },
195
+ "licenseEdition": {
196
+ "type": "string",
197
+ "defaultValue": "COMMUNITY",
198
+ "metadata": {
199
+ "description": "License the installation requests from Mori during tenant activation: an edition name (COMMUNITY, STANDARD, ENTERPRISE) or an Azure Marketplace subscription ID (UUID). Plain string — Miso validates it. Emitted as LICENSE_EDITION."
200
+ }
201
+ },
202
+ "onboardingMode": {
203
+ "type": "string",
204
+ "defaultValue": "customerOwned",
205
+ "allowedValues": ["customerOwned", "saasDirect"],
206
+ "metadata": {
207
+ "description": "Tenant onboarding mode for Miso (ONBOARDING_MODE). customerOwned = portal/Marketplace wizard; saasDirect = Builder SaaS installation. Not exposed in createUiDefinition."
208
+ }
209
+ }
210
+ },
211
+ "variables": {
212
+ "infrastructureName": "[if(not(empty(parameters('infrastructureNameOverride'))), parameters('infrastructureNameOverride'), resourceGroup().name)]",
213
+ "deploymentLocation": "[parameters('location')]",
214
+ "appInsightsLocation": "[if(equals(variables('deploymentLocation'), 'norwaywest'), 'norwayeast', variables('deploymentLocation'))]",
215
+ "openAiLocationKnown": "[contains(createArray('australiaeast', 'brazilsouth', 'canadaeast', 'eastus', 'eastus2', 'francecentral', 'japaneast', 'koreacentral', 'northcentralus', 'norwayeast', 'polandcentral', 'southafricanorth', 'southcentralus', 'southindia', 'swedencentral', 'switzerlandnorth', 'uksouth', 'westeurope', 'westus', 'westus3'), variables('deploymentLocation'))]",
216
+ "openAiLocation": "[if(variables('openAiLocationKnown'), variables('deploymentLocation'), 'swedencentral')]",
217
+ "subnetBase": "[add(20, mod(length(variables('infrastructureName')), 230))]",
218
+ "registryPasswordConfigured": "[not(empty(parameters('acrAdminPassword')))]",
219
+ "acrRegistryName": "[toLower(last(split(parameters('acrResourceId'), '/')))]",
220
+ "effectiveAcrLoginServer": "[if(empty(parameters('acrLoginServer')), format('{0}{1}', variables('acrRegistryName'), environment().suffixes.acrLoginServer), parameters('acrLoginServer'))]",
221
+ "registryUsername": "[if(variables('registryPasswordConfigured'), if(empty(parameters('acrAdminUsername')), variables('acrRegistryName'), parameters('acrAdminUsername')), '')]",
222
+ "keycloakContainerImage": "[format('{0}/aifabrix/keycloak:{1}', variables('effectiveAcrLoginServer'), parameters('keycloakImageTag'))]",
223
+ "misoControllerContainerImage": "[format('{0}/aifabrix/miso-controller:{1}', variables('effectiveAcrLoginServer'), parameters('misoImageTag'))]",
224
+ "keycloakVdirPublic": "/auth",
225
+ "acrResourceIdSegments": "[split(parameters('acrResourceId'), '/')]"
226
+ },
227
+ "resources": [
228
+ {
229
+ "condition": "[parameters('enablePartnerCenterAttribution')]",
230
+ "type": "Microsoft.Resources/deployments",
231
+ "apiVersion": "2020-06-01",
232
+ "name": "pid-ca58d4ea-1339-4201-8971-9dcd4c2619b8-partnercenter",
233
+ "properties": {
234
+ "mode": "Incremental",
235
+ "template": {
236
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
237
+ "contentVersion": "1.0.0.0",
238
+ "resources": []
239
+ }
240
+ }
241
+ },
242
+ {
243
+ "type": "Microsoft.Resources/deployments",
244
+ "apiVersion": "2025-04-01",
245
+ "name": "network",
246
+ "properties": {
247
+ "expressionEvaluationOptions": {
248
+ "scope": "inner"
249
+ },
250
+ "mode": "Incremental",
251
+ "parameters": {
252
+ "location": {
253
+ "value": "[variables('deploymentLocation')]"
254
+ },
255
+ "prefix": {
256
+ "value": "[variables('infrastructureName')]"
257
+ },
258
+ "vnetAddressSpace": {
259
+ "value": "[format('10.{0}.0.0/21', variables('subnetBase'))]"
260
+ },
261
+ "webappSubnetCidr": {
262
+ "value": "[format('10.{0}.1.0/24', variables('subnetBase'))]"
263
+ },
264
+ "privateEndpointSubnetCidr": {
265
+ "value": "[format('10.{0}.2.0/24', variables('subnetBase'))]"
266
+ },
267
+ "postgresSubnetCidr": {
268
+ "value": "[format('10.{0}.3.0/27', variables('subnetBase'))]"
269
+ },
270
+ "deploymentScriptSubnetCidr": {
271
+ "value": "[format('10.{0}.4.0/27', variables('subnetBase'))]"
272
+ },
273
+ "linkPostgresPrivateDnsToVnet": {
274
+ "value": "[not(parameters('enablePostgresPublicAccess'))]"
275
+ }
276
+ },
277
+ "template": {
278
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
279
+ "contentVersion": "1.0.0.0",
280
+ "metadata": {
281
+ "_generator": {
282
+ "name": "bicep",
283
+ "version": "0.47.16.16243",
284
+ "templateHash": "2748258731732394782"
285
+ }
286
+ },
287
+ "parameters": {
288
+ "location": {
289
+ "type": "string",
290
+ "defaultValue": "[resourceGroup().location]",
291
+ "metadata": {
292
+ "description": "Azure region"
293
+ }
294
+ },
295
+ "prefix": {
296
+ "type": "string",
297
+ "metadata": {
298
+ "description": "Resource name prefix (used in VNet, subnets, DNS zones)"
299
+ }
300
+ },
301
+ "vnetAddressSpace": {
302
+ "type": "string",
303
+ "defaultValue": "10.20.0.0/16",
304
+ "metadata": {
305
+ "description": "VNet address space"
306
+ }
307
+ },
308
+ "webappSubnetCidr": {
309
+ "type": "string",
310
+ "defaultValue": "10.20.1.0/24",
311
+ "metadata": {
312
+ "description": "Subnet CIDR for App Service VNet integration (Swift). Must not overlap with others."
313
+ }
314
+ },
315
+ "privateEndpointSubnetCidr": {
316
+ "type": "string",
317
+ "defaultValue": "10.20.2.0/24",
318
+ "metadata": {
319
+ "description": "Subnet CIDR for Private Endpoints. Must not overlap with others."
320
+ }
321
+ },
322
+ "postgresSubnetCidr": {
323
+ "type": "string",
324
+ "defaultValue": "10.20.3.0/27",
325
+ "metadata": {
326
+ "description": "Subnet CIDR for PostgreSQL Flexible Server. Must not overlap with others."
327
+ }
328
+ },
329
+ "deploymentScriptSubnetCidr": {
330
+ "type": "string",
331
+ "defaultValue": "10.20.4.0/27",
332
+ "metadata": {
333
+ "description": "Subnet CIDR for Deployment Scripts. Must not overlap with others."
334
+ }
335
+ },
336
+ "tags": {
337
+ "type": "object",
338
+ "defaultValue": {},
339
+ "metadata": {
340
+ "description": "Optional tags applied to all resources created by this module"
341
+ }
342
+ },
343
+ "linkPostgresPrivateDnsToVnet": {
344
+ "type": "bool",
345
+ "defaultValue": true,
346
+ "metadata": {
347
+ "description": "Link privatelink.postgres.database.azure.com to the VNet. Set false when PostgreSQL uses public access only (avoids empty-zone DNS hijack for App Service)."
348
+ }
349
+ }
350
+ },
351
+ "variables": {
352
+ "vnetName": "[format('{0}-vnet', parameters('prefix'))]",
353
+ "webappSubnetName": "snet-webapp",
354
+ "peSubnetName": "snet-private-endpoints",
355
+ "pgSubnetName": "snet-postgres",
356
+ "deploymentSubnetName": "snet-deployments",
357
+ "zonePostgres": "privatelink.postgres.database.azure.com",
358
+ "zoneStorageBlob": "[format('privatelink.blob.{0}', environment().suffixes.storage)]",
359
+ "zoneKeyVault": "privatelink.vaultcore.azure.net",
360
+ "zoneStorageFile": "[format('privatelink.file.{0}', environment().suffixes.storage)]",
361
+ "zoneAppService": "privatelink.azurewebsites.net"
362
+ },
363
+ "resources": [
364
+ {
365
+ "type": "Microsoft.Network/virtualNetworks",
366
+ "apiVersion": "2024-03-01",
367
+ "name": "[variables('vnetName')]",
368
+ "location": "[parameters('location')]",
369
+ "tags": "[parameters('tags')]",
370
+ "properties": {
371
+ "addressSpace": {
372
+ "addressPrefixes": ["[parameters('vnetAddressSpace')]"]
373
+ },
374
+ "subnets": [
375
+ {
376
+ "name": "[variables('webappSubnetName')]",
377
+ "properties": {
378
+ "addressPrefix": "[parameters('webappSubnetCidr')]",
379
+ "delegations": [
380
+ {
381
+ "name": "delegation-appservice",
382
+ "properties": {
383
+ "serviceName": "Microsoft.Web/serverFarms"
384
+ }
385
+ }
386
+ ],
387
+ "privateEndpointNetworkPolicies": "Enabled",
388
+ "privateLinkServiceNetworkPolicies": "Enabled"
389
+ }
390
+ },
391
+ {
392
+ "name": "[variables('peSubnetName')]",
393
+ "properties": {
394
+ "addressPrefix": "[parameters('privateEndpointSubnetCidr')]",
395
+ "privateEndpointNetworkPolicies": "Disabled",
396
+ "privateLinkServiceNetworkPolicies": "Enabled",
397
+ "serviceEndpoints": [
398
+ {
399
+ "service": "Microsoft.Storage"
400
+ },
401
+ {
402
+ "service": "Microsoft.KeyVault"
403
+ }
404
+ ]
405
+ }
406
+ },
407
+ {
408
+ "name": "[variables('pgSubnetName')]",
409
+ "properties": {
410
+ "addressPrefix": "[parameters('postgresSubnetCidr')]",
411
+ "delegations": [
412
+ {
413
+ "name": "delegation-postgres",
414
+ "properties": {
415
+ "serviceName": "Microsoft.DBforPostgreSQL/flexibleServers"
416
+ }
417
+ }
418
+ ],
419
+ "privateEndpointNetworkPolicies": "Enabled",
420
+ "privateLinkServiceNetworkPolicies": "Enabled"
421
+ }
422
+ },
423
+ {
424
+ "name": "[variables('deploymentSubnetName')]",
425
+ "properties": {
426
+ "addressPrefix": "[parameters('deploymentScriptSubnetCidr')]",
427
+ "delegations": [
428
+ {
429
+ "name": "delegation-aci",
430
+ "properties": {
431
+ "serviceName": "Microsoft.ContainerInstance/containerGroups"
432
+ }
433
+ }
434
+ ],
435
+ "privateEndpointNetworkPolicies": "Enabled",
436
+ "privateLinkServiceNetworkPolicies": "Enabled"
437
+ }
438
+ }
439
+ ]
440
+ }
441
+ },
442
+ {
443
+ "type": "Microsoft.Network/privateDnsZones",
444
+ "apiVersion": "2020-06-01",
445
+ "name": "[variables('zonePostgres')]",
446
+ "location": "global",
447
+ "tags": "[parameters('tags')]"
448
+ },
449
+ {
450
+ "type": "Microsoft.Network/privateDnsZones",
451
+ "apiVersion": "2020-06-01",
452
+ "name": "[variables('zoneStorageBlob')]",
453
+ "location": "global",
454
+ "tags": "[parameters('tags')]"
455
+ },
456
+ {
457
+ "type": "Microsoft.Network/privateDnsZones",
458
+ "apiVersion": "2020-06-01",
459
+ "name": "[variables('zoneStorageFile')]",
460
+ "location": "global",
461
+ "tags": "[parameters('tags')]"
462
+ },
463
+ {
464
+ "type": "Microsoft.Network/privateDnsZones",
465
+ "apiVersion": "2020-06-01",
466
+ "name": "[variables('zoneKeyVault')]",
467
+ "location": "global",
468
+ "tags": "[parameters('tags')]"
469
+ },
470
+ {
471
+ "type": "Microsoft.Network/privateDnsZones",
472
+ "apiVersion": "2020-06-01",
473
+ "name": "[variables('zoneAppService')]",
474
+ "location": "global",
475
+ "tags": "[parameters('tags')]"
476
+ },
477
+ {
478
+ "condition": "[parameters('linkPostgresPrivateDnsToVnet')]",
479
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
480
+ "apiVersion": "2020-06-01",
481
+ "name": "[format('{0}/{1}', variables('zonePostgres'), format('{0}-postgres-vnet-link', parameters('prefix')))]",
482
+ "location": "global",
483
+ "properties": {
484
+ "registrationEnabled": false,
485
+ "virtualNetwork": {
486
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
487
+ }
488
+ },
489
+ "dependsOn": [
490
+ "[resourceId('Microsoft.Network/privateDnsZones', variables('zonePostgres'))]",
491
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
492
+ ]
493
+ },
494
+ {
495
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
496
+ "apiVersion": "2020-06-01",
497
+ "name": "[format('{0}/{1}', variables('zoneStorageBlob'), format('{0}-stgblob-vnet-link', parameters('prefix')))]",
498
+ "location": "global",
499
+ "properties": {
500
+ "registrationEnabled": false,
501
+ "virtualNetwork": {
502
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
503
+ }
504
+ },
505
+ "dependsOn": [
506
+ "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneStorageBlob'))]",
507
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
508
+ ]
509
+ },
510
+ {
511
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
512
+ "apiVersion": "2020-06-01",
513
+ "name": "[format('{0}/{1}', variables('zoneStorageFile'), format('{0}-stgfile-vnet-link', parameters('prefix')))]",
514
+ "location": "global",
515
+ "properties": {
516
+ "registrationEnabled": false,
517
+ "virtualNetwork": {
518
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
519
+ }
520
+ },
521
+ "dependsOn": [
522
+ "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneStorageFile'))]",
523
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
524
+ ]
525
+ },
526
+ {
527
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
528
+ "apiVersion": "2020-06-01",
529
+ "name": "[format('{0}/{1}', variables('zoneKeyVault'), format('{0}-keyvault-vnet-link', parameters('prefix')))]",
530
+ "location": "global",
531
+ "properties": {
532
+ "registrationEnabled": false,
533
+ "virtualNetwork": {
534
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
535
+ }
536
+ },
537
+ "dependsOn": [
538
+ "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneKeyVault'))]",
539
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
540
+ ]
541
+ },
542
+ {
543
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
544
+ "apiVersion": "2020-06-01",
545
+ "name": "[format('{0}/{1}', variables('zoneAppService'), format('{0}-appsvc-vnet-link', parameters('prefix')))]",
546
+ "location": "global",
547
+ "properties": {
548
+ "registrationEnabled": false,
549
+ "virtualNetwork": {
550
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
551
+ }
552
+ },
553
+ "dependsOn": [
554
+ "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneAppService'))]",
555
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
556
+ ]
557
+ }
558
+ ],
559
+ "outputs": {
560
+ "vnetId": {
561
+ "type": "string",
562
+ "value": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
563
+ },
564
+ "webappSubnetId": {
565
+ "type": "string",
566
+ "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '2024-03-01').subnets[0].id]"
567
+ },
568
+ "privateEndpointSubnetId": {
569
+ "type": "string",
570
+ "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '2024-03-01').subnets[1].id]"
571
+ },
572
+ "postgresSubnetId": {
573
+ "type": "string",
574
+ "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '2024-03-01').subnets[2].id]"
575
+ },
576
+ "deploymentScriptSubnetId": {
577
+ "type": "string",
578
+ "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '2024-03-01').subnets[3].id]"
579
+ },
580
+ "postgresPrivateDnsZoneId": {
581
+ "type": "string",
582
+ "value": "[resourceId('Microsoft.Network/privateDnsZones', variables('zonePostgres'))]"
583
+ },
584
+ "storageBlobPrivateDnsZoneId": {
585
+ "type": "string",
586
+ "value": "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneStorageBlob'))]"
587
+ },
588
+ "storageFilePrivateDnsZoneId": {
589
+ "type": "string",
590
+ "value": "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneStorageFile'))]"
591
+ },
592
+ "keyVaultPrivateDnsZoneId": {
593
+ "type": "string",
594
+ "value": "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneKeyVault'))]"
595
+ },
596
+ "appServicePrivateDnsZoneId": {
597
+ "type": "string",
598
+ "value": "[resourceId('Microsoft.Network/privateDnsZones', variables('zoneAppService'))]"
599
+ }
600
+ }
601
+ }
602
+ }
603
+ },
604
+ {
605
+ "type": "Microsoft.Resources/deployments",
606
+ "apiVersion": "2025-04-01",
607
+ "name": "managedIdentity",
608
+ "properties": {
609
+ "expressionEvaluationOptions": {
610
+ "scope": "inner"
611
+ },
612
+ "mode": "Incremental",
613
+ "parameters": {
614
+ "location": {
615
+ "value": "[variables('deploymentLocation')]"
616
+ },
617
+ "name": {
618
+ "value": "[format('{0}-miso-mi', variables('infrastructureName'))]"
619
+ },
620
+ "tags": {
621
+ "value": "[parameters('tags')]"
622
+ }
623
+ },
624
+ "template": {
625
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
626
+ "contentVersion": "1.0.0.0",
627
+ "metadata": {
628
+ "_generator": {
629
+ "name": "bicep",
630
+ "version": "0.47.16.16243",
631
+ "templateHash": "13683008133608886333"
632
+ }
633
+ },
634
+ "parameters": {
635
+ "location": {
636
+ "type": "string",
637
+ "defaultValue": "[resourceGroup().location]",
638
+ "metadata": {
639
+ "description": "Azure region"
640
+ }
641
+ },
642
+ "name": {
643
+ "type": "string",
644
+ "metadata": {
645
+ "description": "Managed identity name"
646
+ }
647
+ },
648
+ "tags": {
649
+ "type": "object",
650
+ "defaultValue": {},
651
+ "metadata": {
652
+ "description": "Optional tags applied to all resources created by this module"
653
+ }
654
+ }
655
+ },
656
+ "resources": [
657
+ {
658
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
659
+ "apiVersion": "2023-01-31",
660
+ "name": "[parameters('name')]",
661
+ "location": "[parameters('location')]",
662
+ "tags": "[parameters('tags')]"
663
+ }
664
+ ],
665
+ "outputs": {
666
+ "managedIdentityId": {
667
+ "type": "string",
668
+ "metadata": {
669
+ "description": "Managed Identity ID"
670
+ },
671
+ "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]"
672
+ },
673
+ "managedIdentityName": {
674
+ "type": "string",
675
+ "metadata": {
676
+ "description": "Managed Identity name"
677
+ },
678
+ "value": "[parameters('name')]"
679
+ },
680
+ "principalId": {
681
+ "type": "string",
682
+ "metadata": {
683
+ "description": "Managed Identity principal ID"
684
+ },
685
+ "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]"
686
+ },
687
+ "clientId": {
688
+ "type": "string",
689
+ "metadata": {
690
+ "description": "Managed Identity client ID (for IMDS token requests)"
691
+ },
692
+ "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').clientId]"
693
+ }
694
+ }
695
+ }
696
+ }
697
+ },
698
+ {
699
+ "type": "Microsoft.Resources/deployments",
700
+ "apiVersion": "2025-04-01",
701
+ "name": "keyVault",
702
+ "properties": {
703
+ "expressionEvaluationOptions": {
704
+ "scope": "inner"
705
+ },
706
+ "mode": "Incremental",
707
+ "parameters": {
708
+ "location": {
709
+ "value": "[variables('deploymentLocation')]"
710
+ },
711
+ "name": {
712
+ "value": "[format('{0}-miso-kv', variables('infrastructureName'))]"
713
+ },
714
+ "tags": {
715
+ "value": "[parameters('tags')]"
716
+ },
717
+ "privateEndpointSubnetId": {
718
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.privateEndpointSubnetId.value]"
719
+ },
720
+ "privateDnsZoneId": {
721
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.keyVaultPrivateDnsZoneId.value]"
722
+ },
723
+ "allowedIPs": {
724
+ "value": ["*"]
725
+ },
726
+ "currentUserObjectId": {
727
+ "value": ""
728
+ },
729
+ "deploymentIdentityObjectIds": {
730
+ "value": [
731
+ "[reference(resourceId('Microsoft.Resources/deployments', 'managedIdentity'), '2025-04-01').outputs.principalId.value]"
732
+ ]
733
+ }
734
+ },
735
+ "template": {
736
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
737
+ "contentVersion": "1.0.0.0",
738
+ "metadata": {
739
+ "_generator": {
740
+ "name": "bicep",
741
+ "version": "0.47.16.16243",
742
+ "templateHash": "132634503198212442"
743
+ }
744
+ },
745
+ "parameters": {
746
+ "location": {
747
+ "type": "string",
748
+ "defaultValue": "[resourceGroup().location]",
749
+ "metadata": {
750
+ "description": "Azure region (defaults to the resource group location)"
751
+ }
752
+ },
753
+ "name": {
754
+ "type": "string",
755
+ "minLength": 3,
756
+ "maxLength": 24,
757
+ "metadata": {
758
+ "description": "Key Vault name"
759
+ }
760
+ },
761
+ "tags": {
762
+ "type": "object",
763
+ "defaultValue": {},
764
+ "metadata": {
765
+ "description": "Optional tags applied to all resources created by this module"
766
+ }
767
+ },
768
+ "privateEndpointSubnetId": {
769
+ "type": "string",
770
+ "metadata": {
771
+ "description": "Subnet resource ID for the Private Endpoint (must be dedicated for PE use)"
772
+ }
773
+ },
774
+ "privateDnsZoneId": {
775
+ "type": "string",
776
+ "metadata": {
777
+ "description": "Private DNS Zone ID for privatelink.vaultcore.azure.net"
778
+ }
779
+ },
780
+ "allowedIPs": {
781
+ "type": "array",
782
+ "defaultValue": [],
783
+ "metadata": {
784
+ "description": "Array of allowed IP addresses/CIDR blocks for Key Vault access. Use [\"*\"] for public access."
785
+ }
786
+ },
787
+ "currentUserObjectId": {
788
+ "type": "string",
789
+ "defaultValue": "",
790
+ "metadata": {
791
+ "description": "Current user object ID for temporary access during deployment"
792
+ }
793
+ },
794
+ "deploymentIdentityObjectIds": {
795
+ "type": "array",
796
+ "defaultValue": [],
797
+ "metadata": {
798
+ "description": "Additional object IDs that require Key Vault secrets get/list/set (applied additively)"
799
+ }
800
+ },
801
+ "sku": {
802
+ "type": "string",
803
+ "defaultValue": "standard",
804
+ "allowedValues": ["standard", "premium"],
805
+ "metadata": {
806
+ "description": "Key Vault SKU (standard or premium)"
807
+ }
808
+ },
809
+ "enablePublicAccessForKeyVaultReferences": {
810
+ "type": "bool",
811
+ "defaultValue": true,
812
+ "metadata": {
813
+ "description": "Enable public network access for App Service Key Vault references. Required when using @Microsoft.KeyVault() in app settings."
814
+ }
815
+ }
816
+ },
817
+ "variables": {
818
+ "copy": [
819
+ {
820
+ "name": "deploymentIdentityPolicies",
821
+ "count": "[length(parameters('deploymentIdentityObjectIds'))]",
822
+ "input": {
823
+ "tenantId": "[subscription().tenantId]",
824
+ "objectId": "[parameters('deploymentIdentityObjectIds')[copyIndex('deploymentIdentityPolicies')]]",
825
+ "permissions": {
826
+ "secrets": ["get", "list", "set"],
827
+ "keys": [],
828
+ "certificates": [],
829
+ "storage": []
830
+ }
831
+ }
832
+ },
833
+ {
834
+ "name": "allowedIPsRules",
835
+ "count": "[length(parameters('allowedIPs'))]",
836
+ "input": {
837
+ "value": "[parameters('allowedIPs')[copyIndex('allowedIPsRules')]]"
838
+ }
839
+ }
840
+ ],
841
+ "isPublicAccess": "[contains(parameters('allowedIPs'), '*')]",
842
+ "isPrivateOnly": "[contains(parameters('allowedIPs'), '-')]",
843
+ "emptyObjectId": "00000000-0000-0000-0000-000000000000",
844
+ "shouldGrantCurrentUserPolicy": "[and(not(empty(parameters('currentUserObjectId'))), not(equals(toLower(parameters('currentUserObjectId')), variables('emptyObjectId'))))]",
845
+ "currentUserPolicy": "[if(variables('shouldGrantCurrentUserPolicy'), createArray(createObject('tenantId', subscription().tenantId, 'objectId', parameters('currentUserObjectId'), 'permissions', createObject('secrets', createArray('set', 'get', 'list'), 'keys', createArray(), 'certificates', createArray(), 'storage', createArray()))), createArray())]"
846
+ },
847
+ "resources": [
848
+ {
849
+ "type": "Microsoft.KeyVault/vaults",
850
+ "apiVersion": "2023-02-01",
851
+ "name": "[parameters('name')]",
852
+ "location": "[parameters('location')]",
853
+ "tags": "[parameters('tags')]",
854
+ "properties": {
855
+ "enableSoftDelete": true,
856
+ "enablePurgeProtection": true,
857
+ "enableRbacAuthorization": false,
858
+ "tenantId": "[subscription().tenantId]",
859
+ "sku": {
860
+ "family": "A",
861
+ "name": "[parameters('sku')]"
862
+ },
863
+ "accessPolicies": [],
864
+ "networkAcls": {
865
+ "bypass": "AzureServices",
866
+ "defaultAction": "[if(variables('isPublicAccess'), 'Allow', 'Deny')]",
867
+ "virtualNetworkRules": [],
868
+ "ipRules": "[if(variables('isPublicAccess'), createArray(), if(variables('isPrivateOnly'), createArray(), variables('allowedIPsRules')))]"
869
+ },
870
+ "publicNetworkAccess": "[if(or(variables('isPublicAccess'), parameters('enablePublicAccessForKeyVaultReferences')), 'Enabled', if(variables('isPrivateOnly'), 'Disabled', if(greater(length(variables('allowedIPsRules')), 0), 'Enabled', 'Disabled')))]"
871
+ }
872
+ },
873
+ {
874
+ "condition": "[and(variables('shouldGrantCurrentUserPolicy'), empty(variables('deploymentIdentityPolicies')))]",
875
+ "type": "Microsoft.KeyVault/vaults/accessPolicies",
876
+ "apiVersion": "2023-07-01",
877
+ "name": "[format('{0}/{1}', parameters('name'), 'add')]",
878
+ "properties": {
879
+ "accessPolicies": "[variables('currentUserPolicy')]"
880
+ },
881
+ "dependsOn": ["[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]"]
882
+ },
883
+ {
884
+ "condition": "[and(not(empty(variables('deploymentIdentityPolicies'))), not(variables('shouldGrantCurrentUserPolicy')))]",
885
+ "type": "Microsoft.KeyVault/vaults/accessPolicies",
886
+ "apiVersion": "2023-07-01",
887
+ "name": "[format('{0}/{1}', parameters('name'), 'add')]",
888
+ "properties": {
889
+ "accessPolicies": "[variables('deploymentIdentityPolicies')]"
890
+ },
891
+ "dependsOn": ["[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]"]
892
+ },
893
+ {
894
+ "condition": "[and(variables('shouldGrantCurrentUserPolicy'), not(empty(variables('deploymentIdentityPolicies'))))]",
895
+ "type": "Microsoft.KeyVault/vaults/accessPolicies",
896
+ "apiVersion": "2023-07-01",
897
+ "name": "[format('{0}/{1}', parameters('name'), 'add')]",
898
+ "properties": {
899
+ "accessPolicies": "[concat(variables('currentUserPolicy'), variables('deploymentIdentityPolicies'))]"
900
+ },
901
+ "dependsOn": ["[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]"]
902
+ },
903
+ {
904
+ "type": "Microsoft.Network/privateEndpoints",
905
+ "apiVersion": "2023-05-01",
906
+ "name": "[format('{0}-pe', parameters('name'))]",
907
+ "location": "[parameters('location')]",
908
+ "tags": "[parameters('tags')]",
909
+ "properties": {
910
+ "subnet": {
911
+ "id": "[parameters('privateEndpointSubnetId')]"
912
+ },
913
+ "customNetworkInterfaceName": "[format('{0}-nic', parameters('name'))]",
914
+ "privateLinkServiceConnections": [
915
+ {
916
+ "name": "kv-pls",
917
+ "properties": {
918
+ "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]",
919
+ "groupIds": ["vault"]
920
+ }
921
+ }
922
+ ]
923
+ },
924
+ "dependsOn": ["[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]"]
925
+ },
926
+ {
927
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
928
+ "apiVersion": "2023-05-01",
929
+ "name": "[format('{0}/{1}', format('{0}-pe', parameters('name')), 'kv-dns')]",
930
+ "properties": {
931
+ "privateDnsZoneConfigs": [
932
+ {
933
+ "name": "vault-dns-zone",
934
+ "properties": {
935
+ "privateDnsZoneId": "[parameters('privateDnsZoneId')]"
936
+ }
937
+ }
938
+ ]
939
+ },
940
+ "dependsOn": [
941
+ "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-pe', parameters('name')))]"
942
+ ]
943
+ }
944
+ ],
945
+ "outputs": {
946
+ "keyVaultId": {
947
+ "type": "string",
948
+ "metadata": {
949
+ "description": "Key Vault ID"
950
+ },
951
+ "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]"
952
+ },
953
+ "keyVaultName": {
954
+ "type": "string",
955
+ "metadata": {
956
+ "description": "Key Vault name"
957
+ },
958
+ "value": "[parameters('name')]"
959
+ },
960
+ "keyVaultUri": {
961
+ "type": "string",
962
+ "metadata": {
963
+ "description": "Key Vault URI"
964
+ },
965
+ "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '2023-02-01').vaultUri]"
966
+ }
967
+ }
968
+ }
969
+ },
970
+ "dependsOn": [
971
+ "[resourceId('Microsoft.Resources/deployments', 'managedIdentity')]",
972
+ "[resourceId('Microsoft.Resources/deployments', 'network')]"
973
+ ]
974
+ },
975
+ {
976
+ "condition": "[parameters('enableAzureOpenAI')]",
977
+ "type": "Microsoft.Resources/deployments",
978
+ "apiVersion": "2025-04-01",
979
+ "name": "azureOpenAI",
980
+ "properties": {
981
+ "expressionEvaluationOptions": {
982
+ "scope": "inner"
983
+ },
984
+ "mode": "Incremental",
985
+ "parameters": {
986
+ "infrastructureName": {
987
+ "value": "[variables('infrastructureName')]"
988
+ },
989
+ "location": {
990
+ "value": "[variables('openAiLocation')]"
991
+ },
992
+ "tags": {
993
+ "value": "[parameters('tags')]"
994
+ },
995
+ "keyVaultName": {
996
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]"
997
+ }
998
+ },
999
+ "template": {
1000
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1001
+ "contentVersion": "1.0.0.0",
1002
+ "metadata": {
1003
+ "_generator": {
1004
+ "name": "bicep",
1005
+ "version": "0.47.16.16243",
1006
+ "templateHash": "11131379543865133238"
1007
+ }
1008
+ },
1009
+ "parameters": {
1010
+ "infrastructureName": {
1011
+ "type": "string",
1012
+ "metadata": {
1013
+ "description": "Infrastructure name prefix (e.g. aifabrix9)"
1014
+ }
1015
+ },
1016
+ "location": {
1017
+ "type": "string",
1018
+ "metadata": {
1019
+ "description": "Azure region for the OpenAI account"
1020
+ }
1021
+ },
1022
+ "tags": {
1023
+ "type": "object",
1024
+ "defaultValue": {},
1025
+ "metadata": {
1026
+ "description": "Resource tags"
1027
+ }
1028
+ },
1029
+ "keyVaultName": {
1030
+ "type": "string",
1031
+ "metadata": {
1032
+ "description": "Key Vault name to store endpoint and API key secrets"
1033
+ }
1034
+ }
1035
+ },
1036
+ "variables": {
1037
+ "accountName": "[format('{0}misoopenai', replace(parameters('infrastructureName'), '-', ''))]"
1038
+ },
1039
+ "resources": [
1040
+ {
1041
+ "type": "Microsoft.CognitiveServices/accounts",
1042
+ "apiVersion": "2023-05-01",
1043
+ "name": "[variables('accountName')]",
1044
+ "location": "[parameters('location')]",
1045
+ "tags": "[parameters('tags')]",
1046
+ "kind": "OpenAI",
1047
+ "sku": {
1048
+ "name": "S0"
1049
+ },
1050
+ "properties": {
1051
+ "customSubDomainName": "[variables('accountName')]",
1052
+ "publicNetworkAccess": "Enabled"
1053
+ }
1054
+ },
1055
+ {
1056
+ "type": "Microsoft.KeyVault/vaults/secrets",
1057
+ "apiVersion": "2023-07-01",
1058
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'azureopenai-endpoint')]",
1059
+ "properties": {
1060
+ "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', variables('accountName')), '2023-05-01').endpoint]"
1061
+ },
1062
+ "dependsOn": [
1063
+ "[resourceId('Microsoft.CognitiveServices/accounts', variables('accountName'))]"
1064
+ ]
1065
+ },
1066
+ {
1067
+ "type": "Microsoft.KeyVault/vaults/secrets",
1068
+ "apiVersion": "2023-07-01",
1069
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'azureopenai-apikey')]",
1070
+ "properties": {
1071
+ "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', variables('accountName')), '2023-05-01').key1]"
1072
+ },
1073
+ "dependsOn": [
1074
+ "[resourceId('Microsoft.CognitiveServices/accounts', variables('accountName'))]"
1075
+ ]
1076
+ }
1077
+ ],
1078
+ "outputs": {
1079
+ "accountName": {
1080
+ "type": "string",
1081
+ "value": "[variables('accountName')]"
1082
+ },
1083
+ "endpoint": {
1084
+ "type": "string",
1085
+ "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', variables('accountName')), '2023-05-01').endpoint]"
1086
+ }
1087
+ }
1088
+ }
1089
+ },
1090
+ "dependsOn": ["[resourceId('Microsoft.Resources/deployments', 'keyVault')]"]
1091
+ },
1092
+ {
1093
+ "type": "Microsoft.Resources/deployments",
1094
+ "apiVersion": "2025-04-01",
1095
+ "name": "storage",
1096
+ "properties": {
1097
+ "expressionEvaluationOptions": {
1098
+ "scope": "inner"
1099
+ },
1100
+ "mode": "Incremental",
1101
+ "parameters": {
1102
+ "location": {
1103
+ "value": "[variables('deploymentLocation')]"
1104
+ },
1105
+ "name": {
1106
+ "value": "[format('{0}misostg', replace(variables('infrastructureName'), '-', ''))]"
1107
+ },
1108
+ "tags": {
1109
+ "value": "[parameters('tags')]"
1110
+ },
1111
+ "keyVaultId": {
1112
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultId.value]"
1113
+ },
1114
+ "privateEndpointSubnetId": {
1115
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.privateEndpointSubnetId.value]"
1116
+ },
1117
+ "privateDnsZoneId": {
1118
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.storageBlobPrivateDnsZoneId.value]"
1119
+ },
1120
+ "filePrivateDnsZoneId": {
1121
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.storageFilePrivateDnsZoneId.value]"
1122
+ },
1123
+ "allowedIPs": {
1124
+ "value": []
1125
+ }
1126
+ },
1127
+ "template": {
1128
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1129
+ "contentVersion": "1.0.0.0",
1130
+ "metadata": {
1131
+ "_generator": {
1132
+ "name": "bicep",
1133
+ "version": "0.47.16.16243",
1134
+ "templateHash": "7520721023259261582"
1135
+ }
1136
+ },
1137
+ "parameters": {
1138
+ "location": {
1139
+ "type": "string",
1140
+ "defaultValue": "[resourceGroup().location]",
1141
+ "metadata": {
1142
+ "description": "Azure region (defaults to the resource group location)"
1143
+ }
1144
+ },
1145
+ "name": {
1146
+ "type": "string",
1147
+ "metadata": {
1148
+ "description": "Storage resource name"
1149
+ }
1150
+ },
1151
+ "sku": {
1152
+ "type": "string",
1153
+ "defaultValue": "Standard_LRS",
1154
+ "allowedValues": ["Standard_LRS", "Standard_GRS", "Standard_RAGRS", "Premium_LRS"],
1155
+ "metadata": {
1156
+ "description": "Storage account SKU (e.g., Standard_LRS, Standard_GRS, Premium_LRS)"
1157
+ }
1158
+ },
1159
+ "tags": {
1160
+ "type": "object",
1161
+ "defaultValue": {},
1162
+ "metadata": {
1163
+ "description": "Optional tags applied to all resources created by this module"
1164
+ }
1165
+ },
1166
+ "keyVaultId": {
1167
+ "type": "string",
1168
+ "metadata": {
1169
+ "description": "Key Vault resource ID to store the Redis primary key"
1170
+ }
1171
+ },
1172
+ "logsContainerName": {
1173
+ "type": "string",
1174
+ "defaultValue": "scriptlogs",
1175
+ "metadata": {
1176
+ "description": "Blob container name for deploymentScripts logs."
1177
+ }
1178
+ },
1179
+ "fileShareName": {
1180
+ "type": "string",
1181
+ "defaultValue": "data",
1182
+ "metadata": {
1183
+ "description": "File Share name for application data."
1184
+ }
1185
+ },
1186
+ "privateEndpointSubnetId": {
1187
+ "type": "string",
1188
+ "metadata": {
1189
+ "description": "Subnet resource ID for the Private Endpoint (must be dedicated for PE use)"
1190
+ }
1191
+ },
1192
+ "privateDnsZoneId": {
1193
+ "type": "string",
1194
+ "metadata": {
1195
+ "description": "Private DNS Zone ID for privatelink.blob.core.windows.com"
1196
+ }
1197
+ },
1198
+ "filePrivateDnsZoneId": {
1199
+ "type": "string",
1200
+ "metadata": {
1201
+ "description": "Private DNS Zone ID for privatelink.file.core.windows.xxx"
1202
+ }
1203
+ },
1204
+ "allowedIPs": {
1205
+ "type": "array",
1206
+ "defaultValue": [],
1207
+ "metadata": {
1208
+ "description": "Array of allowed IP addresses/CIDR blocks for Storage Account access. Use [\"*\"] for public access."
1209
+ }
1210
+ }
1211
+ },
1212
+ "variables": {
1213
+ "copy": [
1214
+ {
1215
+ "name": "allowedIPsRules",
1216
+ "count": "[length(parameters('allowedIPs'))]",
1217
+ "input": {
1218
+ "value": "[parameters('allowedIPs')[copyIndex('allowedIPsRules')]]"
1219
+ }
1220
+ }
1221
+ ],
1222
+ "isPublicAccess": "[contains(parameters('allowedIPs'), '*')]",
1223
+ "isPrivateOnly": "[contains(parameters('allowedIPs'), '-')]",
1224
+ "storageSecretName": "storage-account-key",
1225
+ "kvName": "[last(split(parameters('keyVaultId'), '/'))]"
1226
+ },
1227
+ "resources": [
1228
+ {
1229
+ "type": "Microsoft.Storage/storageAccounts",
1230
+ "apiVersion": "2024-01-01",
1231
+ "name": "[parameters('name')]",
1232
+ "location": "[parameters('location')]",
1233
+ "sku": {
1234
+ "name": "[parameters('sku')]"
1235
+ },
1236
+ "kind": "StorageV2",
1237
+ "tags": "[parameters('tags')]",
1238
+ "properties": {
1239
+ "allowBlobPublicAccess": false,
1240
+ "minimumTlsVersion": "TLS1_2",
1241
+ "supportsHttpsTrafficOnly": true,
1242
+ "allowSharedKeyAccess": true,
1243
+ "publicNetworkAccess": "[if(variables('isPublicAccess'), 'Enabled', if(variables('isPrivateOnly'), 'Disabled', if(greater(length(variables('allowedIPsRules')), 0), 'Enabled', 'Disabled')))]",
1244
+ "networkAcls": {
1245
+ "resourceAccessRules": [],
1246
+ "bypass": "AzureServices",
1247
+ "virtualNetworkRules": [],
1248
+ "defaultAction": "[if(variables('isPublicAccess'), 'Allow', 'Deny')]",
1249
+ "ipRules": "[if(variables('isPublicAccess'), createArray(), if(variables('isPrivateOnly'), createArray(), variables('allowedIPsRules')))]"
1250
+ }
1251
+ }
1252
+ },
1253
+ {
1254
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
1255
+ "apiVersion": "2024-01-01",
1256
+ "name": "[format('{0}/{1}', parameters('name'), 'default')]",
1257
+ "properties": {},
1258
+ "dependsOn": ["[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"]
1259
+ },
1260
+ {
1261
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
1262
+ "apiVersion": "2024-01-01",
1263
+ "name": "[format('{0}/default/{1}', parameters('name'), parameters('logsContainerName'))]",
1264
+ "properties": {
1265
+ "publicAccess": "None"
1266
+ },
1267
+ "dependsOn": [
1268
+ "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]",
1269
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('name'), 'default')]"
1270
+ ]
1271
+ },
1272
+ {
1273
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
1274
+ "apiVersion": "2021-04-01",
1275
+ "name": "[format('{0}/{1}', parameters('name'), 'default')]",
1276
+ "dependsOn": ["[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"]
1277
+ },
1278
+ {
1279
+ "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
1280
+ "apiVersion": "2021-04-01",
1281
+ "name": "[format('{0}/{1}/{2}', parameters('name'), 'default', parameters('fileShareName'))]",
1282
+ "properties": {
1283
+ "accessTier": "TransactionOptimized"
1284
+ },
1285
+ "dependsOn": [
1286
+ "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('name'), 'default')]"
1287
+ ]
1288
+ },
1289
+ {
1290
+ "type": "Microsoft.Network/privateEndpoints",
1291
+ "apiVersion": "2023-05-01",
1292
+ "name": "[format('{0}-pe', parameters('name'))]",
1293
+ "location": "[parameters('location')]",
1294
+ "tags": "[parameters('tags')]",
1295
+ "properties": {
1296
+ "subnet": {
1297
+ "id": "[parameters('privateEndpointSubnetId')]"
1298
+ },
1299
+ "customNetworkInterfaceName": "[format('{0}-nic', parameters('name'))]",
1300
+ "privateLinkServiceConnections": [
1301
+ {
1302
+ "name": "blob-pls",
1303
+ "properties": {
1304
+ "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]",
1305
+ "groupIds": ["blob"]
1306
+ }
1307
+ }
1308
+ ]
1309
+ },
1310
+ "dependsOn": ["[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"]
1311
+ },
1312
+ {
1313
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
1314
+ "apiVersion": "2023-05-01",
1315
+ "name": "[format('{0}/{1}', format('{0}-pe', parameters('name')), 'blob-dns')]",
1316
+ "properties": {
1317
+ "privateDnsZoneConfigs": [
1318
+ {
1319
+ "name": "blob-dns-zone",
1320
+ "properties": {
1321
+ "privateDnsZoneId": "[parameters('privateDnsZoneId')]"
1322
+ }
1323
+ }
1324
+ ]
1325
+ },
1326
+ "dependsOn": [
1327
+ "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-pe', parameters('name')))]"
1328
+ ]
1329
+ },
1330
+ {
1331
+ "type": "Microsoft.Network/privateEndpoints",
1332
+ "apiVersion": "2023-05-01",
1333
+ "name": "[format('{0}-file-pe', parameters('name'))]",
1334
+ "location": "[parameters('location')]",
1335
+ "tags": "[parameters('tags')]",
1336
+ "properties": {
1337
+ "subnet": {
1338
+ "id": "[parameters('privateEndpointSubnetId')]"
1339
+ },
1340
+ "customNetworkInterfaceName": "[format('{0}-file-nic', parameters('name'))]",
1341
+ "privateLinkServiceConnections": [
1342
+ {
1343
+ "name": "file-pls",
1344
+ "properties": {
1345
+ "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]",
1346
+ "groupIds": ["file"]
1347
+ }
1348
+ }
1349
+ ]
1350
+ },
1351
+ "dependsOn": ["[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"]
1352
+ },
1353
+ {
1354
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
1355
+ "apiVersion": "2023-05-01",
1356
+ "name": "[format('{0}/{1}', format('{0}-file-pe', parameters('name')), 'file-dns')]",
1357
+ "properties": {
1358
+ "privateDnsZoneConfigs": [
1359
+ {
1360
+ "name": "file-dns-zone",
1361
+ "properties": {
1362
+ "privateDnsZoneId": "[parameters('filePrivateDnsZoneId')]"
1363
+ }
1364
+ }
1365
+ ]
1366
+ },
1367
+ "dependsOn": [
1368
+ "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-file-pe', parameters('name')))]"
1369
+ ]
1370
+ },
1371
+ {
1372
+ "type": "Microsoft.KeyVault/vaults/secrets",
1373
+ "apiVersion": "2023-07-01",
1374
+ "name": "[format('{0}/{1}', variables('kvName'), variables('storageSecretName'))]",
1375
+ "properties": {
1376
+ "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '2024-01-01').keys[0].value]"
1377
+ },
1378
+ "dependsOn": ["[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"]
1379
+ }
1380
+ ],
1381
+ "outputs": {
1382
+ "scriptStorageAccountName": {
1383
+ "type": "string",
1384
+ "metadata": {
1385
+ "description": "Storage account name for deploymentScripts logs"
1386
+ },
1387
+ "value": "[parameters('name')]"
1388
+ },
1389
+ "scriptLogsContainerName": {
1390
+ "type": "string",
1391
+ "metadata": {
1392
+ "description": "Blob container name for deploymentScripts logs"
1393
+ },
1394
+ "value": "[parameters('logsContainerName')]"
1395
+ },
1396
+ "flowiseFileShareName": {
1397
+ "type": "string",
1398
+ "metadata": {
1399
+ "description": "File share name for Flowise data"
1400
+ },
1401
+ "value": "[parameters('fileShareName')]"
1402
+ },
1403
+ "storageSecretName": {
1404
+ "type": "string",
1405
+ "metadata": {
1406
+ "description": "Storage account key secret name in Key Vault"
1407
+ },
1408
+ "value": "[variables('storageSecretName')]"
1409
+ }
1410
+ }
1411
+ }
1412
+ },
1413
+ "dependsOn": [
1414
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]",
1415
+ "[resourceId('Microsoft.Resources/deployments', 'network')]"
1416
+ ]
1417
+ },
1418
+ {
1419
+ "type": "Microsoft.Resources/deployments",
1420
+ "apiVersion": "2025-04-01",
1421
+ "name": "appServicePlan",
1422
+ "properties": {
1423
+ "expressionEvaluationOptions": {
1424
+ "scope": "inner"
1425
+ },
1426
+ "mode": "Incremental",
1427
+ "parameters": {
1428
+ "location": {
1429
+ "value": "[variables('deploymentLocation')]"
1430
+ },
1431
+ "name": {
1432
+ "value": "[format('{0}-miso-asp', variables('infrastructureName'))]"
1433
+ },
1434
+ "skuDisplay": {
1435
+ "value": "B2"
1436
+ },
1437
+ "capacity": {
1438
+ "value": 2
1439
+ },
1440
+ "tags": {
1441
+ "value": "[parameters('tags')]"
1442
+ }
1443
+ },
1444
+ "template": {
1445
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1446
+ "contentVersion": "1.0.0.0",
1447
+ "metadata": {
1448
+ "_generator": {
1449
+ "name": "bicep",
1450
+ "version": "0.47.16.16243",
1451
+ "templateHash": "4988795866576200937"
1452
+ }
1453
+ },
1454
+ "parameters": {
1455
+ "location": {
1456
+ "type": "string",
1457
+ "defaultValue": "[resourceGroup().location]",
1458
+ "metadata": {
1459
+ "description": "Azure region to deploy the App Service Plan, e.g. westeurope"
1460
+ }
1461
+ },
1462
+ "name": {
1463
+ "type": "string",
1464
+ "metadata": {
1465
+ "description": "Name of the App Service Plan"
1466
+ }
1467
+ },
1468
+ "skuDisplay": {
1469
+ "type": "string",
1470
+ "allowedValues": [
1471
+ "B1",
1472
+ "B2",
1473
+ "B3",
1474
+ "S1",
1475
+ "S2",
1476
+ "S3",
1477
+ "P1v3",
1478
+ "P2v3",
1479
+ "P3v3",
1480
+ "EP1",
1481
+ "EP2",
1482
+ "EP3"
1483
+ ],
1484
+ "metadata": {
1485
+ "description": "Select a Linux-compatible App Service Plan SKU with details"
1486
+ }
1487
+ },
1488
+ "capacity": {
1489
+ "type": "int",
1490
+ "defaultValue": 1,
1491
+ "minValue": 1,
1492
+ "metadata": {
1493
+ "description": "Number of instances to provision in the App Service Plan"
1494
+ }
1495
+ },
1496
+ "tags": {
1497
+ "type": "object",
1498
+ "defaultValue": {},
1499
+ "metadata": {
1500
+ "description": "Resource tags to apply to the App Service Plan"
1501
+ }
1502
+ }
1503
+ },
1504
+ "variables": {
1505
+ "skuCode": "[parameters('skuDisplay')]",
1506
+ "skuTiers": {
1507
+ "B1": "Basic",
1508
+ "B2": "Basic",
1509
+ "B3": "Basic",
1510
+ "S1": "Standard",
1511
+ "S2": "Standard",
1512
+ "S3": "Standard",
1513
+ "P1v3": "PremiumV3",
1514
+ "P2v3": "PremiumV3",
1515
+ "P3v3": "PremiumV3",
1516
+ "EP1": "ElasticPremium",
1517
+ "EP2": "ElasticPremium",
1518
+ "EP3": "ElasticPremium"
1519
+ }
1520
+ },
1521
+ "resources": [
1522
+ {
1523
+ "type": "Microsoft.Web/serverfarms",
1524
+ "apiVersion": "2023-12-01",
1525
+ "name": "[parameters('name')]",
1526
+ "location": "[parameters('location')]",
1527
+ "tags": "[parameters('tags')]",
1528
+ "sku": {
1529
+ "name": "[variables('skuCode')]",
1530
+ "tier": "[variables('skuTiers')[variables('skuCode')]]",
1531
+ "size": "[variables('skuCode')]",
1532
+ "capacity": "[parameters('capacity')]"
1533
+ },
1534
+ "kind": "linux",
1535
+ "properties": {
1536
+ "reserved": true
1537
+ }
1538
+ }
1539
+ ],
1540
+ "outputs": {
1541
+ "planId": {
1542
+ "type": "string",
1543
+ "value": "[resourceId('Microsoft.Web/serverfarms', parameters('name'))]"
1544
+ },
1545
+ "planName": {
1546
+ "type": "string",
1547
+ "value": "[parameters('name')]"
1548
+ }
1549
+ }
1550
+ }
1551
+ }
1552
+ },
1553
+ {
1554
+ "condition": "[parameters('enableAppInsights')]",
1555
+ "type": "Microsoft.Resources/deployments",
1556
+ "apiVersion": "2025-04-01",
1557
+ "name": "appInsights",
1558
+ "properties": {
1559
+ "expressionEvaluationOptions": {
1560
+ "scope": "inner"
1561
+ },
1562
+ "mode": "Incremental",
1563
+ "parameters": {
1564
+ "name": {
1565
+ "value": "[format('{0}-miso-keycloak', variables('infrastructureName'))]"
1566
+ },
1567
+ "location": {
1568
+ "value": "[variables('appInsightsLocation')]"
1569
+ },
1570
+ "tags": {
1571
+ "value": "[parameters('tags')]"
1572
+ }
1573
+ },
1574
+ "template": {
1575
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1576
+ "contentVersion": "1.0.0.0",
1577
+ "metadata": {
1578
+ "_generator": {
1579
+ "name": "bicep",
1580
+ "version": "0.47.16.16243",
1581
+ "templateHash": "4160792943473810009"
1582
+ }
1583
+ },
1584
+ "parameters": {
1585
+ "name": {
1586
+ "type": "string",
1587
+ "metadata": {
1588
+ "description": "Name of the Application Insights component"
1589
+ }
1590
+ },
1591
+ "location": {
1592
+ "type": "string",
1593
+ "defaultValue": "[resourceGroup().location]",
1594
+ "metadata": {
1595
+ "description": "Azure region"
1596
+ }
1597
+ },
1598
+ "tags": {
1599
+ "type": "object",
1600
+ "defaultValue": {},
1601
+ "metadata": {
1602
+ "description": "Resource tags"
1603
+ }
1604
+ }
1605
+ },
1606
+ "resources": [
1607
+ {
1608
+ "type": "Microsoft.Insights/components",
1609
+ "apiVersion": "2020-02-02",
1610
+ "name": "[parameters('name')]",
1611
+ "location": "[parameters('location')]",
1612
+ "kind": "web",
1613
+ "properties": {
1614
+ "Application_Type": "web",
1615
+ "Request_Source": "rest"
1616
+ },
1617
+ "tags": "[parameters('tags')]"
1618
+ }
1619
+ ],
1620
+ "outputs": {
1621
+ "instrumentationKey": {
1622
+ "type": "string",
1623
+ "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]"
1624
+ },
1625
+ "connectionString": {
1626
+ "type": "string",
1627
+ "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]"
1628
+ }
1629
+ }
1630
+ }
1631
+ }
1632
+ },
1633
+ {
1634
+ "type": "Microsoft.Resources/deployments",
1635
+ "apiVersion": "2025-04-01",
1636
+ "name": "bootstrapSecrets",
1637
+ "properties": {
1638
+ "expressionEvaluationOptions": {
1639
+ "scope": "inner"
1640
+ },
1641
+ "mode": "Incremental",
1642
+ "parameters": {
1643
+ "infrastructureName": {
1644
+ "value": "[variables('infrastructureName')]"
1645
+ },
1646
+ "keyVaultName": {
1647
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]"
1648
+ },
1649
+ "adminPassword": {
1650
+ "value": "[parameters('adminPassword')]"
1651
+ },
1652
+ "encryptionKeyOverride": {
1653
+ "value": "[parameters('encryptionKey')]"
1654
+ }
1655
+ },
1656
+ "template": {
1657
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1658
+ "contentVersion": "1.0.0.0",
1659
+ "metadata": {
1660
+ "_generator": {
1661
+ "name": "bicep",
1662
+ "version": "0.47.16.16243",
1663
+ "templateHash": "8977479852148651255"
1664
+ }
1665
+ },
1666
+ "parameters": {
1667
+ "infrastructureName": {
1668
+ "type": "string",
1669
+ "metadata": {
1670
+ "description": "Infrastructure name prefix (secret names + entropy seed)"
1671
+ }
1672
+ },
1673
+ "keyVaultName": {
1674
+ "type": "string",
1675
+ "metadata": {
1676
+ "description": "Key Vault name (existing)"
1677
+ }
1678
+ },
1679
+ "adminPassword": {
1680
+ "type": "securestring",
1681
+ "metadata": {
1682
+ "description": "Initial platform administrator password (user-supplied; not used for PG/Keycloak/encryption)"
1683
+ }
1684
+ },
1685
+ "encryptionKeyOverride": {
1686
+ "type": "securestring",
1687
+ "defaultValue": "",
1688
+ "metadata": {
1689
+ "description": "Optional encryption key override. Empty → generate install-unique key."
1690
+ }
1691
+ }
1692
+ },
1693
+ "variables": {
1694
+ "seed": "[uniqueString(resourceGroup().id, parameters('infrastructureName'), 'mp-bootstrap-secrets-v1')]",
1695
+ "postgresPassword": "[format('Pg!9{0}{1}', take(replace(guid(variables('seed'), 'pg-a'), '-', ''), 16), take(replace(guid(variables('seed'), 'pg-b'), '-', ''), 16))]",
1696
+ "keycloakPassword": "[format('Kc!9{0}{1}', take(replace(guid(variables('seed'), 'kc-a'), '-', ''), 16), take(replace(guid(variables('seed'), 'kc-b'), '-', ''), 16))]",
1697
+ "generatedEncryptionKey": "[take(format('{0}{1}{2}', replace(guid(variables('seed'), 'enc-a'), '-', ''), replace(guid(variables('seed'), 'enc-b'), '-', ''), replace(guid(variables('seed'), 'enc-c'), '-', '')), 64)]",
1698
+ "encryptionKey": "[if(not(empty(parameters('encryptionKeyOverride'))), parameters('encryptionKeyOverride'), variables('generatedEncryptionKey'))]"
1699
+ },
1700
+ "resources": [
1701
+ {
1702
+ "type": "Microsoft.KeyVault/vaults/secrets",
1703
+ "apiVersion": "2023-07-01",
1704
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-onboarding-admin-password', parameters('infrastructureName')))]",
1705
+ "properties": {
1706
+ "value": "[parameters('adminPassword')]"
1707
+ }
1708
+ }
1709
+ ],
1710
+ "outputs": {
1711
+ "postgresPassword": {
1712
+ "type": "string",
1713
+ "value": "[variables('postgresPassword')]"
1714
+ },
1715
+ "keycloakPassword": {
1716
+ "type": "string",
1717
+ "value": "[variables('keycloakPassword')]"
1718
+ },
1719
+ "encryptionKey": {
1720
+ "type": "string",
1721
+ "value": "[variables('encryptionKey')]"
1722
+ },
1723
+ "onboardingAdminPasswordSecretName": {
1724
+ "type": "string",
1725
+ "value": "[format('{0}-onboarding-admin-password', parameters('infrastructureName'))]"
1726
+ }
1727
+ }
1728
+ }
1729
+ },
1730
+ "dependsOn": ["[resourceId('Microsoft.Resources/deployments', 'keyVault')]"]
1731
+ },
1732
+ {
1733
+ "type": "Microsoft.Resources/deployments",
1734
+ "apiVersion": "2025-04-01",
1735
+ "name": "postgresql",
1736
+ "properties": {
1737
+ "expressionEvaluationOptions": {
1738
+ "scope": "inner"
1739
+ },
1740
+ "mode": "Incremental",
1741
+ "parameters": {
1742
+ "name": {
1743
+ "value": "[format('{0}-miso-pg', variables('infrastructureName'))]"
1744
+ },
1745
+ "location": {
1746
+ "value": "[variables('deploymentLocation')]"
1747
+ },
1748
+ "administratorLogin": {
1749
+ "value": "aifabrixadmin"
1750
+ },
1751
+ "administratorLoginPassword": {
1752
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.postgresPassword.value]"
1753
+ },
1754
+ "version": {
1755
+ "value": "15"
1756
+ },
1757
+ "skuName": {
1758
+ "value": "Standard_B2ms"
1759
+ },
1760
+ "skuTier": {
1761
+ "value": "Burstable"
1762
+ },
1763
+ "storageSizeGB": {
1764
+ "value": 64
1765
+ },
1766
+ "subnetId": {
1767
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.postgresSubnetId.value]"
1768
+ },
1769
+ "privateDnsZoneId": {
1770
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.postgresPrivateDnsZoneId.value]"
1771
+ },
1772
+ "keyVaultId": {
1773
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultId.value]"
1774
+ },
1775
+ "secretPrefix": {
1776
+ "value": "[variables('infrastructureName')]"
1777
+ },
1778
+ "tags": {
1779
+ "value": "[parameters('tags')]"
1780
+ },
1781
+ "enablePublicAccess": {
1782
+ "value": "[parameters('enablePostgresPublicAccess')]"
1783
+ },
1784
+ "allowedPublicIPs": {
1785
+ "value": "[parameters('allowedIPs')]"
1786
+ },
1787
+ "prismaMainConnectionLimit": {
1788
+ "value": 5
1789
+ },
1790
+ "prismaMainPoolTimeout": {
1791
+ "value": 20
1792
+ },
1793
+ "prismaLogsConnectionLimit": {
1794
+ "value": 5
1795
+ },
1796
+ "prismaLogsPoolTimeout": {
1797
+ "value": 20
1798
+ }
1799
+ },
1800
+ "template": {
1801
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1802
+ "contentVersion": "1.0.0.0",
1803
+ "metadata": {
1804
+ "_generator": {
1805
+ "name": "bicep",
1806
+ "version": "0.47.16.16243",
1807
+ "templateHash": "15798309166903375415"
1808
+ }
1809
+ },
1810
+ "parameters": {
1811
+ "name": {
1812
+ "type": "string",
1813
+ "metadata": {
1814
+ "description": "PostgreSQL Flexible Server name"
1815
+ }
1816
+ },
1817
+ "location": {
1818
+ "type": "string",
1819
+ "defaultValue": "[resourceGroup().location]",
1820
+ "metadata": {
1821
+ "description": "Azure region for the PostgreSQL server"
1822
+ }
1823
+ },
1824
+ "administratorLogin": {
1825
+ "type": "string",
1826
+ "defaultValue": "aifabrixadmin",
1827
+ "metadata": {
1828
+ "description": "PostgreSQL administrator login username"
1829
+ }
1830
+ },
1831
+ "administratorLoginPassword": {
1832
+ "type": "securestring",
1833
+ "metadata": {
1834
+ "description": "PostgreSQL administrator login password (stored in Key Vault)"
1835
+ }
1836
+ },
1837
+ "version": {
1838
+ "type": "string",
1839
+ "defaultValue": "15",
1840
+ "metadata": {
1841
+ "description": "PostgreSQL version"
1842
+ }
1843
+ },
1844
+ "skuName": {
1845
+ "type": "string",
1846
+ "defaultValue": "Standard_B1ms",
1847
+ "metadata": {
1848
+ "description": "SKU name (e.g., Standard_B1ms)"
1849
+ }
1850
+ },
1851
+ "skuTier": {
1852
+ "type": "string",
1853
+ "defaultValue": "Burstable",
1854
+ "allowedValues": ["Burstable", "GeneralPurpose", "MemoryOptimized"],
1855
+ "metadata": {
1856
+ "description": "SKU tier for PostgreSQL Flexible Server"
1857
+ }
1858
+ },
1859
+ "storageSizeGB": {
1860
+ "type": "int",
1861
+ "defaultValue": 32,
1862
+ "metadata": {
1863
+ "description": "Storage size in GB"
1864
+ }
1865
+ },
1866
+ "subnetId": {
1867
+ "type": "string",
1868
+ "metadata": {
1869
+ "description": "VNet subnet resource ID for PostgreSQL"
1870
+ }
1871
+ },
1872
+ "privateDnsZoneId": {
1873
+ "type": "string",
1874
+ "metadata": {
1875
+ "description": "Private DNS zone ARM resource ID for PostgreSQL (privatelink.postgres.database.azure.com)"
1876
+ }
1877
+ },
1878
+ "tags": {
1879
+ "type": "object",
1880
+ "defaultValue": {},
1881
+ "metadata": {
1882
+ "description": "Resource tags to apply to PostgreSQL"
1883
+ }
1884
+ },
1885
+ "keyVaultId": {
1886
+ "type": "string",
1887
+ "defaultValue": "",
1888
+ "metadata": {
1889
+ "description": "Key Vault resource ID for storing DATABASE_URL secret"
1890
+ }
1891
+ },
1892
+ "secretPrefix": {
1893
+ "type": "string",
1894
+ "defaultValue": "",
1895
+ "metadata": {
1896
+ "description": "Prefix for Key Vault secret naming (e.g., infrastructure name)"
1897
+ }
1898
+ },
1899
+ "enablePublicAccess": {
1900
+ "type": "bool",
1901
+ "defaultValue": false,
1902
+ "metadata": {
1903
+ "description": "Enable public network access for developer access (default: false for security)"
1904
+ }
1905
+ },
1906
+ "allowedPublicIPs": {
1907
+ "type": "array",
1908
+ "defaultValue": [],
1909
+ "metadata": {
1910
+ "description": "List of public IP addresses allowed to access PostgreSQL (CIDR format, e.g., [\"1.2.3.4/32\", \"5.6.7.8/32\"])"
1911
+ }
1912
+ },
1913
+ "prismaMainConnectionLimit": {
1914
+ "type": "int",
1915
+ "defaultValue": 5,
1916
+ "metadata": {
1917
+ "description": "Prisma main database connection limit appended to DATABASE_URL"
1918
+ }
1919
+ },
1920
+ "prismaMainPoolTimeout": {
1921
+ "type": "int",
1922
+ "defaultValue": 20,
1923
+ "metadata": {
1924
+ "description": "Prisma main database pool timeout (seconds) appended to DATABASE_URL"
1925
+ }
1926
+ },
1927
+ "prismaLogsConnectionLimit": {
1928
+ "type": "int",
1929
+ "defaultValue": 5,
1930
+ "metadata": {
1931
+ "description": "Prisma logs database connection limit appended to DATABASELOG_URL"
1932
+ }
1933
+ },
1934
+ "prismaLogsPoolTimeout": {
1935
+ "type": "int",
1936
+ "defaultValue": 20,
1937
+ "metadata": {
1938
+ "description": "Prisma logs database pool timeout (seconds) appended to DATABASELOG_URL"
1939
+ }
1940
+ }
1941
+ },
1942
+ "resources": [
1943
+ {
1944
+ "type": "Microsoft.DBforPostgreSQL/flexibleServers",
1945
+ "apiVersion": "2023-06-01-preview",
1946
+ "name": "[parameters('name')]",
1947
+ "location": "[parameters('location')]",
1948
+ "tags": "[parameters('tags')]",
1949
+ "sku": {
1950
+ "name": "[parameters('skuName')]",
1951
+ "tier": "[parameters('skuTier')]"
1952
+ },
1953
+ "properties": {
1954
+ "version": "[parameters('version')]",
1955
+ "administratorLogin": "[parameters('administratorLogin')]",
1956
+ "administratorLoginPassword": "[parameters('administratorLoginPassword')]",
1957
+ "storage": {
1958
+ "storageSizeGB": "[parameters('storageSizeGB')]"
1959
+ },
1960
+ "network": "[if(parameters('enablePublicAccess'), createObject('publicNetworkAccess', 'Enabled'), createObject('publicNetworkAccess', 'Disabled', 'delegatedSubnetResourceId', parameters('subnetId'), 'privateDnsZoneArmResourceId', parameters('privateDnsZoneId')))]",
1961
+ "backup": {
1962
+ "backupRetentionDays": 7,
1963
+ "geoRedundantBackup": "Disabled"
1964
+ },
1965
+ "highAvailability": {
1966
+ "mode": "Disabled"
1967
+ },
1968
+ "maintenanceWindow": {
1969
+ "customWindow": "Disabled",
1970
+ "dayOfWeek": 0,
1971
+ "startHour": 0,
1972
+ "startMinute": 0
1973
+ }
1974
+ }
1975
+ },
1976
+ {
1977
+ "copy": {
1978
+ "name": "firewallRules",
1979
+ "count": "[length(parameters('allowedPublicIPs'))]",
1980
+ "mode": "serial",
1981
+ "batchSize": 1
1982
+ },
1983
+ "condition": "[parameters('enablePublicAccess')]",
1984
+ "type": "Microsoft.DBforPostgreSQL/flexibleServers/firewallRules",
1985
+ "apiVersion": "2023-06-01-preview",
1986
+ "name": "[format('{0}/{1}', parameters('name'), format('AllowDevIP_{0}', replace(replace(parameters('allowedPublicIPs')[copyIndex()], '.', '_'), '/', '_')))]",
1987
+ "properties": {
1988
+ "startIpAddress": "[split(parameters('allowedPublicIPs')[copyIndex()], '/')[0]]",
1989
+ "endIpAddress": "[split(parameters('allowedPublicIPs')[copyIndex()], '/')[0]]"
1990
+ },
1991
+ "dependsOn": [
1992
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
1993
+ ]
1994
+ },
1995
+ {
1996
+ "condition": "[parameters('enablePublicAccess')]",
1997
+ "type": "Microsoft.DBforPostgreSQL/flexibleServers/firewallRules",
1998
+ "apiVersion": "2023-06-01-preview",
1999
+ "name": "[format('{0}/{1}', parameters('name'), 'AllowAzureServices')]",
2000
+ "properties": {
2001
+ "startIpAddress": "0.0.0.0",
2002
+ "endIpAddress": "0.0.0.0"
2003
+ },
2004
+ "dependsOn": [
2005
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2006
+ ]
2007
+ },
2008
+ {
2009
+ "type": "Microsoft.DBforPostgreSQL/flexibleServers/databases",
2010
+ "apiVersion": "2023-06-01-preview",
2011
+ "name": "[format('{0}/{1}', parameters('name'), 'miso')]",
2012
+ "properties": {
2013
+ "charset": "UTF8",
2014
+ "collation": "en_US.utf8"
2015
+ },
2016
+ "dependsOn": [
2017
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2018
+ ]
2019
+ },
2020
+ {
2021
+ "type": "Microsoft.DBforPostgreSQL/flexibleServers/databases",
2022
+ "apiVersion": "2023-06-01-preview",
2023
+ "name": "[format('{0}/{1}', parameters('name'), 'miso-logs')]",
2024
+ "properties": {
2025
+ "charset": "UTF8",
2026
+ "collation": "en_US.utf8"
2027
+ },
2028
+ "dependsOn": [
2029
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2030
+ ]
2031
+ },
2032
+ {
2033
+ "type": "Microsoft.DBforPostgreSQL/flexibleServers/databases",
2034
+ "apiVersion": "2023-06-01-preview",
2035
+ "name": "[format('{0}/{1}', parameters('name'), 'keycloak')]",
2036
+ "properties": {
2037
+ "charset": "UTF8",
2038
+ "collation": "en_US.utf8"
2039
+ },
2040
+ "dependsOn": [
2041
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2042
+ ]
2043
+ },
2044
+ {
2045
+ "condition": "[and(not(empty(parameters('keyVaultId'))), not(empty(parameters('secretPrefix'))))]",
2046
+ "type": "Microsoft.KeyVault/vaults/secrets",
2047
+ "apiVersion": "2023-07-01",
2048
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), format('{0}-databases-0-urlKeyVault', parameters('secretPrefix')))]",
2049
+ "properties": {
2050
+ "value": "[if(not(empty(parameters('keyVaultId'))), format('postgresql://{0}:{1}@{2}:5432/{3}?sslmode=require&connection_limit={4}&pool_timeout={5}', parameters('administratorLogin'), parameters('administratorLoginPassword'), reference(resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name')), '2023-06-01-preview').fullyQualifiedDomainName, 'miso', parameters('prismaMainConnectionLimit'), parameters('prismaMainPoolTimeout')), '')]"
2051
+ },
2052
+ "dependsOn": [
2053
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers/databases', parameters('name'), 'miso')]",
2054
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2055
+ ]
2056
+ },
2057
+ {
2058
+ "condition": "[and(not(empty(parameters('keyVaultId'))), not(empty(parameters('secretPrefix'))))]",
2059
+ "type": "Microsoft.KeyVault/vaults/secrets",
2060
+ "apiVersion": "2023-07-01",
2061
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), format('{0}-databases-1-urlKeyVault', parameters('secretPrefix')))]",
2062
+ "properties": {
2063
+ "value": "[if(not(empty(parameters('keyVaultId'))), format('postgresql://{0}:{1}@{2}:5432/{3}?sslmode=require&connection_limit={4}&pool_timeout={5}', parameters('administratorLogin'), parameters('administratorLoginPassword'), reference(resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name')), '2023-06-01-preview').fullyQualifiedDomainName, 'miso-logs', parameters('prismaLogsConnectionLimit'), parameters('prismaLogsPoolTimeout')), '')]"
2064
+ },
2065
+ "dependsOn": [
2066
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers/databases', parameters('name'), 'miso-logs')]",
2067
+ "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2068
+ ]
2069
+ }
2070
+ ],
2071
+ "outputs": {
2072
+ "serverId": {
2073
+ "type": "string",
2074
+ "value": "[resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name'))]"
2075
+ },
2076
+ "serverName": {
2077
+ "type": "string",
2078
+ "value": "[parameters('name')]"
2079
+ },
2080
+ "serverFqdn": {
2081
+ "type": "string",
2082
+ "value": "[reference(resourceId('Microsoft.DBforPostgreSQL/flexibleServers', parameters('name')), '2023-06-01-preview').fullyQualifiedDomainName]"
2083
+ },
2084
+ "administratorLogin": {
2085
+ "type": "string",
2086
+ "value": "[parameters('administratorLogin')]"
2087
+ },
2088
+ "misoDatabaseName": {
2089
+ "type": "string",
2090
+ "value": "miso"
2091
+ },
2092
+ "logsDatabaseName": {
2093
+ "type": "string",
2094
+ "value": "miso-logs"
2095
+ },
2096
+ "keycloakDatabaseName": {
2097
+ "type": "string",
2098
+ "value": "keycloak"
2099
+ },
2100
+ "databaseUrlSecretUriWithVersion": {
2101
+ "type": "string",
2102
+ "value": "[if(and(not(empty(parameters('keyVaultId'))), not(empty(parameters('secretPrefix')))), reference(resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-databases-0-urlKeyVault', parameters('secretPrefix'))), '2023-07-01').secretUriWithVersion, '')]"
2103
+ },
2104
+ "databaseLogUrlSecretUriWithVersion": {
2105
+ "type": "string",
2106
+ "value": "[if(and(not(empty(parameters('keyVaultId'))), not(empty(parameters('secretPrefix')))), reference(resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-databases-1-urlKeyVault', parameters('secretPrefix'))), '2023-07-01').secretUriWithVersion, '')]"
2107
+ }
2108
+ }
2109
+ }
2110
+ },
2111
+ "dependsOn": [
2112
+ "[resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets')]",
2113
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]",
2114
+ "[resourceId('Microsoft.Resources/deployments', 'network')]"
2115
+ ]
2116
+ },
2117
+ {
2118
+ "type": "Microsoft.Resources/deployments",
2119
+ "apiVersion": "2025-04-01",
2120
+ "name": "keycloakWebApp",
2121
+ "properties": {
2122
+ "expressionEvaluationOptions": {
2123
+ "scope": "inner"
2124
+ },
2125
+ "mode": "Incremental",
2126
+ "parameters": {
2127
+ "name": {
2128
+ "value": "[format('{0}-miso-keycloak', variables('infrastructureName'))]"
2129
+ },
2130
+ "location": {
2131
+ "value": "[variables('deploymentLocation')]"
2132
+ },
2133
+ "planId": {
2134
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'appServicePlan'), '2025-04-01').outputs.planId.value]"
2135
+ },
2136
+ "keyVaultId": {
2137
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultId.value]"
2138
+ },
2139
+ "image": {
2140
+ "value": "[variables('keycloakContainerImage')]"
2141
+ },
2142
+ "acrLoginServer": {
2143
+ "value": "[variables('effectiveAcrLoginServer')]"
2144
+ },
2145
+ "acrAdminUsername": {
2146
+ "value": "[variables('registryUsername')]"
2147
+ },
2148
+ "acrAdminPassword": {
2149
+ "value": "[parameters('acrAdminPassword')]"
2150
+ },
2151
+ "prefix": {
2152
+ "value": "[variables('infrastructureName')]"
2153
+ },
2154
+ "postgresServerFqdn": {
2155
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.serverFqdn.value]"
2156
+ },
2157
+ "postgresAdminLogin": {
2158
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.administratorLogin.value]"
2159
+ },
2160
+ "postgresDatabaseName": {
2161
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.keycloakDatabaseName.value]"
2162
+ },
2163
+ "postgresAdminPassword": {
2164
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.postgresPassword.value]"
2165
+ },
2166
+ "keycloakAdminUser": {
2167
+ "value": "admin"
2168
+ },
2169
+ "keycloakAdminPassword": {
2170
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.keycloakPassword.value]"
2171
+ },
2172
+ "keycloakVdirPublic": {
2173
+ "value": "[variables('keycloakVdirPublic')]"
2174
+ },
2175
+ "vnetSubnetId": {
2176
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.webappSubnetId.value]"
2177
+ },
2178
+ "appInsightsInstrumentationKey": "[if(parameters('enableAppInsights'), createObject('value', reference(resourceId('Microsoft.Resources/deployments', 'appInsights'), '2025-04-01').outputs.instrumentationKey.value), createObject('value', ''))]",
2179
+ "enablePostgresPublicAccess": {
2180
+ "value": "[parameters('enablePostgresPublicAccess')]"
2181
+ },
2182
+ "tags": {
2183
+ "value": "[parameters('tags')]"
2184
+ }
2185
+ },
2186
+ "template": {
2187
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
2188
+ "contentVersion": "1.0.0.0",
2189
+ "metadata": {
2190
+ "_generator": {
2191
+ "name": "bicep",
2192
+ "version": "0.47.16.16243",
2193
+ "templateHash": "6101318472045983896"
2194
+ }
2195
+ },
2196
+ "parameters": {
2197
+ "name": {
2198
+ "type": "string",
2199
+ "metadata": {
2200
+ "description": "Name of the Keycloak web app"
2201
+ }
2202
+ },
2203
+ "location": {
2204
+ "type": "string",
2205
+ "defaultValue": "[resourceGroup().location]",
2206
+ "metadata": {
2207
+ "description": "Azure region for the web app"
2208
+ }
2209
+ },
2210
+ "planId": {
2211
+ "type": "string",
2212
+ "metadata": {
2213
+ "description": "App Service Plan resource ID"
2214
+ }
2215
+ },
2216
+ "keyVaultId": {
2217
+ "type": "string",
2218
+ "metadata": {
2219
+ "description": "Key Vault resource ID"
2220
+ }
2221
+ },
2222
+ "image": {
2223
+ "type": "string",
2224
+ "defaultValue": "quay.io/keycloak/keycloak:latest",
2225
+ "metadata": {
2226
+ "description": "Docker image for Keycloak (use ACR path when pulling from private registry, e.g. myacr.azurecr.io/aifabrix/keycloak:latest)"
2227
+ }
2228
+ },
2229
+ "acrLoginServer": {
2230
+ "type": "string",
2231
+ "defaultValue": "",
2232
+ "metadata": {
2233
+ "description": "ACR login server (e.g. myacr.azurecr.io). Image pull uses the web app managed identity; registry credentials below are applied only when acrAdminPassword is supplied."
2234
+ }
2235
+ },
2236
+ "acrAdminUsername": {
2237
+ "type": "string",
2238
+ "defaultValue": "",
2239
+ "metadata": {
2240
+ "description": "ACR admin username (typically the registry name) when using admin credentials for image pull"
2241
+ }
2242
+ },
2243
+ "acrAdminPassword": {
2244
+ "type": "securestring",
2245
+ "defaultValue": "",
2246
+ "metadata": {
2247
+ "description": "Optional private-registry pull token/password. When non-empty it is stored as Key Vault {prefix}-acr-registry-password for Keycloak and Miso; when empty no registry secret or DOCKER_REGISTRY_SERVER_USERNAME/PASSWORD setting is created."
2248
+ }
2249
+ },
2250
+ "prefix": {
2251
+ "type": "string",
2252
+ "metadata": {
2253
+ "description": "Prefix for naming and Key Vault secrets"
2254
+ }
2255
+ },
2256
+ "postgresServerFqdn": {
2257
+ "type": "string",
2258
+ "metadata": {
2259
+ "description": "PostgreSQL server FQDN"
2260
+ }
2261
+ },
2262
+ "postgresAdminLogin": {
2263
+ "type": "string",
2264
+ "metadata": {
2265
+ "description": "PostgreSQL administrator login"
2266
+ }
2267
+ },
2268
+ "postgresDatabaseName": {
2269
+ "type": "string",
2270
+ "metadata": {
2271
+ "description": "PostgreSQL database name for Keycloak"
2272
+ }
2273
+ },
2274
+ "postgresAdminPassword": {
2275
+ "type": "securestring",
2276
+ "metadata": {
2277
+ "description": "PostgreSQL administrator password (will be stored in Key Vault)"
2278
+ }
2279
+ },
2280
+ "keycloakAdminUser": {
2281
+ "type": "string",
2282
+ "defaultValue": "admin",
2283
+ "metadata": {
2284
+ "description": "Keycloak admin username"
2285
+ }
2286
+ },
2287
+ "keycloakAdminPassword": {
2288
+ "type": "securestring",
2289
+ "metadata": {
2290
+ "description": "Keycloak admin password (stored in Key Vault)"
2291
+ }
2292
+ },
2293
+ "vnetSubnetId": {
2294
+ "type": "string",
2295
+ "defaultValue": "",
2296
+ "metadata": {
2297
+ "description": "VNet subnet resource ID for App Service VNet integration"
2298
+ }
2299
+ },
2300
+ "tags": {
2301
+ "type": "object",
2302
+ "defaultValue": {},
2303
+ "metadata": {
2304
+ "description": "Resource tags to apply to the Web App"
2305
+ }
2306
+ },
2307
+ "appInsightsInstrumentationKey": {
2308
+ "type": "string",
2309
+ "defaultValue": "",
2310
+ "metadata": {
2311
+ "description": "Application Insights instrumentation key (optional)"
2312
+ }
2313
+ },
2314
+ "enablePostgresPublicAccess": {
2315
+ "type": "bool",
2316
+ "defaultValue": false,
2317
+ "metadata": {
2318
+ "description": "Enable public access for PostgreSQL (affects WEBSITE_VNET_ROUTE_ALL setting)"
2319
+ }
2320
+ },
2321
+ "keycloakVdirPublic": {
2322
+ "type": "string",
2323
+ "defaultValue": "/auth",
2324
+ "metadata": {
2325
+ "description": "HTTP relative path for Keycloak (e.g. /auth when Front Door routes /auth/*)"
2326
+ }
2327
+ }
2328
+ },
2329
+ "variables": {
2330
+ "registryPasswordConfigured": "[and(not(empty(parameters('acrLoginServer'))), not(empty(parameters('acrAdminPassword'))))]",
2331
+ "appInsightsAppSettings": "[if(empty(parameters('appInsightsInstrumentationKey')), createArray(), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', parameters('appInsightsInstrumentationKey')), createObject('name', 'APPLICATIONINSIGHTS_CONNECTION_STRING', 'value', format('InstrumentationKey={0}', parameters('appInsightsInstrumentationKey')))))]",
2332
+ "acrRegistryUrlAppSettings": "[if(not(empty(parameters('acrLoginServer'))), createArray(createObject('name', 'DOCKER_ENABLE_CI', 'value', 'true'), createObject('name', 'DOCKER_REGISTRY_SERVER_URL', 'value', format('https://{0}', parameters('acrLoginServer')))), createArray())]",
2333
+ "acrRegistryCredentialAppSettings": "[if(variables('registryPasswordConfigured'), createArray(createObject('name', 'DOCKER_REGISTRY_SERVER_USERNAME', 'value', parameters('acrAdminUsername')), createObject('name', 'DOCKER_REGISTRY_SERVER_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-acr-registry-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))), createArray())]",
2334
+ "keycloakVdirSegment": "[if(or(equals(parameters('keycloakVdirPublic'), ''), equals(parameters('keycloakVdirPublic'), '/')), '', if(startsWith(parameters('keycloakVdirPublic'), '/'), parameters('keycloakVdirPublic'), format('/{0}', parameters('keycloakVdirPublic'))))]",
2335
+ "keycloakHttpRelativePath": "[if(empty(variables('keycloakVdirSegment')), '/', variables('keycloakVdirSegment'))]",
2336
+ "keycloakHealthCheckPath": "[if(empty(variables('keycloakVdirSegment')), '/health/ready', format('{0}/health/ready', variables('keycloakVdirSegment')))]"
2337
+ },
2338
+ "resources": [
2339
+ {
2340
+ "type": "Microsoft.KeyVault/vaults/secrets",
2341
+ "apiVersion": "2023-07-01",
2342
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), format('{0}-postgres-admin-password', parameters('prefix')))]",
2343
+ "properties": {
2344
+ "value": "[parameters('postgresAdminPassword')]"
2345
+ }
2346
+ },
2347
+ {
2348
+ "type": "Microsoft.KeyVault/vaults/secrets",
2349
+ "apiVersion": "2023-07-01",
2350
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), format('{0}-keycloak-admin-password', parameters('prefix')))]",
2351
+ "properties": {
2352
+ "value": "[parameters('keycloakAdminPassword')]"
2353
+ }
2354
+ },
2355
+ {
2356
+ "condition": "[variables('registryPasswordConfigured')]",
2357
+ "type": "Microsoft.KeyVault/vaults/secrets",
2358
+ "apiVersion": "2023-07-01",
2359
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), format('{0}-acr-registry-password', parameters('prefix')))]",
2360
+ "properties": {
2361
+ "value": "[parameters('acrAdminPassword')]"
2362
+ }
2363
+ },
2364
+ {
2365
+ "type": "Microsoft.Web/sites",
2366
+ "apiVersion": "2023-01-01",
2367
+ "name": "[parameters('name')]",
2368
+ "location": "[parameters('location')]",
2369
+ "tags": "[parameters('tags')]",
2370
+ "kind": "app,linux,container",
2371
+ "identity": {
2372
+ "type": "SystemAssigned"
2373
+ },
2374
+ "properties": {
2375
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', last(split(parameters('planId'), '/')))]",
2376
+ "httpsOnly": true,
2377
+ "keyVaultReferenceIdentity": "SystemAssigned",
2378
+ "vnetImagePullEnabled": "[not(empty(parameters('vnetSubnetId')))]",
2379
+ "virtualNetworkSubnetId": "[if(not(empty(parameters('vnetSubnetId'))), parameters('vnetSubnetId'), null())]",
2380
+ "siteConfig": {
2381
+ "linuxFxVersion": "[format('DOCKER|{0}', parameters('image'))]",
2382
+ "acrUseManagedIdentityCreds": true,
2383
+ "appCommandLine": "start --optimized=false",
2384
+ "healthCheckPath": "[variables('keycloakHealthCheckPath')]",
2385
+ "alwaysOn": true,
2386
+ "appSettings": "[concat(createArray(createObject('name', 'WEBSITES_ENABLE_APP_SERVICE_STORAGE', 'value', 'false'), createObject('name', 'WEBSITES_PORT', 'value', '8080'), createObject('name', 'KEYCLOAK_ADMIN', 'value', parameters('keycloakAdminUser')), createObject('name', 'KEYCLOAK_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(SecretUri={0})', reference(resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-keycloak-admin-password', parameters('prefix'))), '2023-07-01').secretUriWithVersion)), createObject('name', 'KC_DB', 'value', 'postgres'), createObject('name', 'KC_DB_URL_HOST', 'value', parameters('postgresServerFqdn')), createObject('name', 'KC_DB_URL_DATABASE', 'value', parameters('postgresDatabaseName')), createObject('name', 'KC_DB_URL_PORT', 'value', '5432'), createObject('name', 'KC_DB_URL', 'value', format('jdbc:postgresql://{0}:5432/{1}', parameters('postgresServerFqdn'), parameters('postgresDatabaseName'))), createObject('name', 'KC_DB_USERNAME', 'value', parameters('postgresAdminLogin')), createObject('name', 'KC_DB_PASSWORD', 'value', format('@Microsoft.KeyVault(SecretUri={0})', reference(resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-postgres-admin-password', parameters('prefix'))), '2023-07-01').secretUriWithVersion)), createObject('name', 'KC_HOSTNAME', 'value', format('{0}.azurewebsites.net', parameters('name'))), createObject('name', 'KC_HOSTNAME_URL', 'value', format('https://{0}.azurewebsites.net', parameters('name'))), createObject('name', 'KC_HOSTNAME_ADMIN_URL', 'value', format('https://{0}.azurewebsites.net', parameters('name'))), createObject('name', 'KC_HOSTNAME_STRICT', 'value', 'false'), createObject('name', 'KC_HOSTNAME_STRICT_BACKEND', 'value', 'false'), createObject('name', 'KC_HOSTNAME_STRICT_HTTPS', 'value', 'false'), createObject('name', 'KC_HTTP_ENABLED', 'value', 'true'), createObject('name', 'KC_HTTP_RELATIVE_PATH', 'value', variables('keycloakHttpRelativePath')), createObject('name', 'KC_HTTP_MANAGEMENT_ENABLED', 'value', 'true'), createObject('name', 'KC_HTTP_MANAGEMENT_PORT', 'value', '9000'), createObject('name', 'KC_HTTP_MANAGEMENT_HEALTH_ENABLED', 'value', 'false'), createObject('name', 'KC_PROXY', 'value', 'edge'), createObject('name', 'KC_PROXY_HEADERS', 'value', 'xforwarded'), createObject('name', 'KC_PROXY_HEADERS_FORWARDED', 'value', 'true'), createObject('name', 'KC_HEALTH_ENABLED', 'value', 'true'), createObject('name', 'KC_CACHE', 'value', 'local'), createObject('name', 'KC_FEATURES', 'value', 'preview,cimd'), createObject('name', 'KC_SPI_EMAIL_SENDER__PROVIDER', 'value', 'mori'), createObject('name', 'KC_METRICS_ENABLED', 'value', 'true'), createObject('name', 'WEBSITE_VNET_ROUTE_ALL', 'value', if(parameters('enablePostgresPublicAccess'), '0', '1')), createObject('name', 'WEBSITE_DNS_SERVER', 'value', '168.63.129.16'), createObject('name', 'WEBSITES_CONTAINER_START_TIME_LIMIT', 'value', '1800'), createObject('name', 'KC_LOG', 'value', 'console'), createObject('name', 'KC_LOG_LEVEL', 'value', 'INFO')), variables('appInsightsAppSettings'), variables('acrRegistryUrlAppSettings'), variables('acrRegistryCredentialAppSettings'))]",
2387
+ "vnetRouteAllEnabled": "[not(parameters('enablePostgresPublicAccess'))]"
2388
+ },
2389
+ "vnetRouteAllEnabled": "[not(parameters('enablePostgresPublicAccess'))]"
2390
+ },
2391
+ "dependsOn": [
2392
+ "[resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-acr-registry-password', parameters('prefix')))]",
2393
+ "[resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-keycloak-admin-password', parameters('prefix')))]",
2394
+ "[resourceId('Microsoft.KeyVault/vaults/secrets', last(split(parameters('keyVaultId'), '/')), format('{0}-postgres-admin-password', parameters('prefix')))]"
2395
+ ]
2396
+ },
2397
+ {
2398
+ "type": "Microsoft.Web/sites/config",
2399
+ "apiVersion": "2023-01-01",
2400
+ "name": "[format('{0}/{1}', parameters('name'), 'logs')]",
2401
+ "properties": {
2402
+ "applicationLogs": {
2403
+ "fileSystem": {
2404
+ "level": "Information"
2405
+ }
2406
+ },
2407
+ "httpLogs": {
2408
+ "fileSystem": {
2409
+ "enabled": true,
2410
+ "retentionInDays": 7,
2411
+ "retentionInMb": 100
2412
+ }
2413
+ },
2414
+ "detailedErrorMessages": {
2415
+ "enabled": true
2416
+ },
2417
+ "failedRequestsTracing": {
2418
+ "enabled": true
2419
+ }
2420
+ },
2421
+ "dependsOn": ["[resourceId('Microsoft.Web/sites', parameters('name'))]"]
2422
+ }
2423
+ ],
2424
+ "outputs": {
2425
+ "webAppName": {
2426
+ "type": "string",
2427
+ "value": "[parameters('name')]"
2428
+ },
2429
+ "webAppUrl": {
2430
+ "type": "string",
2431
+ "value": "[format('https://{0}', reference(resourceId('Microsoft.Web/sites', parameters('name')), '2023-01-01').defaultHostName)]"
2432
+ },
2433
+ "webAppId": {
2434
+ "type": "string",
2435
+ "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]"
2436
+ },
2437
+ "keycloakAdminUser": {
2438
+ "type": "string",
2439
+ "value": "[parameters('keycloakAdminUser')]"
2440
+ },
2441
+ "keycloakRealmUrl": {
2442
+ "type": "string",
2443
+ "value": "[if(empty(variables('keycloakVdirSegment')), format('https://{0}/realms/aifabrix', reference(resourceId('Microsoft.Web/sites', parameters('name')), '2023-01-01').defaultHostName), format('https://{0}{1}/realms/aifabrix', reference(resourceId('Microsoft.Web/sites', parameters('name')), '2023-01-01').defaultHostName, variables('keycloakVdirSegment')))]"
2444
+ },
2445
+ "principalId": {
2446
+ "type": "string",
2447
+ "metadata": {
2448
+ "description": "System-assigned managed identity object id (for Key Vault access policies)"
2449
+ },
2450
+ "value": "[reference(resourceId('Microsoft.Web/sites', parameters('name')), '2023-01-01', 'full').identity.principalId]"
2451
+ }
2452
+ }
2453
+ }
2454
+ },
2455
+ "dependsOn": [
2456
+ "[resourceId('Microsoft.Resources/deployments', 'appInsights')]",
2457
+ "[resourceId('Microsoft.Resources/deployments', 'appServicePlan')]",
2458
+ "[resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets')]",
2459
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]",
2460
+ "[resourceId('Microsoft.Resources/deployments', 'network')]",
2461
+ "[resourceId('Microsoft.Resources/deployments', 'postgresql')]"
2462
+ ]
2463
+ },
2464
+ {
2465
+ "type": "Microsoft.Resources/deployments",
2466
+ "apiVersion": "2025-04-01",
2467
+ "name": "keyVaultSecretsPhase1",
2468
+ "properties": {
2469
+ "expressionEvaluationOptions": {
2470
+ "scope": "inner"
2471
+ },
2472
+ "mode": "Incremental",
2473
+ "parameters": {
2474
+ "keyVaultName": {
2475
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]"
2476
+ },
2477
+ "prefix": {
2478
+ "value": "[variables('infrastructureName')]"
2479
+ },
2480
+ "keycloakWebAppUrl": {
2481
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppUrl.value]"
2482
+ },
2483
+ "keycloakVdirPublic": {
2484
+ "value": "[variables('keycloakVdirPublic')]"
2485
+ },
2486
+ "misoWebAppUrl": {
2487
+ "value": "[format('https://{0}-miso-miso.azurewebsites.net', variables('infrastructureName'))]"
2488
+ },
2489
+ "misoVdirPublic": {
2490
+ "value": "/miso"
2491
+ },
2492
+ "encryptionKey": {
2493
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.encryptionKey.value]"
2494
+ }
2495
+ },
2496
+ "template": {
2497
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
2498
+ "contentVersion": "1.0.0.0",
2499
+ "metadata": {
2500
+ "_generator": {
2501
+ "name": "bicep",
2502
+ "version": "0.47.16.16243",
2503
+ "templateHash": "9394845529752045254"
2504
+ }
2505
+ },
2506
+ "parameters": {
2507
+ "keyVaultName": {
2508
+ "type": "string",
2509
+ "metadata": {
2510
+ "description": "Key Vault name"
2511
+ }
2512
+ },
2513
+ "prefix": {
2514
+ "type": "string",
2515
+ "metadata": {
2516
+ "description": "Prefix for secret naming"
2517
+ }
2518
+ },
2519
+ "keycloakWebAppUrl": {
2520
+ "type": "string",
2521
+ "metadata": {
2522
+ "description": "Keycloak web app origin URL (App Service hostname, no virtual-directory suffix)"
2523
+ }
2524
+ },
2525
+ "keycloakVdirPublic": {
2526
+ "type": "string",
2527
+ "defaultValue": "/auth",
2528
+ "metadata": {
2529
+ "description": "Public virtual-directory path for Keycloak (e.g. /auth from /auth/* Front Door route)"
2530
+ }
2531
+ },
2532
+ "misoWebAppUrl": {
2533
+ "type": "string",
2534
+ "metadata": {
2535
+ "description": "MISO Controller web app origin URL (App Service hostname, no virtual-directory suffix)"
2536
+ }
2537
+ },
2538
+ "misoVdirPublic": {
2539
+ "type": "string",
2540
+ "defaultValue": "/miso",
2541
+ "metadata": {
2542
+ "description": "Public virtual-directory path for MISO behind Front Door (e.g. /miso from /miso/*)"
2543
+ }
2544
+ },
2545
+ "encryptionKey": {
2546
+ "type": "securestring",
2547
+ "metadata": {
2548
+ "description": "Encryption key for MISO Controller"
2549
+ }
2550
+ },
2551
+ "jwtSecret": {
2552
+ "type": "securestring",
2553
+ "defaultValue": "",
2554
+ "metadata": {
2555
+ "description": "JWT secret for MISO Controller (auto-generated if not provided)"
2556
+ }
2557
+ },
2558
+ "misoClientId": {
2559
+ "type": "string",
2560
+ "defaultValue": "miso-controller-miso-miso-controller",
2561
+ "metadata": {
2562
+ "description": "MISO Client ID - Format: {controllerKey}-{environmentKey}-{applicationKey}"
2563
+ }
2564
+ },
2565
+ "misoClientSecret": {
2566
+ "type": "securestring",
2567
+ "defaultValue": "",
2568
+ "metadata": {
2569
+ "description": "MISO Client Secret (auto-generated if not provided)"
2570
+ }
2571
+ }
2572
+ },
2573
+ "variables": {
2574
+ "misoWebAppOrigin": "[if(endsWith(parameters('misoWebAppUrl'), '/'), substring(parameters('misoWebAppUrl'), 0, max(0, sub(length(parameters('misoWebAppUrl')), 1))), parameters('misoWebAppUrl'))]",
2575
+ "misoVdirSegment": "[if(or(equals(parameters('misoVdirPublic'), ''), equals(parameters('misoVdirPublic'), '/')), '', if(startsWith(parameters('misoVdirPublic'), '/'), parameters('misoVdirPublic'), format('/{0}', parameters('misoVdirPublic'))))]",
2576
+ "misoWebServerPublicUrl": "[if(empty(variables('misoVdirSegment')), variables('misoWebAppOrigin'), format('{0}{1}', variables('misoWebAppOrigin'), variables('misoVdirSegment')))]",
2577
+ "misoVdirSecretValue": "[if(empty(variables('misoVdirSegment')), '/', variables('misoVdirSegment'))]",
2578
+ "keycloakWebAppOrigin": "[if(endsWith(parameters('keycloakWebAppUrl'), '/'), substring(parameters('keycloakWebAppUrl'), 0, max(0, sub(length(parameters('keycloakWebAppUrl')), 1))), parameters('keycloakWebAppUrl'))]",
2579
+ "keycloakVdirSegment": "[if(or(equals(parameters('keycloakVdirPublic'), ''), equals(parameters('keycloakVdirPublic'), '/')), '', if(startsWith(parameters('keycloakVdirPublic'), '/'), parameters('keycloakVdirPublic'), format('/{0}', parameters('keycloakVdirPublic'))))]",
2580
+ "keycloakServerPublicUrl": "[if(empty(variables('keycloakVdirSegment')), variables('keycloakWebAppOrigin'), format('{0}{1}', variables('keycloakWebAppOrigin'), variables('keycloakVdirSegment')))]",
2581
+ "keycloakVdirSecretValue": "[if(empty(variables('keycloakVdirSegment')), '/', variables('keycloakVdirSegment'))]"
2582
+ },
2583
+ "resources": [
2584
+ {
2585
+ "type": "Microsoft.KeyVault/vaults/secrets",
2586
+ "apiVersion": "2023-07-01",
2587
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'keycloak-vdir-public')]",
2588
+ "properties": {
2589
+ "value": "[variables('keycloakVdirSecretValue')]"
2590
+ }
2591
+ },
2592
+ {
2593
+ "type": "Microsoft.KeyVault/vaults/secrets",
2594
+ "apiVersion": "2023-07-01",
2595
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'keycloak-web-server-url')]",
2596
+ "properties": {
2597
+ "value": "[variables('keycloakServerPublicUrl')]"
2598
+ }
2599
+ },
2600
+ {
2601
+ "type": "Microsoft.KeyVault/vaults/secrets",
2602
+ "apiVersion": "2023-07-01",
2603
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'keycloak-internal-server-url')]",
2604
+ "properties": {
2605
+ "value": "[variables('keycloakServerPublicUrl')]"
2606
+ }
2607
+ },
2608
+ {
2609
+ "type": "Microsoft.KeyVault/vaults/secrets",
2610
+ "apiVersion": "2023-07-01",
2611
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-vdir-public')]",
2612
+ "properties": {
2613
+ "value": "[variables('misoVdirSecretValue')]"
2614
+ }
2615
+ },
2616
+ {
2617
+ "type": "Microsoft.KeyVault/vaults/secrets",
2618
+ "apiVersion": "2023-07-01",
2619
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-web-server-url')]",
2620
+ "properties": {
2621
+ "value": "[variables('misoWebServerPublicUrl')]"
2622
+ }
2623
+ },
2624
+ {
2625
+ "type": "Microsoft.KeyVault/vaults/secrets",
2626
+ "apiVersion": "2023-07-01",
2627
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-internal-server-urlKeyVault')]",
2628
+ "properties": {
2629
+ "value": "[parameters('misoWebAppUrl')]"
2630
+ }
2631
+ },
2632
+ {
2633
+ "type": "Microsoft.KeyVault/vaults/secrets",
2634
+ "apiVersion": "2023-07-01",
2635
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-encryption-keyKeyVault', parameters('prefix')))]",
2636
+ "properties": {
2637
+ "value": "[parameters('encryptionKey')]"
2638
+ }
2639
+ },
2640
+ {
2641
+ "type": "Microsoft.KeyVault/vaults/secrets",
2642
+ "apiVersion": "2023-07-01",
2643
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-secrets-jwtKeyVault', parameters('prefix')))]",
2644
+ "properties": {
2645
+ "value": "[if(not(empty(parameters('jwtSecret'))), parameters('jwtSecret'), format('PLACEHOLDER_JWT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]"
2646
+ }
2647
+ },
2648
+ {
2649
+ "type": "Microsoft.KeyVault/vaults/secrets",
2650
+ "apiVersion": "2023-07-01",
2651
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-miso-clientIdKeyVault', parameters('prefix')))]",
2652
+ "properties": {
2653
+ "value": "[parameters('misoClientId')]"
2654
+ }
2655
+ },
2656
+ {
2657
+ "type": "Microsoft.KeyVault/vaults/secrets",
2658
+ "apiVersion": "2023-07-01",
2659
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-client-secretKeyVault')]",
2660
+ "properties": {
2661
+ "value": "[if(not(empty(parameters('misoClientSecret'))), parameters('misoClientSecret'), format('PLACEHOLDER_CLIENT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]"
2662
+ }
2663
+ }
2664
+ ]
2665
+ }
2666
+ },
2667
+ "dependsOn": [
2668
+ "[resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets')]",
2669
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
2670
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]"
2671
+ ]
2672
+ },
2673
+ {
2674
+ "type": "Microsoft.Resources/deployments",
2675
+ "apiVersion": "2025-04-01",
2676
+ "name": "misoWebApp",
2677
+ "properties": {
2678
+ "expressionEvaluationOptions": {
2679
+ "scope": "inner"
2680
+ },
2681
+ "mode": "Incremental",
2682
+ "parameters": {
2683
+ "name": {
2684
+ "value": "[format('{0}-miso-miso', variables('infrastructureName'))]"
2685
+ },
2686
+ "location": {
2687
+ "value": "[variables('deploymentLocation')]"
2688
+ },
2689
+ "planId": {
2690
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'appServicePlan'), '2025-04-01').outputs.planId.value]"
2691
+ },
2692
+ "keyVaultId": {
2693
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultId.value]"
2694
+ },
2695
+ "image": {
2696
+ "value": "[variables('misoControllerContainerImage')]"
2697
+ },
2698
+ "vnetSubnetId": {
2699
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.webappSubnetId.value]"
2700
+ },
2701
+ "prefix": {
2702
+ "value": "[variables('infrastructureName')]"
2703
+ },
2704
+ "storageAccountName": {
2705
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'storage'), '2025-04-01').outputs.scriptStorageAccountName.value]"
2706
+ },
2707
+ "fileShareName": {
2708
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'storage'), '2025-04-01').outputs.flowiseFileShareName.value]"
2709
+ },
2710
+ "keycloakWebAppUrl": {
2711
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppUrl.value]"
2712
+ },
2713
+ "keycloakRealm": {
2714
+ "value": "aifabrix"
2715
+ },
2716
+ "keycloakClientId": {
2717
+ "value": "miso-controller"
2718
+ },
2719
+ "keycloakAdminUsername": {
2720
+ "value": "admin"
2721
+ },
2722
+ "autoCreateTables": {
2723
+ "value": true
2724
+ },
2725
+ "misoControllerUrl": {
2726
+ "value": "[format('https://{0}-miso-miso.azurewebsites.net', variables('infrastructureName'))]"
2727
+ },
2728
+ "acrResourceId": {
2729
+ "value": "[parameters('acrResourceId')]"
2730
+ },
2731
+ "acrLoginServer": {
2732
+ "value": "[variables('effectiveAcrLoginServer')]"
2733
+ },
2734
+ "acrAdminUsername": {
2735
+ "value": "[variables('registryUsername')]"
2736
+ },
2737
+ "registryPasswordConfigured": {
2738
+ "value": "[variables('registryPasswordConfigured')]"
2739
+ },
2740
+ "databaseUrlSecretUriWithVersion": {
2741
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.databaseUrlSecretUriWithVersion.value]"
2742
+ },
2743
+ "databaseLogUrlSecretUriWithVersion": {
2744
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.databaseLogUrlSecretUriWithVersion.value]"
2745
+ },
2746
+ "enablePostgresPublicAccess": {
2747
+ "value": "[parameters('enablePostgresPublicAccess')]"
2748
+ },
2749
+ "deploymentEnvironment": {
2750
+ "value": "[parameters('deploymentEnvironment')]"
2751
+ },
2752
+ "keycloakVerifyAudience": {
2753
+ "value": "[parameters('keycloakVerifyAudience')]"
2754
+ },
2755
+ "moriBaseUrl": {
2756
+ "value": "[parameters('moriBaseUrl')]"
2757
+ },
2758
+ "moriCallbackApiKey": {
2759
+ "value": "[parameters('moriCallbackApiKey')]"
2760
+ },
2761
+ "entraCallbackStateSecret": {
2762
+ "value": "[parameters('entraCallbackStateSecret')]"
2763
+ },
2764
+ "entraIdClientId": {
2765
+ "value": "[parameters('entraIdClientId')]"
2766
+ },
2767
+ "entraIdClientSecret": {
2768
+ "value": "[parameters('entraIdClientSecret')]"
2769
+ },
2770
+ "entraIdTenantId": {
2771
+ "value": "[parameters('entraIdTenantId')]"
2772
+ },
2773
+ "licenseEdition": {
2774
+ "value": "[parameters('licenseEdition')]"
2775
+ },
2776
+ "onboardingMode": {
2777
+ "value": "[parameters('onboardingMode')]"
2778
+ },
2779
+ "resourceGroupName": {
2780
+ "value": "[resourceGroup().name]"
2781
+ },
2782
+ "tags": {
2783
+ "value": "[parameters('tags')]"
2784
+ }
2785
+ },
2786
+ "template": {
2787
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
2788
+ "contentVersion": "1.0.0.0",
2789
+ "metadata": {
2790
+ "_generator": {
2791
+ "name": "bicep",
2792
+ "version": "0.47.16.16243",
2793
+ "templateHash": "3326510661393380434"
2794
+ }
2795
+ },
2796
+ "parameters": {
2797
+ "name": {
2798
+ "type": "string",
2799
+ "metadata": {
2800
+ "description": "Name of the web app"
2801
+ }
2802
+ },
2803
+ "location": {
2804
+ "type": "string",
2805
+ "defaultValue": "[resourceGroup().location]",
2806
+ "metadata": {
2807
+ "description": "Azure region for the web app"
2808
+ }
2809
+ },
2810
+ "planId": {
2811
+ "type": "string",
2812
+ "metadata": {
2813
+ "description": "App Service Plan resource ID"
2814
+ }
2815
+ },
2816
+ "keyVaultId": {
2817
+ "type": "string",
2818
+ "metadata": {
2819
+ "description": "Key Vault resource ID"
2820
+ }
2821
+ },
2822
+ "image": {
2823
+ "type": "string",
2824
+ "defaultValue": "flowise/flowise:latest",
2825
+ "metadata": {
2826
+ "description": "Docker image name"
2827
+ }
2828
+ },
2829
+ "prefix": {
2830
+ "type": "string",
2831
+ "metadata": {
2832
+ "description": "Prefix for naming"
2833
+ }
2834
+ },
2835
+ "databaseUrlSecretUriWithVersion": {
2836
+ "type": "string",
2837
+ "defaultValue": "",
2838
+ "metadata": {
2839
+ "description": "When set, DATABASE_URL uses @Microsoft.KeyVault(SecretUri=...) with this versioned URI (from PostgreSQL module output). Avoids stale App Service resolution after DB password rotation."
2840
+ }
2841
+ },
2842
+ "databaseLogUrlSecretUriWithVersion": {
2843
+ "type": "string",
2844
+ "defaultValue": "",
2845
+ "metadata": {
2846
+ "description": "When set, DATABASELOG_URL uses @Microsoft.KeyVault(SecretUri=...) with this versioned URI (from PostgreSQL module output)."
2847
+ }
2848
+ },
2849
+ "storageAccountName": {
2850
+ "type": "string",
2851
+ "metadata": {
2852
+ "description": "Storage account name for file share mapping"
2853
+ }
2854
+ },
2855
+ "fileShareName": {
2856
+ "type": "string",
2857
+ "defaultValue": "data",
2858
+ "metadata": {
2859
+ "description": "File share name"
2860
+ }
2861
+ },
2862
+ "vnetSubnetId": {
2863
+ "type": "string",
2864
+ "defaultValue": "",
2865
+ "metadata": {
2866
+ "description": "VNet subnet resource ID for App Service VNet integration"
2867
+ }
2868
+ },
2869
+ "tags": {
2870
+ "type": "object",
2871
+ "defaultValue": {},
2872
+ "metadata": {
2873
+ "description": "Resource tags to apply to the Web App"
2874
+ }
2875
+ },
2876
+ "acrResourceId": {
2877
+ "type": "string",
2878
+ "defaultValue": "",
2879
+ "metadata": {
2880
+ "description": "ACR resource ID for role assignment (optional - requires Owner/User Access Administrator)"
2881
+ }
2882
+ },
2883
+ "acrLoginServer": {
2884
+ "type": "string",
2885
+ "defaultValue": "",
2886
+ "metadata": {
2887
+ "description": "ACR login server URL (e.g., myacr.azurecr.io) - required if using admin credentials"
2888
+ }
2889
+ },
2890
+ "acrAdminUsername": {
2891
+ "type": "string",
2892
+ "defaultValue": "",
2893
+ "metadata": {
2894
+ "description": "Optional registry username. Emitted as DOCKER_REGISTRY_SERVER_USERNAME only when registryPasswordConfigured is true."
2895
+ }
2896
+ },
2897
+ "registryPasswordConfigured": {
2898
+ "type": "bool",
2899
+ "defaultValue": false,
2900
+ "metadata": {
2901
+ "description": "True when a registry password was supplied (stored by the Keycloak module as {prefix}-acr-registry-password). False → no DOCKER_REGISTRY_SERVER_USERNAME/PASSWORD settings; image pull uses the managed identity."
2902
+ }
2903
+ },
2904
+ "keycloakWebAppUrl": {
2905
+ "type": "string",
2906
+ "defaultValue": "",
2907
+ "metadata": {
2908
+ "description": "Keycloak web app URL (for KEYCLOAK_* settings)"
2909
+ }
2910
+ },
2911
+ "keycloakRealm": {
2912
+ "type": "string",
2913
+ "defaultValue": "aifabrix",
2914
+ "metadata": {
2915
+ "description": "Keycloak realm name"
2916
+ }
2917
+ },
2918
+ "keycloakClientId": {
2919
+ "type": "string",
2920
+ "defaultValue": "miso-controller",
2921
+ "metadata": {
2922
+ "description": "Keycloak client ID for miso-controller"
2923
+ }
2924
+ },
2925
+ "keycloakAdminUsername": {
2926
+ "type": "string",
2927
+ "defaultValue": "admin",
2928
+ "metadata": {
2929
+ "description": "Keycloak admin username"
2930
+ }
2931
+ },
2932
+ "autoCreateTables": {
2933
+ "type": "bool",
2934
+ "defaultValue": true,
2935
+ "metadata": {
2936
+ "description": "Auto-create database tables on startup"
2937
+ }
2938
+ },
2939
+ "misoControllerUrl": {
2940
+ "type": "string",
2941
+ "defaultValue": "",
2942
+ "metadata": {
2943
+ "description": "MISO controller web app URL (for MISO_WEB_SERVER_URL)"
2944
+ }
2945
+ },
2946
+ "enablePostgresPublicAccess": {
2947
+ "type": "bool",
2948
+ "defaultValue": false,
2949
+ "metadata": {
2950
+ "description": "Enable public access for PostgreSQL (affects WEBSITE_VNET_ROUTE_ALL setting)"
2951
+ }
2952
+ },
2953
+ "deploymentEnvironment": {
2954
+ "type": "string",
2955
+ "defaultValue": "nonprod",
2956
+ "allowedValues": ["prod", "nonprod", "evaluation"],
2957
+ "metadata": {
2958
+ "description": "Deployment environment. Use prod for production; non-prod disables API rate limiting by default"
2959
+ }
2960
+ },
2961
+ "keycloakVerifyAudience": {
2962
+ "type": "bool",
2963
+ "defaultValue": false,
2964
+ "metadata": {
2965
+ "description": "Enable Keycloak access-token audience validation in controller"
2966
+ }
2967
+ },
2968
+ "moriBaseUrl": {
2969
+ "type": "string",
2970
+ "defaultValue": "https://aifabrix-pro-miso-mori.azurewebsites.net",
2971
+ "metadata": {
2972
+ "description": "Mori base URL (no trailing slash); used for tenant activation / handoff"
2973
+ }
2974
+ },
2975
+ "moriCallbackApiKey": {
2976
+ "type": "securestring",
2977
+ "defaultValue": "",
2978
+ "metadata": {
2979
+ "description": "Optional shared secret for Mori → controller callback, supplied through the Miso installation/application contract."
2980
+ }
2981
+ },
2982
+ "entraCallbackStateSecret": {
2983
+ "type": "securestring",
2984
+ "defaultValue": "",
2985
+ "metadata": {
2986
+ "description": "Optional shared HMAC secret for Entra consent OAuth state, supplied through the Miso installation contract."
2987
+ }
2988
+ },
2989
+ "entraIdClientId": {
2990
+ "type": "string",
2991
+ "defaultValue": "481251f2-a318-47ba-9ad8-40c36f790df1",
2992
+ "metadata": {
2993
+ "description": "Vendor multi-tenant Entra app client ID."
2994
+ }
2995
+ },
2996
+ "entraIdClientSecret": {
2997
+ "type": "securestring",
2998
+ "defaultValue": "",
2999
+ "metadata": {
3000
+ "description": "Optional vendor Entra app client secret, supplied through the Miso installation contract."
3001
+ }
3002
+ },
3003
+ "entraIdTenantId": {
3004
+ "type": "string",
3005
+ "defaultValue": "09aff594-ba37-4af2-bf40-b773b654c563",
3006
+ "metadata": {
3007
+ "description": "Publisher/home tenant for vendor Entra app (ENTRA_ID_TENANT_ID). TENANT_ID uses subscription().tenantId at deploy. Default matches vendor miso-controller .env (not in createUiDefinition)."
3008
+ }
3009
+ },
3010
+ "licenseEdition": {
3011
+ "type": "string",
3012
+ "defaultValue": "COMMUNITY",
3013
+ "metadata": {
3014
+ "description": "Requested license: edition name (COMMUNITY, STANDARD, ENTERPRISE) or Azure Marketplace subscription ID (UUID). Validated by Miso. Emitted as LICENSE_EDITION."
3015
+ }
3016
+ },
3017
+ "onboardingMode": {
3018
+ "type": "string",
3019
+ "defaultValue": "customerOwned",
3020
+ "allowedValues": ["customerOwned", "saasDirect"],
3021
+ "metadata": {
3022
+ "description": "Tenant onboarding mode (ONBOARDING_MODE)."
3023
+ }
3024
+ },
3025
+ "resourceGroupName": {
3026
+ "type": "string",
3027
+ "defaultValue": "[resourceGroup().name]",
3028
+ "metadata": {
3029
+ "description": "Resource group that contains the installation (AZURE_RESOURCE_GROUP_NAME)."
3030
+ }
3031
+ },
3032
+ "licenseJwt": {
3033
+ "type": "string",
3034
+ "defaultValue": "",
3035
+ "metadata": {
3036
+ "description": "Mori license JWT for controller. Empty uses Mori subscription validation. DEVELOPMENT is ignored when DEPLOYMENT=azure."
3037
+ }
3038
+ }
3039
+ },
3040
+ "variables": {
3041
+ "shouldDisableRateLimit": "[if(equals(parameters('deploymentEnvironment'), 'prod'), 'false', 'true')]",
3042
+ "databaseUrlAppSettingValue": "[if(not(empty(parameters('databaseUrlSecretUriWithVersion'))), format('@Microsoft.KeyVault(SecretUri={0})', parameters('databaseUrlSecretUriWithVersion')), format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-databases-0-urlKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))]",
3043
+ "databaseLogUrlAppSettingValue": "[if(not(empty(parameters('databaseLogUrlSecretUriWithVersion'))), format('@Microsoft.KeyVault(SecretUri={0})', parameters('databaseLogUrlSecretUriWithVersion')), format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-databases-1-urlKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))]",
3044
+ "moriBaseNormalized": "[if(and(greater(length(parameters('moriBaseUrl')), 1), endsWith(parameters('moriBaseUrl'), '/')), substring(parameters('moriBaseUrl'), 0, max(0, sub(length(parameters('moriBaseUrl')), 1))), parameters('moriBaseUrl'))]",
3045
+ "moriEntraGraphDelegatedRedirectUri": "[format('{0}/entra-graph-delegated/callback', variables('moriBaseNormalized'))]",
3046
+ "moriEntraCentralCallbackUri": "[format('{0}/entra-id-concent/callback', variables('moriBaseNormalized'))]",
3047
+ "registryCredentialAppSettings": "[if(and(parameters('registryPasswordConfigured'), not(empty(parameters('acrLoginServer')))), createArray(createObject('name', 'DOCKER_REGISTRY_SERVER_USERNAME', 'value', parameters('acrAdminUsername')), createObject('name', 'DOCKER_REGISTRY_SERVER_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-acr-registry-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))), createArray())]"
3048
+ },
3049
+ "resources": [
3050
+ {
3051
+ "condition": "[not(empty(parameters('acrLoginServer')))]",
3052
+ "type": "Microsoft.KeyVault/vaults/secrets",
3053
+ "apiVersion": "2023-07-01",
3054
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), 'acr-server')]",
3055
+ "properties": {
3056
+ "value": "[parameters('acrLoginServer')]"
3057
+ }
3058
+ },
3059
+ {
3060
+ "condition": "[not(empty(parameters('acrAdminUsername')))]",
3061
+ "type": "Microsoft.KeyVault/vaults/secrets",
3062
+ "apiVersion": "2023-07-01",
3063
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), 'acr-username')]",
3064
+ "properties": {
3065
+ "value": "[parameters('acrAdminUsername')]"
3066
+ }
3067
+ },
3068
+ {
3069
+ "type": "Microsoft.Web/sites",
3070
+ "apiVersion": "2023-01-01",
3071
+ "name": "[parameters('name')]",
3072
+ "location": "[parameters('location')]",
3073
+ "tags": "[parameters('tags')]",
3074
+ "kind": "app,linux,container",
3075
+ "identity": {
3076
+ "type": "SystemAssigned"
3077
+ },
3078
+ "properties": {
3079
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', last(split(parameters('planId'), '/')))]",
3080
+ "httpsOnly": true,
3081
+ "keyVaultReferenceIdentity": "SystemAssigned",
3082
+ "vnetImagePullEnabled": "[not(empty(parameters('vnetSubnetId')))]",
3083
+ "virtualNetworkSubnetId": "[if(not(empty(parameters('vnetSubnetId'))), parameters('vnetSubnetId'), null())]",
3084
+ "siteConfig": {
3085
+ "linuxFxVersion": "[format('DOCKER|{0}', parameters('image'))]",
3086
+ "acrUseManagedIdentityCreds": true,
3087
+ "healthCheckPath": "/health",
3088
+ "alwaysOn": true,
3089
+ "appSettings": "[concat(createArray(createObject('name', 'WEBSITES_ENABLE_APP_SERVICE_STORAGE', 'value', 'false'), createObject('name', 'WEBSITES_CONTAINER_START_TIME_LIMIT', 'value', '1800'), createObject('name', 'DATABASE_URL', 'value', variables('databaseUrlAppSettingValue')), createObject('name', 'JWT_SECRET', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-secrets-jwtKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'NODE_ENV', 'value', 'production'), createObject('name', 'LOCATION', 'value', parameters('location')), createObject('name', 'PORT', 'value', '3000'), createObject('name', 'CORS_ORIGINS', 'value', format('https://{0}.azurewebsites.net', parameters('name'))), createObject('name', 'ALLOWED_ORIGINS', 'value', format('http://localhost:*,http://127.0.0.1:*,https://{0}.azurewebsites.net', parameters('name'))), createObject('name', 'WEBSITE_VNET_ROUTE_ALL', 'value', if(parameters('enablePostgresPublicAccess'), '0', '1')), createObject('name', 'WEBSITE_DNS_SERVER', 'value', '168.63.129.16'), createObject('name', 'DATABASELOG_URL', 'value', variables('databaseLogUrlAppSettingValue')), createObject('name', 'KEY_VAULT_ENABLED', 'value', 'true'), createObject('name', 'SECURITY_STORAGE_MODE', 'value', 'keyvault'), createObject('name', 'KEY_VAULT_URL', 'value', format('https://{0}.vault.azure.net/', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'ENCRYPTION_KEY', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-encryption-keyKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'AUTO_CREATE_TABLES', 'value', if(parameters('autoCreateTables'), 'true', 'false')), createObject('name', 'KEYCLOAK_REALM', 'value', parameters('keycloakRealm')), createObject('name', 'KEYCLOAK_CLIENT_ID', 'value', parameters('keycloakClientId')), createObject('name', 'KEYCLOAK_ADMIN_USERNAME', 'value', parameters('keycloakAdminUsername')), createObject('name', 'KEYCLOAK_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-keycloak-admin-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'ONBOARDING_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-onboarding-admin-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'KEYCLOAK_SERVER_URL', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=keycloak-web-server-url)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'KEYCLOAK_INTERNAL_SERVER_URL', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=keycloak-internal-server-url)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'KEYCLOAK_TOKEN_TIMEOUT', 'value', '5000'), createObject('name', 'KEYCLOAK_VERIFY_AUDIENCE', 'value', if(parameters('keycloakVerifyAudience'), 'true', 'false')), createObject('name', 'DISABLE_RATE_LIMIT', 'value', variables('shouldDisableRateLimit')), createObject('name', 'SKIP_DB_REDEPLOY', 'value', 'true'), createObject('name', 'MISO_WEB_SERVER_URL', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=miso-controller-web-server-url)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MISO_CONTROLLER_URL', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=miso-controller-internal-server-urlKeyVault)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MISO_RELATIVE_PATH', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=miso-controller-vdir-public)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'CONTROLLER_BASE_URL', 'value', if(not(empty(parameters('misoControllerUrl'))), parameters('misoControllerUrl'), format('https://{0}.azurewebsites.net', parameters('name')))), createObject('name', 'AZURE_SUBSCRIPTION_ID', 'value', subscription().subscriptionId), createObject('name', 'TENANT_ID', 'value', subscription().tenantId), createObject('name', 'AZURE_RESOURCE_GROUP_NAME', 'value', parameters('resourceGroupName')), createObject('name', 'INFRASTRUCTURE_NAME', 'value', parameters('prefix')), createObject('name', 'ENTRA_ID_CLIENT_ID', 'value', parameters('entraIdClientId')), createObject('name', 'ENTRA_ID_CLIENT_SECRET', 'value', parameters('entraIdClientSecret')), createObject('name', 'ENTRA_ID_TENANT_ID', 'value', parameters('entraIdTenantId')), createObject('name', 'MISO_CLIENTID', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-miso-clientIdKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'MISO_CLIENTSECRET', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=miso-controller-client-secretKeyVault)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MORI_BASE_URL', 'value', variables('moriBaseNormalized')), createObject('name', 'MORI_CALLBACK_API_KEY', 'value', parameters('moriCallbackApiKey')), createObject('name', 'ENTRA_GRAPH_DELEGATED_REDIRECT_URI', 'value', variables('moriEntraGraphDelegatedRedirectUri')), createObject('name', 'ENTRA_ID_CENTRAL_CALLBACK_URI', 'value', variables('moriEntraCentralCallbackUri')), createObject('name', 'ENTRA_CALLBACK_STATE_SECRET', 'value', parameters('entraCallbackStateSecret')), createObject('name', 'DOCKER_ENABLE_CI', 'value', 'true'), createObject('name', 'DOCKER_REGISTRY_SERVER_URL', 'value', if(not(empty(parameters('acrLoginServer'))), format('https://{0}', parameters('acrLoginServer')), '')), createObject('name', 'ONBOARDING_CREATE_PIPELINE_CREDENTIALS', 'value', 'true'), createObject('name', 'LICENSE_JWT', 'value', parameters('licenseJwt')), createObject('name', 'LICENSE_EDITION', 'value', parameters('licenseEdition')), createObject('name', 'ONBOARDING_MODE', 'value', parameters('onboardingMode'))), variables('registryCredentialAppSettings'))]",
3090
+ "vnetRouteAllEnabled": "[not(parameters('enablePostgresPublicAccess'))]"
3091
+ },
3092
+ "vnetRouteAllEnabled": "[not(parameters('enablePostgresPublicAccess'))]"
3093
+ }
3094
+ },
3095
+ {
3096
+ "type": "Microsoft.Web/sites/config",
3097
+ "apiVersion": "2023-01-01",
3098
+ "name": "[format('{0}/{1}', parameters('name'), 'logs')]",
3099
+ "properties": {
3100
+ "applicationLogs": {
3101
+ "fileSystem": {
3102
+ "level": "Information"
3103
+ }
3104
+ },
3105
+ "httpLogs": {
3106
+ "fileSystem": {
3107
+ "enabled": true,
3108
+ "retentionInDays": 7,
3109
+ "retentionInMb": 100
3110
+ }
3111
+ },
3112
+ "detailedErrorMessages": {
3113
+ "enabled": true
3114
+ },
3115
+ "failedRequestsTracing": {
3116
+ "enabled": true
3117
+ }
3118
+ },
3119
+ "dependsOn": ["[resourceId('Microsoft.Web/sites', parameters('name'))]"]
3120
+ },
3121
+ {
3122
+ "type": "Microsoft.Web/sites/config",
3123
+ "apiVersion": "2023-01-01",
3124
+ "name": "[format('{0}/{1}', parameters('name'), 'azurestorageaccounts')]",
3125
+ "properties": {
3126
+ "[format('{0}', parameters('storageAccountName'))]": {
3127
+ "type": "AzureFiles",
3128
+ "accountName": "[parameters('storageAccountName')]",
3129
+ "shareName": "[parameters('fileShareName')]",
3130
+ "accessKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2024-01-01').keys[0].value]",
3131
+ "mountPath": "/data"
3132
+ }
3133
+ },
3134
+ "dependsOn": ["[resourceId('Microsoft.Web/sites', parameters('name'))]"]
3135
+ }
3136
+ ],
3137
+ "outputs": {
3138
+ "webAppName": {
3139
+ "type": "string",
3140
+ "value": "[parameters('name')]"
3141
+ },
3142
+ "webAppUrl": {
3143
+ "type": "string",
3144
+ "value": "[format('https://{0}', reference(resourceId('Microsoft.Web/sites', parameters('name')), '2023-01-01').defaultHostName)]"
3145
+ },
3146
+ "webAppId": {
3147
+ "type": "string",
3148
+ "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]"
3149
+ },
3150
+ "principalId": {
3151
+ "type": "string",
3152
+ "value": "[reference(resourceId('Microsoft.Web/sites', parameters('name')), '2023-01-01', 'full').identity.principalId]"
3153
+ }
3154
+ }
3155
+ }
3156
+ },
3157
+ "dependsOn": [
3158
+ "[resourceId('Microsoft.Resources/deployments', 'appServicePlan')]",
3159
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
3160
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]",
3161
+ "[resourceId('Microsoft.Resources/deployments', 'keyVaultSecretsPhase1')]",
3162
+ "[resourceId('Microsoft.Resources/deployments', 'network')]",
3163
+ "[resourceId('Microsoft.Resources/deployments', 'postgresql')]",
3164
+ "[resourceId('Microsoft.Resources/deployments', 'storage')]"
3165
+ ]
3166
+ },
3167
+ {
3168
+ "type": "Microsoft.Resources/deployments",
3169
+ "apiVersion": "2025-04-01",
3170
+ "name": "keyVaultWebAppAccessPolicies",
3171
+ "properties": {
3172
+ "expressionEvaluationOptions": {
3173
+ "scope": "inner"
3174
+ },
3175
+ "mode": "Incremental",
3176
+ "parameters": {
3177
+ "keyVaultId": {
3178
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultId.value]"
3179
+ },
3180
+ "webAppPrincipalIds": {
3181
+ "value": [
3182
+ "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.principalId.value]",
3183
+ "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.principalId.value]"
3184
+ ]
3185
+ }
3186
+ },
3187
+ "template": {
3188
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3189
+ "languageVersion": "2.0",
3190
+ "contentVersion": "1.0.0.0",
3191
+ "metadata": {
3192
+ "_generator": {
3193
+ "name": "bicep",
3194
+ "version": "0.47.16.16243",
3195
+ "templateHash": "1560535279661158869"
3196
+ }
3197
+ },
3198
+ "parameters": {
3199
+ "keyVaultId": {
3200
+ "type": "string",
3201
+ "metadata": {
3202
+ "description": "Existing Key Vault resource id (same resource group as web apps)"
3203
+ }
3204
+ },
3205
+ "webAppPrincipalIds": {
3206
+ "type": "array",
3207
+ "items": {
3208
+ "type": "string"
3209
+ },
3210
+ "metadata": {
3211
+ "description": "Object IDs (principalId) of web apps that use Key Vault references"
3212
+ }
3213
+ }
3214
+ },
3215
+ "resources": {
3216
+ "kv": {
3217
+ "existing": true,
3218
+ "type": "Microsoft.KeyVault/vaults",
3219
+ "apiVersion": "2023-07-01",
3220
+ "name": "[last(split(parameters('keyVaultId'), '/'))]"
3221
+ },
3222
+ "kvWebAppAccessPolicies": {
3223
+ "type": "Microsoft.KeyVault/vaults/accessPolicies",
3224
+ "apiVersion": "2023-07-01",
3225
+ "name": "[format('{0}/{1}', last(split(parameters('keyVaultId'), '/')), 'add')]",
3226
+ "properties": {
3227
+ "copy": [
3228
+ {
3229
+ "name": "accessPolicies",
3230
+ "count": "[length(parameters('webAppPrincipalIds'))]",
3231
+ "input": {
3232
+ "tenantId": "[subscription().tenantId]",
3233
+ "objectId": "[parameters('webAppPrincipalIds')[copyIndex('accessPolicies')]]",
3234
+ "permissions": {
3235
+ "secrets": ["get", "list", "set"],
3236
+ "keys": [],
3237
+ "certificates": [],
3238
+ "storage": []
3239
+ }
3240
+ }
3241
+ }
3242
+ ]
3243
+ }
3244
+ }
3245
+ }
3246
+ }
3247
+ },
3248
+ "dependsOn": [
3249
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
3250
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]",
3251
+ "[resourceId('Microsoft.Resources/deployments', 'misoWebApp')]"
3252
+ ]
3253
+ },
3254
+ {
3255
+ "type": "Microsoft.Resources/deployments",
3256
+ "apiVersion": "2025-04-01",
3257
+ "name": "webAppPostKvPolicyRestart",
3258
+ "properties": {
3259
+ "expressionEvaluationOptions": {
3260
+ "scope": "inner"
3261
+ },
3262
+ "mode": "Incremental",
3263
+ "parameters": {
3264
+ "keycloakWebAppName": {
3265
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppName.value]"
3266
+ },
3267
+ "misoWebAppName": {
3268
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.webAppName.value]"
3269
+ },
3270
+ "readyStamp": {
3271
+ "value": "[format('{0}-{1}', deployment().name, uniqueString(deployment().name, resourceGroup().id))]"
3272
+ }
3273
+ },
3274
+ "template": {
3275
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3276
+ "contentVersion": "1.0.0.0",
3277
+ "metadata": {
3278
+ "_generator": {
3279
+ "name": "bicep",
3280
+ "version": "0.47.16.16243",
3281
+ "templateHash": "16977137676176830260"
3282
+ }
3283
+ },
3284
+ "parameters": {
3285
+ "keycloakWebAppName": {
3286
+ "type": "string",
3287
+ "metadata": {
3288
+ "description": "Keycloak web app name"
3289
+ }
3290
+ },
3291
+ "misoWebAppName": {
3292
+ "type": "string",
3293
+ "metadata": {
3294
+ "description": "MISO Controller web app name"
3295
+ }
3296
+ },
3297
+ "readyStamp": {
3298
+ "type": "string",
3299
+ "metadata": {
3300
+ "description": "Stamp written into AIFABRIX_KV_ACCESS_READY (forces setting change / recycle)"
3301
+ }
3302
+ }
3303
+ },
3304
+ "variables": {
3305
+ "readySetting": {
3306
+ "AIFABRIX_KV_ACCESS_READY": "[parameters('readyStamp')]"
3307
+ }
3308
+ },
3309
+ "resources": [
3310
+ {
3311
+ "type": "Microsoft.Resources/deployments",
3312
+ "apiVersion": "2025-04-01",
3313
+ "name": "keycloakPostKvAppSettings",
3314
+ "properties": {
3315
+ "expressionEvaluationOptions": {
3316
+ "scope": "inner"
3317
+ },
3318
+ "mode": "Incremental",
3319
+ "parameters": {
3320
+ "webAppName": {
3321
+ "value": "[parameters('keycloakWebAppName')]"
3322
+ },
3323
+ "appSettings": {
3324
+ "value": "[union(list(resourceId('Microsoft.Web/sites/config', parameters('keycloakWebAppName'), 'appsettings'), '2022-03-01').properties, variables('readySetting'))]"
3325
+ }
3326
+ },
3327
+ "template": {
3328
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3329
+ "contentVersion": "1.0.0.0",
3330
+ "metadata": {
3331
+ "_generator": {
3332
+ "name": "bicep",
3333
+ "version": "0.47.16.16243",
3334
+ "templateHash": "6721372499638722047"
3335
+ }
3336
+ },
3337
+ "parameters": {
3338
+ "webAppName": {
3339
+ "type": "string",
3340
+ "metadata": {
3341
+ "description": "Web app name"
3342
+ }
3343
+ },
3344
+ "appSettings": {
3345
+ "type": "object",
3346
+ "metadata": {
3347
+ "description": "Full app settings object to apply (existing + merges)"
3348
+ }
3349
+ }
3350
+ },
3351
+ "resources": [
3352
+ {
3353
+ "type": "Microsoft.Web/sites/config",
3354
+ "apiVersion": "2022-03-01",
3355
+ "name": "[format('{0}/{1}', parameters('webAppName'), 'appsettings')]",
3356
+ "properties": "[parameters('appSettings')]"
3357
+ }
3358
+ ]
3359
+ }
3360
+ }
3361
+ },
3362
+ {
3363
+ "type": "Microsoft.Resources/deployments",
3364
+ "apiVersion": "2025-04-01",
3365
+ "name": "misoPostKvAppSettings",
3366
+ "properties": {
3367
+ "expressionEvaluationOptions": {
3368
+ "scope": "inner"
3369
+ },
3370
+ "mode": "Incremental",
3371
+ "parameters": {
3372
+ "webAppName": {
3373
+ "value": "[parameters('misoWebAppName')]"
3374
+ },
3375
+ "appSettings": {
3376
+ "value": "[union(list(resourceId('Microsoft.Web/sites/config', parameters('misoWebAppName'), 'appsettings'), '2022-03-01').properties, variables('readySetting'))]"
3377
+ }
3378
+ },
3379
+ "template": {
3380
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3381
+ "contentVersion": "1.0.0.0",
3382
+ "metadata": {
3383
+ "_generator": {
3384
+ "name": "bicep",
3385
+ "version": "0.47.16.16243",
3386
+ "templateHash": "6721372499638722047"
3387
+ }
3388
+ },
3389
+ "parameters": {
3390
+ "webAppName": {
3391
+ "type": "string",
3392
+ "metadata": {
3393
+ "description": "Web app name"
3394
+ }
3395
+ },
3396
+ "appSettings": {
3397
+ "type": "object",
3398
+ "metadata": {
3399
+ "description": "Full app settings object to apply (existing + merges)"
3400
+ }
3401
+ }
3402
+ },
3403
+ "resources": [
3404
+ {
3405
+ "type": "Microsoft.Web/sites/config",
3406
+ "apiVersion": "2022-03-01",
3407
+ "name": "[format('{0}/{1}', parameters('webAppName'), 'appsettings')]",
3408
+ "properties": "[parameters('appSettings')]"
3409
+ }
3410
+ ]
3411
+ }
3412
+ }
3413
+ }
3414
+ ],
3415
+ "outputs": {
3416
+ "keycloakReadyStamp": {
3417
+ "type": "string",
3418
+ "value": "[parameters('readyStamp')]"
3419
+ },
3420
+ "misoReadyStamp": {
3421
+ "type": "string",
3422
+ "value": "[parameters('readyStamp')]"
3423
+ }
3424
+ }
3425
+ }
3426
+ },
3427
+ "dependsOn": [
3428
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
3429
+ "[resourceId('Microsoft.Resources/deployments', 'keyVaultWebAppAccessPolicies')]",
3430
+ "[resourceId('Microsoft.Resources/deployments', 'misoWebApp')]"
3431
+ ]
3432
+ },
3433
+ {
3434
+ "type": "Microsoft.Resources/deployments",
3435
+ "apiVersion": "2025-04-01",
3436
+ "name": "keyVaultSecretsPhase2",
3437
+ "properties": {
3438
+ "expressionEvaluationOptions": {
3439
+ "scope": "inner"
3440
+ },
3441
+ "mode": "Incremental",
3442
+ "parameters": {
3443
+ "keyVaultName": {
3444
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]"
3445
+ },
3446
+ "prefix": {
3447
+ "value": "[variables('infrastructureName')]"
3448
+ },
3449
+ "keycloakWebAppUrl": {
3450
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppUrl.value]"
3451
+ },
3452
+ "keycloakVdirPublic": {
3453
+ "value": "[variables('keycloakVdirPublic')]"
3454
+ },
3455
+ "misoWebAppUrl": {
3456
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.webAppUrl.value]"
3457
+ },
3458
+ "misoVdirPublic": {
3459
+ "value": "/miso"
3460
+ },
3461
+ "encryptionKey": {
3462
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.encryptionKey.value]"
3463
+ }
3464
+ },
3465
+ "template": {
3466
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3467
+ "contentVersion": "1.0.0.0",
3468
+ "metadata": {
3469
+ "_generator": {
3470
+ "name": "bicep",
3471
+ "version": "0.47.16.16243",
3472
+ "templateHash": "9394845529752045254"
3473
+ }
3474
+ },
3475
+ "parameters": {
3476
+ "keyVaultName": {
3477
+ "type": "string",
3478
+ "metadata": {
3479
+ "description": "Key Vault name"
3480
+ }
3481
+ },
3482
+ "prefix": {
3483
+ "type": "string",
3484
+ "metadata": {
3485
+ "description": "Prefix for secret naming"
3486
+ }
3487
+ },
3488
+ "keycloakWebAppUrl": {
3489
+ "type": "string",
3490
+ "metadata": {
3491
+ "description": "Keycloak web app origin URL (App Service hostname, no virtual-directory suffix)"
3492
+ }
3493
+ },
3494
+ "keycloakVdirPublic": {
3495
+ "type": "string",
3496
+ "defaultValue": "/auth",
3497
+ "metadata": {
3498
+ "description": "Public virtual-directory path for Keycloak (e.g. /auth from /auth/* Front Door route)"
3499
+ }
3500
+ },
3501
+ "misoWebAppUrl": {
3502
+ "type": "string",
3503
+ "metadata": {
3504
+ "description": "MISO Controller web app origin URL (App Service hostname, no virtual-directory suffix)"
3505
+ }
3506
+ },
3507
+ "misoVdirPublic": {
3508
+ "type": "string",
3509
+ "defaultValue": "/miso",
3510
+ "metadata": {
3511
+ "description": "Public virtual-directory path for MISO behind Front Door (e.g. /miso from /miso/*)"
3512
+ }
3513
+ },
3514
+ "encryptionKey": {
3515
+ "type": "securestring",
3516
+ "metadata": {
3517
+ "description": "Encryption key for MISO Controller"
3518
+ }
3519
+ },
3520
+ "jwtSecret": {
3521
+ "type": "securestring",
3522
+ "defaultValue": "",
3523
+ "metadata": {
3524
+ "description": "JWT secret for MISO Controller (auto-generated if not provided)"
3525
+ }
3526
+ },
3527
+ "misoClientId": {
3528
+ "type": "string",
3529
+ "defaultValue": "miso-controller-miso-miso-controller",
3530
+ "metadata": {
3531
+ "description": "MISO Client ID - Format: {controllerKey}-{environmentKey}-{applicationKey}"
3532
+ }
3533
+ },
3534
+ "misoClientSecret": {
3535
+ "type": "securestring",
3536
+ "defaultValue": "",
3537
+ "metadata": {
3538
+ "description": "MISO Client Secret (auto-generated if not provided)"
3539
+ }
3540
+ }
3541
+ },
3542
+ "variables": {
3543
+ "misoWebAppOrigin": "[if(endsWith(parameters('misoWebAppUrl'), '/'), substring(parameters('misoWebAppUrl'), 0, max(0, sub(length(parameters('misoWebAppUrl')), 1))), parameters('misoWebAppUrl'))]",
3544
+ "misoVdirSegment": "[if(or(equals(parameters('misoVdirPublic'), ''), equals(parameters('misoVdirPublic'), '/')), '', if(startsWith(parameters('misoVdirPublic'), '/'), parameters('misoVdirPublic'), format('/{0}', parameters('misoVdirPublic'))))]",
3545
+ "misoWebServerPublicUrl": "[if(empty(variables('misoVdirSegment')), variables('misoWebAppOrigin'), format('{0}{1}', variables('misoWebAppOrigin'), variables('misoVdirSegment')))]",
3546
+ "misoVdirSecretValue": "[if(empty(variables('misoVdirSegment')), '/', variables('misoVdirSegment'))]",
3547
+ "keycloakWebAppOrigin": "[if(endsWith(parameters('keycloakWebAppUrl'), '/'), substring(parameters('keycloakWebAppUrl'), 0, max(0, sub(length(parameters('keycloakWebAppUrl')), 1))), parameters('keycloakWebAppUrl'))]",
3548
+ "keycloakVdirSegment": "[if(or(equals(parameters('keycloakVdirPublic'), ''), equals(parameters('keycloakVdirPublic'), '/')), '', if(startsWith(parameters('keycloakVdirPublic'), '/'), parameters('keycloakVdirPublic'), format('/{0}', parameters('keycloakVdirPublic'))))]",
3549
+ "keycloakServerPublicUrl": "[if(empty(variables('keycloakVdirSegment')), variables('keycloakWebAppOrigin'), format('{0}{1}', variables('keycloakWebAppOrigin'), variables('keycloakVdirSegment')))]",
3550
+ "keycloakVdirSecretValue": "[if(empty(variables('keycloakVdirSegment')), '/', variables('keycloakVdirSegment'))]"
3551
+ },
3552
+ "resources": [
3553
+ {
3554
+ "type": "Microsoft.KeyVault/vaults/secrets",
3555
+ "apiVersion": "2023-07-01",
3556
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'keycloak-vdir-public')]",
3557
+ "properties": {
3558
+ "value": "[variables('keycloakVdirSecretValue')]"
3559
+ }
3560
+ },
3561
+ {
3562
+ "type": "Microsoft.KeyVault/vaults/secrets",
3563
+ "apiVersion": "2023-07-01",
3564
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'keycloak-web-server-url')]",
3565
+ "properties": {
3566
+ "value": "[variables('keycloakServerPublicUrl')]"
3567
+ }
3568
+ },
3569
+ {
3570
+ "type": "Microsoft.KeyVault/vaults/secrets",
3571
+ "apiVersion": "2023-07-01",
3572
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'keycloak-internal-server-url')]",
3573
+ "properties": {
3574
+ "value": "[variables('keycloakServerPublicUrl')]"
3575
+ }
3576
+ },
3577
+ {
3578
+ "type": "Microsoft.KeyVault/vaults/secrets",
3579
+ "apiVersion": "2023-07-01",
3580
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-vdir-public')]",
3581
+ "properties": {
3582
+ "value": "[variables('misoVdirSecretValue')]"
3583
+ }
3584
+ },
3585
+ {
3586
+ "type": "Microsoft.KeyVault/vaults/secrets",
3587
+ "apiVersion": "2023-07-01",
3588
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-web-server-url')]",
3589
+ "properties": {
3590
+ "value": "[variables('misoWebServerPublicUrl')]"
3591
+ }
3592
+ },
3593
+ {
3594
+ "type": "Microsoft.KeyVault/vaults/secrets",
3595
+ "apiVersion": "2023-07-01",
3596
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-internal-server-urlKeyVault')]",
3597
+ "properties": {
3598
+ "value": "[parameters('misoWebAppUrl')]"
3599
+ }
3600
+ },
3601
+ {
3602
+ "type": "Microsoft.KeyVault/vaults/secrets",
3603
+ "apiVersion": "2023-07-01",
3604
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-encryption-keyKeyVault', parameters('prefix')))]",
3605
+ "properties": {
3606
+ "value": "[parameters('encryptionKey')]"
3607
+ }
3608
+ },
3609
+ {
3610
+ "type": "Microsoft.KeyVault/vaults/secrets",
3611
+ "apiVersion": "2023-07-01",
3612
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-secrets-jwtKeyVault', parameters('prefix')))]",
3613
+ "properties": {
3614
+ "value": "[if(not(empty(parameters('jwtSecret'))), parameters('jwtSecret'), format('PLACEHOLDER_JWT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]"
3615
+ }
3616
+ },
3617
+ {
3618
+ "type": "Microsoft.KeyVault/vaults/secrets",
3619
+ "apiVersion": "2023-07-01",
3620
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), format('{0}-miso-clientIdKeyVault', parameters('prefix')))]",
3621
+ "properties": {
3622
+ "value": "[parameters('misoClientId')]"
3623
+ }
3624
+ },
3625
+ {
3626
+ "type": "Microsoft.KeyVault/vaults/secrets",
3627
+ "apiVersion": "2023-07-01",
3628
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'miso-controller-client-secretKeyVault')]",
3629
+ "properties": {
3630
+ "value": "[if(not(empty(parameters('misoClientSecret'))), parameters('misoClientSecret'), format('PLACEHOLDER_CLIENT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]"
3631
+ }
3632
+ }
3633
+ ]
3634
+ }
3635
+ },
3636
+ "dependsOn": [
3637
+ "[resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets')]",
3638
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
3639
+ "[resourceId('Microsoft.Resources/deployments', 'keyVault')]",
3640
+ "[resourceId('Microsoft.Resources/deployments', 'misoWebApp')]"
3641
+ ]
3642
+ },
3643
+ {
3644
+ "condition": "[parameters('deployRoleAssignments')]",
3645
+ "type": "Microsoft.Resources/deployments",
3646
+ "apiVersion": "2025-04-01",
3647
+ "name": "roleAssignment",
3648
+ "properties": {
3649
+ "expressionEvaluationOptions": {
3650
+ "scope": "inner"
3651
+ },
3652
+ "mode": "Incremental",
3653
+ "parameters": {
3654
+ "managedIdentityId": {
3655
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managedIdentity'), '2025-04-01').outputs.managedIdentityId.value]"
3656
+ },
3657
+ "principalId": {
3658
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managedIdentity'), '2025-04-01').outputs.principalId.value]"
3659
+ },
3660
+ "resourceGroupId": {
3661
+ "value": "[resourceGroup().id]"
3662
+ }
3663
+ },
3664
+ "template": {
3665
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3666
+ "contentVersion": "1.0.0.0",
3667
+ "metadata": {
3668
+ "_generator": {
3669
+ "name": "bicep",
3670
+ "version": "0.47.16.16243",
3671
+ "templateHash": "17283107409340257409"
3672
+ }
3673
+ },
3674
+ "parameters": {
3675
+ "managedIdentityId": {
3676
+ "type": "string",
3677
+ "metadata": {
3678
+ "description": "Managed Identity resource ID"
3679
+ }
3680
+ },
3681
+ "principalId": {
3682
+ "type": "string",
3683
+ "metadata": {
3684
+ "description": "Managed Identity principal ID"
3685
+ }
3686
+ },
3687
+ "resourceGroupId": {
3688
+ "type": "string",
3689
+ "metadata": {
3690
+ "description": "Resource Group ID"
3691
+ }
3692
+ }
3693
+ },
3694
+ "resources": [
3695
+ {
3696
+ "type": "Microsoft.Authorization/roleAssignments",
3697
+ "apiVersion": "2022-04-01",
3698
+ "name": "[guid(parameters('resourceGroupId'), parameters('managedIdentityId'), 'Contributor')]",
3699
+ "properties": {
3700
+ "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
3701
+ "principalId": "[parameters('principalId')]",
3702
+ "principalType": "ServicePrincipal"
3703
+ }
3704
+ }
3705
+ ]
3706
+ }
3707
+ },
3708
+ "dependsOn": [
3709
+ "[resourceId('Microsoft.Resources/deployments', 'keyVaultSecretsPhase2')]",
3710
+ "[resourceId('Microsoft.Resources/deployments', 'managedIdentity')]"
3711
+ ]
3712
+ },
3713
+ {
3714
+ "condition": "[parameters('deployRoleAssignments')]",
3715
+ "type": "Microsoft.Resources/deployments",
3716
+ "apiVersion": "2025-04-01",
3717
+ "name": "misoControllerManagedRgContributor",
3718
+ "properties": {
3719
+ "expressionEvaluationOptions": {
3720
+ "scope": "inner"
3721
+ },
3722
+ "mode": "Incremental",
3723
+ "parameters": {
3724
+ "misoControllerPrincipalId": {
3725
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.principalId.value]"
3726
+ },
3727
+ "resourceGroupId": {
3728
+ "value": "[resourceGroup().id]"
3729
+ }
3730
+ },
3731
+ "template": {
3732
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3733
+ "contentVersion": "1.0.0.0",
3734
+ "metadata": {
3735
+ "_generator": {
3736
+ "name": "bicep",
3737
+ "version": "0.47.16.16243",
3738
+ "templateHash": "11111042069715569690"
3739
+ }
3740
+ },
3741
+ "parameters": {
3742
+ "misoControllerPrincipalId": {
3743
+ "type": "string",
3744
+ "metadata": {
3745
+ "description": "MISO Controller web app system-assigned managed identity principal object id"
3746
+ }
3747
+ },
3748
+ "resourceGroupId": {
3749
+ "type": "string",
3750
+ "metadata": {
3751
+ "description": "ARM resource id of this resource group (stable input for role assignment name)"
3752
+ }
3753
+ }
3754
+ },
3755
+ "resources": [
3756
+ {
3757
+ "type": "Microsoft.Authorization/roleAssignments",
3758
+ "apiVersion": "2022-04-01",
3759
+ "name": "[guid(parameters('resourceGroupId'), parameters('misoControllerPrincipalId'), 'MisoControllerManagedRgContributor')]",
3760
+ "properties": {
3761
+ "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
3762
+ "principalId": "[parameters('misoControllerPrincipalId')]",
3763
+ "principalType": "ServicePrincipal"
3764
+ }
3765
+ }
3766
+ ]
3767
+ }
3768
+ },
3769
+ "dependsOn": [
3770
+ "[resourceId('Microsoft.Resources/deployments', 'keyVaultSecretsPhase2')]",
3771
+ "[resourceId('Microsoft.Resources/deployments', 'misoWebApp')]"
3772
+ ]
3773
+ },
3774
+ {
3775
+ "condition": "[parameters('deployRoleAssignments')]",
3776
+ "type": "Microsoft.Resources/deployments",
3777
+ "apiVersion": "2025-04-01",
3778
+ "name": "acrPullRoleAssignment",
3779
+ "subscriptionId": "[variables('acrResourceIdSegments')[2]]",
3780
+ "resourceGroup": "[variables('acrResourceIdSegments')[4]]",
3781
+ "properties": {
3782
+ "expressionEvaluationOptions": {
3783
+ "scope": "inner"
3784
+ },
3785
+ "mode": "Incremental",
3786
+ "parameters": {
3787
+ "registryName": {
3788
+ "value": "[last(variables('acrResourceIdSegments'))]"
3789
+ },
3790
+ "principalIds": {
3791
+ "value": [
3792
+ "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.principalId.value]",
3793
+ "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.principalId.value]"
3794
+ ]
3795
+ }
3796
+ },
3797
+ "template": {
3798
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3799
+ "contentVersion": "1.0.0.0",
3800
+ "metadata": {
3801
+ "_generator": {
3802
+ "name": "bicep",
3803
+ "version": "0.47.16.16243",
3804
+ "templateHash": "1882530004456299732"
3805
+ }
3806
+ },
3807
+ "parameters": {
3808
+ "registryName": {
3809
+ "type": "string",
3810
+ "minLength": 5,
3811
+ "metadata": {
3812
+ "description": "Container registry name in this resource group"
3813
+ }
3814
+ },
3815
+ "principalIds": {
3816
+ "type": "array",
3817
+ "minLength": 1,
3818
+ "metadata": {
3819
+ "description": "Object IDs of the web app system-assigned identities that pull images"
3820
+ }
3821
+ }
3822
+ },
3823
+ "variables": {
3824
+ "acrPullRoleDefinitionId": "7f951dda-4ed3-4680-a7ca-43b17298f9ac"
3825
+ },
3826
+ "resources": [
3827
+ {
3828
+ "copy": {
3829
+ "name": "acrPull",
3830
+ "count": "[length(parameters('principalIds'))]"
3831
+ },
3832
+ "type": "Microsoft.Authorization/roleAssignments",
3833
+ "apiVersion": "2022-04-01",
3834
+ "scope": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('registryName'))]",
3835
+ "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries', parameters('registryName')), parameters('principalIds')[copyIndex()], variables('acrPullRoleDefinitionId'))]",
3836
+ "properties": {
3837
+ "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('acrPullRoleDefinitionId'))]",
3838
+ "principalId": "[parameters('principalIds')[copyIndex()]]",
3839
+ "principalType": "ServicePrincipal"
3840
+ }
3841
+ }
3842
+ ]
3843
+ }
3844
+ },
3845
+ "dependsOn": [
3846
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
3847
+ "[resourceId('Microsoft.Resources/deployments', 'misoWebApp')]"
3848
+ ]
3849
+ },
3850
+ {
3851
+ "type": "Microsoft.Resources/deployments",
3852
+ "apiVersion": "2025-04-01",
3853
+ "name": "vnetIntegrationScript",
3854
+ "properties": {
3855
+ "expressionEvaluationOptions": {
3856
+ "scope": "inner"
3857
+ },
3858
+ "mode": "Incremental",
3859
+ "parameters": {
3860
+ "infrastructureName": {
3861
+ "value": "[variables('infrastructureName')]"
3862
+ },
3863
+ "location": {
3864
+ "value": "[variables('deploymentLocation')]"
3865
+ },
3866
+ "managedIdentityId": {
3867
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managedIdentity'), '2025-04-01').outputs.managedIdentityId.value]"
3868
+ },
3869
+ "keycloakWebAppName": {
3870
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppName.value]"
3871
+ },
3872
+ "misoWebAppName": {
3873
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.webAppName.value]"
3874
+ },
3875
+ "vnetName": {
3876
+ "value": "[last(split(reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.vnetId.value, '/'))]"
3877
+ },
3878
+ "resourceGroupName": {
3879
+ "value": "[resourceGroup().name]"
3880
+ },
3881
+ "enablePostgresPublicAccess": {
3882
+ "value": "[parameters('enablePostgresPublicAccess')]"
3883
+ }
3884
+ },
3885
+ "template": {
3886
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3887
+ "contentVersion": "1.0.0.0",
3888
+ "metadata": {
3889
+ "_generator": {
3890
+ "name": "bicep",
3891
+ "version": "0.47.16.16243",
3892
+ "templateHash": "14727918554068505017"
3893
+ }
3894
+ },
3895
+ "parameters": {
3896
+ "infrastructureName": {
3897
+ "type": "string",
3898
+ "metadata": {
3899
+ "description": "Infrastructure name prefix"
3900
+ }
3901
+ },
3902
+ "location": {
3903
+ "type": "string",
3904
+ "metadata": {
3905
+ "description": "Azure region"
3906
+ }
3907
+ },
3908
+ "keycloakWebAppName": {
3909
+ "type": "string",
3910
+ "metadata": {
3911
+ "description": "Keycloak web app name"
3912
+ }
3913
+ },
3914
+ "misoWebAppName": {
3915
+ "type": "string",
3916
+ "metadata": {
3917
+ "description": "MISO Controller web app name"
3918
+ }
3919
+ },
3920
+ "vnetName": {
3921
+ "type": "string",
3922
+ "metadata": {
3923
+ "description": "Virtual Network name"
3924
+ }
3925
+ },
3926
+ "resourceGroupName": {
3927
+ "type": "string",
3928
+ "metadata": {
3929
+ "description": "Resource Group name"
3930
+ }
3931
+ },
3932
+ "managedIdentityId": {
3933
+ "type": "string",
3934
+ "metadata": {
3935
+ "description": "Managed Identity resource ID for Deployment Script"
3936
+ }
3937
+ },
3938
+ "enablePostgresPublicAccess": {
3939
+ "type": "bool",
3940
+ "defaultValue": false,
3941
+ "metadata": {
3942
+ "description": "Desired PostgreSQL public network access mode from main deployment"
3943
+ }
3944
+ }
3945
+ },
3946
+ "resources": [
3947
+ {
3948
+ "type": "Microsoft.Resources/deploymentScripts",
3949
+ "apiVersion": "2023-08-01",
3950
+ "name": "[format('{0}-vnet-integration', parameters('infrastructureName'))]",
3951
+ "location": "[parameters('location')]",
3952
+ "kind": "AzureCLI",
3953
+ "identity": {
3954
+ "type": "UserAssigned",
3955
+ "userAssignedIdentities": {
3956
+ "[format('{0}', parameters('managedIdentityId'))]": {}
3957
+ }
3958
+ },
3959
+ "properties": {
3960
+ "azCliVersion": "2.53.0",
3961
+ "retentionInterval": "PT1H",
3962
+ "forceUpdateTag": "[format('{0}-arm-owned-v1', parameters('infrastructureName'))]",
3963
+ "environmentVariables": [
3964
+ {
3965
+ "name": "KEYCLOAK_APP",
3966
+ "value": "[parameters('keycloakWebAppName')]"
3967
+ },
3968
+ {
3969
+ "name": "MISO_APP",
3970
+ "value": "[parameters('misoWebAppName')]"
3971
+ },
3972
+ {
3973
+ "name": "RG",
3974
+ "value": "[parameters('resourceGroupName')]"
3975
+ },
3976
+ {
3977
+ "name": "VNET",
3978
+ "value": "[parameters('vnetName')]"
3979
+ },
3980
+ {
3981
+ "name": "DESIRED_DB_PUBLIC",
3982
+ "value": "[string(parameters('enablePostgresPublicAccess'))]"
3983
+ }
3984
+ ],
3985
+ "scriptContent": " set -euo pipefail\n\n echo \"==========================================\"\n echo \"Post-Deployment: VNet / network (ARM-owned)\"\n echo \"==========================================\"\n echo \"Resource Group: $RG\"\n echo \"VNet: $VNET\"\n echo \"Keycloak: $KEYCLOAK_APP\"\n echo \"MISO: $MISO_APP\"\n echo \"enablePostgresPublicAccess: $DESIRED_DB_PUBLIC\"\n echo \"\"\n echo \"Skipping az webapp / VNet CLI (broken under Marketplace MI tenant account).\"\n echo \"Expected from Bicep/ARM:\"\n echo \" - virtualNetworkSubnetId -> snet-webapp (05_miso / 07_keycloak)\"\n echo \" - WEBSITE_VNET_ROUTE_ALL = 0 if Postgres public, else 1\"\n echo \" - Postgres public/firewall/private DNS from 06_postgresql + network\"\n echo \"\"\n echo \"VNet integration marker complete (always succeed).\"\n echo \"==========================================\"\n "
3986
+ }
3987
+ }
3988
+ ],
3989
+ "outputs": {
3990
+ "scriptName": {
3991
+ "type": "string",
3992
+ "value": "[format('{0}-vnet-integration', parameters('infrastructureName'))]"
3993
+ }
3994
+ }
3995
+ }
3996
+ },
3997
+ "dependsOn": [
3998
+ "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('acrResourceIdSegments')[2], variables('acrResourceIdSegments')[4]), 'Microsoft.Resources/deployments', 'acrPullRoleAssignment')]",
3999
+ "[resourceId('Microsoft.Resources/deployments', 'keycloakWebApp')]",
4000
+ "[resourceId('Microsoft.Resources/deployments', 'keyVaultSecretsPhase2')]",
4001
+ "[resourceId('Microsoft.Resources/deployments', 'managedIdentity')]",
4002
+ "[resourceId('Microsoft.Resources/deployments', 'misoWebApp')]",
4003
+ "[resourceId('Microsoft.Resources/deployments', 'network')]",
4004
+ "[resourceId('Microsoft.Resources/deployments', 'postgresql')]",
4005
+ "[resourceId('Microsoft.Resources/deployments', 'webAppPostKvPolicyRestart')]"
4006
+ ]
4007
+ }
4008
+ ],
4009
+ "outputs": {
4010
+ "resourceGroupName": {
4011
+ "type": "string",
4012
+ "value": "[resourceGroup().name]"
4013
+ },
4014
+ "vnetId": {
4015
+ "type": "string",
4016
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2025-04-01').outputs.vnetId.value]"
4017
+ },
4018
+ "keyVaultName": {
4019
+ "type": "string",
4020
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]"
4021
+ },
4022
+ "scriptStorageAccountName": {
4023
+ "type": "string",
4024
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'storage'), '2025-04-01').outputs.scriptStorageAccountName.value]"
4025
+ },
4026
+ "flowiseFileShareName": {
4027
+ "type": "string",
4028
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'storage'), '2025-04-01').outputs.flowiseFileShareName.value]"
4029
+ },
4030
+ "managedIdentityName": {
4031
+ "type": "string",
4032
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managedIdentity'), '2025-04-01').outputs.managedIdentityName.value]"
4033
+ },
4034
+ "appServicePlanName": {
4035
+ "type": "string",
4036
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'appServicePlan'), '2025-04-01').outputs.planName.value]"
4037
+ },
4038
+ "webAppName": {
4039
+ "type": "string",
4040
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.webAppName.value]"
4041
+ },
4042
+ "webAppUrl": {
4043
+ "type": "string",
4044
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'misoWebApp'), '2025-04-01').outputs.webAppUrl.value]"
4045
+ },
4046
+ "postgresServerName": {
4047
+ "type": "string",
4048
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.serverName.value]"
4049
+ },
4050
+ "postgresServerFqdn": {
4051
+ "type": "string",
4052
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.serverFqdn.value]"
4053
+ },
4054
+ "postgresMisoDatabaseName": {
4055
+ "type": "string",
4056
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.misoDatabaseName.value]"
4057
+ },
4058
+ "postgresLogsDatabaseName": {
4059
+ "type": "string",
4060
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.logsDatabaseName.value]"
4061
+ },
4062
+ "postgresKeycloakDatabaseName": {
4063
+ "type": "string",
4064
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'postgresql'), '2025-04-01').outputs.keycloakDatabaseName.value]"
4065
+ },
4066
+ "keycloakWebAppName": {
4067
+ "type": "string",
4068
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppName.value]"
4069
+ },
4070
+ "keycloakWebAppUrl": {
4071
+ "type": "string",
4072
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.webAppUrl.value]"
4073
+ },
4074
+ "keycloakRealmUrl": {
4075
+ "type": "string",
4076
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.keycloakRealmUrl.value]"
4077
+ },
4078
+ "keycloakAdminUser": {
4079
+ "type": "string",
4080
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keycloakWebApp'), '2025-04-01').outputs.keycloakAdminUser.value]"
4081
+ },
4082
+ "portalUrl": {
4083
+ "type": "string",
4084
+ "value": "[format('https://portal.azure.com/#@{0}/resource/subscriptions/{1}/resourceGroups/{2}', subscription().tenantId, subscription().subscriptionId, resourceGroup().name)]"
4085
+ }
4086
+ }
4087
+ }