A cloud infrastructure audit is a structured review of what is running in your cloud accounts, who can change it, how it is exposed, and whether there is evidence to prove each answer. It covers seven areas: inventory and ownership, identity and access, network exposure, data protection, logging and monitoring, change management, and resilience and recovery.
This checklist has 29 checks across those areas. Each one states the evidence to collect and what a pass looks like, and each area comes with read-only AWS CLI commands that gather the evidence. A blank copy is available as a CSV worksheet with empty columns for scope, finding, owner, and next action.
What a cloud infrastructure audit is, and is not
An infrastructure audit answers one question for each control: is it in place, and can you show it? That makes it different from three reviews it is often confused with.
- A security assessment or penetration test looks for ways in. An audit checks whether the controls you say you have are operating, and asks for the record.
- A cost review looks for waste. An audit touches unused resources only because something nobody owns is something nobody is patching.
- A compliance certification such as SOC 2 or ISO 27001 is performed by an external auditor against a framework. An internal infrastructure audit is the preparation: the same questions, asked early enough to fix the answers.
The output is not a score. It is a list of findings, each tied to a resource, an owner, and a piece of evidence, plus the evidence for every check that passed.
Before you start: scope, period, and evidence rules
Three decisions made up front prevent most of the rework later.
- 1Scope. List the accounts, regions, and environments under review, and state what is excluded. An account left off the list is the most common audit gap, because nobody checks what nobody listed.
- 2Period. Decide whether the audit is point-in-time, meaning the configuration today, or covers a period, meaning every change in the last quarter. Configuration checks are point-in-time. Change management and access reviews need a period.
- 3Evidence rules. Agree that every piece of evidence records its source, the time it was collected, and who collected it, and that it can be reproduced by running the same command again. A screenshot with no timestamp fails all three.
Collect evidence with a read-only role. On AWS, the managed SecurityAudit policy grants read access to security configuration without access to the data itself, and it is what most external auditors ask for.
The checklist
The commands below are for AWS and are read-only. Run them per account and per region in use, and save the output with the date. Angle brackets mark values to replace.
Inventory and ownership
An audit can only cover what is on the list, so the first question is whether the list is complete and every item has an owner.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| Every account, subscription, and project is in scope | Organization account list with a named owner for each account | No account exists outside the list, and each one has an owner who answers for it |
| Every resource carries an owner and an environment tag | Resource export with the count of untagged resources | Untagged resources are listed, and each has a decision recorded against it |
| Resources are managed as code, or the exception is written down | Infrastructure-as-code state compared with the live inventory | Resources no code manages are known, assigned, and scheduled for import or removal |
| Unused resources are identified | Unattached volumes, idle load balancers, and unassociated IP addresses | Each one has an owner decision: keep, snapshot, or delete |
aws organizations list-accounts --query 'Accounts[].[Id,Name,Status]' --output table
aws resource-explorer-2 search --query-string 'tag:none' --query 'Resources[].Arn'
aws ec2 describe-volumes --filters Name=status,Values=available --query 'Volumes[].[VolumeId,Size,CreateTime]' --output table
The untagged-resource search needs AWS Resource Explorer turned on with an aggregator index, and tag:none matches resources with no user-created tags. On Azure the equivalent source is an Azure Resource Graph query, and on Google Cloud it is gcloud asset search-all-resources. Whatever the source, save the export with its timestamp: the inventory is the population every later check samples from.
Identity and access
Most cloud incidents start with a credential, so auditors spend the most time on who can do what and how that access is protected.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| The root user has MFA and no access keys | Account summary and credential report | MFA is enabled on root, and the report shows no root access keys |
| People sign in through single sign-on with MFA | Credential report filtered to users with a console password | No user has a console password without MFA, and standing IAM users are the documented exception |
| Access keys are rotated or removed | Credential report columns for key age and last use | No active key is older than the rotation policy, and unused keys are deactivated |
| No policy grants every action on every resource outside named admin roles | Policy review for Action "*" with Resource "*", and privilege-escalation actions | Each administrator-level grant maps to a role with a recorded approval |
| Access from outside the organization is known | IAM Access Analyzer active findings | Every external principal is expected, and unexpected findings have an owner |
aws iam get-account-summary --query 'SummaryMap.[AccountMFAEnabled,AccountAccessKeysPresent]'
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 -d > credential-report.csv
aws accessanalyzer list-findings --analyzer-arn <analyzer-arn> --filter '{"status":{"eq":["ACTIVE"]}}'
The credential report is a CSV with one row per user, including the root user. The columns to read are mfa_active, password_last_used, access_key_1_last_rotated, and access_key_1_last_used_date. To review one policy for wildcards and privilege-escalation actions, paste it into the free IAM policy analyzer, which runs in the browser and maps each finding to the control it affects.
Network exposure
Anything reachable from the internet should be reachable on purpose, and the audit asks for the list and the reason behind each entry.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| No security group opens admin or database ports to the internet | Security group rules that allow 0.0.0.0/0 or::/0 | Open rules are limited to public web ports on resources meant to be public |
| Every internet-facing load balancer and public IP address is intended | List of internet-facing load balancers and instances with public addresses | Each entry names the service it fronts and the team that owns it |
| Object storage is not publicly readable | Account-level and bucket-level public access block settings | All four block settings are on at the account level, with exceptions documented per bucket |
| Databases are not publicly accessible | Database instances with the public accessibility flag | No production database has a public endpoint |
aws ec2 describe-security-groups --filters Name=ip-permission.cidr,Values=0.0.0.0/0 --query 'SecurityGroups[].[GroupId,GroupName]' --output table
aws elbv2 describe-load-balancers --query 'LoadBalancers[?Scheme==`internet-facing`].[LoadBalancerName,DNSName]' --output table
aws s3control get-public-access-block --account-id <account-id>
aws rds describe-db-instances --query 'DBInstances[?PubliclyAccessible==`true`].DBInstanceIdentifier'
The security group filter returns every group with any rule open to the internet, including the ones that should be. Read the port on each rule before recording a finding: port 443 on a public load balancer is expected, port 22 or 5432 is not. Run the same commands in every region in use.
Data protection
Encryption and key handling are easy to check and easy to get wrong, which is why they appear in every framework.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| Volumes, databases, and buckets are encrypted at rest | Default encryption setting and the list of unencrypted stores | Encryption by default is on, and no production data store is unencrypted |
| Traffic is encrypted in transit | Load balancer listeners and their protocols and TLS policies | Public listeners use HTTPS or TLS with a current security policy |
| Encryption keys rotate and their policies are scoped | Key rotation status and key policies for customer-managed keys | Rotation is enabled, and no key policy grants use to every principal |
| Secrets are stored in a secrets manager, not in code or state | Secret-scanning results for repositories and a review of state files | No live credential appears in a repository, variable file, or plain-text state |
aws ec2 get-ebs-encryption-by-default
aws rds describe-db-instances --query 'DBInstances[?StorageEncrypted==`false`].DBInstanceIdentifier'
aws elbv2 describe-listeners --load-balancer-arn <load-balancer-arn> --query 'Listeners[].[Port,Protocol,SslPolicy]' --output table
aws kms get-key-rotation-status --key-id <key-id>
Encryption by default only applies to volumes created after it was switched on, so an account can have the setting enabled and still hold older unencrypted volumes. Check the stores themselves, not only the setting. For secrets, keeping them out of Terraform state is its own review.
Logging and monitoring
Without a complete audit trail none of the other answers can be verified, so the trail itself is audited.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| An audit trail records API activity in every region | Trail configuration: multi-region flag, log file validation, destination | A multi-region trail with validation on writes to storage the workload accounts cannot alter |
| Logs are kept for as long as policy requires | Retention setting on each log group and log bucket | No log group is set to never expire by accident, and retention matches the written policy |
| Alerts exist for high-risk events and reach a person | Alarm list for root sign-in, IAM changes, and network rule changes, with the route each one takes | Each alert has an owner, and a test alert was acknowledged within the audit period |
| Threat detection is turned on in every region in use | Detector status per region | A detector is enabled wherever resources run, and findings have an owner |
aws cloudtrail describe-trails --query 'trailList[].[Name,IsMultiRegionTrail,LogFileValidationEnabled]' --output table
aws logs describe-log-groups --query 'logGroups[?!retentionInDays].logGroupName'
aws guardduty list-detectors
A trail that writes to a bucket in the same account can be altered by anyone who compromises that account. Auditors look for log storage in a separate account with validation turned on, so the trail can be trusted as evidence for every other area on this list.
Change management
Auditors sample changes and ask for the request, the review, and the approval behind each one.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| Production changes go through a reviewed pull request and a plan | A sample of changes, each with its pull request, plan output, and approver | Every sampled change has a reviewer who is not the author |
| Live infrastructure matches the code | Result of a scheduled drift check for each production workspace | The latest check shows no drift, or each difference has a ticket |
| Console changes in production are detected | Audit trail query for write events from console sessions | Console writes are rare, and each one links to an incident or an approved exception |
| Emergency changes are recorded and reconciled into code | Break-glass log with the follow-up change for each use | Every emergency change was reviewed afterwards and the code was updated to match |
terraform plan -detailed-exitcode -input=false -lock=false
aws cloudtrail lookup-events --lookup-attributes AttributeKey=ReadOnly,AttributeValue=false --max-results 50 --query 'Events[].[EventTime,Username,EventName]' --output table
The drift check exits with code 2 when live infrastructure differs from the code, which makes it easy to schedule; Terraform drift detection covers the setup. lookup-events returns management events from the last 90 days. Console activity is marked by sessionCredentialFromConsole in each event record, so filter the output on that field to separate console writes from pipeline writes.
Resilience and recovery
A backup that has never been restored is an assumption, and the audit asks for the restore test, not the backup setting.
| Check | Evidence to collect | What passing looks like |
|---|---|---|
| Every stateful resource is backed up | Backup coverage report with retention period per resource | No production database or volume is missing from a backup plan |
| A restore was tested during the audit period | Restore test record with the date, the duration, and who ran it | The restore completed inside the recovery time objective |
| Production databases survive the loss of one zone | Multi-zone setting for each production database | Single-zone production databases are listed with an accepted-risk decision |
| Production data stores have deletion protection | Deletion protection flag for databases and versioning for buckets | Protection is on, and turning it off requires a reviewed change |
aws rds describe-db-instances --query 'DBInstances[].[DBInstanceIdentifier,MultiAZ,BackupRetentionPeriod,DeletionProtection]' --output table
aws backup list-protected-resources --query 'Results[].[ResourceArn,LastBackupTime]' --output table
Retention settings prove that backups are configured. Only a restore proves that they work. Record the restore test like any other evidence: the date, the resource restored, how long it took, and who ran it.
How the areas map to SOC 2, ISO 27001, and CIS
The same evidence usually satisfies more than one framework, so it is worth collecting once and referencing many times. This table shows where each area most often lands.
| Area | SOC 2 | ISO 27001:2022 | CIS AWS Foundations section |
|---|---|---|---|
| Inventory and ownership | CC6.1 | A.5.9 | Not covered |
| Identity and access | CC6.1, CC6.2, CC6.3 | A.5.15, A.5.18, A.8.2 | Identity and Access Management |
| Network exposure | CC6.6 | A.8.20, A.8.22 | Networking |
| Data protection | CC6.1, CC6.7 | A.8.24 | Storage |
| Logging and monitoring | CC7.2 | A.8.15, A.8.16 | Logging, Monitoring |
| Change management | CC8.1 | A.8.9, A.8.32 | Not covered |
| Resilience and recovery | A1.2 | A.8.13, A.8.14 | Not covered |
Treat the mapping as orientation. Your auditor decides which evidence satisfies which control, and frameworks are scoped differently for every organization. The CIS AWS Foundations Benchmark is a configuration baseline, so it has nothing to say about ownership, change approval, or restore testing.
Recording findings so they survive the audit
A finding that says "some security groups are too open" cannot be assigned, fixed, or verified. A usable finding names one resource and carries its own evidence. The worksheet columns exist to force that. Here is one illustrative row filled in, using a made-up resource:
| Column | Example entry |
|---|---|
| Area | Network exposure |
| Check | No security group opens admin or database ports to the internet |
| Scope | Production account, us-east-1 |
| Evidence reference | describe-security-groups output, 12 November 2026 09:14 UTC, saved as evidence/network/sg-open.json |
| Finding | sg-0a1b2c3d allows 0.0.0.0/0 on port 22 and is attached to two instances |
| Owner | Platform team |
| Next action | Remove the rule through a reviewed change and use Session Manager for access |
| Verified at | Empty until a fresh run of the same command shows the rule is gone |
The last column is the one audits most often skip. A finding is closed when the same check, run again, passes, and the new output is saved next to the old one. Marking it done in a ticket is not verification.
How often to run it
Running all checks once a year, the week before an external audit, is how teams end up with a long list and no time. Splitting the checklist by how fast each area changes keeps the workload flat:
- Continuously or daily: drift between code and live infrastructure, new public exposure, and root user activity. These change without warning and are cheap to check automatically.
- Monthly: access key age, unused credentials, untagged resources, and unused resources.
- Quarterly: the full checklist, a sample of changes for the change management review, and a restore test.
Where automation helps
Every check above can be run by hand, and for a first audit it is worth doing so, because it shows where the evidence lives. The cost appears on the second and third pass, when the same exports have to be collected again and reconciled against the previous round.
ops0 records deployments, policy checks, compliance scans, and approvals as infrastructure changes, so the evidence for the change management and configuration areas already exists when the audit starts. Infrastructure audit in ops0 turns that history into point-in-time reports that can be shared with an auditor through a password-protected link, and audit packs bundle evidence for a chosen period. The checklist stays the same. What changes is that the evidence is collected as the work happens.
What is a cloud infrastructure audit?
A cloud infrastructure audit is a structured review of what is running in your cloud accounts, who can change it, how it is exposed, and whether evidence exists to prove each answer. It checks that controls are in place and operating, and it produces a list of findings with owners plus the evidence for every check that passed.
What should a cloud infrastructure audit checklist include?
Seven areas: inventory and ownership, identity and access, network exposure, data protection, logging and monitoring, change management, and resilience and recovery. For each check, the checklist should state the evidence to collect and what a pass looks like, so two reviewers reach the same answer.
What access does an auditor need to a cloud account?
Read-only access to configuration, not to data. On AWS the managed SecurityAudit policy is the usual choice: it can read IAM, network, logging, and encryption settings without reading the contents of buckets or databases. Many teams avoid granting access at all by sharing exported evidence or point-in-time reports.
How is an infrastructure audit different from a security assessment?
A security assessment or penetration test looks for weaknesses an attacker could use. An audit checks whether the controls you have defined are in place and asks for proof. The two overlap on findings such as open ports, but an audit also covers ownership, change approval, and restore testing, which an assessment does not.
How long does a cloud infrastructure audit take?
The evidence-collection commands in this checklist run in minutes per account. The time goes into reviewing the output, agreeing an owner for each finding, and fixing what was found, which grows with the number of accounts in scope and how much evidence already exists.
How often should cloud infrastructure be audited?
Split the checklist by how fast each area changes. Check drift, public exposure, and root activity continuously; check access keys and untagged resources monthly; and run the full checklist with a restore test each quarter. A single annual audit leaves most of the year unchecked.
- Getting credential reports for your AWS account: AWS IAM documentation. The credential report columns used in the identity checks.
- SecurityAudit managed policy: AWS managed policy reference. Read-only access to security configuration for auditors.
- Blocking public access to your Amazon S3 storage: Amazon S3 documentation. The four account-level and bucket-level block settings.
- Search query syntax reference for Resource Explorer: AWS Resource Explorer documentation. The tag:none filter used to find untagged resources.
- CIS Amazon Web Services Benchmarks: Center for Internet Security. The configuration baseline referenced in the framework mapping.
- ISO/IEC 27001:2022: International Organization for Standardization. Annex A control numbering referenced in the framework mapping.