Inside the Azure Onboarding Template (Terraform)
The Azure Terraform template automates the onboarding of an Azure subscription into ACE. It provisions the required Azure resources and role assignments, enabling AlgoSec to integrate with and monitor the environment.
This page documents each section of the template to help users understand its purpose and functionality.
There are two templates available.
-
For ACE with license for Cloud App Analyzer
-
For ACE without license for Cloud App Analyzer
terraform {
required_version = ">= 1.5.7"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.53.0"
}
azuread = {
source = "hashicorp/azuread"
version = ">= 3.7.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.10"
}
null = {
source = "hashicorp/null"
version = ">= 3.2.0"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
template_deployment {
delete_nested_items_during_deletion = false
}
}
}
provider "azuread" {}
data "azurerm_client_config" "current" {}
data "azurerm_subscription" "target" {
subscription_id = var.subscription_id
}
variable "subscription_id" { ... }
variable "algosec_client_id" { ... }
variable "algosec_client_secret" { ... }
locals {
subscription_id = data.azurerm_subscription.target.subscription_id
subscription_name = data.azurerm_subscription.target.display_name
app_id = "<ALGOSEC_ONBOARDING_APP_ID>"
algosec_tenant_id = "<ALGOSEC_TENANT_ID>"
algosec_cloud_host = "https://<HOST>"
algosec_cloud_login_url = "${local.algosec_cloud_host}/api/algosaas/auth/v1/access-keys/login"
algosec_cloud_onboarding_url = "${local.algosec_cloud_host}<ONBOARDING_PATH>"
target_resource = "/subscriptions/${local.subscription_id}"
az_tenant = data.azurerm_client_config.current.tenant_id
support_changes = "<SUPPORT_CHANGES>"
}
data "azuread_service_principals" "existing" {
client_ids = [local.app_id]
}
resource "azuread_service_principal" "algosec_app_sp" {
count = local.sp_exists ? 0 : 1
client_id = local.app_id
}
locals {
role_hash = substr(md5(local.subscription_id), 0, 8)
vm_scan_role_name = "Algosec App Analyzer VM Scan (${local.role_hash})"
}
variable "vm_scan_actions" {
type = list(string)
description = "Actions granted by the Algosec App Analyzer VM Scan custom role."
default = [
<actions_vm_scan>
]
}
resource "azurerm_role_definition" "vm_scan" {
name = local.vm_scan_role_name
scope = local.target_resource
description = "Allows Algosec App Analyzer to create/delete resources as needed for VM scanning purposes"
permissions {
actions = var.vm_scan_actions
not_actions = []
data_actions = []
not_data_actions = []
}
}
resource "time_sleep" "wait_for_vm_scan_role_propagation" {
depends_on = [azurerm_role_definition.vm_scan]
create_duration = "15s"
}
resource "azurerm_role_assignment" "algosec_vm_scan_assignments" {
scope = local.target_resource
role_definition_name = local.vm_scan_role_name
principal_id = ...
}
variable "roles" {
type = list(string)
default = [
<SERVICE_PRINCIPAL_ROLES>
]
}
resource "azurerm_role_assignment" "algosec_assignments" {
for_each = toset(var.roles)
scope = local.target_resource
role_definition_name = each.value
principal_id = local.principal_object_id
}
locals {
region = "<AZURE_REGION>"
prevasio_host = "<PREVASIO_HOST>"
prevasio_hash = "${substr(local.algosec_tenant_id, 0, 4)}${substr(local.subscription_id, 0, 4)}"
resource_group_name = "prevasio-${local.prevasio_hash}-resource-group"
role_name = "Prevasio Application Role (${local.prevasio_hash})"
app_name = "prevasio-${local.prevasio_hash}-app"
storage_name = "prevasio${local.prevasio_hash}storage"
func_zip_path = "${path.module}/function.zip"
func_zip_sha = filesha256(local.func_zip_path)
additionals = base64encode(jsonencode({
tenantId = local.algosec_tenant_id
clientId = var.algosec_client_id
clientSecret = var.algosec_client_secret
}))
}
additionals = base64encode(jsonencode({
tenantId = local.algosec_tenant_id
clientId = var.algosec_client_id
clientSecret = var.algosec_client_secret
}))
resource "azurerm_resource_group" "prevasio_rg" {
name = local.resource_group_name
location = local.region
}
resource "time_sleep" "wait_for_rg" {
depends_on = [azurerm_resource_group.prevasio_rg]
create_duration = "60s"
}
resource "azurerm_application_insights" "appinsights" {
name = local.app_name
location = local.region
resource_group_name = local.resource_group_name
application_type = "web"
}
resource "azurerm_service_plan" "func_plan" {
name = "prevasio-${local.prevasio_hash}-application-farm"
location = local.region
resource_group_name = local.resource_group_name
os_type = "Linux"
sku_name = "Y1"
}
resource "azurerm_storage_account" "storage_account" {
name = local.storage_name
resource_group_name = local.resource_group_name
location = local.region
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
}
resource "azurerm_linux_function_app" "app" {
name = local.app_name
...
identity {
type = "SystemAssigned"
}
...
zip_deploy_file = local.func_zip_path
}
resource "azurerm_key_vault" "kv" {
name = "prevasio-${local.prevasio_hash}-kv"
...
}
resource "time_sleep" "wait_for_kv_access" {
depends_on = [azurerm_key_vault.kv]
create_duration = "30s"
}
resource "azurerm_key_vault_secret" "sec_additionals" { ... }
resource "azurerm_key_vault_secret" "sec_prevasio_host" { ... }
resource "azurerm_key_vault_secret" "sec_algosec_cloud_host" { ... }
resource "azurerm_role_definition" "prevasio_role" {
name = local.role_name
scope = local.target_resource
description = "Allows to create EventGrid subscriptions for ACR registries events."
...
}
resource "null_resource" "algosec_onboarding" {
provisioner "local-exec" {
when = create
command = "bash ${path.module}/scripts/algosec_azure_onboard.sh"
}
}
resource "null_resource" "algosec_offboarding" {
provisioner "local-exec" {
when = destroy
command = "bash ${path.module}/scripts/algosec_azure_offboard.sh"
on_failure = continue
}
}
Script Sections
1. Define Terraform version and providers
terraform {
required_version = ">= 1.5.7"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.53.0"
}
azuread = {
source = "hashicorp/azuread"
version = ">= 3.7.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.10"
}
null = {
source = "hashicorp/null"
version = ">= 3.2.0"
}
}
}
Purpose: This section defines the Terraform version and the providers required by the onboarding template.
-
azurerm manages Azure infrastructure resources.
-
azuread manages Azure AD service principal operations.
-
time adds deliberate waits for Azure propagation delays.
-
null is used for local execution steps such as onboarding and offboarding API calls.
This is the Terraform equivalent of preparing the runtime before the onboarding logic begins.
2. Configure Azure providers
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
template_deployment {
delete_nested_items_during_deletion = false
}
}
}
provider "azuread" {}
Purpose: This section configures the Azure providers that Terraform uses to deploy and manage the onboarding resources.
The azurerm provider manages subscription resources such as the resource group, storage account, function app, key vault, and role assignments. The azuread provider is used to locate or create the AlgoSec service principal in the Azure tenant.
3. Retrieve current Azure context
data "azurerm_client_config" "current" {}
data "azurerm_subscription" "target" {
subscription_id = var.subscription_id
}
Purpose: This section retrieves details about the currently authenticated Azure identity and the target subscription.
It provides values used later in the template, such as:
-
The Azure tenant ID
-
The current object ID
-
The target subscription ID
-
The target subscription display name
4. Define input variables
variable "subscription_id" { ... }
variable "algosec_client_id" { ... }
variable "algosec_client_secret" { ... }
Purpose: This section defines the values that must be supplied by the user.
-
Subscription_id identifies the Azure subscription to onboard.
-
Algosec_client_id and algosec_client_secret are used to authenticate to AlgoSec APIs.
-
Validation ensures that the AlgoSec API credentials are set before Terraform runs.
5. Initialize local values
locals {
subscription_id = data.azurerm_subscription.target.subscription_id
subscription_name = data.azurerm_subscription.target.display_name
app_id = "<ALGOSEC_ONBOARDING_APP_ID>"
algosec_tenant_id = "<ALGOSEC_TENANT_ID>"
algosec_cloud_host = "https://<HOST>"
algosec_cloud_login_url = "${local.algosec_cloud_host}/api/algosaas/auth/v1/access-keys/login"
algosec_cloud_onboarding_url = "${local.algosec_cloud_host}<ONBOARDING_PATH>"
target_resource = "/subscriptions/${local.subscription_id}"
az_tenant = data.azurerm_client_config.current.tenant_id
support_changes = "<SUPPORT_CHANGES>"
}
Purpose: This section centralizes the core values used throughout the deployment.
It defines:
-
The AlgoSec onboarding application ID
-
The AlgoSec tenant ID
-
The AlgoSec API host and endpoint URLs
-
The Azure tenant ID
-
The target Azure subscription scope
-
The support-changes flag used during onboarding
6. Check for an existing AlgoSec service principal
data "azuread_service_principals" "existing" {
client_ids = [local.app_id]
}
Purpose: This section checks whether a service principal already exists in the Azure tenant for the AlgoSec application.
The result is used to decide whether Terraform should reuse the existing service principal or create a new one.
7. Create the service principal if needed
resource "azuread_service_principal" "algosec_app_sp" {
count = local.sp_exists ? 0 : 1
client_id = local.app_id
}
Purpose: If the AlgoSec service principal does not already exist in the Azure tenant, Terraform creates it automatically.
8. VM Scan Custom Role Creation (Optional - Onboard VM Scanning enabled)
locals {
role_hash = substr(md5(local.subscription_id), 0, 8)
vm_scan_role_name = "Algosec App Analyzer VM Scan (${local.role_hash})"
}
variable "vm_scan_actions" {
type = list(string)
description = "Actions granted by the Algosec App Analyzer VM Scan custom role."
default = [
<actions_vm_scan>
]
}
resource "azurerm_role_definition" "vm_scan" {
name = local.vm_scan_role_name
scope = local.target_resource
description = "Allows Algosec App Analyzer to create/delete resources as needed for VM scanning purposes"
permissions {
actions = var.vm_scan_actions
not_actions = []
data_actions = []
not_data_actions = []
}
}
Purpose: Creates a custom Azure role required for the Cloud App Analyzer VM scanning feature. This section is only present when the VM scanning capability is enabled. A unique, deterministic role name is generated by hashing the AlgoSec tenant ID and the target Azure resource ID (first 8 characters of the MD5 hash), ensuring the role name is consistent across re-runs but scoped per tenant/resource combination.
9. Wait for custom role propagation
resource "time_sleep" "wait_for_vm_scan_role_propagation" {
depends_on = [azurerm_role_definition.vm_scan]
create_duration = "15s"
}
Purpose: Azure RBAC changes are not always available immediately after creation. This section adds a short wait so the new custom role can propagate before Terraform attempts to assign it.
Without this delay, role assignment can fail due to eventual consistency in Azure.
10. Assign the VM scan custom role
resource "azurerm_role_assignment" "algosec_vm_scan_assignments" {
scope = local.target_resource
role_definition_name = local.vm_scan_role_name
principal_id = ...
}
Purpose: This section assigns the custom Cloud App Analyzer VM scan role to the AlgoSec service principal at the subscription scope.
11. Add Roles and Assign to Target Resource
variable "roles" {
type = list(string)
default = [
<SERVICE_PRINCIPAL_ROLES>
]
}
Purpose: This section defines the built-in Azure roles that AlgoSec requires for the onboarding flow.
These roles allow the onboarding principal to access the Azure services and resources required for the integrated monitoring and App Analyzer workflow.
12. Assign the built-in Azure roles
resource "azurerm_role_assignment" "algosec_assignments" {
for_each = toset(var.roles)
scope = local.target_resource
role_definition_name = each.value
principal_id = local.principal_object_id
}
Purpose: This section assigns all required built-in roles to the AlgoSec service principal.
The assignments are made at the subscription scope so the AlgoSec onboarding principal can access the relevant resources throughout the onboarded subscription.
13. Initialize Cloud App Analyzer resource names and settings (Optional - Onboard CM Mitigation enabled)
locals {
region = "<AZURE_REGION>"
prevasio_host = "<PREVASIO_HOST>"
prevasio_hash = "${substr(local.algosec_tenant_id, 0, 4)}${substr(local.subscription_id, 0, 4)}"
resource_group_name = "prevasio-${local.prevasio_hash}-resource-group"
role_name = "Prevasio Application Role (${local.prevasio_hash})"
app_name = "prevasio-${local.prevasio_hash}-app"
storage_name = "prevasio${local.prevasio_hash}storage"
func_zip_path = "${path.module}/function.zip"
func_zip_sha = filesha256(local.func_zip_path)
additionals = base64encode(jsonencode({
tenantId = local.algosec_tenant_id
clientId = var.algosec_client_id
clientSecret = var.algosec_client_secret
}))
}
Purpose: This section initializes the values used by the Cloud App Analyzer deployment.
It defines:
-
Region - The Azure region where Cloud App Analyzer resources will be created
-
PREVASIO_HOST — The host of AlgoSec's Cloud App Analyzer APIs
-
A derived hash used in resource naming
-
The resource group name
-
The function app name
-
The storage account name
-
The packaged Azure Function ZIP path and hash
14. Encode additional AlgoSec credentials for secure storage
additionals = base64encode(jsonencode({
tenantId = local.algosec_tenant_id
clientId = var.algosec_client_id
clientSecret = var.algosec_client_secret
}))
Purpose: This section prepares the AlgoSec access details that the Azure Function will later use when forwarding events.
The values are JSON-encoded and then base64-encoded before being stored as a Key Vault secret.
15. Create the Cloud App Analyzer resource group
resource "azurerm_resource_group" "prevasio_rg" {
name = local.resource_group_name
location = local.region
}
Purpose: This section creates the dedicated Azure resource group used by the Cloud App Analyzer components.
All related resources, including the function app, storage account, Application Insights instance, and Key Vault, are deployed into this resource group.
16. Wait for resource group readiness
resource "time_sleep" "wait_for_rg" {
depends_on = [azurerm_resource_group.prevasio_rg]
create_duration = "60s"
}
Purpose: This section adds a delay after resource group creation so Azure has time to fully register the new resource group before dependent resources are created.
17. Create Application Insights
resource "azurerm_application_insights" "appinsights" {
name = local.app_name
location = local.region
resource_group_name = local.resource_group_name
application_type = "web"
}
Purpose: This section creates Application Insights for the Azure Function deployment.
It provides application monitoring and telemetry for the Cloud App Analyzer function app.
18. Create the Azure Function App service plan
resource "azurerm_service_plan" "func_plan" {
name = "prevasio-${local.prevasio_hash}-application-farm"
location = local.region
resource_group_name = local.resource_group_name
os_type = "Linux"
sku_name = "Y1"
}
Purpose: This section creates the Linux Consumption plan used by the Azure Function App.
It provides the compute hosting layer for the event-forwarding and registry-management functions.
19. Create the storage account
resource "azurerm_storage_account" "storage_account" {
name = local.storage_name
resource_group_name = local.resource_group_name
location = local.region
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
}
Purpose: This section creates the storage account required by the Azure Function App.
The function app uses it for runtime storage and deployment configuration.
20. Deploy the Azure Linux Function App
resource "azurerm_linux_function_app" "app" {
name = local.app_name
...
identity {
type = "SystemAssigned"
}
...
zip_deploy_file = local.func_zip_path
}
Purpose: This section deploys the Azure Function App and enables a system-assigned managed identity for it.
The function package is deployed from function.zip, and the app settings provide the subscription, tenant, deployment hash, and storage configuration required by the runtime.
The function package supports the Cloud App Analyzer flow by handling registry event forwarding and management logic.
21. Create the Key Vault
resource "azurerm_key_vault" "kv" {
name = "prevasio-${local.prevasio_hash}-kv"
...
}
Purpose: This section creates the Azure Key Vault used to store secrets needed by the function app.
The template grants secret permissions both to:
-
The function app managed identity
-
The current deployment identity
This allows the deployment process to populate secrets and the function app to retrieve them during runtime.
22. Wait for Key Vault access policy propagation
resource "time_sleep" "wait_for_kv_access" {
depends_on = [azurerm_key_vault.kv]
create_duration = "30s"
}
Purpose: This section adds a delay so Key Vault access policies can propagate before secrets are created.
Without this delay, secret creation may fail if Azure has not yet applied the access policies.
23. Store onboarding secrets in Key Vault
resource "azurerm_key_vault_secret" "sec_additionals" { ... }
resource "azurerm_key_vault_secret" "sec_prevasio_host" { ... }
resource "azurerm_key_vault_secret" "sec_algosec_cloud_host" { ... }
Purpose: This section stores the configuration values required by the Azure Function runtime.
The secrets include:
-
Encoded AlgoSec credentials
-
The Prevasio host
-
The AlgoSec cloud host
This allows the function to authenticate and forward events securely without embedding secrets directly in code.
24. Create the Prevasio custom role
resource "azurerm_role_definition" "prevasio_role" {
name = local.role_name
scope = local.target_resource
description = "Allows to create EventGrid subscriptions for ACR registries events."
...
}
Purpose: This section creates a custom Azure role used for Event Grid subscription management on Azure Container Registry resources.
It grants permissions needed to:
-
Read and write Event Grid subscriptions
-
Read Container Registry resources
-
Write Azure Function site function settings
This supports the Cloud App Analyzer event-subscription workflow.
25. Run AlgoSec onboarding API call after Azure roles are ready
resource "null_resource" "algosec_onboarding" {
provisioner "local-exec" {
when = create
command = "bash ${path.module}/scripts/algosec_azure_onboard.sh"
}
}
Purpose: This section performs the final AlgoSec onboarding API call after the Azure role assignments have been created.
The onboarding script:
-
Logs in to the AlgoSec API
-
Obtains an access token
-
Submits the Azure subscription onboarding request
This corresponds to the final API-driven onboarding step that connects the Azure subscription to AlgoSec after Azure-side preparation is complete.
26. Run AlgoSec offboarding API call during destroy
resource "null_resource" "algosec_offboarding" {
provisioner "local-exec" {
when = destroy
command = "bash ${path.module}/scripts/algosec_azure_offboard.sh"
on_failure = continue
}
}
Purpose: This section performs AlgoSec offboarding when the Terraform deployment is destroyed.
The offboarding script:
-
Logs in to the AlgoSec API
-
Obtains an access token
-
Submits the offboarding request for the subscription
This keeps the AlgoSec platform in sync with the Terraform lifecycle.
1. Define Terraform version and providers
terraform {
required_version = ">= 1.5.7"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.53.0"
}
azuread = {
source = "hashicorp/azuread"
version = ">= 3.7.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.10"
}
null = {
source = "hashicorp/null"
version = ">= 3.2.0"
}
}
}
Purpose: This section defines the Terraform version and the providers required by the onboarding template.
-
azurerm manages Azure infrastructure resources.
-
azuread manages Azure AD service principal operations.
-
time adds deliberate waits for Azure propagation delays.
-
null is used for local execution steps such as onboarding and offboarding API calls.
This is the Terraform equivalent of preparing the runtime before the onboarding logic begins.
2. Configure Azure providers
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
template_deployment {
delete_nested_items_during_deletion = false
}
}
}
provider "azuread" {}
Purpose: This section configures the Azure providers that Terraform uses to deploy and manage the onboarding resources.
The azurerm provider manages subscription resources such as the resource group, storage account, function app, key vault, and role assignments. The azuread provider is used to locate or create the AlgoSec service principal in the Azure tenant.
3. Retrieve current Azure context
data "azurerm_client_config" "current" {}
data "azurerm_subscription" "target" {
subscription_id = var.subscription_id
}
Purpose: This section retrieves details about the currently authenticated Azure identity and the target subscription.
It provides values used later in the template, such as:
-
The Azure tenant ID
-
The current object ID
-
The target subscription ID
-
The target subscription display name
4. Define input variables
variable "subscription_id" { ... }
variable "algosec_client_id" { ... }
variable "algosec_client_secret" { ... }
Purpose: This section defines the values that must be supplied by the user.
-
Subscription_id identifies the Azure subscription to onboard.
-
Algosec_client_id and algosec_client_secret are used to authenticate to AlgoSec APIs.
-
Validation ensures that the AlgoSec API credentials are set before Terraform runs.
5. Initialize local values
locals {
subscription_id = data.azurerm_subscription.target.subscription_id
subscription_name = data.azurerm_subscription.target.display_name
app_id = "<ALGOSEC_ONBOARDING_APP_ID>"
algosec_tenant_id = "<ALGOSEC_TENANT_ID>"
algosec_cloud_host = "https://<HOST>"
algosec_cloud_login_url = "${local.algosec_cloud_host}/api/algosaas/auth/v1/access-keys/login"
algosec_cloud_onboarding_url = "${local.algosec_cloud_host}<ONBOARDING_PATH>"
target_resource = "/subscriptions/${local.subscription_id}"
az_tenant = data.azurerm_client_config.current.tenant_id
support_changes = "<SUPPORT_CHANGES>"
}
Purpose: This section centralizes the core values used throughout the deployment.
It defines:
-
The AlgoSec onboarding application ID
-
The AlgoSec tenant ID
-
The AlgoSec API host and endpoint URLs
-
The Azure tenant ID
-
The target Azure subscription scope
-
The support-changes flag used during onboarding
6. Check for an existing AlgoSec service principal
data "azuread_service_principals" "existing" {
client_ids = [local.app_id]
}
Purpose: This section checks whether a service principal already exists in the Azure tenant for the AlgoSec application.
The result is used to decide whether Terraform should reuse the existing service principal or create a new one.
7. Create the service principal if needed
resource "azuread_service_principal" "algosec_app_sp" {
count = local.sp_exists ? 0 : 1
client_id = local.app_id
}
Purpose: If the AlgoSec service principal does not already exist in the Azure tenant, Terraform creates it automatically.
8. Add Roles and Assign to Target Resource
variable "roles" {
type = list(string)
default = [
<SERVICE_PRINCIPAL_ROLES>
]
}
Purpose: This section defines the built-in Azure roles that AlgoSec requires for the onboarding flow.
These roles allow the onboarding principal to access the Azure services and resources required for the integrated monitoring and App Analyzer workflow.
9. Assign the built-in Azure roles
resource "azurerm_role_assignment" "algosec_assignments" {
for_each = toset(var.roles)
scope = local.target_resource
role_definition_name = each.value
principal_id = local.principal_object_id
}
Purpose: This section assigns all required built-in roles to the AlgoSec service principal.
The assignments are made at the subscription scope so the AlgoSec onboarding principal can access the relevant resources throughout the onboarded subscription.
10. Run AlgoSec onboarding API call after Azure roles are ready
resource "null_resource" "algosec_onboarding" {
provisioner "local-exec" {
when = create
command = "bash ${path.module}/scripts/algosec_azure_onboard.sh"
}
}
Purpose: This section performs the final AlgoSec onboarding API call after the Azure role assignments have been created.
The onboarding script:
-
Logs in to the AlgoSec API
-
Obtains an access token
-
Submits the Azure subscription onboarding request
This corresponds to the final API-driven onboarding step that connects the Azure subscription to AlgoSec after Azure-side preparation is complete.
11. Run AlgoSec offboarding API call during destroy
resource "null_resource" "algosec_offboarding" {
provisioner "local-exec" {
when = destroy
command = "bash ${path.module}/scripts/algosec_azure_offboard.sh"
on_failure = continue
}
}
Purpose: This section performs AlgoSec offboarding when the Terraform deployment is destroyed.
The offboarding script:
-
Logs in to the AlgoSec API
-
Obtains an access token
-
Submits the offboarding request for the subscription
This keeps the AlgoSec platform in sync with the Terraform lifecycle.