DEV Community

Abhishek Kadlii
Abhishek Kadlii

Posted on

Stopping Rogue Deployments: How I Programmed Azure API Guardrails to Protect the Cloud Wallet

Every cloud architect knows the sudden wave of anxiety that comes with opening a billing dashboard and seeing an unexpected cost spike. In large enterprise environments, these spikes rarely happen due to malicious attacks—they happen due to human error. A junior developer spinning up a high-performance compute or GPU node for a minor test case and forgetting to deprovision it over the weekend can vaporize a sandbox budget in hours.

Instead of relying on warning emails, training manuals, or reactive cleanup scripts, I stepped up as a DevSecOps engineer to solve this problem at the source. I programmed the Azure Resource Manager (ARM) API gateway itself to automatically intercept and decline unauthorized resource allocations using Azure Policy and Custom RBAC Least-Privilege roles.


🏰 The Analogy: The Bouncing Corporate Credit Card

To understand this architecture, look at how corporate spending is managed in the physical world:

  • The Weak Setup (The Honor System): Handing a corporate credit card with a ₹5,00,000 limit to an employee, giving them a policy handbook, and hoping they do not buy a luxury watch. If they make a mistake, the money is already gone, and you are left doing damage control.
  • The Guardrail Setup (The Terminal Lock): Programming the payment terminal directly at the cash register. If the employee attempts to swipe the card for anything outside approved inventory codes or locations, the transaction is forcefully declined on the spot before a single rupee leaves the corporate account.
                               ┌──► [ Approved SKU: B-Series Only ] ──► ✅ ALLOWED (Passes Gate)
                               │
[ Deployment Request ] ──► [ Azure Policy ARM Gate ]
                               │
                               └──► [ Prohibited SKU: D-Series Node ] ──► ❌ DENIED (Declined at Register)
Enter fullscreen mode Exit fullscreen mode

🛠️ The Step-by-Step Command-Line Execution Blueprint

To demonstrate a production-grade governance implementation, I configured the deployment ring via the Azure CLI inside the Southeast Asia datacenter region using an isolated sandbox perimeter scope:

1. Designing the Custom Policy Criteria (allowed-skus.json)

I engineered a strict JSON rule block that targets virtual machine resources, setting the active evaluation flag to a forceful deny effect if the requested hardware profile falls outside of approved low-cost shapes:

{
  "if": {
    "allOf": [
      {
        "field": "type",
        "equals": "Microsoft.Compute/virtualMachines"
      },
      {
        "field": "Microsoft.Compute/virtualMachines/sku.name",
        "notIn": [
          "Standard_B1s",
          "Standard_B2ats_v2"
        ]
      }
    ]
  },
  "then": {
    "effect": "deny"
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Provisioning and Binding the Policy Scope

I initialized the baseline target resource group perimeter and registered the schema with the control plane, passing metadata parameters as explicit flags to satisfy the Azure CLI parser rules:

# Initialize the target perimeter container
az group create --name "Marathahalli_Lab_RG" --location "southeastasia"

# Register the core custom definition rule container
az policy definition create \
  --name "restrict-vm-skus" \
  --display-name "Restrict VM SKUs to B-Series" \
  --rules allowed-skus.json \
  --mode "Indexed"

# Assign the policy enforcer live to the specific Resource Group scope
az policy assignment create \
  --name "Enforce_B_Series_Only" \
  --policy "restrict-vm-skus" \
  --display-name "Enforce B-Series Only" \
  --resource-group "Marathahalli_Lab_RG"
Enter fullscreen mode Exit fullscreen mode

⚙️ Forging a Custom Least-Privilege RBAC Identity Role

Governance is incomplete without identity isolation. Instead of granting wide open contributor rights, I constructed a tailored JSON permission schema (vm-operator-role.json) defining a custom VM Restart Operator role. This role explicitly limits identity capabilities to virtual machine visibility and reboot actions, leaving write actions, configurations changes, and resource deletions sealed shut.

{
  "Name": "VM Restart Operator",
  "IsCustom": true,
  "Description": "Can only read and restart virtual machines inside the environment.",
  "Actions": [
    "Microsoft.Compute/virtualMachines/read",
    "Microsoft.Compute/virtualMachines/restart/action"
  ],
  "NotActions": [],
  "AssignableScopes": [
    "/subscriptions/YOUR_SUBSCRIPTION_ID"
  ]
}
Enter fullscreen mode Exit fullscreen mode

I programmatically extracted the subscription context and registered the custom RBAC identity blueprint live with the Azure identity control plane API:

# Dynamically fetch current active subscription ID text string
SUB_ID=$(az account show --query id --output tsv)

# Inject the active subscription path into our assignable scopes payload
sed -i "s|YOUR_SUBSCRIPTION_ID|$SUB_ID|g" vm-operator-role.json

# Register the custom RBAC identity blueprint live in the active directory tenant
az role definition create --role-definition vm-operator-role.json
Enter fullscreen mode Exit fullscreen mode

📓 The TAC Engineer's Troubleshooting Journal

Engineering in production means breaking things and solving real conflicts. During this implementation lab, I ran into two distinct runtime errors that provided vital architectural insights:

🚨 1. Top-Level Schema Mismatch Failures (Code: InvalidPolicyRule)

  • The Challenge: The initial registration utility rejected the code payload, throwing a fatal syntax validation error pointing to a schema failure on the "properties" and "mode" parameters.
  • The Root Cause: The az policy definition create engine expects the underlying --rules file configuration to start directly with the structural logic parameters (if and then). Wrapping these keys in metadata tags violates the parser's expected payload format.
  • The Fix: Stripped the outer property layers out of the allowed-skus.json file completely, and passed meta properties like --mode "Indexed" out to explicit command-line flags.

🚨 2. Scope Binding Failures via Missing Context Containers (Code: ResourceGroupNotFound)

  • The Challenge: The policy assignment mapping crashed, returning a terminal exception indicating that the target resource group room could not be found.
  • The Root Cause: Strict cost-control practices dictate tearing down all sandbox groups immediately after structural validations. Running an active policy assignment against an environment scope that doesn't exist crashes the engine call.
  • The Fix: Refactored the command execution queue to explicitly guarantee the presence of the resource group container (az group create) before executing assignment mappings.

🚀 Live Workload Validation: The RequestDisallowedByPolicy Block

To prove the automated guardrails work perfectly under load, I simulated an accidental budget breach by forcing a deployment command for an unauthorized, high-tier enterprise instance shape (Standard_D4s_v3) directly against the protected resource group perimeter:

az vm create \
  --resource-group "Marathahalli_Lab_RG" \
  --name "Rogue_VM" \
  --image "Ubuntu2204" \
  --size "Standard_D4s_v3" \
  --admin-username "abhishek" \
  --generate-ssh-keys
Enter fullscreen mode Exit fullscreen mode

📸 Defensive Proof Point: The Wallet Secured

The deployment call spun for a brief moment, hit the ARM API gateway checkpoint, evaluated against the active assignment metadata constraints, and was violently rejected! The core control plane blocked resource generation immediately:

azure.core.exceptions.HttpResponseError: (InvalidTemplateDeployment) The template deployment failed because of policy violation. 
Code: InvalidTemplateDeployment
Message: The template deployment failed because of policy violation. Please see details for more information.

Exception Details: (RequestDisallowedByPolicy) Resource 'Rogue_VM' was disallowed by policy 'Enforce_B_Series_Only'.
Enter fullscreen mode Exit fullscreen mode


💡 Core FinOps & AZ-104 Exam Lessons Learned

  • Deny vs. Audit Effects: The deny effect completely halts non-compliant resource allocation at the gate, protecting the budget instantly. The audit effect, conversely, allows deployments to succeed but flags them inside a compliance dashboard—ideal for mapping out production estates without creating application downtime risks.
  • RBAC Scoping Precision: Security parameters inside custom role definitions require pinpoint precision. Explicitly matching actions to granular tasks ensures teams maintain access velocity without breaking zero-trust boundaries.

Top comments (3)

Collapse
 
peterbuildssecure profile image
Peter •

This closes the SKU vector well, but two gaps worth flagging: 1) the policy is scoped to one resource group, so nothing stops the same actor from running az group create on a fresh RG and deploying the D-series node there instead — the policy assignment doesn't follow. To actually close the wallet, this needs subscription or management-group scope, not RG scope. 2) SKU restriction alone doesn't stop a cost blowup from quantity or duration — 200 Standard_B2ats_v2 instances, or one instance running for three weeks, pass this policy every time. A budget alert with an action group (auto-shutdown past a threshold) covers the axis this policy can't.

Collapse
 
abhishek_kadlii_9ef4ca8bc profile image
Abhishek Kadlii •

Brilliant feedback, Peter!

I completely missed the resource group isolation bypass and the horizontal scaling loophole.

I'm shifting the policy deployment to the subscription scope and adding an Azure Budget auto-shutdown action group to close the loop on the wallet entirely.

Thanks for the solid peer review!"

Collapse
 
peterbuildssecure profile image
Peter •

Worth knowing before you rely on the budget action: cost data arrives with a lag of hours, and an action group notifies but doesn't stop anything by itself. You need a function or automation behind it to deallocate. So it's a lagging control for the wallet, not a preventive one. Keep the policy deny and subscription quotas as the layer that stops spend, and use the budget as the backstop for what slips through.