For years, auditing a cloud environment meant asking an engineer to log in, click through a console, and send you a screenshot. You filed the screenshot as evidence, trusted that it represented reality on the day it was taken, and moved on. That model is breaking down, because the teams you audit no longer click through consoles to build anything. They write code that builds it for them.
That code is your new evidence, and it is better evidence than a screenshot ever was. This guide teaches you how to read it. You will not write a single line. If you are new to this way of working, the foundation lives in GRC Engineering 101. GRC here means Governance, Risk, and Compliance.
What infrastructure as code actually is
Infrastructure as code, or IaC, is the practice of describing cloud resources in plain text files instead of clicking buttons in a web console. A team that wants a storage bucket, a database, or a network does not create it by hand. They write a file that says what they want, and a tool reads that file and builds it.
The two most common IaC tools are Terraform and CloudFormation. Terraform is made by a company called HashiCorp and works across AWS, Azure, Google Cloud, and many others. CloudFormation is Amazon's own tool and works only on AWS. They look slightly different on the page, but they do the same job: turn a written description into running infrastructure.
Here is the part that matters to you. Because the environment is defined in text, it lives in version control, usually Git. That means every change is recorded, dated, and attributed to a person. It usually goes through a pull request, which is a review and approval step before the change takes effect. So the same files that build the cloud also document who changed what, when, and who approved it. That is change management evidence sitting right next to configuration evidence.
How to read a module like an auditor
A module is just a folder of related IaC files. Reading one is closer to reading a configuration form than to reading a program. You are not following logic from top to bottom. You are scanning for the specific resource and the specific setting your control cares about.
Here is a short Terraform snippet that defines an AWS storage bucket and turns on encryption at rest. Read it the way you would read a checklist.
resource "aws_s3_bucket" "customer_data" {
bucket = "acme-customer-data-prod"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "customer_data" {
bucket = aws_s3_bucket.customer_data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}You do not need to understand every word. Walk it in plain English. The first block says: create a storage bucket and name it acme-customer-data-prod. The second block says: for that same bucket, turn on encryption by default, using a key-management service algorithm. The phrase sse_algorithm = "aws:kms" is your evidence that encryption at rest is enabled. If that whole second block were missing, encryption would not be configured, and that is a finding.
That is the entire skill. Find the resource. Find the setting your control requires. Read its value. CloudFormation does the same thing with slightly different wording, but the reading habit is identical: locate, then verify.
Sampling resources from code instead of screenshots
When you sample from screenshots, you are at the mercy of what the engineer chose to show you. You ask for five buckets, you get five screenshots, and you have no way to know whether those five are representative or hand-picked. The population is invisible.
Code flips that. The population is the code. Every bucket the team owns is defined somewhere in the repository, so you can see the whole set before you sample. You can search the files for every aws_s3_bucket resource and count them, then select your sample from a known, complete population. That is a stronger sampling basis than any screenshot pull.
It also lets you test the whole population at once for a single setting. Instead of sampling five buckets to check encryption, you can confirm that every bucket resource in the repository has the encryption block. When the population is text, full coverage stops being a luxury and becomes the default. This is the same logic behind AI compliance evidence, where the evidence is structured enough to test completely rather than by sample.
Using state files and drift as audit evidence
There is a fair question hiding under everything so far. The code says encryption is on, but the code is just intent. How do you know the live environment matches it? Infrastructure as code answers this with two things: the state file and drift detection.
The state file is a record of what the tool actually built and is tracking. If the code is the blueprint, the state file is the as-built record. Terraform keeps one, and reading it tells you what is genuinely deployed, not just what someone wrote down. It is machine-generated, so it is harder to stage for an audit than a screenshot.
Drift detection is the part that makes auditors smile. Running terraform plan compares the code against the live environment and reports any differences. If someone logged into the console and turned off encryption by hand after the code was approved, the plan shows that gap. You now have direct evidence of unauthorized change, the kind of thing that used to take an interview and a lot of luck to catch.
So your evidence stack for one control becomes: the code shows the intended setting, the pull request shows it was reviewed and approved, the state file shows it was deployed, and the drift check shows it has not been changed since. That is a far more complete story than a single dated screenshot.
Mapping IaC findings to controls
IaC findings map cleanly to the frameworks you already work in. The encryption example touches several controls at once, which is the efficiency payoff.
| What the code shows | SOC 2 control | ISO/IEC 27001 control |
|---|---|---|
| Encryption block present on data stores | CC6.1 (logical access and protection) | A.8.24 (use of cryptography) |
| Pull request review before changes deploy | CC8.1 (change management) | A.8.32 (change management) |
| Change history attributing each change | CC7.2 (monitoring and detection) | A.8.15 (logging) |
| Drift check showing no unauthorized change | CC6.8 (unauthorized change prevention) | A.8.9 (configuration management) |
Treat the specific control numbers as a starting point and confirm them against your framework version. The lesson is the pattern: one piece of IaC evidence usually satisfies more than one control. For more on how this differs from manual programs, see GRC vs Traditional GRC and GRC automation.
What "bad" looks like in the code
You do not need deep technical knowledge to spot common problems. A handful of patterns come up again and again, and once you know them you can flag them on sight.
- A secret written directly in the file: A password, API key, or token typed as plain text in the code. Secrets belong in a secrets manager, never committed to version control. If you see one, it is also exposed in the change history forever.
- A missing encryption block: A data store defined with no corresponding encryption configuration. The resource works, but the data sits unencrypted. The absence is the finding.
- A wide-open network rule: A rule allowing traffic from 0.0.0.0/0, which means the entire internet. Sometimes intentional for a public website, often a mistake for a database. Ask why it is open.
- Public access left on: A storage bucket or resource explicitly set to public when it holds internal or customer data. The code states the exposure plainly.
- No review on the change: A change that reached production without a pull request approval. That breaks segregation of duties regardless of what the change did.
Where to build this skill
Reading code is a learnable skill, and it is the core of where audit is heading. The CGE-AUD, Certified GRC Engineer - Auditor Specialty, teaches exactly this literacy: how to read Terraform and CloudFormation, sample from code, work with state and drift, and map what you find to controls. It is built for auditors who want to keep doing audit work in environments that have moved to code.
You can also let tooling do the first pass. The open-source claude-grc-engineering toolkit can scan infrastructure as code for control violations with a single command:
/grc-engineer:scan-iacIt reads the files, flags missing encryption, open network rules, exposed secrets, and public access, and reports each as a structured finding you can review. It does not replace your judgment. It gives you a fast, complete first read of the code so your time goes to the findings that matter. For more on how an agent fits into evidence work, read CGE-AUD vs CISA.
Frequently Asked Questions
How do auditors audit infrastructure as code?
Auditors audit infrastructure as code by reading the code that defines the environment rather than taking screenshots of consoles. They locate the resources in scope, read the relevant settings for each control, sample directly from the code, compare the deployed state file against the code, and check for drift. The code becomes the primary evidence and the screenshots become a backup.
Can you audit Terraform?
Yes. Terraform is one of the most common infrastructure as code tools and it is well suited to audit. You read the .tf files to see how resources are configured, read the state file to see what is actually deployed, and run terraform plan to detect drift between the two. You do not need to write Terraform to audit it, you need to be able to read it.
What evidence does infrastructure as code provide for an audit?
Infrastructure as code provides the configuration of every resource as version-controlled text, a state file showing what is actually deployed, a change history showing who changed what and when, and pull request approvals showing review and authorization. Together these cover configuration, change management, and segregation of duties with far less manual effort than screenshots.
Do auditors need to know how to code?
No. Auditors do not need to write code to audit infrastructure as code. They need to read it, which is a different and far easier skill. Reading a Terraform or CloudFormation resource block to confirm a setting like encryption is closer to reading a configuration form than to programming. The CGE-AUD Auditor Specialty teaches exactly this reading literacy.