---
title: "I Automated My SOC2 Audit Prep in an Afternoon (And You Can Too)"
published: false
tags: [security, devops, python, compliance]
---
Picture this: it's 11 PM, three weeks before your SOC2 audit, and you're manually screenshotting AWS console pages into a Google Doc labeled `evidence_FINAL_v3_USE_THIS_ONE.docx`.
We've all been there. Or we've *heard* the war stories.
This tutorial is the exit ramp from that highway to misery. We're going to wire up **ComplianceWeave** to your infrastructure and have it do the evidence-hunting while you do literally anything else.
By the end, you'll have a Python script that scans your environment, surfaces gaps, triggers remediation, and hands you a polished audit report — automatically.
Let's build it.
---
## What We're Working With
ComplianceWeave exposes three endpoints that, chained together, form a complete compliance automation loop:
| Endpoint | What it does |
|---|---|
| `POST /compliance/scan` | Kicks off an infrastructure scan against your chosen frameworks |
| `GET /compliance/reports` | Fetches generated audit-ready reports |
| `POST /compliance/remediate` | Auto-remediates fixable violations |
We'll use all three. In order. Like sensible engineers.
---
## Prerequisites
bash
pip install requests python-dotenv rich
We're using `rich` for readable terminal output because staring at raw JSON at 11 PM is a form of self-harm.
Create a `.env` file:
bash
COMPLIANCEWEAVE_API_KEY=your_api_key_here
COMPLIANCEWEAVE_BASE_URL=https://api.complianceweave.io/v1
---
## Step 1: Set Up Your Client
First, let's build a small, reusable client so we're not copy-pasting headers into every request.
python
compliance_client.py
import os
import time
import requests
from dotenv import load_dotenv
from rich.console import Console
from rich.panel import Panel
load_dotenv()
console = Console()
class ComplianceWeaveClient:
def init(self):
self.api_key = os.getenv("COMPLIANCEWEAVE_API_KEY")
self.base_url = os.getenv("COMPLIANCEWEAVE_BASE_URL")
if not self.api_key:
raise EnvironmentError(
"COMPLIANCEWEAVE_API_KEY not set. "
"Check your .env file."
)
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"Accept": "application/json",
})
def _request(self, method, endpoint, **kwargs):
url = f"{self.base_url}{endpoint}"
try:
response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
console.print(f"[bold red]HTTP Error:[/bold red] {e.response.status_code} — {e.response.text}")
raise
except requests.exceptions.ConnectionError:
console.print("[bold red]Connection failed.[/bold red] Is the API URL correct?")
raise
except requests.exceptions.Timeout:
console.print("[bold red]Request timed out.[/bold red] Try again or check your network.")
raise
**Why a session object?** It reuses the TCP connection across requests, which matters when you're hitting the API repeatedly during a scan-poll loop. Small wins add up.
---
## Step 2: Trigger a Compliance Scan
Now let's initiate a scan. ComplianceWeave lets you specify which frameworks to check — we'll target SOC2 and GDPR simultaneously.
python
scan.py
from compliance_client import ComplianceWeaveClient, console
from rich.progress import Progress, SpinnerColumn, TextColumn
import time
def run_scan(client, frameworks=None, environment="production"):
if frameworks is None:
frameworks = ["SOC2", "GDPR"]
console.print(Panel(
f"Starting scan for: [bold cyan]{', '.join(frameworks)}[/bold cyan]\n"
f"Environment: [bold]{environment}[/bold]",
title="🔍 ComplianceWeave Scan"
))
payload = {
"frameworks": frameworks,
"environment": environment,
"options": {
"deep_scan": True, # Check resource-level configs, not just account-level
"include_remediation": True # Flag auto-fixable issues
}
}
response = client._request("POST", "/compliance/scan", json=payload)
scan_id = response.get("scan_id")
if not scan_id:
raise ValueError("No scan_id returned. Something went wrong on the API side.")
console.print(f"[green]✓ Scan initiated.[/green] ID: [bold]{scan_id}[/bold]")
# Poll until the scan completes
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
task = progress.add_task("Scanning infrastructure...", total=None)
while True:
status_response = client._request("GET", f"/compliance/scan/{scan_id}")
status = status_response.get("status")
if status == "completed":
progress.stop()
break
elif status == "failed":
raise RuntimeError(f"Scan failed: {status_response.get('error', 'Unknown error')}")
time.sleep(10) # Don't hammer the API
console.print(f"[green]✓ Scan complete.[/green]")
return status_response
**Expected output:**
plaintext
╭──────────────── 🔍 ComplianceWeave Scan ────────────────╮
│ Starting scan for: SOC2, GDPR │
│ Environment: production │
╰─────────────────────────────────────────────────────────╯
✓ Scan initiated. ID: scan_a3f92b1c
⠸ Scanning infrastructure...
✓ Scan complete.
The polling loop is intentional. Scans against real infrastructure take time — fighting that with impatient code just gets you incomplete results.
---
## Step 3: Auto-Remediate What You Can
Before pulling the report, let's fix the low-hanging fruit. ComplianceWeave flags issues it can remediate automatically (think: misconfigured S3 bucket ACLs, overly permissive security groups, missing encryption settings).
python
remediate.py
from compliance_client import ComplianceWeaveClient, console
from rich.table import Table
def remediate_violations(client, scan_results):
violations = scan_results.get("violations", [])
auto_fixable = [v for v in violations if v.get("auto_remediable") is True]
if not auto_fixable:
console.print("[yellow]No auto-remediable violations found.[/yellow] Manual review required for all issues.")
return []
table = Table(title="Auto-Remediating Violations")
table.add_column("Resource", style="cyan")
table.add_column("Issue", style="yellow")
table.add_column("Framework", style="magenta")
for v in auto_fixable:
table.add_row(v["resource_id"], v["description"], v["framework"])
console.print(table)
violation_ids = [v["violation_id"] for v in auto_fixable]
payload = {
"violation_ids": violation_ids,
"dry_run": False # Set True to preview changes without applying
}
result = client._request("POST", "/compliance/remediate", json=payload)
fixed = result.get("remediated", [])
failed = result.get("failed", [])
console.print(f"[green]✓ Fixed {len(fixed)} violation(s).[/green]")
if failed:
console.print(f"[red]✗ {len(failed)} remediation(s) failed.[/red] Check the report for details.")
return fixed
> **Best practice:** Always run with `dry_run: True` first in a staging environment. Automated remediation is powerful. So is accidentally locking yourself out of a resource.
---
## Step 4: Pull Your Audit-Ready Report
The scan is done, violations are patched. Time to collect your evidence.
python
report.py
from compliance_client import ComplianceWeaveClient, console
import json
from datetime import datetime
def fetch_report(client, scan_id, output_format="pdf"):
console.print(f"\n[bold]Fetching report for scan:[/bold] {scan_id}")
params = {
"scan_id": scan_id,
"format": output_format, # "pdf", "json", or "csv"
"include_evidence": True, # Attach raw evidence artifacts
"frameworks": ["SOC2", "GDPR"]
}
report = client._request("GET", "/compliance/reports", params=params)
# Save the report locally
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"compliance_report_{timestamp}.json"
with open(filename, "w") as f:
json.dump(report, f, indent=2)
# Print a human-readable summary
summary = report.get("summary", {})
console.print(f"\n[bold green]Report Summary[/bold green]")
console.print(f" Overall Score: {summary.get('compliance_score', 'N/A')}%")
console.print(f" Controls Passed: {summary.get('controls_passed', 0)}")
console.print(f" Controls Failed: {summary.get('controls_failed', 0)}")
console.print(f" Report saved to: [cyan]{filename}[/cyan]")
return report, filename
**Expected output:**
plaintext
Fetching report for scan: scan_a3f92b1c
Report Summary
Overall Score: 91%
Controls Passed: 147
Controls Failed: 13
Report saved to: compliance_report_20240315_143022.json
---
## Step 5: Wire It All Together
python
main.py
from compliance_client import ComplianceWeaveClient, console
from scan import run_scan
from remediate import remediate_violations
from report import fetch_report
def main():
client = ComplianceWeaveClient()
# 1. Scan
scan_results = run_scan(
client,
frameworks=["SOC2", "GDPR", "HIPAA"],
environment="production"
)
# 2. Remediate what we can
remediate_violations(client, scan_results)
# 3. Re-scan to capture remediation (optional but recommended for auditors)
final_results = run_scan(client, frameworks=["SOC2", "GDPR", "HIPAA"])
# 4. Generate the report
report, filename = fetch_report(
client,
scan_id=final_results["scan_id"]
)
console.print(f"\n[bold green]✓ Done.[/bold green] Hand [cyan]{filename}[/cyan] to your auditor.")
if name == "main":
main()
---
## What You Actually Built
In ~150 lines of Python, you now have:
- **Automated evidence collection** across SOC2, GDPR, HIPAA, and ISO 27001
- **Intelligent remediation** that fixes what it safely can
- **Audit-ready reports** generated without a single screenshot
The real win isn't the code — it's the meeting you won't have to schedule to explain why the evidence doc is named `FINAL_v3_USE_THIS_ONE`.
Schedule this as a weekly cron job. Let ComplianceWeave run continuously. Show up to your audit with a folder of timestamped reports instead of a folder of regrets.
Your future self, three weeks before the next audit, will thank you.
---
*Questions? Drop them in the comments. I check. Eventually.*
Top comments (0)