Tushar Dhadiwal 58b2a414a7
Update frontend deployment script - managed identity fix (#179)
Co-authored-by: Gabriel Nieves-Ponce <39567323+nievespg1@users.noreply.github.com>
2024-10-01 18:44:48 -04:00

269 lines
11 KiB
Bash
Executable File

#!/bin/bash
set -eu # use set -eux for debugging
function load_env_variables() {
set -a
source .env
set +a
}
function checkRequiredParams () {
requiredParams=(
LOCATION
RESOURCE_GROUP
SUBSCRIPTION_ID
AAD_CLIENT_ID
AAD_OBJECT_ID
AAD_TENANT_ID
)
local paramsFile=$1
for param in "${requiredParams[@]}"; do
local paramValue=$(jq -r .$param < $paramsFile)
if [ "null" == "$paramValue" ] || [ -z "$paramValue" ]; then
echo "Parameter $param is required, exiting..."
exit 1
fi
done
}
function populateRequiredParams () {
local paramsFile=$1
printf "Checking required parameters... "
checkRequiredParams $paramsFile
# The jq command below sets environment variables based on the key-value pairs in a JSON-formatted file
eval $(jq -r 'to_entries | .[] | "export \(.key)=\(.value)"' $paramsFile)
printf "Done.\n"
}
function set_variables() {
printf "Setting environment variables...\n"
SUBSCRIPTION_ID=${SUBSCRIPTION_ID:-""}
RESOURCE_GROUP=${RESOURCE_GROUP:-""}
LOCATION=${LOCATION:-""}
AAD_CLIENT_ID=${AAD_CLIENT_ID:-""}
AAD_OBJECT_ID=${AAD_OBJECT_ID:-""}
AAD_TENANT_ID=${AAD_TENANT_ID:-""}
AAD_TOKEN_ISSUER_URL=${AAD_TOKEN_ISSUER_URL:-"https://login.microsoftonline.com/$AAD_TENANT_ID/v2.0"}
IMAGE_NAME=${IMAGE_NAME:-"graphrag:frontend"}
REGISTRY_NAME=${REGISTRY_NAME:-"${RESOURCE_GROUP}reg"}
APP_SERVICE_PLAN=${APP_SERVICE_PLAN:-"${RESOURCE_GROUP}-asp"}
WEB_APP=${WEB_APP:-"${RESOURCE_GROUP}-playground"}
WEB_APP_IDENTITY=${WEB_APP_IDENTITY:-"${WEB_APP}-identity"}
#BACKEND_RESOURCE_GROUP=${BACKEND_RESOURCE_GROUP:-""} # needed for backend outbound vnet integration
printf "Done setting environment variables.\n"
}
function create_resource_group {
printf "Setting subsctiption to $SUBSCRIPTION_ID and Creating resource group...\n"
az account set --subscription $SUBSCRIPTION_ID > /dev/null
az group create --name $RESOURCE_GROUP --location $LOCATION > /dev/null
printf "Resource group created.\n"
}
function create_acr() {
printf "Creating Azure Container Registry...\n"
az acr create --resource-group $RESOURCE_GROUP \
--name $REGISTRY_NAME \
--sku Basic \
--admin-enabled false > /dev/null
printf "Azure Container Registry created.\n"
}
function build_and_push_image() {
printf "Building and pushing image...\n"
local SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
az acr build --registry $REGISTRY_NAME -f $SCRIPT_DIR/../docker/Dockerfile-frontend --image $IMAGE_NAME $SCRIPT_DIR/../
printf "Image built and pushed.\n"
}
function create_app_service_plan() {
printf "Creating app service plan...\n"
az appservice plan create --name $APP_SERVICE_PLAN \
--resource-group $RESOURCE_GROUP \
--sku B3 \
--is-linux > /dev/null
printf "App service plan created.\n"
}
function create_web_app_identity() {
printf "Creating web app identity...\n"
IDENTITY_RESULT=$(az identity create --resource-group $RESOURCE_GROUP --name $WEB_APP_IDENTITY --output json)
WEBAPP_IDENTITY_ID=$(jq -r .id <<< $IDENTITY_RESULT)
WEBAPP_IDENTITY_OBJECT_ID=$(jq -r .principalId <<< $IDENTITY_RESULT)
WEBAPP_IDENTITY_CLIENT_ID=$(jq -r .clientId <<< $IDENTITY_RESULT)
printf "Web app identity created.\n"
}
function configure_registry_credentials() {
printf "Configuring registry credentials...\n"
ACR_ID=$(az acr show --name $REGISTRY_NAME --resource-group $RESOURCE_GROUP --query id --output tsv)
az role assignment create --assignee $WEBAPP_IDENTITY_CLIENT_ID \
--role AcrPull \
--scope $ACR_ID > /dev/null
printf "Registry credentials configured.\n"
}
function create_web_app() {
printf "Creating web app...\n"
az webapp create --resource-group $RESOURCE_GROUP \
--plan $APP_SERVICE_PLAN \
--name $WEB_APP \
--assign-identity $WEBAPP_IDENTITY_ID \
--acr-use-identity \
--acr-identity $WEBAPP_IDENTITY_ID \
--https-only true \
--container-image-name $REGISTRY_NAME.azurecr.io/$IMAGE_NAME > /dev/null
printf "Web app created.\n"
}
function configure_app_settings() {
printf "Configuring app settings...\n"
APP_SETTINGS=""
while IFS='=' read -r name value
do
value="${value%\"}" # Remove opening quote
value="${value#\"}" # Remove closing quote
APP_SETTINGS="$APP_SETTINGS $name=$value"
done < .env
# echo $APP_SETTINGS
az webapp config appsettings set --name $WEB_APP \
--resource-group $RESOURCE_GROUP \
--settings $APP_SETTINGS > /dev/null
printf "App settings configured.\n"
}
function create_federated_identity_credentials() {
printf "Creating federated identity credentials...\n"
EXISTING_CREDENTIAL_SUBJECTS=$(az rest --method GET --uri "https://graph.microsoft.com/beta/applications/$AAD_OBJECT_ID/federatedIdentityCredentials" -o json | jq -r '.value[].subject')
if [[ "$EXISTING_CREDENTIAL_SUBJECTS" == *"$WEBAPP_IDENTITY_OBJECT_ID"* ]]; then
echo "Federated identity credential already exists for the subject: $WEBAPP_IDENTITY_OBJECT_ID"
else
az webapp auth update \
--name $WEB_APP \
--resource-group $RESOURCE_GROUP \
--enabled true \
--action LoginWithAzureActiveDirectory \
--aad-client-id $AAD_CLIENT_ID \
--aad-token-issuer-url $AAD_TOKEN_ISSUER_URL > /dev/null
az rest --method POST \
--uri "https://graph.microsoft.com/beta/applications/$AAD_OBJECT_ID/federatedIdentityCredentials" \
--body "{'name': '$WEB_APP', 'issuer': '$AAD_TOKEN_ISSUER_URL', 'subject': '$WEBAPP_IDENTITY_OBJECT_ID', 'audiences': [ 'api://AzureADTokenExchange' ]}" > /dev/null
fi
printf "Federated identity credentials created.\n"
}
function configure_auth_settings() {
printf "Configuring auth settings...\n"
az webapp config appsettings set --resource-group $RESOURCE_GROUP \
--name $WEB_APP \
--slot-settings OVERRIDE_USE_MI_FIC_ASSERTION_CLIENTID=$WEBAPP_IDENTITY_CLIENT_ID \
--verbose > /dev/null
az webapp config appsettings list --resource-group $RESOURCE_GROUP \
--name $WEB_APP > /dev/null
authSettings=$(az rest --method GET --url "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/sites/$WEB_APP/config/authsettingsV2/list?api-version=2020-12-01" --output json)
echo $authSettings > auth.json
jq '.properties.identityProviders.azureActiveDirectory.registration.clientSecretSettingName = "OVERRIDE_USE_MI_FIC_ASSERTION_CLIENTID"' auth.json > tmp.json && mv tmp.json auth.json # pragma: allowlist secret
az rest --method PUT \
--url "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/sites/$WEB_APP/config/authsettingsV2?api-version=2020-12-01" \
--body @auth.json \
--headers "Content-Type=application/json" > /dev/null
rm auth.json
printf "Auth settings configured.\n"
}
function update_appreg_redirect_uris() {
printf "Updating app registration redirect URIs...\n"
WEB_APP_URL=$(az webapp show --name $WEB_APP --resource-group $RESOURCE_GROUP --query defaultHostName --output tsv)
NEW_REDIRECT_URI=https://$WEB_APP_URL/.auth/login/aad/callback
# Fetch the current list of web redirect URIs
CURRENT_URIS=$(az ad app show --id $AAD_CLIENT_ID --query "web.redirectUris" --output tsv)
if ! echo "${CURRENT_URIS}" | grep -q "${NEW_REDIRECT_URI}"; then
az ad app update --id $AAD_CLIENT_ID --web-redirect-uris ${CURRENT_URIS[@]} "$NEW_REDIRECT_URI" > /dev/null
fi
printf "App registration redirect URIs updated.\n"
}
function restart_web_app() {
printf "Restarting web app...\n"
az webapp restart --name $WEB_APP --resource-group $RESOURCE_GROUP > /dev/null
printf "Waiting for webapp to restart, webapp might take a few minutes to load.....\n"
sleep 180
printf "Web app restarted. \n"
}
## The following function adds outbound vnet integration on the webapp so that the frontend container can access resources in the AKS cluster directly.
## This will create a new subnet named "frontend" in the backend resource group's vnet if it does not exist.
## This may not be needed in simplified backend architecture but useful for folks using a prior version of the accelerator that had a different network architecture.
# function add_vnet_integration() {
# VNET_NAME=$(az network vnet list --resource-group $BACKEND_RESOURCE_GROUP --query "[0].name" --output tsv)
# VNET_ID=$(az network vnet list --resource-group $BACKEND_RESOURCE_GROUP --query "[0].id" --output tsv)
# SUBNET_NAMES=$(az network vnet subnet list --resource-group $BACKEND_RESOURCE_GROUP --vnet-name $VNET_NAME --query "[].name" --output tsv)
# SUBNET_NAME="frontend"
# if [[ $SUBNET_NAMES == *$SUBNET_NAME* ]]; then
# echo "Subnet with name $SUBNET_NAME already exists"
# else
# echo "Subnet with name $SUBNET_NAME does not exist, creating one now."
# az network vnet subnet create --resource-group $BACKEND_RESOURCE_GROUP --vnet-name $VNET_NAME --name $SUBNET_NAME --address-prefixes 10.0.10.0/24
# fi
# az webapp vnet-integration add --name $WEB_APP --resource-group $RESOURCE_GROUP --vnet $VNET_ID --subnet $SUBNET_NAME
# }
function usage() {
echo
echo "Usage: bash $0 [-h] -p <frontend_deploy.parameters.json>"
echo "Description: Deployment script for the Frontend App for GraphRAG Solution Accelerator."
echo "options:"
echo " -h Print this help menu."
echo " -p A JSON file containing the deployment parameters (frontend_deploy.parameters.json)."
echo
}
function main() {
load_env_variables
populateRequiredParams $PARAMS_FILE
set_variables
create_resource_group
create_acr
build_and_push_image
create_app_service_plan
create_web_app_identity
configure_registry_credentials
create_web_app
configure_app_settings
create_federated_identity_credentials
configure_auth_settings
# add_vnet_integration
update_appreg_redirect_uris
restart_web_app
echo "**********Graphrag Frontend Web app deployment successful!**********"
echo "Please visit the webapp at https://$WEB_APP_URL"
echo "*******************************************************************"
}
# print usage if no arguments are supplied
[ $# -eq 0 ] && usage && exit 0
PARAMS_FILE=""
while getopts ":p:h" option; do
case "${option}" in
p)
PARAMS_FILE=${OPTARG}
;;
h | *)
usage
exit 0
;;
esac
done
shift $((OPTIND-1))
# check if required arguments are supplied
if [ ! -f $PARAMS_FILE ]; then
echo "Error: invalid required argument."
usage
exit 1
fi
main