diff --git a/scripts/add_reply_url_for_cluster.sh b/scripts/add_reply_url_for_cluster.sh index aeb83cce9..d7509f02d 100755 --- a/scripts/add_reply_url_for_cluster.sh +++ b/scripts/add_reply_url_for_cluster.sh @@ -7,8 +7,6 @@ # Example 1: # AAD_APP_NAME="Omnia Radix Web Console" K8S_NAMESPACE="radix-web-console-prod" K8S_INGRESS_NAME="web" REPLY_PATH="/auth-callback" WEB_REDIRECT_URI="/applications" ./add_reply_url_for_cluster.sh # -# Example 2: Using a subshell to avoid polluting parent shell -# (AAD_APP_NAME="ar-radix-grafana-development" K8S_NAMESPACE="default" K8S_INGRESS_NAME="grafana" REPLY_PATH="/login/generic_oauth" ./add_reply_url_for_cluster.sh) # INPUTS: # AAD_APP_NAME (Mandatory) diff --git a/scripts/radix-zone/monitoring-infrastructure/bootstrap.sh b/scripts/radix-zone/monitoring-infrastructure/bootstrap.sh deleted file mode 100644 index 278b87a29..000000000 --- a/scripts/radix-zone/monitoring-infrastructure/bootstrap.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env bash - -####################################################################################### -### PURPOSE -### - -# Bootstrap radix zone infrastructure for monitoring, resource gorups, keyvault etc - - -####################################################################################### -### INPUTS -### - -# Required: -# - RADIX_ZONE_ENV : Path to *.env file - -# Optional: -# - USER_PROMPT : Is human interaction is required to run script? true/false. Default is true. - - -####################################################################################### -### HOW TO USE -### - -# ./bootstrap.sh env - - -####################################################################################### -### START -### - -echo "" -echo "Start bootstrap of Monitoring infrastructure.. " - - -####################################################################################### -### Check for prerequisites binaries -### - -echo "" -echo "Check for neccesary executables... " -hash az 2> /dev/null || { echo -e "\nERROR: Azure-CLI not found in PATH. Exiting... " >&2; exit 1; } -echo "Done.\n" - - -####################################################################################### -### Read inputs and configs -### -RADIX_ZONE_ENV="../radix_zone_$1.env" - -if [[ -z "$RADIX_ZONE_ENV" ]]; then - echo "ERROR: Please provide RADIX_ZONE_ENV" >&2 - exit 1 -else - if [[ ! -f "$RADIX_ZONE_ENV" ]]; then - echo "ERROR: RADIX_ZONE_ENV=$RADIX_ZONE_ENV is invalid, the file does not exist." >&2 - exit 1 - fi - source "$RADIX_ZONE_ENV" -fi - -if [[ -z "$USER_PROMPT" ]]; then - USER_PROMPT=true -fi - - -####################################################################################### -### Prepare az session -### - -echo "Logging you in to Azure if not already logged in... " -az account show >/dev/null || az login >/dev/null -az account set --subscription "$AZ_SUBSCRIPTION_ID" >/dev/null -echo "Done.\n" - - -cat << EOF -Will use the following configuration: - ------------------------------------------------------------------ - - RADIX_ZONE : $RADIX_ZONE - - AZ_RADIX_ZONE_LOCATION : $AZ_RADIX_ZONE_LOCATION - - RADIX_ENVIRONMENT : $RADIX_ENVIRONMENT - - ------------------------------------------------------------------- - - AZ_RESOURCE_GROUP_MONITORING : $AZ_RESOURCE_GROUP_MONITORING - - KEYVAULT : $AZ_RESOURCE_MON_KEYVAULT - - AD DEV GROUP : $AZ_AD_DEV_GROUP - - AD OPS GROUP : $AZ_AD_OPS_GROUP - ------------------------------------------------------------------- - - AZ_SUBSCRIPTION : $(az account show --query name -otsv) - - AZ_USER : $(az account show --query user.name -o tsv) -EOF - - -if [[ $USER_PROMPT == true ]]; then - while true; do - read -p "Is this correct? (Y/n) " yn - case $yn in - [Yy]* ) break;; - [Nn]* ) echo ""; echo "Quitting."; exit 0;; - * ) echo "Please answer yes or no.";; - esac - done -fi - - -### Resource groups -### - -function create_resource_groups(){ - echo "Creating resource group..." - az group create --location "${AZ_RADIX_ZONE_LOCATION}" --name "${AZ_RESOURCE_GROUP_MONITORING}" --subscription "${AZ_SUBSCRIPTION_ID}" --output none -} - -## Create Grafana database - -function create_grafana_mysql(){ - -# TODO -# See https://github.com/equinor/radix-grafana - -} - -## Create Keyvault - -function create_keyvault(){ - - echo "Creating key vault: ${AZ_RESOURCE_MON_KEYVAULT}..." - az keyvault create \ - --name "${AZ_RESOURCE_MON_KEYVAULT}" \ - --resource-group "${AZ_RESOURCE_GROUP_MONITORING}" \ - --subscription "${AZ_SUBSCRIPTION_ID}" \ - --enable-purge-protection \ - --only-show-errors - echo "...Done" - - echo "Set access policy for group Radix Platform Operators in key vault: ${AZ_RESOURCE_KEYVAULT}..." - az keyvault set-policy \ - --object-id "$(az ad group show --group "${AZ_AD_OPS_GROUP}" --query id --output tsv --only-show-errors)" \ - --name "${AZ_RESOURCE_MON_KEYVAULT}" \ - --resource-group "${AZ_RESOURCE_GROUP_MONITORING}" \ - --subscription "${AZ_SUBSCRIPTION_ID}" \ - --certificate-permissions get list update create import delete recover backup restore managecontacts manageissuers getissuers listissuers setissuers deleteissuers \ - --key-permissions get list update create import delete recover backup restore \ - --secret-permissions get list set delete recover backup restore \ - --storage-permissions - echo "...Done" - - echo "Set access policy for group Radix Platform Developers in key vault: ${AZ_RESOURCE_KEYVAULT}..." - - az keyvault set-policy \ - --object-id "$(az ad group show --group "${AZ_AD_DEV_GROUP}" --query id --output tsv --only-show-errors)" \ - --name "${AZ_RESOURCE_MON_KEYVAULT}" \ - --resource-group "${AZ_RESOURCE_GROUP_MONITORING}" \ - --subscription "${AZ_SUBSCRIPTION_ID}" \ - --certificate-permissions get list \ - --key-permissions get list \ - --secret-permissions get list \ - --storage-permissions \ - --only-show-errors - echo "...Done" -} - - -create_resource_groups -create_keyvault -create_monitoring_service_principal "$APP_REGISTRATION_GRAFANA" "Grafana Oauth, main app for user authentication to Grafana" -create_monitoring_ar_secret "$APP_REGISTRATION_GRAFANA" "$APP_REGISTRATION_GRAFANA" "Grafana OAuth secret" -create_grafana_mysql - -echo "" -echo "Bootstrap done!" diff --git a/scripts/radix-zone/monitoring-infrastructure/create-sp.sh b/scripts/radix-zone/monitoring-infrastructure/create-sp.sh index d8d252975..0c970c5e9 100644 --- a/scripts/radix-zone/monitoring-infrastructure/create-sp.sh +++ b/scripts/radix-zone/monitoring-infrastructure/create-sp.sh @@ -1,30 +1,12 @@ #!/usr/bin/env bash -####################################################################################### -### PURPOSE -### - -# Bootstrap radix zone infrastructure for monitoring, resource gorups, keyvault etc - - -####################################################################################### -### INPUTS -### - -# Required: -# - RADIX_ZONE_ENV : Path to *.env file - -# Optional: -# - USER_PROMPT : Is human interaction is required to run script? true/false. Default is true. - - ####################################################################################### ### HOW TO USE ### # sh ./create-sp.sh 'env' -echo "Start bootstrap of Monitoring infrastructure.. " +echo "Create Grafana Service Principal " RADIX_ZONE_ENV="../radix_zone_$1.env" ####################################################################################### @@ -41,6 +23,18 @@ printf "Done.\n" ### Read inputs and configs ### + +if [[ $1 == "ext-mon" ]]; then + APP_REGISTRATION="radix-ar-grafana-ext-mon" + KEYVAULT="radix-keyv-extmon" +else + APP_REGISTRATION="radix-ar-grafana-$1" + KEYVAULT="radix-keyv-$1" +fi + +SECRETNAME="radix-ar-grafana-oauth" + + if [[ -z "$RADIX_ZONE_ENV" ]]; then echo "ERROR: Please provide RADIX_ZONE_ENV" >&2 exit 1 @@ -63,19 +57,14 @@ fi printf "Logging you in to Azure if not already logged in... " az account show >/dev/null || az login >/dev/null -az account set --subscription "$AZ_SUBSCRIPTION_ID" >/dev/null printf "Done.\n" - - cat << EOF Will use the following configuration: ------------------------------------------------------------------ - RADIX_ZONE : $RADIX_ZONE - - AZ_RADIX_ZONE_LOCATION : $AZ_RADIX_ZONE_LOCATION - - RADIX_ENVIRONMENT : $RADIX_ENVIRONMENT - - APP_REGISTRATION_GRAFANA : $APP_REGISTRATION_GRAFANA + - APP_REGISTRATION_GRAFANA : $APP_REGISTRATION ------------------------------------------------------------------- - AZ_SUBSCRIPTION : $(az account show --query name -otsv) @@ -101,68 +90,43 @@ else source "$LIB_SERVICE_PRINCIPAL_PATH" fi -function create_monitoring_service_principal() { - - local name # Input 1 - local description # Input 2, optional - local password - local id - - name="$1" - description="$2" - - echo "Working on ${name}: Creating service principal..." - - # Skip creation if the sp exist - local testSP - testSP="$(az ad sp list --display-name "${name}" --query [].id --output tsv 2>/dev/null)" - if [ -z "$testSP" ]; then - echo "creating ${name}..." - password="$(az ad sp create-for-rbac --name "${name}" --query password --output tsv)" - id="$(az ad sp list --display-name "${name}" --query [].id --output tsv)" - secret="$(az ad sp credential list --id "${id}" --query "sort_by([?displayName=='rbac'], &endDateTime)[-1:].{endDateTime:endDateTime,keyId:keyId}")" - secret_id="$(echo "${secret}" | jq -r .[].keyId)" - expiration_date="$(echo "${secret}" | jq -r .[].endDateTime | sed 's/\..*//')" - echo " Done.\n" - - echo "Update credentials in keyvault..." - update_app_credentials_in_az_keyvault "${name}" "${id}" "${password}" "${description}" "${secret_id}" "${expiration_date}" "${AZ_RESOURCE_MON_KEYVAULT}" - else - echo "${name} exists.\n" - fi - echo "Update owners of app registration...." - update_ad_app_owners "${name}" +name=$APP_REGISTRATION +description="Grafana Oauth, main app for user authentication to Grafana" - echo "Update owners of service principal..." - update_service_principal_owners "${name}" +# Skip creation if the sp exist - echo "Update additional SP info..." +testSP="$(az ad sp list --display-name "${name}" --query [].id --output tsv 2>/dev/null)" +if [ -z "$testSP" ]; then + echo "$testSP, ${name} does not exist" + + password="$(az ad sp create-for-rbac --name "${name}" --query password --output tsv)" id="$(az ad sp list --display-name "${name}" --query [].id --output tsv)" - echo "This id ${id} and description: ${description}" - az ad sp update --id "${id}" --set notes="${description}" + password="$(az ad sp credential reset --id "${id}" --display-name "${SECRETNAME}" --append --query password --output tsv --only-show-errors)" + secret="$(az ad sp credential list --id "${id}" --query "sort_by([?displayName=='${SECRETNAME}'], &endDateTime)[-1:].{endDateTime:endDateTime,keyId:keyId}")" + expiration_date="$(echo "${secret}" | jq -r .[].endDateTime | sed 's/\..*//')" + + echo "Update credentials in keyvault for appId $id, $secret with exp. date $expiration_date" + az keyvault secret set --vault-name $KEYVAULT --name $SECRETNAME --value "${password}" --expires ${expiration_date} 2>&1 >/dev/null +else + id=$testSP + echo "${name} exists.\n" +fi - echo "Done." -} +echo "Update additional info, $name - $description" +id="$(az ad sp list --display-name "${name}" --query [].id --output tsv)" + +echo "Update description" +az ad sp update --id "${id}" --set notes="${description}" + +echo "Update owners of app registration...." +update_ad_app_owners "${name}" + +echo "Update owners of service principal..." +update_service_principal_owners "${name}" + + +echo "Done." -function create_monitoring_ar_secret(){ - local name # Input 1 - local secretname # Input 2 - local description # Input 3, optional - name="$1" - secretname="$2" - description="$3" - - echo "Create secret for ${name}" - id="$(az ad app list --filter "displayname eq '${name}'" --query [].id --output tsv)" - - password="$(az ad app credential reset --id "${id}" --display-name "${secretname}" --append --query password --output tsv --only-show-errors)" - secret="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${secretname}'], &endDateTime)[-1].{endDateTime:endDateTime,keyId:keyId}")" - secret_id="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${secretname}'], &endDateTime)[-1].keyId")" - expiration_date="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${secretname}'], &endDateTime)[-1].endDateTime" --output tsv)" - - echo "Update credentials in keyvault..." - update_app_credentials_in_az_keyvault "${secretname}" "${id}" "${password}" "${description}" "${secret_id}" ${expiration_date} "${AZ_RESOURCE_MON_KEYVAULT}" -} diff --git a/scripts/radix-zone/monitoring-infrastructure/refresh-secret.sh b/scripts/radix-zone/monitoring-infrastructure/refresh-secret.sh index cb7c81e2e..fb021f0c9 100644 --- a/scripts/radix-zone/monitoring-infrastructure/refresh-secret.sh +++ b/scripts/radix-zone/monitoring-infrastructure/refresh-secret.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# sh ./create-sp.sh 'env' +# sh ./refresh-secret.sh 'env' if [[ -z "$USER_PROMPT" ]]; then USER_PROMPT=true @@ -15,14 +15,6 @@ printf "Check for neccesary executables... " hash az 2> /dev/null || { echo -e "\nERROR: Azure-CLI not found in PATH. Exiting... " >&2; exit 1; } printf "Done.\n" -# tenantId="$(az ad app show --id ${id} --query appOwnerOrganizationId --output tsv)" -script_dir_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -template_path="${script_dir_path}/template-credentials.json" - -if [ ! -e "$template_path" ]; then - echo "Error in func \"update_service_principal_credentials_in_az_keyvault\": sp credentials template not found at ${template_path}" >&2 - exit 1 -fi if [[ $1 == "ext-mon" ]]; then APP_REGISTRATION="radix-ar-grafana-ext-mon" @@ -32,16 +24,18 @@ else KEYVAULT="radix-keyv-$1" fi +SECRETNAME="radix-ar-grafana-oauth" cat << EOF Will use the following configuration: ------------------------------------------------------------------- - - APP REGISTRATION : $APP_REGISTRATION - - KEYVAULT : $KEYVAULT + - APP Registration : $APP_REGISTRATION + - Key Vault : $KEYVAULT + - Secret : $SECRETNAME ------------------------------------------------------------------- - - AZ_SUBSCRIPTION : $(az account show --query name -otsv) - - AZ_USER : $(az account show --query user.name -o tsv) + - AZ subscription : $(az account show --query name -otsv) + - AZ user : $(az account show --query user.name -o tsv) EOF @@ -63,36 +57,18 @@ fi printf "Logging you in to Azure if not already logged in... " az account show >/dev/null || az login >/dev/null -name="$APP_REGISTRATION" -secretname="$name" -description="Grafana OAuth secret" - -echo "Create secret for ${name}" -id="$(az ad app list --filter "displayname eq '${name}'" --query [].id --output tsv)" +appname="$APP_REGISTRATION" -password="$(az ad app credential reset --id "${id}" --display-name "${secretname}" --append --query password --output tsv --only-show-errors)" -secret="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${secretname}'], &endDateTime)[-1].{endDateTime:endDateTime,keyId:keyId}")" -secret_id="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${secretname}'], &endDateTime)[-1].keyId")" -expiration_date="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${secretname}'], &endDateTime)[-1].endDateTime" --output tsv)" - -# update_app_credentials_in_az_keyvault "${secretname}" "${id}" "${password}" "${description}" "${secret_id}" ${expiration_date} "${KEYVAULT}" +echo "Create secret for ${appname}" +id="$(az ad app list --filter "displayname eq '${appname}'" --query [].id --output tsv)" -# Use jq together with a credentials json template to ensure we end up with valid json, and then put the result into a tmp file which we will upload to the keyvault. -tmp_file_path="${script_dir_path}/${secretname}.json" -cat "$template_path" | jq -r \ - --arg name "${secretname}" \ - --arg id "${id}" \ - --arg password "${password}" \ - --arg description "${description}" \ - --arg tenantId "" \ - --arg secretId "${secret_id}" \ - '.name=$name | .id=$id | .password=$password | .description=$description | .tenantId=$tenantId | .secretId=$secretId' >"$tmp_file_path" +password="$(az ad app credential reset --id "${id}" --display-name "${SECRETNAME}" --append --query password --output tsv --only-show-errors)" +expiration_date="$(az ad app credential list --id "${id}" --query "sort_by([?displayName=='${SECRETNAME}'], &endDateTime)[-1].endDateTime" --output tsv)" +# update_app_credentials_in_az_keyvault echo "Update credentials in keyvault..." -az keyvault secret set --vault-name $KEYVAULT --name "${secretname}" --file "${tmp_file_path}" --expires ${expiration_date} 2>&1 >/dev/null -# Clean up -rm -rf "$tmp_file_path" +az keyvault secret set --vault-name $KEYVAULT --name $SECRETNAME --value "${password}" --expires ${expiration_date} 2>&1 >/dev/null echo "Client secret refreshed and stored in Keyvault" diff --git a/scripts/radix-zone/monitoring-infrastructure/template-credentials.json b/scripts/radix-zone/monitoring-infrastructure/template-credentials.json deleted file mode 100644 index 36d1b0a17..000000000 --- a/scripts/radix-zone/monitoring-infrastructure/template-credentials.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name":"", - "id":"", - "password":"", - "description":"", - "tenantId": "", - "secretId": "" -} diff --git a/scripts/radix-zone/radix_zone_c2.env b/scripts/radix-zone/radix_zone_c2.env index 97e53355c..321e9dd33 100644 --- a/scripts/radix-zone/radix_zone_c2.env +++ b/scripts/radix-zone/radix_zone_c2.env @@ -100,7 +100,6 @@ AZ_SYSTEM_USER_APP_REGISTRY_SECRET_KEY="radix-app-registry-secret" AZ_SYSTEM_USER_APP_REGISTRY_USERNAME="radix-app-registry-secret-${RADIX_ZONE}" # App registrations -APP_REGISTRATION_GRAFANA="ar-radix-grafana-${RADIX_ZONE}-${RADIX_ENVIRONMENT}" APP_REGISTRATION_NETWORKPOLICY_CANARY="ar-radix-networkpolicy-canary" APP_REGISTRATION_GITHUB_MAINTENANCE="ar-radix-platform-github-${RADIX_ENVIRONMENT}-cluster-maintenance" APP_REGISTRATION_RESOURCE_LOCK_OPERATOR="ar-radix-resource-lock-operator-${RADIX_ENVIRONMENT}" diff --git a/scripts/radix-zone/radix_zone_dev.env b/scripts/radix-zone/radix_zone_dev.env index 6bef81486..458be2511 100644 --- a/scripts/radix-zone/radix_zone_dev.env +++ b/scripts/radix-zone/radix_zone_dev.env @@ -103,8 +103,8 @@ AZ_SYSTEM_USER_APP_REGISTRY_USERNAME="radix-app-registry-secret-${RADIX_ZONE}" AZ_SYSTEM_USER_CLUSTER="radix-cluster-${RADIX_ENVIRONMENT}" # App registrations -APP_REGISTRATION_GRAFANA="ar-radix-grafana-${CLUSTER_TYPE}" -APP_REGISTRATION_NETWORKPOLICY_CANARY="ar-radix-networkpolicy-canary" +APP_REGISTRATION_GRAFANA="radix-ar-grafana-${CLUSTER_TYPE}" +APP_REGISTRATION_NETWORKPOLICY_CANARY="radix-ar-networkpolicy-canary" APP_REGISTRATION_WEB_CONSOLE="Omnia Radix Web Console - ${CLUSTER_TYPE}" APP_REGISTRATION_GITHUB_MAINTENANCE="ar-radix-platform-github-${RADIX_ENVIRONMENT}-cluster-maintenance" APP_REGISTRATION_RESOURCE_LOCK_OPERATOR="ar-radix-resource-lock-operator-${RADIX_ENVIRONMENT}" @@ -112,7 +112,7 @@ APP_REGISTRATION_RESOURCE_LOCK_OPERATOR="ar-radix-resource-lock-operator-${RADIX # Managed identities: id--- MI_AKS="id-radix-aks-${CLUSTER_TYPE}-${AZ_LOCATION}" MI_AKSKUBELET="id-radix-akskubelet-${CLUSTER_TYPE}-${AZ_LOCATION}" -MI_CERT_MANAGER="id-radix-certmanager-${CLUSTER_TYPE}-${AZ_LOCATION}" +# MI_CERT_MANAGER="id-radix-certmanager-${CLUSTER_TYPE}-${AZ_LOCATION}" MI_GITHUB_MAINTENANCE="radix-github-maintenance" ####################################################################################### diff --git a/scripts/radix-zone/radix_zone_playground.env b/scripts/radix-zone/radix_zone_playground.env index b9142ee2b..b9bf4f757 100644 --- a/scripts/radix-zone/radix_zone_playground.env +++ b/scripts/radix-zone/radix_zone_playground.env @@ -102,7 +102,6 @@ AZ_SYSTEM_USER_APP_REGISTRY_USERNAME="radix-app-registry-secret-${RADIX_ZONE}" AZ_SYSTEM_USER_CLUSTER="radix-cluster-${RADIX_ENVIRONMENT}" # App registrations -APP_REGISTRATION_GRAFANA="ar-radix-grafana-${CLUSTER_TYPE}" APP_REGISTRATION_NETWORKPOLICY_CANARY="ar-radix-networkpolicy-canary" APP_REGISTRATION_WEB_CONSOLE="Omnia Radix Web Console - ${CLUSTER_TYPE}" APP_REGISTRATION_GITHUB_MAINTENANCE="ar-radix-platform-github-${RADIX_ENVIRONMENT}-cluster-maintenance" diff --git a/scripts/radix-zone/radix_zone_prod.env b/scripts/radix-zone/radix_zone_prod.env index 91cf2ad88..487068c26 100644 --- a/scripts/radix-zone/radix_zone_prod.env +++ b/scripts/radix-zone/radix_zone_prod.env @@ -102,8 +102,6 @@ AZ_SYSTEM_USER_APP_REGISTRY_USERNAME="radix-app-registry-secret-${RADIX_ZONE}" AZ_SYSTEM_USER_CLUSTER="radix-cluster-${RADIX_ENVIRONMENT}" # App registrations -APP_REGISTRATION_GRAFANA="ar-radix-grafana-${CLUSTER_TYPE}" -APP_REGISTRATION_EXT_MON="ar-radix-grafana-ext-mon" APP_REGISTRATION_NETWORKPOLICY_CANARY="ar-radix-networkpolicy-canary" APP_REGISTRATION_WEB_CONSOLE="Omnia Radix Web Console - ${CLUSTER_TYPE}" APP_REGISTRATION_GITHUB_MAINTENANCE="ar-radix-platform-github-${RADIX_ENVIRONMENT}-cluster-maintenance" diff --git a/scripts/service-principals-and-aad-apps/lib_service_principal.sh b/scripts/service-principals-and-aad-apps/lib_service_principal.sh index e972043c8..dc927b4df 100755 --- a/scripts/service-principals-and-aad-apps/lib_service_principal.sh +++ b/scripts/service-principals-and-aad-apps/lib_service_principal.sh @@ -393,7 +393,7 @@ function set_app_registration_api_scopes { fi new_scopes=$(jq --argjson new_scope "$new_scope" '.? + [$new_scope]' <<<$new_scopes) || return - done < <(echo "${iterate_scopes[@]}") + done patch=$(jq -n --argjson new_scopes "$new_scopes" '{"api":{"oauth2PermissionScopes":$new_scopes}}' .) || return az rest -m PATCH -u https://graph.microsoft.com/v1.0/myorganization/applications/${app_obj_id} -b "$patch" || return