Inside the Azure Onboarding Script
The Azure template automates the onboarding of an Azure subscription into ACE. This template provisions the required Azure resources and role assignments, enabling AlgoSec to integrate with and monitor the environment. The onboarding process is fully automated through an Azure Function.
There are two scripts available.
-
For ACE with license for Cloud App Analyzer
-
For ACE without license for Cloud App Analyzer
This topic outlines each step of the script to help users understand its purpose and functionality.
Script Sections
1. Initialize variables
#Algosec Cloud tenantId
ALGOSEC_TENANT_ID='<ALGOSEC_TENANT_ID>'
#Algosec Cloud multi-tenant application
APP_ID='<ALGOSEC_ONBOARDING_APP_ID>'
#Algosec Cloud onboarding URL
ALGOSEC_CLOUD_HOST='https://<HOST>'
ALGOSEC_CLOUD_ONBOARDING_URL="$ALGOSEC_CLOUD_HOST<ONBOARDING_PATH>"
#Token
TOKEN='<ONBOARDING_TOKEN>'
ADDITIONALS='<ALGOSEC_ADDITIONALS>' # Optional for CD Mitigation
#Target resource
TARGET_RESOURCE='<AZURE_TARGET_RESOURCE>'
TARGET_ID='<AZURE_TARGET_RESOURCE_ID>'
Purpose: The script starts by defining several variables:
-
ALGOSEC_TENANT_ID = AlgoSec Tenant ID
-
APP_ID = AlgoSec onboarding Azure application ID
-
ALGOSEC_CLOUD_HOST = The host of AlgoSec's APIs
-
ALGOSEC_CLOUD_ONBOARDING_URL = The URL for AlgoSec's onboarding API
-
TOKEN = An authentication token for the API
-
ADDITIONALS = base64-encoded login credentials
-
TARGET_RESOURCE = The Azure resource to be onboarded
-
TARGET_ID = The Azure resource ID to be onboarded
2. Retrieve Azure Tenant ID
out=$(az account show)
az_tenant=$(echo "$out" | jq -r '.tenantId')
Purpose: It then uses the Azure CLI to get the current Azure account information and extracts the tenant ID
3. Echo Information about the Target Resource
echo "Preparing to onboard the target resource [$TARGET_RESOURCE] of [$az_tenant] tenant"
Purpose: Provides information about the target resource being onboarded, along with the Azure tenant ID, for logging purposes
4. Retrieve Existing Service Principal
echo "Check if service principal already exists for Algosec Cloud AZ-AD Application"
sp=$(az ad sp list --filter "appId eq '$APP_ID'" | jq length)
if [ $? -ne 0 ]; then
echo "ERROR: User does not have permission to view service principals"
echo "The onboarding process has failed — please ensure you have the required permissions"
exit 1
fi
Purpose: The script checks if a service principal already exists for the AlgoSec Azure AD application
5. Create Service Principal
if [ $sp -eq 0 ];then
echo "Service Principal not found"
echo "Onboard Algosec Cloud AZ-AD Application"
az ad sp create --id $APP_ID
if [ $? -ne 0 ]; then
echo "ERROR: Failed to create service principal for Algosec Cloud AZ-AD Application"
echo "The onboarding process has failed — please ensure you have the required permissions"
exit 1
fi
echo "Service Principal created successfully"
else
echo "Service Principal found for Algosec Cloud AZ-AD Application"
fi
Purpose: The script checks if a service principal already exists for the AlgoSec Azure AD application: - If the user doesn't have permission to view service principals, the script exits with an error. - If the service principal doesn't exist, it creates one using the Azure CLI.
6. Initialize Roles
#Roles
roles=( <SERVICE_PRINCIPAL_ROLES> )
Purpose: Initialize roles based on selection Read OR Read / Write
7. Assign Roles to Target Resource
for role in "${roles[@]}"; do
echo "Assign a role to the [$TARGET_RESOURCE]: [$role]"
az role assignment create --role "$role" --assignee $APP_ID --scope $TARGET_RESOURCE
if [ $? -ne 0 ]; then
echo "ERROR: The target resource [$TARGET_RESOURCE] wasn't found or the user has no permission to work with it"
echo "The onboarding process has failed — please ensure you have the required permissions"
exit 1
fi
done
Purpose: The script then assigns roles to the target resource: It loops through a predefined array of roles. (e.g., 'Reader' 'Storage Account Contributor' 'Network Contributor','Contributor'.) For each role, it uses the Azure CLI to assign the role to the service principal for the target resource. If there's an error in role assignment (e.g., resource not found or lack of permissions), the script exits with an error.
8. Initialize Cloud App Analyzer related variables
#Azure region
REGION='<AZURE_REGION>'
#Prevasio host
PREVASIO_HOST='<PREVASIO_HOST>'
#Prevasio source code location
SOURCES_URL='<PREVASIO_SOURCES_URL>'
Purpose: Define Cloud App Analyzer related variables:
-
REGION=The Azure region where resources will be created
-
PREVASIO_HOST=The host of Algosec's APIs
-
SOURCES_URL=The URL to download Cloud App Analyzer application source files
9. Rollback function
rollback_resources() {
local prevasio_hash=$1
local subscription_id=$2
local deploy_group_name=$3
local role_name=$4
local assignee_id=$5
echo "Rolling back resources in [$subscription_id]"
if [ -n "$assignee_id" ] && [ -n "$role_name" ]; then
echo "Removing role assignment..."
az role assignment delete --assignee "$assignee_id" --role "$role_name" --scope "/subscriptions/$subscription_id"
fi
if [ -n "$role_name" ]; then
echo "Deleting custom role definition..."
delete_role_if_exists "$role_name"
fi
if [ -n "$deploy_group_name" ]; then
echo "Deleting deployment group..."
az deployment group delete --name "$deploy_group_name" --resource-group "prevasio-$prevasio_hash-resource-group"
fi
echo "Deleting resource group..."
az group delete --name "prevasio-$prevasio_hash-resource-group" --yes --no-wait
}
Purpose: This method automates the cleanup and rollback of Azure resources associated with a specific deployment. It safely removes role assignments, deletes custom roles, deployment groups, and the resource group itself to ensure no residual resources remain after a failed deployment.
10. Delete role assignments from Specific Role Name
delete_role_assignments(){
local role_name="$1"
az role assignment list --role "$role_name" --output json |
jq -c '.[]' |
while IFS= read -r role_assignment; do
id=$(echo "$role_assignment" | jq -r '.id')
echo "Deleting role assignment: $id"
az role assignment delete --ids "$id"
done
}
Purpose: This function deletes all Azure role assignments associated with a specified role name. It queries all role assignments for the given role and iteratively removes each assignment, helping to clean up access permissions efficiently.
11. Delete Role if exists
delete_role_if_exists() {
local role_name="$1"
get_role_scope() {
az role definition list --query "[?roleName==\`${role_name}\`].[assignableScopes[0]] | [0]" -o tsv
}
role_scope=$(get_role_scope)
if [ -n "$role_scope" ]; then
delete_role_assignments "$role_name"
az role definition delete --name "$role_name" --scope "$role_scope"
echo "Waiting for role to be deleted..."
local max_retries=5
local retries=0
while true; do
sleep 10
role_scope=$(get_role_scope)
if [ -z "$role_scope" ]; then
echo "Role '$role_name' deleted successfully."
break
else
retries=$((retries + 1))
echo "Role still exists. Retry $retries/$max_retries..."
if [ "$retries" -ge "$max_retries" ]; then
echo "Unable to delete role automatically after $max_retries attempts. Please delete manually:"
echo " az role definition delete --name \"$role_name\" --scope \"$role_scope\""
break
fi
fi
done
else
echo "Role '$role_name' does not exist. No deletion needed."
fi
}
Purpose: This function checks if a specified Azure custom role exists and, if so, deletes all its role assignments before deleting the role definition itself. It includes a retry mechanism to verify role deletion and prompts the user for manual deletion if automatic deletion fails after multiple attempts.
12. Delete Key Vaults if exists
delete_kv_if_exists(){
local kv_name="prevasio-$prevasio_hash-kv"
if az keyvault show --name "$kv_name" --query "name" --output tsv 2>/dev/null | grep -q "$kv_name"; then
echo "Purging key vault '$kv_name'..."
az keyvault purge --name "$kv_name" --location "$REGION" --no-wait
fi
if az keyvault show-deleted --name "$kv_name" --query "name" --output tsv 2>/dev/null | grep -q "$kv_name"; then
echo "Purging key vault '$kv_name'..."
az keyvault purge --name "$kv_name" --location "$REGION" --no-wait
fi
}
Purpose: This function checks if an Azure Key Vault with a specific name exists or is soft-deleted, and if found, initiates a purge operation to permanently delete the Key Vault. It helps ensure that any existing or previously deleted Key Vault with the given name is completely removed.
13. Delete existing resources if exists
cleanup_existing_resources() {
local subscription_id="$1"
az account set --subscription "$subscription_id"
local rg_name="prevasio-$prevasio_hash-resource-group"
echo "Checking for existing resources to clean in [$subscription_id]..."
# Delete resource group if it exists
if az group exists --name "$rg_name" | grep -q true; then
echo "Deleting resource group '$rg_name'..."
az group delete --name "$rg_name" --yes --no-wait
fi
delete_kv_if_exists
}
Purpose: This function cleans up existing Azure resources in a specified subscription by deleting a predefined resource group (named using a specific hash) if it exists. It also calls a helper function to purge any associated Key Vaults linked to that resource group, ensuring a thorough cleanup before a fresh deployment.
14. Azure CLI Error handling
check_az_failure() {
local response="$1"
local context_message="$2"
local json_part
json_part=$(echo "$response" | sed -n '/^{/,$p')
if ! echo "$json_part" | jq -e . > /dev/null 2>&1; then
echo "$context_message failed: $response"
return 1
fi
if echo "$json_part" | jq -e 'has("error")' > /dev/null; then
local error_message
error_message=$(echo "$json_part" | jq -r '.error.message // "Unknown error"')
echo "$context_message failed: $error_message"
return 1
fi
return 0
}
Purpose: This function analyzes the response from an Azure CLI command to detect errors. It extracts the JSON portion from the response and checks if it contains an error message. If an error is found or the JSON is invalid, it prints a descriptive failure message including the context and returns a failure status. Otherwise, it returns success, allowing the script to handle Azure CLI errors gracefully.
15. Deploy resources to subscription
deploy_code_to_subscription() {
local subscription_id=$1
local prevasio_hash="${ALGOSEC_TENANT_ID:0:4}${subscription_id:0:4}"
az account set --subscription "$subscription_id"
echo "Deploying resources to [$subscription_id] subscription"
should_drop_resource_group=false
local resource_group_name="prevasio-$prevasio_hash-resource-group"
if [ "$(az group exists --name $resource_group_name)" == "true" ]; then
resource_group_version=$(az group show --name $resource_group_name --query "tags.version" --output tsv)
if [ -n "$resource_group_version" ] && [ "$resource_group_version" == "$CURRENT_PREVASIO_RESOURCES_MD5" ]; then
echo "The current onboarding in [$subscription_id] subscription is up to date. Skipping the resources creation."
return
fi
should_drop_resource_group=true
fi
if $should_drop_resource_group ; then
echo "Deleting resource group prevasio-$prevasio_hash-resource-group in [$subscription_id] subscription"
cleanup_existing_resources "$prevasio_hash" "$subscription_id"
fi
echo '{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"subscription-id": {
"value": "'$subscription_id'"
},
"tenant-id": {
"value": "'$az_tenant'"
},
"prevasio-hash": {
"value": "'$prevasio_hash'"
},
"prevasio-host": {
"value": "'$PREVASIO_HOST'"
},
"prevasio-additionals": {
"value": "'$ADDITIONALS'"
},
"algosec-cloud-host": {
"value": "'$ALGOSEC_CLOUD_HOST'"
}
}
}' > parameters.json
# Deleting Key Vault if it exists
delete_kv_if_exists
# Creating Resource Group
echo "Creating resource group prevasio-$prevasio_hash-resource-group in [$subscription_id] subscription"
group_response=$(az group create --name prevasio-$prevasio_hash-resource-group --location $REGION --tag version=$CURRENT_PREVASIO_RESOURCES_MD5 2>&1)
if ! check_az_failure "$group_response" "Resource group creation"; then
return
fi
# Creating Deployment Group and checking if the operation is success
# Optional fo CD Mitigation
echo "Creating application resources in [$subscription_id] subscription"
deploy_group_response=$(az deployment group create --resource-group prevasio-$prevasio_hash-resource-group --template-file template.json --parameters parameters.json 2>&1)
if ! check_az_failure "$deploy_group_response" "Deployment group creation"; then
rollback_resources "$prevasio_hash" "$subscription_id"
return
fi
deploy_group_name=$(echo $deploy_group_response | jq -r '.name')
# Creating and assigning role
# Optional for CD Mitigation
echo "Assigning roles to application in [$subscription_id] subscription"
role_name="Prevasio Application Role ($prevasio_hash)"
echo '{
"Name": "'"$role_name"'",
"IsCustom": true,
"Description": "Allows to create EventGrid subscriptions for ACR registries events.",
"Actions": [
"Microsoft.EventGrid/eventSubscriptions/read",
"Microsoft.ContainerRegistry/registries/read",
"Microsoft.EventGrid/eventSubscriptions/write",
"Microsoft.Web/sites/functions/write"
],
"NotActions": [],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/'$subscription_id'"
]
}' > ./role_def.json
# Delete, if a role with same name already exists.
delete_role_if_exists "$role_name"
# Role creation
# Optional for CD Mitigation
role_creation_response=$(az role definition create --role-definition role_def.json 2>&1)
exit_code=$?
if [ $exit_code -ne 0 ]; then
rollback_resources "$prevasio_hash" "$subscription_id" "$deploy_group_name"
return
fi
generated_role_name=$(echo "$role_creation_response" | sed '/^{/,$!d' | jq -r '.name')
# Role Assignment
# Optional for CD Mitigation
assignee_id=$(az ad sp list --display-name prevasio-$prevasio_hash-app --query [].id --output tsv)
role_assignment_create_response=$(az role assignment create --assignee $assignee_id --role "$generated_role_name" --scope /subscriptions/$subscription_id 2>&1)
if ! check_az_failure "$role_assignment_create_response" "Role assignment creation"; then
rollback_resources "$prevasio_hash" "$subscription_id" "$deploy_group_name" "$generated_role_name"
return
fi
# Creating and deploying function app.
# Optional for CD Mitigation
echo "Deploying application sources in [$subscription_id] subscription"
functionapp_deploy_response=$(az functionapp deployment source config-zip -g prevasio-$prevasio_hash-resource-group -n prevasio-$prevasio_hash-app --src function.zip --build-remote true 2>&1)
if ! check_az_failure "$functionapp_deploy_response" "Function app creation/deployment"; then
rollback_resources "$prevasio_hash" "$subscription_id" "$deploy_group_name" "$generated_role_name" "$assignee_id"
return
fi
echo "Application sources were deployed successfully"
}
Purpose: This method automates the deployment of the Cloud App Analyzer application resources to a specific Azure subscription. It manages resource groups, deployment templates, role definitions and assignments, and function app deployments while handling error checking and cleanup if deployment steps fail:
-
Set Azure Subscription Context: Use the provided subscription ID to set the active Azure subscription context.
-
Generate Unique Prevasio Hash and Resource Group Name: Create a unique hash based on tenant ID and subscription ID to namespace resources consistently.
-
Check for Existing Resource Group
-
Verify if the resource group prevasio-$prevasio_hash-resource-group exists.
-
If it exists, check the version tag to determine if the existing resources are up to date.
-
If up to date, skip deployment; otherwise, mark for resource group deletion.
-
-
Cleanup Old Resources: If marked, delete existing resource group and associated Key Vault to start fresh.
-
Create Deployment Parameters JSON: Generate a parameters.json file dynamically with required parameters such as subscription ID, tenant ID, prevasio hash, hosts, and additional configuration.
-
Create Resource Group
-
Create the Azure resource group with the new version tag, checking for any errors and halting if creation fails.
-
Deploy Application Resources: Deploy resources defined in template.json to the resource group using Azure Deployment Group. On failure, trigger rollback.
-
Role Definition and Assignment
-
Define a custom Azure Role with required permissions.
-
Delete existing roles with the same name before creating a new one to avoid conflicts.
-
Assign the created role to the ACE service principal in the subscription scope. On failure, trigger rollback.
-
-
Deploy Function App Sources: Upload and deploy the application source zip (function.zip) to the Azure Function App. On failure, trigger rollback.
-
Success Confirmation
16. Download and Unpack Application Sources
Optional for CD Mitigation
mkdir -p prevasio-onboarding && rm -rf prevasio-onboarding/*
cd prevasio-onboarding
echo "Downloading Prevasio application resources"
wget -O sources.zip "${SOURCES_URL}?tenant_id=${ALGOSEC_TENANT_ID}"
if [[ -f "sources.zip" ]]; then
CURRENT_PREVASIO_RESOURCES_MD5=$(md5sum "sources.zip" | awk '{print $1}')
else
CURRENT_PREVASIO_RESOURCES_MD5=0
echo "Error: Failed to download sources.zip"
return
fi
unzip sources.zip
echo "Resources were downloaded successfully. Resources version: $CURRENT_PREVASIO_RESOURCES_MD5"
Purpose: Downloads and unpacks application source files, preparing for deployment.
17. Deploy to Target Resource (Subscription or Management Group)
Optional for CD Mitigation
if [[ $TARGET_RESOURCE == /subscriptions/* ]]; then
deploy_code_to_subscription $TARGET_ID
else
if [[ $TARGET_RESOURCE == '/' ]]; then
TARGET_ID="/"
fi
subscriptions_json=$(az account management-group subscription show-sub-under-mg --name $TARGET_ID)
for subscription in $(echo "${subscriptions_json}" | jq -r '.[].name'); do
deploy_code_to_subscription $subscription
done
fi
Purpose: Based on the target resource type, deploys code either to a single subscription or iterates through subscriptions within a management group
18. Cleanup resources
Optional for CD Mitigation
cd ..
rm -rf prevasio-onboarding
Purpose: Remove the downloaded application source files
19. Onboarding Call
response=$(curl -X POST "$ALGOSEC_CLOUD_ONBOARDING_URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: $TOKEN" \
--silent \
-d '{ "azure_tenant":"'"$az_tenant"'", "supportChanges": "<SUPPORT_CHANGES>", "event": { "RequestType": "Create" } }')
Purpose: Makes a POST request to the ALGOSEC_CLOUD_ONBOARDING_URL with the azure tenant and other details.
20. Response Handling
status=$(echo $response | jq -r '.initialOnboardResult' | jq -r '.status')
message=$(echo $response | jq -r '.initialOnboardResult' | jq -r '.message')
if [ "$status" == 200 ]; then
echo "The onboarding process is finished: $message"
echo "Press CTRL+D to close the terminal session"
else
echo "ERROR: The onboarding process has failed: $message"
fi
Purpose: Finally, the script processes the API response:
-
It extracts the status and body from the response.
-
If the status is 200, it prints a success message.
-
If the status is not 200, it prints an error message.
1. Initialize variables
#Algosec Cloud tenantId
ALGOSEC_TENANT_ID='<ALGOSEC_TENANT_ID>'
#Algosec Cloud multi-tenant application
APP_ID='<ALGOSEC_ONBOARDING_APP_ID>'
#Algosec Cloud onboarding URL
ALGOSEC_CLOUD_HOST='https://<HOST>'
ALGOSEC_CLOUD_ONBOARDING_URL="$ALGOSEC_CLOUD_HOST<ONBOARDING_PATH>"
#Token
TOKEN='<ONBOARDING_TOKEN>'
ADDITIONALS='<ALGOSEC_ADDITIONALS>'
#Target resource
TARGET_RESOURCE='<AZURE_TARGET_RESOURCE>'
TARGET_ID='<AZURE_TARGET_RESOURCE_ID>'
Purpose: The script starts by defining several variables:
-
ALGOSEC_TENANT_ID = Algosec tenant ID
-
APP_ID = AlgoSec onboarding Azure application ID
-
ALGOSEC_CLOUD_HOST = The host of AlgoSec's APIs
-
ALGOSEC_CLOUD_ONBOARDING_URL = The URL for AlgoSec's onboarding API
-
TOKEN = An authentication token for the API
-
ADDITIONALS = base64-encoded login credentials
-
TARGET_RESOURCE = The Azure resource to be onboarded
-
TARGET_ID = The Azure resource ID to be onboarded
2. Retrieve Azure Tenant ID
out=$(az account show)
az_tenant=$(echo "$out" | jq -r '.tenantId')
Purpose: It then uses the Azure CLI to get the current Azure account information and extracts the tenant ID
3. Echo Information about the Target Resource
echo "Preparing to onboard the target resource [$TARGET_RESOURCE] of [$az_tenant] tenant"
Purpose: Provides information about the target resource being onboarded, along with the Azure tenant ID, for logging purposes
4. Retrieve Existing Service Principal
echo "Check if service principal already exists for Algosec Cloud AZ-AD Application"
sp=$(az ad sp list --filter "appId eq '$APP_ID'" | jq length)
if [ $? -ne 0 ]; then
echo "ERROR: User does not have permission to view service principals"
echo "The onboarding process has failed — please ensure you have the required permissions"
exit 1
fi
Purpose: The script checks if a service principal already exists for the AlgoSec Azure AD application
5. Create Service Principal
if [ $sp -eq 0 ];then
echo "Service Principal not found"
echo "Onboard Algosec Cloud AZ-AD Application"
az ad sp create --id $APP_ID
if [ $? -ne 0 ]; then
echo "ERROR: Failed to create service principal for Algosec Cloud AZ-AD Application"
echo "The onboarding process has failed — please ensure you have the required permissions"
exit 1
fi
echo "Service Principal created successfully"
else
echo "Service Principal found for Algosec Cloud AZ-AD Application"
fi
Purpose: The script checks if a service principal already exists for the AlgoSec Azure AD application: - If the user doesn't have permission to view service principals, the script exits with an error. - If the service principal doesn't exist, it creates one using the Azure CLI.
6. Initialize Roles
#Roles
roles=( <SERVICE_PRINCIPAL_ROLES> )
Purpose: Initialize roles based on selection Read OR Read / Write
7. Assign Roles to Target Resource
for role in "${roles[@]}"; do
echo "Assign a role to the [$TARGET_RESOURCE]: [$role]"
az role assignment create --role "$role" --assignee $APP_ID --scope $TARGET_RESOURCE
if [ $? -ne 0 ]; then
echo "ERROR: The target resource [$TARGET_RESOURCE] wasn't found or the user has no permission to work with it"
echo "The onboarding process has failed — please ensure you have the required permissions"
exit 1
fi
done
Purpose: The script then assigns roles to the target resource: It loops through a predefined array of roles. (e.g., 'Reader' 'Storage Account Contributor' 'Network Contributor','Contributor'.) For each role, it uses the Azure CLI to assign the role to the service principal for the target resource. If there's an error in role assignment (e.g., resource not found or lack of permissions), the script exits with an error.
8. Onboarding Call
response=$(curl -X POST "$ALGOSEC_CLOUD_ONBOARDING_URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: $TOKEN" \
--silent \
-d '{ "azure_tenant":"'"$az_tenant"'", "supportChanges": "<SUPPORT_CHANGES>", "event": { "RequestType": "Create" } }')
Purpose: Makes a POST request to the ALGOSEC_CLOUD_ONBOARDING_URL with the azure tenant and other details.
9. Response Handling
status=$(echo $response | jq -r '.initialOnboardResult' | jq -r '.status')
message=$(echo $response | jq -r '.initialOnboardResult' | jq -r '.message')
if [ "$status" == 200 ]; then
echo "The onboarding process is finished: $message"
echo "Press CTRL+D to close the terminal session"
else
echo "ERROR: The onboarding process has failed: $message"
fi
Purpose: Finally, the script processes the API response:
-
It extracts the status and body from the response.
-
If the status is 200, it prints a success message.
-
If the status is not 200, it prints an error message.