Join our Newsletter — 33% off our NHI Course
Home› Guides› IaC identity policy checks: Blocking Risky IAM Before…
Guide Non-Human Identity (NHI)

IaC identity policy checks: Blocking Risky IAM Before Terraform or CloudFormation Deploys

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 28 October 2026 12 min read
On this page

Most over-privileged cloud roles, wildcard trust policies and long-lived access keys are not created by attackers: they are written into Terraform or CloudFormation by engineers and merged without anyone reading the IAM JSON. Infrastructure as code gives you a single point where every non-human identity, role, trust relationship and permission policy passes through review before it exists. This guide shows how to use that point: which identity checks to run, how static rules (Checkov, Conftest with Rego) differ from semantic checks (IAM Access Analyzer policy validation and custom policy checks), how to scan the Terraform plan rather than only the source, and how to roll gates out without stopping delivery. It is written for platform, cloud security and DevSecOps engineers who own pipelines that deploy IAM.

Key takeaways

  • IAM in code is the cheapest place to stop excess privilege. A wildcard action or an open trust policy caught in a pull request costs a comment; the same policy found in production costs an access review and a change window.
  • Scan the plan, not only the source. Modules, variables and jsonencode() hide the final policy; the JSON output of terraform show -json contains the rendered values that will be applied.
  • Combine pattern rules with semantic checks. Checkov flags known bad shapes such as "Action": "*"; IAM Access Analyzer custom checks answer "does this grant new access?" and "does this grant this sensitive action?" using automated reasoning.
  • Trust policies deserve their own rules. Who can assume a role matters as much as what the role can do, especially for GitHub Actions and other OIDC federations.
  • Exceptions must be visible. Inline skips with a written reason, reviewed by security, beat a global skip list that nobody reads.

What to check: the identity risks that live in IaC

RiskWhat it looks like in codeExample built-in checks (Checkov)
Full administrative policyAction: "*" with Resource: "*", or attaching AdministratorAccessCKV_AWS_62 / CKV_AWS_1 (no full "*-*" admin policies), CKV_AWS_274 and CKV_AWS_275 (disallow AdministratorAccess)
Wildcard actions or resources"s3:*", "Action": "*", "Resource": "*" on restrictable actionsCKV_AWS_63 / CKV_AWS_49 (no "*" actions), CKV_AWS_355 / CKV_AWS_356 (no "*" resource for restrictable actions)
Privilege escalation pathsUnconstrained iam:PassRole, iam:CreatePolicyVersion, iam:AttachRolePolicyCKV_AWS_286 / CKV_AWS_110 (no privilege escalation), CKV_AWS_289 / CKV_AWS_109 (no unconstrained permissions management)
Credential exposure and exfiltrationActions that return secrets or tokens, broad read on data storesCKV_AWS_287 / CKV_AWS_107 (credentials exposure), CKV_AWS_288 / CKV_AWS_108 (data exfiltration)
Open trust policyPrincipal: "*" or any service allowed to assume the roleCKV_AWS_60 (only specific services or principals may assume), CKV_AWS_61 (no assume role across all services), CKV_AWS_283 (no ALL or any AWS principal)
Weak OIDC federationGitHub Actions trust with no or wildcard sub claimCKV_AWS_358 and CKV_AWS_393 (safe claims and claim order), CKV_AZURE_249, CKV_GCP_125, CKV_GCP_118 (workload identity pool provider restricted)
Static credentialsKeys in provider blocks, Lambda environment or user data; IAM users instead of rolesCKV_AWS_41, CKV_AWS_45, CKV_AWS_46, CKV_AWS_273 (SSO rather than IAM users), CKV_AWS_40 (policies on groups or roles only)
Google Cloud service account abuseBasic roles, default service accounts, project-wide impersonation rolesCKV_GCP_115 to CKV_GCP_117 (no basic roles), CKV_GCP_30 and CKV_GCP_46 (no default service account), CKV_GCP_41, CKV_GCP_42, CKV_GCP_49 (no Service Account User or Token Creator at project level, no admin service accounts)
Azure custom owner rolesCustom role definitions with subscription-wide * actionsCKV_AZURE_39 (no custom subscription owner roles)

Check IDs above are from the Checkov policy index for Terraform; several have CloudFormation equivalents (for example CKV_AWS_60, CKV_AWS_61, CKV_AWS_62, CKV_AWS_63, CKV_AWS_107, CKV_AWS_110 and CKV_AWS_111). Pairs such as CKV_AWS_62 and CKV_AWS_1 cover the same rule for a policy resource and for an aws_iam_policy_document data source. Confirm IDs against the version you run, because the index changes between releases.

Some identity risks have no built-in rule and need a custom policy: creation of aws_iam_access_key or google_service_account_key resources outside an approved module, roles with no owner tag, trust policies that allow an external account not on an allow list, and permission boundaries missing from roles created by delegated teams. See the AWS IAM Policies and Roles Guide for the policy model behind these rules.

Three kinds of check and where each fits

ApproachHow it worksStrengthsLimits
Pattern rules (Checkov built-in and YAML custom policies)Matches resource attributes and policy shapes in source or plan JSONOffline, fast, multi-cloud, runs on every commitCannot reason about combined effect of statements, conditions and NotAction
Policy as code (Conftest with OPA Rego)Your own Rego rules evaluated against plan JSON or templates (conftest test)Encodes organisation rules exactly: tags, allow lists, module provenanceYou write and test every rule; easy to get JSON parsing wrong
Semantic checks (IAM Access Analyzer via cfn-policy-validator or tf-policy-validator)ValidatePolicy plus custom checks CheckNoNewAccess, CheckAccessNotGranted, CheckNoPublicAccessAutomated reasoning over the full policy; resolves CloudFormation intrinsic functionsAWS only, needs AWS credentials in the pipeline, and AWS states that a charge applies to each custom policy check

CheckNoNewAccess compares a candidate policy with a reference policy and passes only if the candidate is a subset. The AWS sample reference policies start with an allow-all statement and add deny statements for the access you want to forbid, such as s3:PutBucketPolicy on a sensitive bucket, so any candidate granting that access fails. CheckAccessNotGranted takes a list of critical actions (for example secretsmanager:DeleteSecret or iam:PassRole) and fails if the policy grants any of them. The validators exit non-zero on blocking findings, and --treat-finding-type-as-blocking sets which of ERROR, SECURITY_WARNING, WARNING and SUGGESTION block a build. For background on the analyser itself see the IAM Access Analyzer Guide.

Scanning Terraform plans

Source scanning misses what modules and variables produce. The Checkov documentation describes the plan workflow: terraform plan --out tfplan.binary, then terraform show -json tfplan.binary > tfplan.json, then checkov -f tfplan.json. Adding --repo-root-for-plan-enrichment maps findings back to source files, and --deep-analysis combines the plan graph with the source graph where the plan lacks information such as locals. The Terraform IAM policy validator also takes the plan JSON as its --template-path.

A Conftest rule over plan JSON reads resource_changes, filters on type and planned action, and decodes the policy string. A simplified example that denies a role trust policy with a wildcard principal:

package main
import rego.v1

deny contains msg if {
  rc := input.resource_changes[_]
  rc.type == "aws_iam_role"
  rc.change.actions[_] != "delete"
  doc := json.unmarshal(rc.change.after.assume_role_policy)
  stmt := doc.Statement[_]
  stmt.Effect == "Allow"
  stmt.Principal.AWS == "*"
  msg := sprintf("%s: trust policy allows any AWS principal", [rc.address])
}

Test rules like this against known good and known bad fixtures in the policy repository, because a parsing mistake makes a rule silently pass. The plan file contains every planned attribute value, so treat it as sensitive: see the Terraform and IaC Pipeline Credential Security Guide for handling state and plan files.

Where identity fits

Every rule on this page is about non-human identities: workload roles, service accounts, pipeline federations and the keys they hold. Two points are easy to miss. First, the pipeline that runs the checks is itself a privileged NHI: the Access Analyzer validators need access-analyzer:ValidatePolicy, CheckNoNewAccess, CheckAccessNotGranted and CheckNoPublicAccess, which should be a separate, read-only role from the one that applies changes. Second, the checks only work if IaC is the sole path for creating identities; roles created by hand in the console bypass them. Pair the gate with discovery of identities that exist without a matching IaC resource. See the GitHub Actions OIDC Federation Hardening Guide for trust policy claims and the NHI Discovery Across Cloud, SaaS, CI/CD and On-Premises Environments guide for finding drift.

How to implement

  1. Baseline: run Checkov with --check limited to the IAM checks in the table across all repositories, with --soft-fail, and record the current findings per repository and owner.
  2. Fix the worst first: admin policies, open trust policies and static keys. Use the Least Privilege Techniques Guide and access activity data to right-size policies.
  3. Gate new code: use --hard-fail-on for the agreed critical checks on pull requests while leaving the rest as soft failures. Existing findings are tracked as debt rather than blocking unrelated changes.
  4. Add plan scanning: scan tfplan.json in the pipeline before apply, so module outputs are covered.
  5. Add semantic checks for AWS: run check-access-not-granted with your list of critical actions and check-no-new-access against reference policies for sensitive resources.
  6. Add organisation rules: write Conftest or Checkov YAML custom policies for owner tags, approved external accounts, permission boundaries and key creation.
  7. Govern exceptions: require inline skips in the form # checkov:skip=CKV_AWS_111: reason with a reason, and route any change that adds a skip to security review through code owners.
  8. Rollback: if a gate blocks urgent delivery wrongly, move that specific check from hard fail to soft fail in the shared configuration, keep reporting it, and fix the rule. Do not disable the scanner step.

Decision guidance

  • Small teams on one cloud: built-in Checkov IAM checks on source and plan cover most risk with little effort. Add Access Analyzer validation if you deploy on AWS.
  • Large or regulated estates: add a central policy repository of Conftest or custom rules, versioned and tested, consumed by every pipeline, plus semantic checks for sensitive accounts.
  • Pattern rule or semantic check: use pattern rules where the bad shape is unambiguous (wildcards, AdministratorAccess); use semantic checks where conditions, NotAction or multiple statements decide the effective access.
  • Pre-deploy versus runtime: IaC checks prevent new risk; they do not find existing roles or console drift. Keep runtime analysis of unused and external access alongside them.

Detection and measurement

  • Coverage: percentage of repositories and pipelines that deploy IAM and run the identity checks on plan output.
  • Blocked risk: count of critical IAM findings blocked at pull request per month, by check.
  • Exception health: number of active skips, percentage with a reason, and age of the oldest.
  • Drift: roles, service accounts and keys in the cloud with no matching IaC resource; alert on IAM changes in CloudTrail or audit logs made by humans outside the pipeline role.
  • Debt burn-down: open IAM findings on existing code, trending down quarter on quarter.

Framework mapping

FrameworkReferenceRelevance
NIST CSF 2.0PR.AA-05Permissions defined in policy and enforced with least privilege
NIST CSF 2.0PR.PS-01, PR.PS-06Configuration management and secure development practices in the pipeline
NIST SP 800-53 Rev. 5AC-6, AC-6(5)Least privilege and restricted privileged accounts
NIST SP 800-53 Rev. 5CM-3, CM-4, CM-6, SA-11Change control, impact analysis, configuration settings and developer testing
MITRE ATT&CKT1078.004, T1098.001, T1098.003Reduces the value of stolen cloud accounts and blocks added cloud credentials or roles arriving through code

Common failure patterns

  • Roles broader than the workload needs: the Capital One breach turned a firewall flaw into a large data loss because the firewall's role could list and read many S3 buckets it never needed. A rule blocking broad S3 read on non-data roles is exactly the check that catches this in code.
  • Source-only scanning: policies built from module variables pass source checks and fail nothing, while the rendered plan grants *.
  • Trust policy blind spot: teams lint permission policies but never check who can assume the role, leaving OIDC federations open to any repository.
  • Global skip lists: a check disabled for one legacy module in the shared config is disabled for everyone.
  • Console bypass: urgent fixes made by hand never pass the gate and are never codified, so drift grows.
  • Over-privileged scanner role: giving the validator the deploy role's permissions, so the checking step can itself change IAM.

Practitioner checklist

  • Enable the Checkov IAM checks listed above on every repository that deploys identities.
  • Scan terraform show -json output before every apply, not just source files.
  • Write explicit trust policy rules: no wildcard principals, OIDC subject claims pinned to repository and branch or environment.
  • Block static access keys and service account keys outside an approved, owned module.
  • Run IAM Access Analyzer check-access-not-granted with a list of critical actions on AWS deployments.
  • Keep custom Rego or YAML rules in a tested, versioned central repository.
  • Require a written reason and security review for every inline skip.
  • Use a separate read-only role for policy checks from the role that applies changes.
  • Reconcile cloud identities against IaC and alert on IAM changes made outside the pipeline.

Standards and references

Related NHI Mgmt Group resources: Infrastructure as Code Security Guide · Terraform and IaC Pipeline Credential Security Guide · Policy-as-Code for Authorisation Guide · CI/CD Pipeline Identity Security Guide · Non-Human Identity (NHI) Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 28 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org