-
Notifications
You must be signed in to change notification settings - Fork 25
/
handleAzureAuthAndDBConnectionString.ps1
executable file
·273 lines (238 loc) · 13.4 KB
/
handleAzureAuthAndDBConnectionString.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# This script is part of the sample's workflow for configuring the Azure CosmosDB connection
# string on the App Service, create the App Registrations in Entra ID, saving the appropriate
# values in Key Vault, and Azure App Config Service.
# Note that an app registration is something you'll want to set up once, and reuse for every version
# of the web app that you deploy. You can learn more about app registrations at
# https://learn.microsoft.com/azure/active-directory/develop/application-model
#
# If you do not have permission to create App Registrations consider
# sharing this script, or something similar, with your administrators to help them
# set up the variables you need to integrate with Azure AD
#
# This code may be repurposed for your scenario as desired
# but is not covered by the guidance in this content.
# Using Azure CLI command to create an app registration on Entra ID: https://learn.microsoft.com/cli/azure/ad/app?view=azure-cli-latest
# and https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad?tabs=workforce-tenant
function ToJsonWithPlatformEscaping {
# Hack due to.
# https://github.com/Azure/azure-cli/blob/dev/doc/quoting-issues-with-powershell.md
param (
[Parameter(ValueFromPipeline=$true)]
[object]$InputObject
)
$retVal = $InputObject | ConvertTo-Json -Depth 7 -Compress
If ($IsWindows) {
$retVal = $retVal.Replace('"', '\"')
}
return $retVal
}
try {
# This script section is used to inject the Cosmos DB connection string into the Azure App Service configuration after AZD deployment.
# It requires the Azure CLI to be installed, configured on the build agent, and logged in interactively or via a service principal.
$serverName = "$env:COSMOSDB_CLUSTER_NAME-c"
Write-Output "Retrieving CosmosDB for PSQL (citus) FQDN..."
# Call cosmos DB API to get the FQDN to compose the connection string
$CosmosDBMetadata = az cosmosdb postgres cluster server show --server-name $serverName --cluster-name $env:COSMOSDB_CLUSTER_NAME --resource-group $env:AZURE_RESOURCE_GROUP --subscription $env:AZURE_SUBSCRIPTION_ID | ConvertFrom-Json
$FQDN = $CosmosDBMetadata.fullyQualifiedDomainName
Write-Output "Storing CosmosDB for PSQL (citus) FQDN as POSTGRES_HOSTNAME in azure app settings..."
# Store the FQDN on the Azure App Service configuration as normal key value pair
az webapp config appsettings set --name $env:WEB_APP_NAME --resource-group $env:AZURE_RESOURCE_GROUP --settings POSTGRES_HOSTNAME=$FQDN --subscription $env:AZURE_SUBSCRIPTION_ID
Write-Output "Retrieving CosmosDB for PSQL (citus) admin password..."
# Read Cosmos DB admin password from the Azure Key Vault
$KeyVaultSecret = az keyvault secret show --name $env:AZURE_KEY_VAULT_COSMOS_SECRET_NAME --vault-name $env:AZURE_KEY_VAULT_NAME | ConvertFrom-Json
# Regular expression pattern to match special characters in CosmosDBpwd String
$Pattern = "([\.\^\$\*\+\?\{\}\[\]\\\|\(\)])"
$CosmosDBpwd = $KeyVaultSecret.value -replace $Pattern, '`$1'
# Compose the connection string
$connectionString = "postgres://citus:${CosmosDBpwd}@${FQDN}:5432/citus?sslmode=require"
# Regular expression pattern to match special characters in CosmosDBpwd String, especially pipe, ^, {} and $.
$Pattern = "([\.\^\$\*\+\?\{\}\[\]\\\|\(\)])"
# Encapsulating special characters in the connection string with double quotes to avoid interpretation by Azure CLI
$connectionString = $connectionString -replace $Pattern, '"$1"'
Write-Output "Injecting CosmosDB for PSQL (citus) connection string into the Azure App Service configuration..."
# Inject the connection string into the Azure App Service configuration as normal key value pair
#az webapp config appsettings set --name $WEB_APP_NAME --resource-group $AZURE_RESOURCE_GROUP --settings COSMOSDB_CONNECTION_STRING=$connectionString --subscription $AZURE_SUBSCRIPTION_ID
# Inject the connection string into the Azure App Service configuration https://learn.microsoft.com/cli/azure/webapp/config/connection-string?view=azure-cli-latest
az webapp config connection-string set --resource-group $env:AZURE_RESOURCE_GROUP --name $env:WEB_APP_NAME -t postgresql --settings psql1=$connectionString --subscription $env:AZURE_SUBSCRIPTION_ID
if ($env:USE_EntraIDAuthentication -eq "false") {
Write-Output "Skipping app registration creation because USE_EntraIDAuthentication is set to false"
Write-Output "Done."
exit 0
} else {
#Ensure Authv2 extension is installed. See https://learn.microsoft.com/en-us/cli/azure/webapp/auth/microsoft?view=azure-cli-latest
az config set extension.use_dynamic_install=yes_without_prompt
#Calculate redirect uri for web app using Entra ID authentication
$redirectUri = "https://${env:WEB_APP_NAME}.azurewebsites.net/.auth/login/aad/callback"
# In case web app shall be authorized to call SAP OData api via Azure APIM
$APIM_API_SCOPES_JSON_ROOT = @()
$APIM_API_SCOPES_JSON_LIST = @()
$APIM_API_METADATA = $null
if($env:AZURE_APIM_APP_ID){
Write-Output "Reading Azure APIM app registration metadata..."
#Read scopes array from Azure APIM app registration
$APIM_API_METADATA = az ad app show --id $env:AZURE_APIM_APP_ID | ConvertFrom-Json
if (!$APIM_API_METADATA) {
Write-Error "Error reading Azure APIM app registration metadata. Ensure AZURE_APIM_APP_ID is set correctly. Trace: $_"
exit 1
}
#See the JSON structure of 'oauth2PermissionScopes' etc via: az ad app show --id <entra-id-app-id>
$APIM_API_SCOPES = $APIM_API_METADATA.api.oauth2PermissionScopes
#Loop $APIM_API_SCOPES and create a json array of json objects for each scope
foreach($scope in $APIM_API_SCOPES){
$APIM_API_SCOPES_JSON_LIST += @{
id = $scope.id
type = "Scope"
}
}
$APIM_API_SCOPES_JSON_ROOT += @{
resourceAppId = $env:AZURE_APIM_APP_ID
resourceAccess = $APIM_API_SCOPES_JSON_LIST
}
#Always add standard Microsoft Graph User.Read scope to the array
$APIM_API_SCOPES_JSON_ROOT += @{
resourceAppId = "00000003-0000-0000-c000-000000000000"
resourceAccess = @(
@{
id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
type = "Scope"
}
)
}
}
else{
Write-Output "Skipping web app authorization to Azure APIM app registration because AZURE_APIM_APP_ID is not set"
}
#Compose aad app registration config supporting AUTH v2 https://learn.microsoft.com/graph/api/application-post-applications?view=graph-rest-1.0&tabs=http#response-1
#Access token version 2 is required
$appRegistrationConfig = (@{
displayName = $env:WEB_APP_NAME
signInAudience = "AzureADMyOrg"
api = @{
requestedAccessTokenVersion = 2
}
requiredResourceAccess = $APIM_API_SCOPES_JSON_ROOT
web = @{
redirectUris = @($redirectUri)
implicitGrantSettings = @{
enableIdTokenIssuance = "true"
}
}
} | ToJsonWithPlatformEscaping)
Write-Output "Creating web app Entra ID app registration..."
$APP_REG_METADATA = az rest --method POST `
--headers "{'Content-Type':'application/json'}" `
--uri "https://graph.microsoft.com/v1.0/applications" `
--body $appRegistrationConfig | ConvertFrom-Json
if(!$APP_REG_METADATA){
Write-Error "Error creating web app Entra ID app registration for your web app. Trace: $_"
exit 1
}
$OBJECT_ID = $APP_REG_METADATA.id
$CLIENT_ID = $APP_REG_METADATA.appId
Write-Output "Generating secret for app registration..."
# Create secret for app registration
$NEW_CREDENTIAL = az ad app credential reset --id $OBJECT_ID `
--append `
--display-name 'Generated by AZD' | ConvertFrom-Json
if(!$NEW_CREDENTIAL){
Write-Error "Error generating secret for app registration. Trace: $_"
exit 1
}
$SECRET = $NEW_CREDENTIAL.password
Write-Output "Storing app registration secret in key vault..."
#Store secret in key vault
$STORE_CMD = az keyvault secret set --vault-name $env:AZURE_KEY_VAULT_NAME `
--name $env:AAD_KV_SECRET_NAME `
--value $SECRET | ConvertFrom-Json
if(!$STORE_CMD){
Write-Error "Error storing app registration secret in key vault. Trace: $_"
exit 1
}
Write-Output "Adding Authentication settings to Azure App Service..."
$audiences = "api://${CLIENT_ID}" #comma delimited list of audiences
$ISSUER_URL = "https://login.microsoftonline.com/${env:AZURE_TENANT_ID}/v2.0"
$authSettingsJson = (@{
globalValidation = @{
redirectToProvider = "azureActiveDirectory"
requireAuthentication = "true"
unauthenticatedClientAction = "RedirectToLoginPage"
}
identityProviders = @{
azureActiveDirectory = @{
enabled = "true"
registration = @{
clientId = $CLIENT_ID
clientSecretSettingName = 'AADAPPSETTINGSECRET'
openIdIssuer = $ISSUER_URL
}
validation = @{
allowedAudiences = @($audiences)
}
}
}
login = @{
tokenStore = @{
enabled = "true"
}
}
platform = @{
enabled = "true"
}
} | ToJsonWithPlatformEscaping)
Write-Output "Setting the authentication settings for the webapp in the v2 format, overwriting any existing settings..."
#https://learn.microsoft.com/cli/azure/webapp/auth/config-version?view=azure-cli-latest#az-webapp-auth-config-version-upgrade
#https://learn.microsoft.com/cli/azure/webapp/auth?view=azure-cli-latest#az-webapp-auth-set
$AUTH_SET_CMD = az webapp auth set --name $env:WEB_APP_NAME `
--resource-group $env:AZURE_RESOURCE_GROUP `
--subscription $env:AZURE_SUBSCRIPTION_ID `
--body $authSettingsJson | ConvertFrom-Json
if(!$AUTH_SET_CMD){
Write-Error "Error setting the authentication settings for the webapp in the v2 format. Trace: $_"
exit 1
}
# In case web app shall be authorized to call SAP OData api via Azure APIM
if($env:AZURE_APIM_APP_ID){
Write-Output "Adding web app registration as pre-authorized client to the Azure APIM app registration..."
#Compose PATCH request to add the web app as pre-authorized client to the Azure APIM app registration
$delegatedPermissions = @()
foreach($scope in $APIM_API_SCOPES){
#Skip the Microsoft Graph User.Read scope
if($scope.id -eq "e1fe6dd8-ba31-4d61-89e7-88639da4683d"){
continue
}
#Add ids of Azure APIM exposed API scopes to delegate permission to the web app and pre-authorize
$delegatedPermissions += @($scope.id)
}
$APIM_API_METADATA.api.preAuthorizedApplications += @(
@{
appId = $CLIENT_ID
delegatedPermissionIds = $delegatedPermissions
}
)
#Write-Output $APIM_API_METADATA.api
$preAuthorizedWebAppsSection = (@{
api = @{
preAuthorizedApplications = $APIM_API_METADATA.api.preAuthorizedApplications
}
} | ToJsonWithPlatformEscaping)
#Inject the azure web app as pre-authorized client to the Azure APIM app registration using object id
$AZURE_APIM_APP_METADATA = az ad app show --id $env:AZURE_APIM_APP_ID | ConvertFrom-Json
$APIM_APP_OBJECT_ID = $AZURE_APIM_APP_METADATA.id
$URI = "https://graph.microsoft.com/v1.0/applications/${APIM_APP_OBJECT_ID}"
Write-Output $URI
az rest --method PATCH `
--headers "{'Content-Type':'application/json'}" `
--uri $URI `
--body $preAuthorizedWebAppsSection
Write-Output "Asynch App registration finished. Check log for status code 204. Consider --debug flag. Trace: $_"
}else{
Write-Output "Skipping pre-authorizing web app on Azure APIM app registration because AZURE_APIM_APP_ID is not set"
}
Write-Output "done"
# all done
exit 0
}
} catch {
Write-Error "An error occurred: $_"
exit 1
}