Project 01 · Azure

Cost Visibility Dashboard

Cloud cost governance built as infrastructure-as-code: alerts that warn you before the money is gone, not after.

In progress Azure Terraform Cost Management RBAC
Status: building. The architecture, the Terraform, and the security decisions below are real and written down. Nothing is deployed yet, so there are no dashboard screenshots — only the baseline evidence captured before any resource existed. This page gets updated as the build progresses rather than published all at once.

The problem

A business moves to the cloud on a $5,000/month estimate. The first bill is $8,000. Nobody notices, because nobody is watching a number that only appears once a month. Three months later it is $30,000 and the money is already spent.

Nothing failed. Nobody was breached. The bill grew where nobody was looking.

Most people respond by setting a budget alert at 100% and stopping there. That alert tells you the money is already gone. This project is about the one that arrives early enough to matter.

Architecture

What gets built

Fourteen resources, all defined in Terraform so the environment can be rebuilt from source and destroyed the moment the evidence is captured.

ComponentPurpose
consumption_budget_subscriptionFour thresholds: 50/80/100% actual, plus 100% forecasted
cost_anomaly_alertFlags daily spend deviating from the learned pattern
monitor_action_groupAlert delivery, swappable for SMS or a ticket queue later
subscription_cost_management_exportDaily CSV of actual usage to blob storage
storage_accountTLS 1.2 floor, no public access, versioning, 7-day soft delete
user_assigned_identity + roleScoped to Cost Management Reader and nothing else
monitor_diagnostic_settingLogs who read the billing data, not just what it said
application_insights_workbookThe dashboard
Decisions

The choices that matter

Each of these is a question an interviewer would ask, so the reasoning is written down as it was made rather than reconstructed afterward.

Forecasted alerts, not just actual

Three actual thresholds tell you what already happened. The forecast alert fires when the month is projected to exceed budget. It is the only one of the four that arrives while there is still time to act — and the one that would have caught the $5K estimate becoming $30K.

Cost Management Reader, not Contributor

The identity that reads billing data holds exactly one role. If it were compromised, the blast radius is disclosure of a bill — not the ability to create, modify, or delete anything. Reader would also work, but grants visibility into every resource's configuration, which is more than the job needs.

Anomaly detection alongside the budget

A monthly budget is blunt. If something starts burning money on the 3rd, the budget stays silent until the running total crosses a threshold — possibly two weeks and thousands of dollars later. Anomaly detection watches the daily shape instead of the monthly total.

LRS, not geo-redundant storage

The export is a daily regenerating copy of data Azure also holds. Losing a region's copy means re-running an export, not losing anything irreplaceable. Paying for geo-redundancy here would be spending money to protect against a scenario with no real consequence — which is the exact failure this project exists to catch.

A gap I could not close. The storage account keeps public network access enabled. The Cost Management export service writes from Azure's side and cannot reach a fully locked-down account without private endpoints, which are not in the free tier. It is protected by a TLS 1.2 floor, no public blob access, and private container ACLs — but the correct fix is a private endpoint, and this is not that. Documented here rather than papered over.
Method

How it gets built and torn down

Loop 1
Local, free

Azurite, Azure Functions Core Tools, and SQL Server in Docker. Iterate and break things at zero cost.

Loop 2
Real deploy

terraform apply on free tier, exercise it, then capture screenshots, logs, the cost report, and Defender for Cloud findings.

Loop 3
Destroy

terraform destroy, confirm nothing is still billing, then write up what happened including what failed first time.

Why the local emulator is not enough on its own. Azurite does not support blob versioning, soft delete, lifecycle policy, or immutability. There is no emulator at all for Cost Management, Azure Monitor, managed identity, RBAC, or Defender for Cloud. Local development is for iteration speed; anything involving security or cost has to run in a real subscription or the claim is not honest.
Evidence

Baseline, before anything existed

Captured first because they cannot be retaken. Subscription ID, account email, and tenant domain are blurred — there is no upside to publishing them.

Azure resource groups list showing only NetworkWatcherRG
Resource groups before the lab. Only the auto-created NetworkWatcherRG, “Showing 1 - 1 of 1”. Proof nothing was pre-built.
Azure subscription overview showing Owner role and zero cost
Owner role, current cost 0.00, and the $200 free trial credit intact.
Azure cost analysis showing no cost, no budget and no forecast
The useful one. BUDGET: NONE and FORECAST UNAVAILABLE. Once the budget is deployed those same two tiles read $25 and a live forecast, so the before and after sit in a single frame.
Notes

What I have run into so far

The tenant blocked my first login

Authenticating the Azure CLI with device code failed:

AADSTS530035: Access has been blocked by security defaults.

Entra ID security defaults block the device code flow. That is deliberate — device code is a known phishing vector, because an attacker can generate a code and talk a target into entering it on their own machine. The browser-based flow ties authentication to the session that started it, so it is allowed.

The tempting fix was to disable security defaults. That would have traded a real protection for convenience on the subscription holding my billing data. Used the browser flow instead.

Two deprecations caught before they became a problem

resource_manager_id on storage containers and the metric block on diagnostic settings are both deprecated in the azurerm provider, and metric disappears entirely in v5. Fixed at write time rather than inheriting a break at the next upgrade.

Next

What is left

  • Finish the Terraform, deploy, and capture the working dashboard
  • Test that the least-privilege identity is actually denied when it attempts a write — configuring a control is not evidence that it works
  • Verify the alert path end to end, from Terraform through to an email in an inbox
  • Confirm the daily cost export lands and contains real usage rows
  • Tear it all down and publish the total cost of the exercise