Azure

Follow this guide to provision Supermetal BYOC (Bring Your Own Cloud), which deploys Supermetal's data replication agents in your own Azure virtual cloud environment, maintaining full control and ownership over your data. This provides an additional layer of security and isolation. Supermetal handles provisioning, operations, and maintenance.


Overview

Supermetal BYOC Azure

Supermetal's BYOC model for Azure separates responsibilities between Supermetal's control plane and your Azure environment. The data replication agents run within your Azure Virtual Network (VNet), giving you full control over your data while Supermetal manages the agent lifecycle.

Prerequisites

To successfully deploy Supermetal BYOC in your Azure environment, please ensure you have the following:

Supermetal Account Identifiers

You'll need the following Supermetal Azure identifiers (obtainable from the Supermetal Console under Settings):

  • supermetal_tenant_id: Tenant ID of Supermetal's Azure AD
  • supermetal_app_id: The Application (Client) ID of Supermetal's Azure AD application registration
  • supermetal_principal_id: The Object ID of Supermetal's service principal (required for role assignments)
  • external_id: A unique identifier for securing cross-tenant access

Azure Account & Permissions

  • An Azure subscription with Owner or User Access Administrator permissions to create service principals and assign roles

Deployment Parameters

  • Azure Subscription ID (subscription_id): The ID of your Azure subscription where resources will be deployed
  • Azure Region (location): The Azure region for deployment (e.g., eastus2, westeurope)
  • Resource Group (resource_group_name): The name of a Resource Group where Supermetal will deploy resources
  • VNet ID (vnet_id): The resource ID of your existing Virtual Network where Supermetal agents will be deployed
  • Subnet IDs (private_subnet_ids): A list of subnet resource IDs within your specified VNet. Supermetal agents will be deployed in these subnets. Ensure these subnets are in the same region as your source (and target) databases
  • (Optional) Key Vault ID (credentials_key_vault_id): Decide if you'll use an existing Azure Key Vault for storing sensitive connector credentials. If you have an existing Key Vault, have its resource ID ready. If left blank, the bootstrap process will create a new Key Vault specifically for Supermetal

Setup

A two-phase process: first bootstrap the environment, then create connectors to deploy the data plane / agents in your Azure VNet.

Environment Bootstrap

The bootstrap process uses a Terraform script provided by Supermetal to provision the necessary resources, including the Service Principal, Role, and private endpoint. These resources enable Supermetal to automatically register your Azure environment with the Supermetal control plane over the private endpoint, and to deploy Supermetal data plane / agents in your VNet.

Environment Bootstrap

The initial environment bootstrap process does not provision any compute resources or the data plane / agents. Once the environment bootstrap is complete, you can create connectors to deploy Supermetal data plane / agents on-demand in your VNet.

Obtain Terraform Script

  • Obtain the latest Terraform script from the Supermetal Console. The Terraform code in the appendix can be used as a reference.

Configure Terraform

  • Ensure you have Terraform installed (version 1.0 or later).
  • Set up your Azure credentials for Terraform. This typically involves authenticating with the Azure CLI using az login and ensuring you have the appropriate permissions.
  • Verify access by running az account show. You should see your Azure subscription details.

Prepare Terraform Files

  • Create a new directory for your Terraform configuration.
  • If using the appendix script, save the Terraform code into files within this directory (e.g., main.tf).
  • Create a terraform.tfvars file in the same directory to specify the required variable values. Alternatively, you can pass variables via the command line.

Example terraform.tfvars:

# Supermetal identifiers (provided by Supermetal)
supermetal_tenant_id      = "00000000-0000-0000-0000-000000000000"
supermetal_app_id         = "00000000-0000-0000-0000-000000000000"
supermetal_principal_id   = "00000000-0000-0000-0000-000000000000"
external_id                = "unique-external-id-from-supermetal"

# Azure environment parameters
subscription_id                      = "00000000-0000-0000-0000-000000000000"
location                              = "eastus2"
resource_group_name                   = "supermetal-byoc-rg"
vnet_id                              = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/network-rg/providers/Microsoft.Network/virtualNetworks/production-vnet"
private_subnet_ids                           = [
  "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/network-rg/providers/Microsoft.Network/virtualNetworks/production-vnet/subnets/data-subnet",
  "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/network-rg/providers/Microsoft.Network/virtualNetworks/production-vnet/subnets/private-endpoint-subnet"
]
# credentials_key_vault_id                        = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/security-rg/providers/Microsoft.KeyVault/vaults/my-existing-keyvault" # Optional

Parameters

Refer to the Deployment Parameters section and the variable descriptions within the Terraform script for detailed explanations of each.

  • supermetal_tenant_id - Tenant ID of Supermetal's Azure AD
  • supermetal_app_id - Application (Client) ID of Supermetal's Azure AD application
  • supermetal_principal_id - Object ID of Supermetal's service principal
  • external_id - Unique identifier for securing cross-tenant access
  • subscription_id - Your Azure subscription ID
  • location - Azure region for deployment
  • resource_group_name - Resource group name for Supermetal resources
  • vnet_id - Resource ID of your existing VNet
  • private_subnet_ids - List of subnet resource IDs within your VNet
  • credentials_key_vault_id (Optional) - Resource ID of your existing Key Vault

Deploy with Terraform CLI

  • Navigate to your Terraform configuration directory in your terminal.
  • Initialize Terraform:
    terraform init
  • Review the execution plan with your tfvars file:
    terraform plan -var-file=terraform.tfvars
  • Apply the configuration using your tfvars file:
    terraform apply -var-file=terraform.tfvars
    Confirm the action by typing yes when prompted.

Verification

  • Monitor the terraform apply command output. It will indicate when the resources have been successfully created.
  • Once the Terraform apply is complete, Supermetal's Control Plane automatically performs a registration process for the new environment.
  • Monitor the Environments page in your Supermetal Console. The environment should show up with a "Ready" status within a few minutes after Terraform completes.

Create Connector(s) to deploy Supermetal agents

Once the environment bootstrap is complete, we are now ready to create a connector to deploy Supermetal agents in your Azure environment.

Select source database

Follow the connector setup instructions in the Supermetal Console to select a source database, supermetal can optionally auto discover available source databases.

Configure Database Network Security Group

To give Supermetal access to your source database and to validate network reachability and connection credentials, you need to ensure your database's network security group allows connections from the Supermetal agent subnet.

Configure Database Access

Supermetal Console will list pre-filled steps to configure the database access as part of the prerequisite steps similar to the steps listed below.

# Set variables for your environment
RESGROUP="your-resource-group-name"
CONNECTOR_NAME="connector1"  # Unique identifier for this connector
NSG_NAME="supermetal-${CONNECTOR_NAME}-nsg"  # Unique NSG name for this connector
DB_NSG_NAME="your-db-nsg-name"
VNET_NAME="your-vnet-name"
AGENT_SUBNET_NAME="your-agent-subnet-name"  # Subnet where Supermetal agents will be deployed
LOCATION="eastus2"
DB_PORT=5432  # For PostgreSQL
SUPERMETAL_METADATA_ENDPOINT="Provided-By-Supermetal"  # Specific endpoint for Supermetal metadata service

# Create the NSG for Supermetal agents
az network nsg create \
  --resource-group $RESGROUP \
  --name $NSG_NAME \
  --location $LOCATION

# Add outbound rule for metadata private endpoint access
az network nsg rule create \
  --resource-group $RESGROUP \
  --nsg-name $NSG_NAME \
  --name "AllowMetadataAccess" \
  --priority 100 \
  --direction "Outbound" \
  --access "Allow" \
  --protocol "Tcp" \
  --destination-port-ranges 443 \
  --destination-address-prefixes $SUPERMETAL_METADATA_ENDPOINT \
  --description "Allow access to Supermetal metadata service"

# Add outbound rule for database access
az network nsg rule create \
  --resource-group $RESGROUP \
  --nsg-name $NSG_NAME \
  --name "AllowDatabaseAccess" \
  --priority 110 \
  --direction "Outbound" \
  --access "Allow" \
  --protocol "Tcp" \
  --destination-port-ranges $DB_PORT \
  --destination-address-prefixes "YOUR_DATABASE_SUBNET_PREFIX" \
  --description "Allow access to database"

# Add inbound rule to database NSG to allow access from Supermetal agent
az network nsg rule create \
  --resource-group $RESGROUP \
  --nsg-name $DB_NSG_NAME \
  --name "AllowSupermetalAgentAccess" \
  --priority 100 \
  --direction "Inbound" \
  --access "Allow" \
  --protocol "Tcp" \
  --source-address-prefixes "YOUR_AGENT_SUBNET_PREFIX" \
  --destination-port-ranges $DB_PORT \
  --description "Allow Supermetal agent access to database"

# Associate the NSG with the agent subnet
az network vnet subnet update \
  --resource-group $RESGROUP \
  --vnet-name $VNET_NAME \
  --name $AGENT_SUBNET_NAME \
  --network-security-group $NSG_NAME

Parameters

  • RESGROUP: Your Azure resource group name where the NSGs will be created
  • CONNECTOR_NAME: A unique identifier for this connector (important if you're deploying multiple connectors)
  • VNET_NAME: The name of your Azure Virtual Network
  • AGENT_SUBNET_NAME: The name of the subnet where Supermetal agents will be deployed
  • YOUR_DATABASE_SUBNET_PREFIX: CIDR range of your database subnet (e.g., "10.0.2.0/24")
  • YOUR_AGENT_SUBNET_PREFIX: CIDR range of the subnet where Supermetal agents will be deployed
  • DB_PORT: The port number your database listens on (e.g., 5432 for PostgreSQL)
  • SUPERMETAL_METADATA_ENDPOINT: Specific endpoint for the Supermetal metadata service (provided by Supermetal)

Create Network Security Group and Configure Database Access

  1. Navigate to the Azure Portal > Network Security Groups
  2. Click Create network security group
  3. Configure the basic details:
    • Resource group: Select your resource group
    • Name: "supermetal-${connector-name}-nsg" (e.g., "supermetal-connector1-nsg")
    • Region: Select the same region as your Azure resources
  4. Click Review + create and then Create

Configure Outbound Rules

  1. Once created, navigate to the new NSG
  2. Select Outbound security rules
  3. Click Add to create an outbound rule for metadata access:
    • Destination: IP Addresses
    • Destination IP addresses: Supermetal-provided metadata endpoint address
    • Service: HTTPS
    • Port ranges: 443
    • Priority: 100
    • Name: "AllowMetadataAccess"
  4. Add another outbound rule for database access:
    • Destination: IP Addresses
    • Destination IP addresses/CIDR ranges: Your database subnet CIDR
    • Service: Custom
    • Protocol: TCP
    • Port ranges: Your database port (e.g., 5432)
    • Priority: 110
    • Name: "AllowDatabaseAccess"

Configure Database NSG Inbound Rule

  1. Navigate to your database's NSG
  2. Select Inbound security rules
  3. Click Add to create a new rule:
    • Source: IP Addresses
    • Source IP addresses/CIDR ranges: Your agent subnet CIDR
    • Service: Custom
    • Protocol: TCP
    • Port ranges: Your database port (e.g., 5432)
    • Priority: 100
    • Name: "AllowSupermetal${ConnectorName}Access" (e.g., "AllowSupermetalConnector1Access")
  4. Save the rule

Associate NSG with Subnet

  1. Navigate to Virtual Networks > your VNet > Subnets
  2. Click on the subnet where Supermetal agents will be deployed
  3. Under Network security group, select the NSG you created earlier
  4. Click Save

Create Network Security Group and Configure Database Access

# Variables for connector-specific resources
variable "connector_name" {
  description = "Unique identifier for this connector (used in resource naming)"
  type        = string
  default     = "connector1"
}

variable "vnet_name" {
  description = "Name of your virtual network"
  type        = string
}

variable "agent_subnet_name" {
  description = "Name of the subnet where Supermetal agents will be deployed"
  type        = string
}

variable "supermetal_metadata_endpoint" {
  description = "Specific endpoint for Supermetal metadata service (provided by Supermetal)"
  type        = string
}

# Create the agent network security group with unique name for this connector
resource "azurerm_network_security_group" "supermetal_agent_nsg" {
  name                = "supermetal-${var.connector_name}-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  # Outbound rule for metadata private endpoint access
  security_rule {
    name                       = "AllowMetadataAccess"
    priority                   = 100
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = var.supermetal_metadata_endpoint
    description                = "Allow access to Supermetal metadata service"
  }

  # Outbound rule for database access
  security_rule {
    name                       = "AllowDatabaseAccess"
    priority                   = 110
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = var.database_port
    source_address_prefix      = "*"
    destination_address_prefix = var.database_subnet_cidr
    description                = "Allow access to database"
  }
}

# Look up existing database NSG
data "azurerm_network_security_group" "database_nsg" {
  name                = var.database_nsg_name
  resource_group_name = var.database_resource_group_name
}

# Add inbound rule to database NSG
resource "azurerm_network_security_rule" "allow_supermetal_agent" {
  name                        = "AllowSupermetal${title(var.connector_name)}Access"
  priority                    = 100
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = var.database_port
  source_address_prefix       = var.agent_subnet_cidr
  destination_address_prefix  = "*"
  resource_group_name         = var.database_resource_group_name
  network_security_group_name = var.database_nsg_name
}

# Associate the NSG with the agent subnet
data "azurerm_virtual_network" "vnet" {
  name                = var.vnet_name
  resource_group_name = var.resource_group_name
}

data "azurerm_subnet" "agent_subnet" {
  name                 = var.agent_subnet_name
  virtual_network_name = var.vnet_name
  resource_group_name  = var.resource_group_name
}

resource "azurerm_subnet_network_security_group_association" "nsg_association" {
  subnet_id                 = data.azurerm_subnet.agent_subnet.id
  network_security_group_id = azurerm_network_security_group.supermetal_agent_nsg.id
}

# Output the NSG ID for reference
output "supermetal_agent_nsg_id" {
  value = azurerm_network_security_group.supermetal_agent_nsg.id
}

Parameters

  • connector_name: Unique identifier for this connector (important if you're deploying multiple connectors)
  • resource_group_name: Your Azure resource group name
  • location: Azure region for deployment
  • vnet_name: The name of your Azure Virtual Network
  • agent_subnet_name: The name of the subnet where Supermetal agents will be deployed
  • database_subnet_cidr: CIDR range of your database subnet (e.g., "10.0.2.0/24")
  • agent_subnet_cidr: CIDR range of the subnet where Supermetal agents will be deployed
  • database_nsg_name: The name of your existing database NSG
  • database_resource_group_name: The resource group containing your database NSG
  • database_port: The port number your database listens on (e.g., 5432 for PostgreSQL)
  • supermetal_metadata_endpoint: Specific endpoint for Supermetal metadata service (provided by Supermetal)

Validate Source database

Follow the setup instructions in the Supermetal Console to input the created NSG ID for the source database and validate the network reachability and connection credentials.

Repeat for Target database

Repeat the same steps for the target database.

Finalize

Once the source and target database connections are validated, you can finalize the connector setup which will launch the Supermetal agents in your Azure VNet.


Appendix

Environment Bootstrap Scripts

Supermetal provides Terraform scripts for bootstrapping your Azure environment. The scripts create all necessary resources and permissions for Supermetal to manage the data replication agents in your Azure subscription.

terraform {
  required_version = ">= 1.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 2.0"
    }
  }
}

provider "azurerm" {
  features {
    key_vault {
      purge_soft_delete_on_destroy    = false
      recover_soft_deleted_key_vaults = true
    }
  }
}

provider "azuread" {}

# =============================================================================
# Data Sources
# =============================================================================

data "azurerm_client_config" "current" {}

data "azurerm_subscription" "current" {}

# =============================================================================
# Input Variables
# =============================================================================

variable "resource_group_name" {
  description = "Name of the Resource Group where Supermetal will deploy resources"
  type        = string
  validation {
    condition     = can(regex("^[a-zA-Z0-9-_]+$", var.resource_group_name))
    error_message = "Resource group name must contain only alphanumeric characters, hyphens, and underscores."
  }
}

variable "location" {
  description = "Azure region for deployment (e.g., eastus, westeurope)"
  type        = string
}

variable "vnet_id" {
  description = "Resource ID of your existing Virtual Network (e.g., /subscriptions/.../resourceGroups/.../providers/Microsoft.Network/virtualNetworks/...)"
  type        = string
}

variable "private_subnet_ids" {
  description = "List of private subnet Resource IDs where agents and private endpoints will be deployed"
  type        = list(string)
  validation {
    condition     = length(var.private_subnet_ids) > 0
    error_message = "At least one private subnet must be provided."
  }
}

variable "credentials_key_vault_id" {
  description = "Resource ID of existing Key Vault for storing credentials. If empty, a new Key Vault will be created"
  type        = string
  default     = ""
}

variable "external_id" {
  description = "Unique identifier provided by Supermetal Console for securing cross-tenant access"
  type        = string
  sensitive   = true
}

variable "supermetal_tenant_id" {
  description = "Azure AD Tenant ID of Supermetal Control Plane"
  type        = string
}

variable "supermetal_app_id" {
  description = "Azure AD Application ID of Supermetal Control Plane"
  type        = string
}

variable "supermetal_principal_id" {
  description = "Object ID of Supermetal's Service Principal"
  type        = string
}

variable "supermetal_metadata_service_id" {
  description = "Resource ID of Supermetal Metadata Service's Private Link Service"
  type        = string
}

variable "supermetal_container_registry_name" {
  description = "Name of Supermetal's container registry for agent images"
  type        = string
}

# =============================================================================
# Local Values
# =============================================================================

locals {
  # Parse VNet information
  vnet_parts          = split("/", var.vnet_id)
  vnet_name          = element(local.vnet_parts, length(local.vnet_parts) - 1)
  vnet_resource_group = element(local.vnet_parts, index(local.vnet_parts, "resourceGroups") + 1)
  
  # Subnet selection
  primary_subnet_id = var.private_subnet_ids[0]
  
  # Key Vault configuration
  create_key_vault      = var.credentials_key_vault_id == ""
  key_vault_id         = local.create_key_vault ? azurerm_key_vault.credentials[0].id : var.credentials_key_vault_id
  key_vault_name       = local.create_key_vault ? "supermetal-kv-${substr(md5(var.resource_group_name), 0, 8)}" : ""
  existing_kv_rg       = local.create_key_vault ? "" : split("/", var.credentials_key_vault_id)[4]
  existing_kv_name     = local.create_key_vault ? "" : split("/", var.credentials_key_vault_id)[8]
  
  # Common tags
  common_tags = {
    "supermetal:byoc:external-id"     = var.external_id
    "supermetal:byoc:resource-group"  = var.resource_group_name
    "supermetal:byoc:stack-name"      = "supermetal-bootstrap-${var.resource_group_name}"
    "supermetal:managed"              = "true"
    "Environment"                     = "Production"
  }
}

# =============================================================================
# Resource Group Data Source
# =============================================================================

data "azurerm_resource_group" "target" {
  name = var.resource_group_name
}

# =============================================================================
# Networking Data Sources
# =============================================================================

data "azurerm_virtual_network" "vnet" {
  name                = local.vnet_name
  resource_group_name = local.vnet_resource_group
}

data "azurerm_subnet" "primary" {
  name                 = element(split("/", local.primary_subnet_id), length(split("/", local.primary_subnet_id)) - 1)
  virtual_network_name = local.vnet_name
  resource_group_name  = local.vnet_resource_group
}

# =============================================================================
# Custom Role Definition for Supermetal Control Plane
# =============================================================================

resource "azurerm_role_definition" "supermetal_control_plane" {
  name        = "SupermetalControlPlaneRole-${var.resource_group_name}"
  scope       = data.azurerm_subscription.current.id
  description = "Allows Supermetal Control Plane to manage resources in customer subscription"

  permissions {
    actions = [
      # ===== Virtual Machine Scale Sets =====
      "Microsoft.Compute/virtualMachineScaleSets/read",
      "Microsoft.Compute/virtualMachineScaleSets/write",
      "Microsoft.Compute/virtualMachineScaleSets/delete",
      "Microsoft.Compute/virtualMachineScaleSets/start/action",
      "Microsoft.Compute/virtualMachineScaleSets/powerOff/action",
      "Microsoft.Compute/virtualMachineScaleSets/restart/action",
      "Microsoft.Compute/virtualMachineScaleSets/deallocate/action",
      "Microsoft.Compute/virtualMachineScaleSets/manualUpgrade/action",
      "Microsoft.Compute/virtualMachineScaleSets/scale/action",
      "Microsoft.Compute/virtualMachineScaleSets/instanceView/read",
      "Microsoft.Compute/virtualMachineScaleSets/skus/read",
      
      # ===== VMSS Extensions (for Docker/Agent setup) =====
      "Microsoft.Compute/virtualMachineScaleSets/extensions/read",
      "Microsoft.Compute/virtualMachineScaleSets/extensions/write",
      "Microsoft.Compute/virtualMachineScaleSets/extensions/delete",
      
      # ===== Virtual Machines (VMSS instances) =====
      "Microsoft.Compute/virtualMachines/read",
      "Microsoft.Compute/virtualMachines/instanceView/read",
      
      # ===== Managed Disks =====
      "Microsoft.Compute/disks/read",
      "Microsoft.Compute/disks/write",
      "Microsoft.Compute/disks/delete",
      
      # ===== Container Instances (for validation) =====
      "Microsoft.ContainerInstance/containerGroups/read",
      "Microsoft.ContainerInstance/containerGroups/write",
      "Microsoft.ContainerInstance/containerGroups/delete",
      "Microsoft.ContainerInstance/containerGroups/restart/action",
      "Microsoft.ContainerInstance/containerGroups/stop/action",
      "Microsoft.ContainerInstance/containerGroups/start/action",
      "Microsoft.ContainerInstance/containerGroups/containers/logs/read",
      "Microsoft.ContainerInstance/containerGroups/containers/exec/action",
      
      # ===== Container Registry Access =====
      "Microsoft.ContainerRegistry/registries/pull/read",
      "Microsoft.ContainerRegistry/registries/artifacts/read",
      "Microsoft.ContainerRegistry/registries/metadata/read",
      
      # ===== Storage Accounts =====
      "Microsoft.Storage/storageAccounts/read",
      "Microsoft.Storage/storageAccounts/write",
      "Microsoft.Storage/storageAccounts/delete",
      "Microsoft.Storage/storageAccounts/listkeys/action",
      "Microsoft.Storage/storageAccounts/listAccountSas/action",
      "Microsoft.Storage/storageAccounts/blobServices/containers/read",
      "Microsoft.Storage/storageAccounts/blobServices/containers/write",
      "Microsoft.Storage/storageAccounts/blobServices/containers/delete",
      
      # ===== Networking =====
      "Microsoft.Network/virtualNetworks/subnets/join/action"
      "Microsoft.Network/networkInterfaces/read"  
      "Microsoft.Network/networkSecurityGroups/read"
      "Microsoft.Network/networkSecurityGroups/join/action"
      
      # ===== Key Vault Management =====
      "Microsoft.KeyVault/vaults/read",
      
      # ===== Database Discovery (Read-only) - Scoped to resource group =====
      "Microsoft.Sql/servers/read",
      "Microsoft.Sql/servers/databases/read",
      "Microsoft.DBforPostgreSQL/servers/read",
      "Microsoft.DBforPostgreSQL/flexibleServers/read",
      "Microsoft.DBforMySQL/servers/read",
      "Microsoft.DBforMySQL/flexibleServers/read",
      "Microsoft.DBforMariaDB/servers/read"
    ]
    
    not_actions = []
    
    data_actions = [
      # ===== Key Vault Data Operations (scoped to supermetal-* items) =====
      "Microsoft.KeyVault/vaults/secrets/getSecret/action",
      "Microsoft.KeyVault/vaults/secrets/setSecret/action",
      "Microsoft.KeyVault/vaults/keys/encrypt/action",
      "Microsoft.KeyVault/vaults/keys/decrypt/action",
      
      # ===== Storage Data Operations =====
      "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read",
      "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write",
      "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete",
      "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/add/action"
    ]
    
    not_data_actions = []
  }

  assignable_scopes = [
    data.azurerm_resource_group.target.id
  ]
}

# =============================================================================
# Role Assignment for Supermetal Control Plane
# =============================================================================

resource "azurerm_role_assignment" "supermetal_control_plane" {
  scope                = data.azurerm_resource_group.target.id
  role_definition_id   = azurerm_role_definition.supermetal_control_plane.role_definition_resource_id
  principal_id         = var.supermetal_principal_id
  principal_type       = "ServicePrincipal"
  
  # Condition to validate this is the correct service principal
  condition = <<-EOT
    (
      @Request[Microsoft.Authorization/roleAssignments:principalId] == '${var.supermetal_principal_id}'
    )
    AND
    (
      @Request[Microsoft.Authorization/roleAssignments:principalType] == 'ServicePrincipal'
    )
  EOT
  
  condition_version = "2.0"
  description      = "Supermetal Control Plane access with External ID: ${var.external_id}"
}

# =============================================================================
# User-Assigned Managed Identities
# =============================================================================

# Identity for Supermetal Agents (VMSS)
resource "azurerm_user_assigned_identity" "agent" {
  name                = "supermetal-agent-identity-${var.resource_group_name}"
  location            = var.location
  resource_group_name = var.resource_group_name
  tags                = local.common_tags
}

# Identity for Validation Containers (ACI)
resource "azurerm_user_assigned_identity" "validation" {
  name                = "supermetal-validation-identity-${var.resource_group_name}"
  location            = var.location
  resource_group_name = var.resource_group_name
  tags                = local.common_tags
}

# =============================================================================
# Key Vault for Credentials
# =============================================================================

resource "azurerm_key_vault" "credentials" {
  count                      = local.create_key_vault ? 1 : 0
  name                       = local.key_vault_name
  location                   = var.location
  resource_group_name        = var.resource_group_name
  tenant_id                  = data.azurerm_client_config.current.tenant_id
  sku_name                   = "standard"
  soft_delete_retention_days = 90
  purge_protection_enabled   = true
  enable_rbac_authorization  = false

  network_acls {
    default_action = "Deny"
    bypass         = "AzureServices"
    ip_rules       = []
    virtual_network_subnet_ids = var.private_subnet_ids
  }

  tags = local.common_tags
}

# =============================================================================
# Key Vault Access Policies
# =============================================================================

# Access for Supermetal Control Plane (encrypt only)
resource "azurerm_key_vault_access_policy" "control_plane" {
  key_vault_id = local.key_vault_id
  tenant_id    = var.supermetal_tenant_id
  object_id    = var.supermetal_principal_id

  key_permissions = [
    "Get",
    "List",
    "Encrypt"
  ]

  secret_permissions = [
    "Set",
    "List"
  ]
}

# Access for Agent Identity (decrypt)
resource "azurerm_key_vault_access_policy" "agent" {
  key_vault_id = local.key_vault_id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_user_assigned_identity.agent.principal_id

  key_permissions = [
    "Get",
    "Decrypt"
  ]

  secret_permissions = [
    "Get"
  ]
}

# Access for Validation Identity
resource "azurerm_key_vault_access_policy" "validation" {
  key_vault_id = local.key_vault_id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_user_assigned_identity.validation.principal_id

  key_permissions = [
    "Get",
    "Decrypt"
  ]

  secret_permissions = [
    "Get"
  ]
}

# =============================================================================
# Network Security Groups
# =============================================================================

# NSG for Agent Metadata Access
resource "azurerm_network_security_group" "agent_metadata" {
  name                = "supermetal-agent-metadata-nsg-${var.resource_group_name}"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "AllowHttpsToMetadataEndpoint"
    priority                   = 100
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "VirtualNetwork"
    destination_address_prefix = "PrivateEndpoint"
    description                = "Allow HTTPS traffic to Supermetal metadata private endpoint"
  }

  security_rule {
    name                       = "AllowAzureStorage"
    priority                   = 110
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "VirtualNetwork"
    destination_address_prefix = "Storage.${var.location}"
    description                = "Allow access to Azure Storage for buffer operations"
  }

  security_rule {
    name                       = "AllowAzureKeyVault"
    priority                   = 120
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "VirtualNetwork"
    destination_address_prefix = "AzureKeyVault.${var.location}"
    description                = "Allow access to Azure Key Vault for credential retrieval"
  }

  tags = local.common_tags
}

# =============================================================================
# Private Endpoint for Metadata Service
# =============================================================================

resource "azurerm_private_endpoint" "metadata" {
  name                = "supermetal-metadata-endpoint-${var.resource_group_name}"
  location            = var.location
  resource_group_name = var.resource_group_name
  subnet_id           = local.primary_subnet_id

  private_service_connection {
    name                           = "supermetal-metadata-connection"
    private_connection_resource_id = var.supermetal_metadata_service_id
    is_manual_connection           = false
    subresource_names              = ["metadata"]
  }

  tags = local.common_tags
}

# =============================================================================
# Container Registry Access Role Assignments
# =============================================================================

# Role assignment for agent identity to pull images
resource "azurerm_role_assignment" "agent_acr_pull" {
  scope                = "/subscriptions/${var.supermetal_tenant_id}/resourceGroups/supermetal-control-plane/providers/Microsoft.ContainerRegistry/registries/${var.supermetal_container_registry_name}"
  role_definition_name = "AcrPull"
  principal_id         = azurerm_user_assigned_identity.agent.principal_id
  principal_type       = "ServicePrincipal"
}

# Role assignment for validation identity to pull images
resource "azurerm_role_assignment" "validation_acr_pull" {
  scope                = "/subscriptions/${var.supermetal_tenant_id}/resourceGroups/supermetal-control-plane/providers/Microsoft.ContainerRegistry/registries/${var.supermetal_container_registry_name}"
  role_definition_name = "AcrPull"
  principal_id         = azurerm_user_assigned_identity.validation.principal_id
  principal_type       = "ServicePrincipal"
}

# =============================================================================
# Outputs
# =============================================================================

output "control_plane_role_definition_id" {
  description = "ID of the custom role definition for Supermetal Control Plane"
  value       = azurerm_role_definition.supermetal_control_plane.role_definition_resource_id
}

output "control_plane_role_assignment_id" {
  description = "ID of the role assignment for Supermetal Control Plane"
  value       = azurerm_role_assignment.supermetal_control_plane.id
}

output "agent_identity_resource_id" {
  description = "Resource ID of the Managed Identity for Supermetal agents"
  value       = azurerm_user_assigned_identity.agent.id
}

output "agent_identity_client_id" {
  description = "Client ID of the Managed Identity for Supermetal agents"
  value       = azurerm_user_assigned_identity.agent.client_id
}

output "agent_identity_principal_id" {
  description = "Principal ID of the Managed Identity for Supermetal agents"
  value       = azurerm_user_assigned_identity.agent.principal_id
}

output "validation_identity_resource_id" {
  description = "Resource ID of the Managed Identity for validation containers"
  value       = azurerm_user_assigned_identity.validation.id
}

output "validation_identity_client_id" {
  description = "Client ID of the Managed Identity for validation containers"
  value       = azurerm_user_assigned_identity.validation.client_id
}

output "key_vault_id" {
  description = "Resource ID of the Key Vault for credentials"
  value       = local.key_vault_id
}

output "key_vault_uri" {
  description = "URI of the Key Vault for credentials"
  value       = local.create_key_vault ? azurerm_key_vault.credentials[0].vault_uri : "https://${local.existing_kv_name}.vault.azure.net/"
}

output "metadata_endpoint_id" {
  description = "Resource ID of the Private Endpoint for Supermetal Metadata Service"
  value       = azurerm_private_endpoint.metadata.id
}

output "metadata_endpoint_ip" {
  description = "Private IP address of the Metadata Service endpoint"
  value       = azurerm_private_endpoint.metadata.private_service_connection[0].private_ip_address
}

output "agent_metadata_nsg_id" {
  description = "Resource ID of the NSG for agent metadata access"
  value       = azurerm_network_security_group.agent_metadata.id
}

output "bootstrap_timestamp" {
  description = "Timestamp when bootstrap was completed"
  value       = timestamp()
}

Azure Roles & Permissions

Bootstrap creates several Azure roles and managed identities with specific permissions necessary for Supermetal BYOC to function securely. These roles ensure that the Supermetal Control Plane can manage resources in your Azure subscription on your behalf, and that the Supermetal agents have the necessary permissions to operate. The principle of least privilege is applied to scope down permissions as much as possible.

Key roles created and their purposes:

Supermetal Control Plane Role

This role is assigned to the Supermetal Service Principal. It enables the Supermetal Control Plane to orchestrate and manage the lifecycle of resources required for the Supermetal data plane within your Azure subscription.

  • Virtual Machine Scale Sets Management

    • Create, read, update, and delete VMSS resources (Microsoft.Compute/virtualMachineScaleSets/*)
    • Control instance lifecycle (start, stop, restart, scale) for agent deployment
    • Manage VMSS extensions for Docker/agent setup
    • Read-only access to VM instances (Microsoft.Compute/virtualMachines/read)
  • Container Instances and Registry

    • Full management of Container Instances for validation tasks (Microsoft.ContainerInstance/containerGroups/*)
    • Pull access to container registry for agent images (Microsoft.ContainerRegistry/registries/pull/read)
    • Used for deploying validation containers
  • Storage and Data Management

    • Storage Account management (Microsoft.Storage/storageAccounts/*)
    • Used for data buffering and staging per connector
  • Networking

    • Read VNet and subnet information (Microsoft.Network/virtualNetworks/*/read)
    • Join subnets for resource deployment (Microsoft.Network/virtualNetworks/subnets/join/action)
    • Read and join network security groups
  • Security and Access Control

    • Key Vault read access (Microsoft.KeyVault/vaults/read)
    • Key Vault data operations (get/set secrets, encrypt/decrypt with keys)
    • Restricted to resource group scope specified during bootstrap
  • Database Discovery

    • Read-only access to database resources (Microsoft.Sql/servers/read, Microsoft.DBforPostgreSQL/*/read, etc.)
    • Limited to discovery capabilities for validating connections

Agent and Validation Identities

This User-Assigned Managed Identity is attached to the VMSS instances running Supermetal agents. It provides the following permissions:

  • Key Vault Access

    • Get and decrypt operations on keys (Get, Decrypt)
    • Retrieve secrets from Key Vault (Get)
    • Used to securely access connection credentials
  • Container Registry Access

    • AcrPull role on Supermetal's container registry
    • Enables pulling agent container images from Supermetal's registry

This User-Assigned Managed Identity is used by Azure Container Instances (ACI) that may be deployed for validation tasks such as testing database connectivity or network reachability.

  • Key Vault Access

    • Similar permissions to the Agent identity (Get, Decrypt)
    • Retrieves necessary connection credentials for validation
  • Container Registry Access

    • AcrPull role on Supermetal's container registry
    • Enables pulling validation container images

Last updated on