Skip to main content
INS // Insights

SOC 2 Infrastructure Change Evidence From Apply

Updated October 2026 · 6 min read

SOC 2 infrastructure change evidence is the artifact an auditor asks for when they sample a production change: who approved it, what the diff was, and when it landed. Most teams have the pull request in git and the resource in AWS, and nothing that joins them. The compliance manager spends the sample window reconstructing Slack messages. The CTO discovers that three console edits never had a reviewer at all.

Rutagon builds the join. A merged change, the plan digest, the approver who is not the author, and the CloudTrail event from the apply role become one record. That record is filed in the GRC you already run. We are not your auditor and we do not hold your SOC 2 attestation. We make the sample answerable.

The population auditors actually draw from

The population is production infrastructure changes in the audit window, not every commit on a feature branch. We define production as the account and the apply role you already named in the system description. A plan that never applied is not a change. A console edit that mutated a production resource is a change even though git is quiet.

Two sources cover that population. The first is the version-control history of the infrastructure repository: merged pull requests that touch Terraform, CloudFormation, or the pipeline that applies them. The second is CloudTrail management events in the production account for mutating API calls. The CloudTrail evidence pipeline is how those events leave the account in a form a reviewer can sample. This article is the change record that sits on top of those events.

A change that appears in CloudTrail under a human SSO role, and does not appear in a merged pull request, is an exception. It belongs on the emergency path described in SOC 2 emergency change evidence or it is a finding. We do not silently drop console events because they are inconvenient to attribute.

What each evidence record contains

Every in-population apply produces one record with fields the auditor can trace:

  • Change id, usually the pull request number and commit SHA
  • Author and the required approver, who must be a different person
  • A digest of the saved plan, so the applied diff is the diff that was reviewed
  • The CI role ARN and the CloudTrail eventID of the AssumeRoleWithWebIdentity or AssumeRole that ran the apply
  • The apply timestamp from CloudTrail, not from a laptop clock

The digest matters. Approving a pull request and then applying a different local plan is a change the reviewer did not see. We store the plan file from the CI job that posted the comment, hash it, and expect the apply job to use that artifact. A fresh plan at apply time, with no second approval when the digest changes, breaks the chain.

change_id: pr-1842
approver: distinct from author
plan_sha256: recorded from the CI artifact
cloudtrail_event_id: from the apply role session
applied_at: CloudTrail eventTime

AWS describes the event fields we join on in the CloudTrail record contents reference. We use eventID, eventTime, userIdentity.arn, and recipientAccountId. We do not ask the auditor to accept a git history as a substitute for the account's own log.

SOC 2 infrastructure change evidence we file

The pipeline runs after merge. It waits until the apply job finishes, reads the plan digest from the job artifact, queries CloudTrail for the apply role session in a short window around that job, and writes the record. If CloudTrail has no matching assume, the record stays open. An open record is the alert. A green pipeline icon is not evidence.

Approval rules live in the repository: required reviewers, code owners on the production module paths, and the CI approval gates you already intend to enforce. We do not reimplement your source host. We read its decision. If the host says the pull request merged with one approval and your policy says two, the record is an exception with the actual approver list attached.

Filing is an API call or a structured upload into the GRC you already run, mapped to the change-management control you already named. The GRC ticket id comes back onto the record so a sample request is a lookup, not a search through object storage. When the GRC cannot store structured fields, we still attach the same JSON and keep the ticket id. The shape stays stable so the next audit window is the same export.

Separation of duties is checked in the record, not in a policy PDF. The author field and the approver field are compared. The CI role is not a human approver. A pipeline that applies its own plan does not count as the reviewer. Someone accountable approved the digest before apply.

Drift, plans, and the console

Terraform drift is a change that happened somewhere else. When the next plan shows an update in production, we treat the drift as a CloudTrail exception that still needs an owner. The follow-up pull request that reconciles the code is a normal change record. Until it merges, the emergency or break-glass record remains open. Closing it because "we fixed it later" without the original actor and timestamp is how samples go badly.

State backends and lock tables are in scope for the same reason. A change to the state bucket policy is an infrastructure change. We include those paths in the pull request filter so a "small" IAM edit on the pipeline account is not invisible.

Multi-account organizations need the apply role named per account. A record that cites a CloudTrail event in the wrong account will not match the auditor's export. We stamp recipientAccountId on the record and reject joins that cross accounts by accident.

Volume is manageable because the human does not write the record. Reviewers review the pull request, which they were going to do. The pipeline writes the evidence. The compliance lead samples the GRC, which they were going to open anyway. The work we remove is the reconstruction.

Rutagon's own infrastructure changes follow the same shape: OIDC apply roles, no long-lived keys on the pipeline, plan digest before apply. That is engineering practice. It is not an attestation you can borrow.

Frequently Asked Questions

What is the population for infrastructure change evidence?

Production changes in the audit window. We take merged infrastructure pull requests that applied, and CloudTrail mutating events in the production account. Plans that never applied stay out. Console edits stay in, as exceptions, until they are tied to an approved record.

Why hash the Terraform plan?

The reviewer approved a specific diff. A later local plan can differ. We hash the plan artifact from the CI job that was reviewed and expect apply to use that artifact. If the digest changes, the record does not close on the old approval.

How do you show separation of duties?

The record stores the author and the approver as different people, and it stores the CI role that performed the apply. The role is not treated as the approver. A missing second person leaves the record open for the compliance lead.

Where are console changes recorded?

They are CloudTrail events under a human identity with no matching merged pull request. They attach to an emergency change record or they remain findings. We do not drop them to keep the change list tidy.

Do you replace our GRC tool with this pipeline?

No. The pipeline writes the change record into the GRC you already run and stores the ticket id. Rutagon builds the join between git, the plan, and CloudTrail. Your auditor samples your GRC.

Ask us to file this evidence in the GRC you already run. Contact Rutagon at rutagon.com/contact, 907-841-8407, or contact@rutagon.com.