- IaC governance is the primary layer. IAM governance is the backstop. Even with IaC mandated, IAM policies keep governance in force when someone creates resources outside the pipeline.
- IAM policies let you govern by role. A senior architect and a junior developer get different permissions — governance doesn’t have to mean “same rules for everyone.”
- Use IAM deny-conditions to enforce FinOps rules (instance types, regions, sizes) the same way you’d use them for security rules.
Why a second layer matters
In article #09 — Implementing Governance Through Policy, Part One: IaC, the argument was that preventing waste at deploy-time beats cleaning it up later. IaC validation blocks, policy-as-code (OPA / Sentinel), and enterprise tools all work at the deploy pipeline level.
That covers the happy path. But there’s a failure mode that pipeline-level governance doesn’t catch: resources created outside the pipeline. Someone clicks through the AWS console to “just test something quick.” A script runs against the cloud API with a service account’s credentials. A migration tool lifts and shifts a VM without touching Terraform. Each of these bypasses every IaC safeguard you wrote.
You can answer that with a policy (“all cloud changes must go through IaC”). Policies on paper stop nothing. The real question is: what enforces the policy when someone forgets, is in a hurry, or is a tool that doesn’t know about your rules?
That’s the job of IAM policies. They live at the account/project level — below the pipeline — and the cloud enforces them on every API call, regardless of which tool made the call.
What IAM-level governance looks like
Cloud IAM (Identity and Access Management) services let you attach conditional permissions to users, groups, roles, and accounts. The usual framing for IAM is security (“this user can read from this bucket but not write”). The same mechanism works for FinOps (“this role cannot launch instances larger than X in this account”).
Two concrete patterns I’ve used often:
Pattern 1 — Environment-aware restrictions.
“In sandbox and dev accounts, nobody can launch instances above
m5.largeor storage above 500 GB gp3.”
Expensive resources shouldn’t be available where they don’t belong. A sandbox doesn’t need r5.24xlarge instances. Prevent them at the IAM layer and the question never comes up.
Pattern 2 — Commitment-aligned restrictions.
“In production, only launch instance families that are covered by our RI/SP portfolio — unless a specific exception role is assumed.”
If you’ve bought commitments on t3 and m5 families, you want new instances to land there by default. The IAM policy makes compliance the path of least resistance; exceptions require someone to explicitly elevate privileges, which creates a trail.
The pattern in code (AWS example)
Here’s the second pattern as an AWS IAM policy. It denies any ec2:RunInstances call that tries to launch something outside the allowed families:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInstancesOutsideAllowedFamilies",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": ["arn:aws:ec2:*:*:instance/*"],
"Condition": {
"StringNotLike": {
"ec2:InstanceType": ["t3.*", "m5.*"]
}
}
}
]
} A few properties of this approach worth noting:
- The policy is enforced by the cloud itself. No pipeline to bypass, no Terraform provider to avoid. Anything that calls
RunInstances— console, CLI, API, SDK, third-party tool — is evaluated against the rule. - The rule is a
Deny, not anAllow. Explicit denies override allows in AWS IAM. That makes this pattern safe to layer on top of broader permission grants without rewriting them. - The condition keys are standardised. AWS, Azure, and GCP each publish a list of condition keys you can use (
ec2:InstanceType,aws:RequestedRegion, equivalents in other clouds). Start with the instance-type and region keys — they cover most FinOps rules.
The same policy idea translates to Azure (via Azure Policy assignments with deny effects) and GCP (via Organization Policy Service constraints). The syntax differs; the principle does not.
Govern by role, not by rule
One property of IAM governance that IaC governance can’t match: different rules for different roles in the same account.
A senior architect or platform engineer provisioning a production database might legitimately need r6i.24xlarge or a premium SSD tier. A developer in the same account working on a feature branch should not. In IaC, that distinction is hard to enforce — the same Terraform module is used by both. In IAM, you just assign different policies to the two roles and the cloud does the rest.
Account: production
┌─────────────────┐ ┌─────────────────────────────┐
│ Architect role │ ────► │ IAM: full EC2 + RDS │
└─────────────────┘ │ (no instance-type limit) │
└─────────────────────────────┘
┌─────────────────┐ ┌─────────────────────────────┐
│ Senior eng │ ────► │ IAM: EC2 in approved │
│ role │ │ families (t3.*, m5.*, │
└─────────────────┘ │ r5.*), RDS up to db.m5.2xl │
└─────────────────────────────┘
┌─────────────────┐ ┌─────────────────────────────┐
│ Developer role │ ────► │ IAM: EC2 in t3.* only, │
└─────────────────┘ │ no RDS, no EBS > 100GB │
└─────────────────────────────┘ The same account, three very different blast radii. Junior developers get guardrails that match their context; senior engineers get the rope they need to do their job. Governance stops being a blanket ban and becomes a graduated permission system.
One thing to be careful of: this only works if the roles themselves are sensibly assigned. IAM governance presumes a reasonable identity model underneath. If everyone in your organisation logs in as root or as a shared service account, no amount of policy writing will save you.
When to use each layer
The two layers complement rather than compete. A rough split:
| Rule type | Best enforced at |
|---|---|
“No instance type above m5.large in sandbox” | IaC and IAM |
| “All S3 buckets must have encryption enabled” | IaC |
| “Specific roles can bypass the instance-family restriction” | IAM |
| “Nobody can launch in regions we don’t operate in” | IAM |
“Production VMs must be tagged with cost-center” | IaC |
| “Only the FinOps role can purchase RIs” | IAM |
The pattern that emerges: IaC is best for shape rules (what the resource looks like), IAM is best for who-and-where rules (who can do what, where they can do it). Anywhere the answer is “both,” set both — redundancy here is a feature, not waste.
Summary
- IaC governance catches most cases. IAM governance catches the rest.
- Use IAM to enforce rules that depend on who is doing the action or where it’s happening.
- Keep policies graduated by role — don’t apply the same restrictions to juniors and architects; it breaks trust and productivity.
- The two layers together give you defence in depth. Neither alone is enough at any meaningful scale.
Thanks for reading. If this helped, share it. Questions or topic suggestions — send them through.