October 15, 2026
What SOC 2 Really Asks of a DevOps Team
Forty-five audit rows, two AWS regions, and the gap between running a secure system and proving you run one
By Khondaker Ahmed Neehad Umar
11 min read
Forty-five audit rows, two AWS regions, and the gap between running a secure system and proving you run one
It started with a spreadsheet. Forty-five rows, each one a control the auditor wanted proof of: admin users on the cloud console, encryption at rest, VPN tunnels, firewall rule reviews, backup restores, emergency changes, repository branch structure. Each row had a column for our evidence, a column for the reviewer's comments, and a status column. Most of the status column said the same thing: To be done yet.
The reviewer comments were the interesting part. "Could not evidence." "Attached evidence is incorrect." "Evidence not uploaded for review." "Mapped evidence is post-dated and cannot be considered." None of them said our infrastructure was broken. They said we hadn't shown that it worked.
Running a secure system and proving you run one are two different jobs. SOC 2 is about the second.
I'm a platform engineer, and I held the AWS keys for this round of evidence. This article covers what SOC 2 is, why companies like ours need it, what it actually takes from a DevOps team, and the obstacles we hit while turning that spreadsheet green, along with how we got past them.
What SOC 2 is
SOC 2 (System and Organization Controls 2) is an attestation framework from the AICPA, the American Institute of Certified Public Accountants. An independent auditor examines how a service organisation protects the systems and data it handles for customers, against five Trust Services Criteria:
- Security, the mandatory baseline: protection against unauthorised access.
- Availability: the system is up and usable as committed.
- Processing integrity: processing is complete, accurate and authorised.
- Confidentiality: confidential information is protected.
- Privacy: personal information is handled according to commitments.
The five Trust Services Criteria. Security is always in scope; the rest depend on what you promise customers.
There are two report types. A Type I report says your controls are suitably designed at a point in time. A Type II report says they operated effectively over a period, usually six to twelve months. Type II is the one customers ask for, and it's the harder one: a policy written last week proves nothing about the eleven months before it.
SOC 2 is not a fixed checklist like PCI DSS. You define your own controls, and the auditor tests whether you actually follow them. That flexibility is useful, but it means the burden of proof sits entirely with you.
Why it's required
No law forces a SaaS company to get SOC 2. Customers do. For a B2B platform that handles other companies' billing and revenue data, enterprise buyers send security questionnaires before they sign, and "Do you have a SOC 2 Type II report?" is usually the first question. A clean report replaces hundreds of individual questionnaire answers with one document from an independent auditor. Without it, deals stall in procurement.
There's a second, less advertised benefit. Preparing for SOC 2 makes you look at your own systems with an outsider's eyes. That turned out to be where the real value was.
What the effort actually looks like
From the outside, SOC 2 sounds like a documentation exercise. From the DevOps seat, it's an inventory of everything you've built, checked against what you've claimed about it. The evidence list touched almost every layer of our AWS footprint across two production regions.
The eight areas the evidence list covered.
- Identity and access: who has admin access, how MFA is enforced, credential rotation.
- Encryption: data at rest (databases, backups, storage) and in transit (TLS settings, certificates).
- Network: VPC design, public vs private subnets, security groups, VPN, a firewall rule review.
- Change management: change requests traced from ticket to deployment, peer review, emergency changes.
- Resilience: backup configuration, backup alerts, and proof that restores work.
- Monitoring: availability, alerting, system health.
- Vulnerability management: pen test retests, patching, static code analysis.
- Code and configuration: repository structure per environment, branch protection.
Each row needed more than an answer. It needed dated, verifiable evidence that ties a claim to the real system. For almost every row, that meant the same loop:
Read the reviewer's comment carefully to understand what they rejected and why.
- Pull the current configuration from AWS, GitHub, Grafana or the identity provider.
- Compare it with what our existing evidence claimed, which often didn't match.
- Capture screenshots and data exports with the account, region and timestamp visible.
- Write the evidence text, the comment, and any gap with a remediation plan.
Multiply that by forty-five rows, two regions and several tools, and the effort becomes clear. It's not one big task; it's hundreds of small, careful ones.
The obstacles, and how we got past them
1. "A screenshot of commands" is not evidence
Several earlier submissions had been rejected because they showed the commands we ran, not the system itself. One reviewer wrote that our repository evidence "shows a list of commands" and needed to be "more granular to display the tree and branch structure."
What worked: evidence that shows the result in the system of record. For the repositories, that meant the GitHub tree for each environment folder, a full directory-tree export, and the branch rulesets in force on main. For AWS, every console screenshot had the account name and region visible, and every terminal capture started with date -u. A reviewer should be able to tell from the image alone what, where and when.
2. Evidence has to come from inside the audit period
Our emergency change process document was rejected because it was written after the period being audited. That's fair: a Type II report tests whether you followed a process, not whether you can describe one.
What worked: looking for contemporaneous traces of real work instead of writing new documents. A genuine emergency change during the period, an urgent credential rotation, had left a full trail: the incident announcement, the plan reviewed in the team channel with conditions set by leadership (a no-downtime sequence and a low-traffic change window), the dry run in dev, the runbook in Confluence, and the post-change log checks. We exported those records and redacted every secret. The process document became the formal version of steps we could prove we had already taken.
The same idea saved the backup-restore row. There was no scheduled restore-test plan, but CloudTrail showed two real restores of production snapshots into temporary clusters during the period, and the clean-up events afterwards.
3. The auditor's mental model isn't your architecture
The evidence list asked for a "cloud firewall console", "Group Policy in the AD database", a "VPN connection to the office network" and "Patch Compliance Reports from AWS Systems Manager". We have no office network, no Active Directory, and no customer-managed OS on most compute: workloads run on managed Kubernetes with Fargate and auto-managed nodes.
What worked: translating rather than forcing a fit. The "firewall" is security groups plus network ACLs, so we reviewed those. The "office network" is the production VPC reached over a client VPN. Patching on managed nodes is shown by node age and version rotation rather than an SSM patch report. Every translation was stated explicitly in the evidence text, so the reviewer didn't have to guess.
4. The tool they asked for wasn't there, and changes were frozen
One row asked for an AWS Audit Manager console aggregating AWS Config and CloudTrail data. Config wasn't enabled, Audit Manager only collects evidence from the day you turn it on, and we were working under a no-new-changes constraint.
What worked: read-only scripts. We wrote small, re-runnable shell scripts that queried each service directly (EKS, EC2, EBS, RDS, DynamoDB, load balancers, S3, security groups, backups) and produced timestamped text exports plus CSVs with a per-row assessment column. That gave the auditor the data Audit Manager would have aggregated, and it gave us a repeatable evidence pipeline for next year.
for R in us-west-2 eu-north-1; do
aws rds describe-db-clusters --region "$R" \
--query 'DBClusters[].{Cluster:DBClusterIdentifier,Encrypted:StorageEncrypted,BackupDays:BackupRetentionPeriod,DeletionProtection:DeletionProtection}' \
--output table
donefor R in us-west-2 eu-north-1; do
aws rds describe-db-clusters --region "$R" \
--query 'DBClusters[].{Cluster:DBClusterIdentifier,Encrypted:StorageEncrypted,BackupDays:BackupRetentionPeriod,DeletionProtection:DeletionProtection}' \
--output table
done5. Evidence that contradicted our own claims
This was the most uncomfortable obstacle, and the most valuable. Several existing statements in the evidence sheet didn't survive contact with the real configuration:
- "All external communication uses TLS 1.2+": the load balancers were on a default security policy that still allows older TLS versions.
- The VPN was described as SSO-authenticated; it actually used certificate-based authentication.
- A status page showed "1 year uptime: 100%", but monitoring had only existed for a few months, and some components turned out to be updated manually rather than checked automatically.
- Load-balancer health checks reported "unhealthy" targets that were healthy services with misconfigured health-check paths.
What worked: rewriting claims to match reality, then evidencing them. A precise claim with a known gap is far more defensible than a confident one the auditor can disprove with a single screenshot. Where an independent measurement existed, such as Prometheus-based service uptime in Grafana, we led with that.
A precise claim with a known gap beats a confident claim the auditor can disprove with one screenshot.
6. Collecting evidence surfaces real security issues
Running every check end to end found things nobody had been looking for: SSH open to the internet on security groups attached to stopped test instances, a database security group broader than its peers, dozens of unattached security groups, a region without a backup plan, and a credential that should have been retired still authorised on a host after its rotation.
What worked: triaging by real exposure, not by rule text. For every "open to 0.0.0.0/0" rule, we checked what it was attached to and whether that resource had a public IP or sat in a private subnet. That turned a scary list into an honest risk rating: no high-severity exposure, a handful of mediums, and a set of hygiene items. Anything that couldn't wait was fixed immediately and recorded as part of the evidence; the rest went into a rated backlog with owners and dates.
7. Access and visibility gaps
Some evidence wasn't visible to a non-admin. The GitHub branch-protection API returned 404 ("branch not protected") because protection was implemented through organisation rulesets instead. Bypass lists for those rulesets were visible only to org admins. Reading bastion auth logs needed an elevated session.
What worked: never trusting a single API response. When one check said "not protected", we queried the effective rules from another angle and found pull-request review, CODEOWNERS approval and force-push protection all enforced. Then we traced a real production deployment back through the pipeline to an approved pull request, which proved the control end to end.
gh api repos/ORG/REPO/rules/branches/main \
--jq '.[] | {type, params: .parameters}'gh api repos/ORG/REPO/rules/branches/main \
--jq '.[] | {type, params: .parameters}'
8. Small things that cost real time
Shell portability (zsh doesn't split words the way bash does), placeholder values pasted literally into a terminal, the AWS CLI pager swallowing output, macOS shipping an old bash. None of it is interesting, but all of it slowed things down. Writing evidence scripts as portable bash files, rather than pasting multi-line loops into an interactive shell, removed most of it.
How we turned it into a good report
A few principles ended up shaping every row.
Answer the reviewer's comment, literally. Each rejection said what was missing. "Provide the network topology showing resources communicating with the databases through the subnets" became exactly that: a per-region diagram tracing every path to the databases, backed by a subnet classification export and the database security group rules.
Pair a screenshot with a data export. Screenshots give the reviewer visual context; CSVs and text exports give completeness. A firewall rule review is far more convincing as a report plus every rule tagged with a finding ID and rating than as ten screenshots of the console.
Make every file self-describing. Consistent naming tied each file to its row (for example AWS-22.4a_restore-2026-09-29_detail.png), and every evidence bundle went into one zip per row. Text exports carried a header saying where the data came from, which command produced it, and when.
Redact deliberately. Tokens, private keys and credentials never went into evidence. Where a log line was the evidence, key fingerprints were partially masked. Messages that contained secrets were left out entirely rather than blurred, because a blurred private key still tells the auditor a private key was shared.
State gaps instead of hiding them. Every row that wasn't perfect said so, with a remediation item, an owner and a target date. Cropping a red badge out of a dashboard only works until the auditor runs the same check. An honest "medium, mitigated, fixing by date X" holds up; a hidden finding doesn't.
Reuse strong evidence across rows. One production deployment, traced from ticket to branch, to a pull request with a real "changes requested, then approved" review, to the CI build and the GitOps deployment, answered questions about repository controls, change management and peer review at once.
A SOC 2 readiness checklist for DevOps teams
If your audit is coming up, these are the checks I'd run first, because each one maps to a row that is easy to get wrong:
- Every claim in your evidence sheet matches the live configuration today.
- Load balancers use a TLS 1.2+ security policy, and HTTP is redirected or not exposed.
- No security group allows 0.0.0.0/0 to anything except intended public endpoints.
- Unused security groups, instances and target groups are cleaned up.
- Every production region has a backup plan, and at least one restore was tested and recorded during the period.
- Rotated credentials are actually removed everywhere, and the removal is verified.
- The main branch of every deployment repository requires reviewed pull requests, and any bot bypass is documented.
- Monitoring shows real, automated measurements, and you know how long the history goes back.
- AWS Config and Security Hub (or equivalents) are enabled, so evidence accumulates continuously.
Tools we leaned on
- AWS CLI with JMESPath queries: read-only exports for every service, in table or CSV form.
- CloudTrail event history: proof of real actions (restores, deletions) with who and when.
- GitHub CLI (
gh api): rulesets, pull requests and reviews, and the commit-to-PR trace. - kubectl and ArgoCD: cluster and application health across environments.
- Grafana and Prometheus: measured service uptime over the period.
- AWS Systems Manager: running checks on hosts without opening SSH.
- Slack and Confluence history: contemporaneous records of decisions and changes.
Compliance is a habit, not a project
The hardest part of this exercise was reconstructing a year of evidence after the fact. Most of the effort we spent would disappear if the evidence had been produced as a by-product of normal work. That's where we're heading.
Turn on the recorders. AWS Config and Security Hub, so configuration history and compliance checks exist continuously, not only on the day someone asks.
- Schedule the reviews. Firewall rule reviews, access reviews and certificate checks on a fixed cadence, each producing a dated report.
- Automate restore testing. A scheduled restore-testing plan, so "prove backups work" is a report, not an investigation.
- Keep the evidence scripts. The read-only export scripts written for this audit are the starting point for next year's evidence.
- Close the loop on findings. Every finding from this round sits in the backlog with an owner and a date, and the next review re-verifies them.
SOC 2 didn't tell us anything we couldn't have found ourselves. It made us look. The spreadsheet of forty-five rows was really a list of questions every platform team should be able to answer about its own systems at any time. The goal for next year is simple: when the auditor asks, the answer already exists.
I'm a Platform/DevOps engineer working on infrastructure, observability, incident management and CI/CD. If you're preparing for your own SOC 2 audit and want to compare notes, feel free to reach out.