Azure Subscription Vending: Automated Workload Onboarding

min read

A workload team submits a ticket for a new Azure subscription. Three weeks later, a subscription exists — manually created, placed in the wrong management group, and named subscription-1 because no naming convention was enforced. The team deploys anyway, into an environment with no tags, no diagnostic settings, and no budget alert. This is the default outcome when subscription creation is manual.

As a landing zone scales, the cost of manual onboarding compounds. Every manual entry is a governance gap. Subscription vending replaces this with a PR-based workflow: a workload team submits a YAML file; the pipeline creates the subscription, places it in the hierarchy, deploys the spoke network, and configures the monitoring baseline — consistently, in under 10 minutes.

By the end of this guide, you will:

  • Identify which Azure billing account types support programmatic creation.
  • Build a Terraform vending module that provisions a complete application landing zone.
  • Implement the Bicep sub-vending AVM pattern module.
  • Design a PR approval workflow with automated validation.

This is Post 5 in the Azure Platform Engineering series.


Subscription Creation Prerequisites

Billing Account Compatibility

Programmatic creation requires specific billing roles:

  • Enterprise Agreement (EA): Requires Account Owner on the EA enrollment account.
  • Microsoft Customer Agreement (MCA): Requires Billing Profile Contributor or Invoice Section Owner. Note that MCA accounts default to a 5-subscription limit and 1 creation per 24 hours. Open a support ticket to increase these quotas early.
  • Pay-As-You-Go: Does not support programmatic creation.

Tip: Find your MCA billing IDs via CLI:

az billing profile list --account-name <account-name> --query "[].{Name:name, Id:id}"

What Every Vended Subscription Receives

The vending module is a contract. Every subscription receives:

  1. Hierarchy Placement: Correct MG tier (Corp, Online, Sandbox).
  2. Mandatory Tags: Owner, CostCenter, Environment.
  3. Spoke Networking: VNet with consistent naming and CIDR allocation.
  4. Hub Connectivity: Bidirectional peering with gateway transit.
  5. Egress Control: UDR routing all 0.0.0.0/0 traffic through the hub Firewall.
  6. Baseline RBAC: Contributor for the workload team.
  7. Monitoring: Activity logs forwarded to the platform Log Analytics Workspace.

Terraform Vending Module

Subscription and Propagation

resource "azurerm_subscription" "workload" {
  subscription_name = local.subscription_display_name
  alias             = local.subscription_display_name # Idempotent anchor
  billing_scope_id  = var.billing_scope
  workload          = var.environment == "sandbox" ? "DevTest" : "Production"
}

# Wait for Entra directory replication (90s)
resource "time_sleep" "wait_for_propagation" {
  create_duration = "90s"
  depends_on      = [azurerm_subscription.workload]
}

Spoke Networking

module "spoke_vnet" {
  source  = "Azure/avm-res-network-virtualnetwork/azurerm"
  version = "~> 0.7"
  providers = { azurerm = azurerm.workload }

  name                = "vnet-${var.workload_name}-001"
  address_space       = [var.vnet_address_prefix]

  subnets = {
    workload = {
      name                    = "snet-workload-001"
      address_prefixes        = [cidrsubnet(var.vnet_address_prefix, 2, 0)]
      route_table_resource_id = module.spoke_udr.resource_id # Correct attribute
    }
  }
}

Bicep Vending Module

Use the official AVM pattern module br/public:avm/ptn/lz/sub-vending.

module subVend 'br/public:avm/ptn/lz/sub-vending:0.3.0' = {
  name: 'subVend-${workloadName}'
  params: {
    subscriptionAliasEnabled: true
    subscriptionDisplayName: subscriptionDisplayName
    subscriptionAliasName: subscriptionDisplayName
    subscriptionBillingScope: billingScope
    subscriptionManagementGroupId: targetManagementGroupId
    virtualNetworkEnabled: true
    virtualNetworkAddressSpace: [vnetAddressPrefix]
    hubNetworkResourceId: hubVnetResourceId
  }
}

PR Approval Workflow

The workflow uses GitHub Actions to validate requests before they reach a platform engineer.

Validation Stage:

  1. Schema Check: Validates the YAML request.
  2. CIDR Overlap: Checks ipam.yaml to ensure no address space conflicts.
  3. Plan Preview: Posts the terraform plan output as a PR comment.

Apply Stage: Executes the deployment only after the PR is merged to main.

Fixing GHA Path Parsing: Ensure your GitHub Script correctly parses multiline file lists:

// Correct regex splitting for multiline git diff output
const files = '${{ steps.changed.outputs.files }}'.split(/\s+/).filter(Boolean);
  graph TD
    User[Workload Team] -- Submit YAML --> PR[Pull Request]
    subgraph CI_Validation [Automation Pipeline]
        PR -- Trigger --> Valid[Schema & CIDR Validation]
        Valid -- Plan --> Preview[Terraform Plan / What-If]
    end
    Preview -- Comment --> Approver[Platform Engineer]
    Approver -- Merge --> Deploy[Deployment Phase]
    subgraph Deployment [Azure Infrastructure]
        Deploy --> Sub[Create Subscription]
        Sub --> Net[VNet & Peering]
        Net --> Gov[Apply Policy & RBAC]
    end
    Gov --> Notify[Notify Team: Ready]

Notes:

  • PR-based self-service allows teams to request resources without manual ticketing delays.
  • Validation steps prevent IP address overlaps and configuration errors before deployment.
  • The final Notification provides the workload team with the credentials/access to their new, compliant environment.

Best Practices

  • Isolate State: Use one Terraform state file per subscription to isolate the blast radius of failures.
  • Mandatory Budgets: Make budget alerts a required field in the request schema.
  • Log Category Support: When configuring diagnostic settings, ensure the requested categories (like Administrative) are supported at the subscription level to avoid deployment warnings.
  • IPAM Source of Truth: Update your ipam.yaml in the same pipeline run that provisions the subscription to prevent race conditions.

Sources

Next, move to Post 6: Centralized Monitoring. We will deploy the Log Analytics Workspace that receives the activity logs from these vended subscriptions.