Skip to content

Commit ce3af4f

Browse files
authored
feat: add option to deploy with enhanced security (closes #34) (#91)
* chore: update deps * refactor: migrate from deprecated package * chore: update azd templates * feat: enable app insights * chore: update infra * feat: add infra vnet option * fix: upload url when vnet is enabled * docs: update dependencies * chore: update xo * refactor: migrate to the new ollama package * docs: add enhance security docs (#34) * chore: update lockfile * fix: container for deployment * fix: dependency conflict * chore: update packages * fix: bicep when using vnet * chore: clean up * chore: update chat protocol * docs: typo * chore: clean up * chore: force ftps state in infra * docs: update enhance security doc
1 parent 81c955a commit ce3af4f

27 files changed

+3885
-1438
lines changed

Diff for: README.md

+4
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ The deployment process will take a few minutes. Once it's done, you'll see the U
136136

137137
You can now open the web app in your browser and start chatting with the bot.
138138

139+
##### Enhance security
140+
141+
When deploying the sample in an enterprise context, you may want to enforce tighter security restrictions to protect your data and resources. See the [enhance security](./docs/enhance-security.md) guide for more information.
142+
139143
#### Clean up
140144

141145
To clean up all the Azure resources created by this sample:

Diff for: azure.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ hooks:
3535
postup:
3636
windows:
3737
shell: pwsh
38-
run: node scripts/upload-documents.js "$env:API_URL"
38+
run: node scripts/upload-documents.js "$env:UPLOAD_URL"
3939
posix:
4040
shell: sh
41-
run: node scripts/upload-documents.js "$API_URL"
41+
run: node scripts/upload-documents.js "$UPLOAD_URL"

Diff for: docs/enhance-security.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Enhance security
2+
3+
To achieve enterprise grade security we've ensured you can leverage the features below through an opt-in flag:
4+
5+
- **Deploy in a [virtual network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview)**, to restrict access to the resources including the Azure Functions API and the Azure Storage where the documents are stored.
6+
7+
- **Leverage [Azure Entra managed identity](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)** to disable all local authentication methods (ie API keys) and rely [Role-based Access Control (RBAC)](https://learn.microsoft.com/azure/role-based-access-control/overview).
8+
9+
You can enable these features when deploying this sample by following these steps:
10+
11+
1. Create a new environment for your deployment (you cannot update an existing one):
12+
```bash
13+
azd env create my-secure-env
14+
```
15+
2. Enable the virtual network feature and disable local authentication:
16+
```bash
17+
azd env set USE_VNET true
18+
```
19+
3. Deploy the sample to the new environment:
20+
```bash
21+
azd up
22+
```
23+
24+
Note that enabling virtual network will induce additional costs, as it requires the deployment of extra resources and needs to switch to paid plans for the Azure Functions and Azure Static Web App.
25+
26+
> [!IMPORTANT]
27+
> When VNET is enabled, you will lose the ability to run the sample locally while connected to Azure resources.
28+
> You can always fall back to using a local AI model and database for development purposes, by deleting the `api/.env` file

Diff for: infra/abbreviations.json

+135-134
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,137 @@
11
{
2-
"analysisServicesServers": "as",
3-
"apiManagementService": "apim-",
4-
"appConfigurationConfigurationStores": "appcs-",
5-
"appManagedEnvironments": "cae-",
6-
"appContainerApps": "ca-",
7-
"authorizationPolicyDefinitions": "policy-",
8-
"automationAutomationAccounts": "aa-",
9-
"blueprintBlueprints": "bp-",
10-
"blueprintBlueprintsArtifacts": "bpa-",
11-
"cacheRedis": "redis-",
12-
"cdnProfiles": "cdnp-",
13-
"cdnProfilesEndpoints": "cdne-",
14-
"cognitiveServicesAccounts": "cog-",
15-
"cognitiveServicesFormRecognizer": "cog-fr-",
16-
"cognitiveServicesTextAnalytics": "cog-ta-",
17-
"computeAvailabilitySets": "avail-",
18-
"computeCloudServices": "cld-",
19-
"computeDiskEncryptionSets": "des",
20-
"computeDisks": "disk",
21-
"computeDisksOs": "osdisk",
22-
"computeGalleries": "gal",
23-
"computeSnapshots": "snap-",
24-
"computeVirtualMachines": "vm",
25-
"computeVirtualMachineScaleSets": "vmss-",
26-
"containerInstanceContainerGroups": "ci",
27-
"containerRegistryRegistries": "cr",
28-
"containerServiceManagedClusters": "aks-",
29-
"databricksWorkspaces": "dbw-",
30-
"dataFactoryFactories": "adf-",
31-
"dataLakeAnalyticsAccounts": "dla",
32-
"dataLakeStoreAccounts": "dls",
33-
"dataMigrationServices": "dms-",
34-
"dBforMySQLServers": "mysql-",
35-
"dBforPostgreSQLServers": "psql-",
36-
"devicesIotHubs": "iot-",
37-
"devicesProvisioningServices": "provs-",
38-
"devicesProvisioningServicesCertificates": "pcert-",
39-
"documentDBDatabaseAccounts": "cosmos-",
40-
"eventGridDomains": "evgd-",
41-
"eventGridDomainsTopics": "evgt-",
42-
"eventGridEventSubscriptions": "evgs-",
43-
"eventHubNamespaces": "evhns-",
44-
"eventHubNamespacesEventHubs": "evh-",
45-
"hdInsightClustersHadoop": "hadoop-",
46-
"hdInsightClustersHbase": "hbase-",
47-
"hdInsightClustersKafka": "kafka-",
48-
"hdInsightClustersMl": "mls-",
49-
"hdInsightClustersSpark": "spark-",
50-
"hdInsightClustersStorm": "storm-",
51-
"hybridComputeMachines": "arcs-",
52-
"insightsActionGroups": "ag-",
53-
"insightsComponents": "appi-",
54-
"keyVaultVaults": "kv-",
55-
"kubernetesConnectedClusters": "arck",
56-
"kustoClusters": "dec",
57-
"kustoClustersDatabases": "dedb",
58-
"loadTesting": "lt-",
59-
"logicIntegrationAccounts": "ia-",
60-
"logicWorkflows": "logic-",
61-
"machineLearningServicesWorkspaces": "mlw-",
62-
"managedIdentityUserAssignedIdentities": "id-",
63-
"managementManagementGroups": "mg-",
64-
"migrateAssessmentProjects": "migr-",
65-
"networkApplicationGateways": "agw-",
66-
"networkApplicationSecurityGroups": "asg-",
67-
"networkAzureFirewalls": "afw-",
68-
"networkBastionHosts": "bas-",
69-
"networkConnections": "con-",
70-
"networkDnsZones": "dnsz-",
71-
"networkExpressRouteCircuits": "erc-",
72-
"networkFirewallPolicies": "afwp-",
73-
"networkFirewallPoliciesWebApplication": "waf",
74-
"networkFirewallPoliciesRuleGroups": "wafrg",
75-
"networkFrontDoors": "fd-",
76-
"networkFrontdoorWebApplicationFirewallPolicies": "fdfp-",
77-
"networkLoadBalancersExternal": "lbe-",
78-
"networkLoadBalancersInternal": "lbi-",
79-
"networkLoadBalancersInboundNatRules": "rule-",
80-
"networkLocalNetworkGateways": "lgw-",
81-
"networkNatGateways": "ng-",
82-
"networkNetworkInterfaces": "nic-",
83-
"networkNetworkSecurityGroups": "nsg-",
84-
"networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
85-
"networkNetworkWatchers": "nw-",
86-
"networkPrivateDnsZones": "pdnsz-",
87-
"networkPrivateLinkServices": "pl-",
88-
"networkPublicIPAddresses": "pip-",
89-
"networkPublicIPPrefixes": "ippre-",
90-
"networkRouteFilters": "rf-",
91-
"networkRouteTables": "rt-",
92-
"networkRouteTablesRoutes": "udr-",
93-
"networkTrafficManagerProfiles": "traf-",
94-
"networkVirtualNetworkGateways": "vgw-",
95-
"networkVirtualNetworks": "vnet-",
96-
"networkVirtualNetworksSubnets": "snet-",
97-
"networkVirtualNetworksVirtualNetworkPeerings": "peer-",
98-
"networkVirtualWans": "vwan-",
99-
"networkVpnGateways": "vpng-",
100-
"networkVpnGatewaysVpnConnections": "vcn-",
101-
"networkVpnGatewaysVpnSites": "vst-",
102-
"notificationHubsNamespaces": "ntfns-",
103-
"notificationHubsNamespacesNotificationHubs": "ntf-",
104-
"operationalInsightsWorkspaces": "log-",
105-
"portalDashboards": "dash-",
106-
"powerBIDedicatedCapacities": "pbi-",
107-
"purviewAccounts": "pview-",
108-
"recoveryServicesVaults": "rsv-",
109-
"resourcesResourceGroups": "rg-",
110-
"searchSearchServices": "srch-",
111-
"serviceBusNamespaces": "sb-",
112-
"serviceBusNamespacesQueues": "sbq-",
113-
"serviceBusNamespacesTopics": "sbt-",
114-
"serviceEndPointPolicies": "se-",
115-
"serviceFabricClusters": "sf-",
116-
"signalRServiceSignalR": "sigr",
117-
"sqlManagedInstances": "sqlmi-",
118-
"sqlServers": "sql-",
119-
"sqlServersDataWarehouse": "sqldw-",
120-
"sqlServersDatabases": "sqldb-",
121-
"sqlServersDatabasesStretch": "sqlstrdb-",
122-
"storageStorageAccounts": "st",
123-
"storageStorageAccountsVm": "stvm",
124-
"storSimpleManagers": "ssimp",
125-
"streamAnalyticsCluster": "asa-",
126-
"synapseWorkspaces": "syn",
127-
"synapseWorkspacesAnalyticsWorkspaces": "synw",
128-
"synapseWorkspacesSqlPoolsDedicated": "syndp",
129-
"synapseWorkspacesSqlPoolsSpark": "synsp",
130-
"timeSeriesInsightsEnvironments": "tsi-",
131-
"webServerFarms": "plan-",
132-
"webSitesAppService": "app-",
133-
"webSitesAppServiceEnvironment": "ase-",
134-
"webSitesFunctions": "func-",
135-
"webStaticSites": "stapp-"
2+
"analysisServicesServers": "as",
3+
"apiManagementService": "apim-",
4+
"appConfigurationStores": "appcs-",
5+
"appManagedEnvironments": "cae-",
6+
"appContainerApps": "ca-",
7+
"authorizationPolicyDefinitions": "policy-",
8+
"automationAutomationAccounts": "aa-",
9+
"blueprintBlueprints": "bp-",
10+
"blueprintBlueprintsArtifacts": "bpa-",
11+
"cacheRedis": "redis-",
12+
"cdnProfiles": "cdnp-",
13+
"cdnProfilesEndpoints": "cdne-",
14+
"cognitiveServicesAccounts": "cog-",
15+
"cognitiveServicesFormRecognizer": "cog-fr-",
16+
"cognitiveServicesTextAnalytics": "cog-ta-",
17+
"cognitiveServicesSpeech": "cog-sp-",
18+
"computeAvailabilitySets": "avail-",
19+
"computeCloudServices": "cld-",
20+
"computeDiskEncryptionSets": "des",
21+
"computeDisks": "disk",
22+
"computeDisksOs": "osdisk",
23+
"computeGalleries": "gal",
24+
"computeSnapshots": "snap-",
25+
"computeVirtualMachines": "vm",
26+
"computeVirtualMachineScaleSets": "vmss-",
27+
"containerInstanceContainerGroups": "ci",
28+
"containerRegistryRegistries": "cr",
29+
"containerServiceManagedClusters": "aks-",
30+
"databricksWorkspaces": "dbw-",
31+
"dataFactoryFactories": "adf-",
32+
"dataLakeAnalyticsAccounts": "dla",
33+
"dataLakeStoreAccounts": "dls",
34+
"dataMigrationServices": "dms-",
35+
"dBforMySQLServers": "mysql-",
36+
"dBforPostgreSQLServers": "psql-",
37+
"devicesIotHubs": "iot-",
38+
"devicesProvisioningServices": "provs-",
39+
"devicesProvisioningServicesCertificates": "pcert-",
40+
"documentDBDatabaseAccounts": "cosmos-",
41+
"eventGridDomains": "evgd-",
42+
"eventGridDomainsTopics": "evgt-",
43+
"eventGridEventSubscriptions": "evgs-",
44+
"eventHubNamespaces": "evhns-",
45+
"eventHubNamespacesEventHubs": "evh-",
46+
"hdInsightClustersHadoop": "hadoop-",
47+
"hdInsightClustersHbase": "hbase-",
48+
"hdInsightClustersKafka": "kafka-",
49+
"hdInsightClustersMl": "mls-",
50+
"hdInsightClustersSpark": "spark-",
51+
"hdInsightClustersStorm": "storm-",
52+
"hybridComputeMachines": "arcs-",
53+
"insightsActionGroups": "ag-",
54+
"insightsComponents": "appi-",
55+
"keyVaultVaults": "kv-",
56+
"kubernetesConnectedClusters": "arck",
57+
"kustoClusters": "dec",
58+
"kustoClustersDatabases": "dedb",
59+
"loadTesting": "lt-",
60+
"logicIntegrationAccounts": "ia-",
61+
"logicWorkflows": "logic-",
62+
"machineLearningServicesWorkspaces": "mlw-",
63+
"managedIdentityUserAssignedIdentities": "id-",
64+
"managementManagementGroups": "mg-",
65+
"migrateAssessmentProjects": "migr-",
66+
"networkApplicationGateways": "agw-",
67+
"networkApplicationSecurityGroups": "asg-",
68+
"networkAzureFirewalls": "afw-",
69+
"networkBastionHosts": "bas-",
70+
"networkConnections": "con-",
71+
"networkDnsZones": "dnsz-",
72+
"networkExpressRouteCircuits": "erc-",
73+
"networkFirewallPolicies": "afwp-",
74+
"networkFirewallPoliciesWebApplication": "waf",
75+
"networkFirewallPoliciesRuleGroups": "wafrg",
76+
"networkFrontDoors": "fd-",
77+
"networkFrontdoorWebApplicationFirewallPolicies": "fdfp-",
78+
"networkLoadBalancersExternal": "lbe-",
79+
"networkLoadBalancersInternal": "lbi-",
80+
"networkLoadBalancersInboundNatRules": "rule-",
81+
"networkLocalNetworkGateways": "lgw-",
82+
"networkNatGateways": "ng-",
83+
"networkNetworkInterfaces": "nic-",
84+
"networkNetworkSecurityGroups": "nsg-",
85+
"networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
86+
"networkNetworkWatchers": "nw-",
87+
"networkPrivateDnsZones": "pdnsz-",
88+
"networkPrivateLinkServices": "pl-",
89+
"networkPublicIPAddresses": "pip-",
90+
"networkPublicIPPrefixes": "ippre-",
91+
"networkRouteFilters": "rf-",
92+
"networkRouteTables": "rt-",
93+
"networkRouteTablesRoutes": "udr-",
94+
"networkTrafficManagerProfiles": "traf-",
95+
"networkVirtualNetworkGateways": "vgw-",
96+
"networkVirtualNetworks": "vnet-",
97+
"networkVirtualNetworksSubnets": "snet-",
98+
"networkVirtualNetworksVirtualNetworkPeerings": "peer-",
99+
"networkVirtualWans": "vwan-",
100+
"networkVpnGateways": "vpng-",
101+
"networkVpnGatewaysVpnConnections": "vcn-",
102+
"networkVpnGatewaysVpnSites": "vst-",
103+
"notificationHubsNamespaces": "ntfns-",
104+
"notificationHubsNamespacesNotificationHubs": "ntf-",
105+
"operationalInsightsWorkspaces": "log-",
106+
"portalDashboards": "dash-",
107+
"powerBIDedicatedCapacities": "pbi-",
108+
"purviewAccounts": "pview-",
109+
"recoveryServicesVaults": "rsv-",
110+
"resourcesResourceGroups": "rg-",
111+
"searchSearchServices": "srch-",
112+
"serviceBusNamespaces": "sb-",
113+
"serviceBusNamespacesQueues": "sbq-",
114+
"serviceBusNamespacesTopics": "sbt-",
115+
"serviceEndPointPolicies": "se-",
116+
"serviceFabricClusters": "sf-",
117+
"signalRServiceSignalR": "sigr",
118+
"sqlManagedInstances": "sqlmi-",
119+
"sqlServers": "sql-",
120+
"sqlServersDataWarehouse": "sqldw-",
121+
"sqlServersDatabases": "sqldb-",
122+
"sqlServersDatabasesStretch": "sqlstrdb-",
123+
"storageStorageAccounts": "st",
124+
"storageStorageAccountsVm": "stvm",
125+
"storSimpleManagers": "ssimp",
126+
"streamAnalyticsCluster": "asa-",
127+
"synapseWorkspaces": "syn",
128+
"synapseWorkspacesAnalyticsWorkspaces": "synw",
129+
"synapseWorkspacesSqlPoolsDedicated": "syndp",
130+
"synapseWorkspacesSqlPoolsSpark": "synsp",
131+
"timeSeriesInsightsEnvironments": "tsi-",
132+
"webServerFarms": "plan-",
133+
"webSitesAppService": "app-",
134+
"webSitesAppServiceEnvironment": "ase-",
135+
"webSitesFunctions": "func-",
136+
"webStaticSites": "stapp-"
136137
}

Diff for: infra/app/api.bicep

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
param name string
2+
param location string = resourceGroup().location
3+
param tags object = {}
4+
5+
param appServicePlanId string
6+
param storageAccountName string
7+
param virtualNetworkSubnetId string
8+
param applicationInsightsName string
9+
param allowedOrigins array
10+
param appSettings object
11+
param staticWebAppName string = ''
12+
13+
var useVnet = !empty(virtualNetworkSubnetId)
14+
var finalApi = useVnet ? apiFlex : api
15+
16+
module apiFlex '../core/host/functions-flex.bicep' = if (useVnet) {
17+
name: 'api-flex'
18+
scope: resourceGroup()
19+
params: {
20+
name: name
21+
location: location
22+
tags: tags
23+
allowedOrigins: allowedOrigins
24+
alwaysOn: false
25+
runtimeName: 'node'
26+
runtimeVersion: '20'
27+
appServicePlanId: appServicePlanId
28+
storageAccountName: storageAccountName
29+
applicationInsightsName: applicationInsightsName
30+
virtualNetworkSubnetId: virtualNetworkSubnetId
31+
appSettings: appSettings
32+
}
33+
}
34+
35+
module api '../core/host/functions.bicep' = if (!useVnet) {
36+
name: 'api'
37+
scope: resourceGroup()
38+
params: {
39+
name: name
40+
location: location
41+
tags: tags
42+
allowedOrigins: allowedOrigins
43+
alwaysOn: false
44+
runtimeName: 'node'
45+
runtimeVersion: '20'
46+
appServicePlanId: appServicePlanId
47+
storageAccountName: storageAccountName
48+
applicationInsightsName: applicationInsightsName
49+
managedIdentity: true
50+
appSettings: appSettings
51+
}
52+
}
53+
54+
// Link the Function App to the Static Web App
55+
module linkedBackend './linked-backend.bicep' = if (useVnet) {
56+
name: 'linkedbackend'
57+
scope: resourceGroup()
58+
params: {
59+
staticWebAppName: staticWebAppName
60+
backendResourceId: finalApi.outputs.id
61+
backendLocation: location
62+
}
63+
}
64+
65+
output identityPrincipalId string = finalApi.outputs.identityPrincipalId
66+
output name string = finalApi.outputs.name
67+
output uri string = finalApi.outputs.uri

0 commit comments

Comments
 (0)