Skip to content

EdwinJdevops/driftguard

Repository files navigation

DriftGuard

CI License: MIT Python 3.12 PRs Welcome

Terraform drift detection and remediation. Compares terraform.tfstate against live AWS state, scores every discrepancy against CIS AWS Benchmarks and MITRE ATT&CK, prices the monthly cost delta, and opens a GitHub PR with a suggested Terraform patch — for a human to review, not to auto-merge.

▶ Interactive demo · CLI · VS Code extension · API docs


The problem

Terraform state describes what your infrastructure should look like. It does not verify your infrastructure still looks like that. A security group rule changed through the console, an S3 bucket with encryption disabled mid-incident, an RDS instance flipped to publicly accessible — none of it shows up in terraform plan unless someone re-applies against the same resource. Most teams find out during an audit, or after an incident.

DriftGuard checks continuously instead, and turns every finding into a reviewable PR.

Architecture

flowchart LR
    subgraph Sources["State sources"]
        TF["terraform.tfstate\n(uploaded or S3 backend)"]
        AWS["Live AWS API\n(via STS AssumeRole)"]
    end

    subgraph Engine["Drift Engine"]
        Parser["TerraformStateParser"]
        Collector["AWSStateCollector"]
        Analyzer["DriftAnalyzer\n→ CIS + MITRE ATT&CK mapping"]
        Scorer["PostureScorer\n→ 0-100"]
        PatchGen["Patch generator\n→ HCL"]
    end

    subgraph Outputs["Outputs"]
        DB[("PostgreSQL\nDriftFinding")]
        GH["GitHub App\n→ PR to driftguard-remediations/"]
    end

    subgraph Clients["Access"]
        API["FastAPI (async, API-key auth)"]
        CLI["driftguard-cli"]
        VSC["VS Code extension"]
    end

    TF --> Parser
    AWS --> Collector
    Parser --> Analyzer
    Collector --> Analyzer
    Analyzer --> Scorer
    Analyzer --> PatchGen
    Scorer --> DB
    PatchGen --> DB
    DB --> GH
    API --> DB
    CLI --> API
    VSC --> API
Loading

Scans run as FastAPI background tasks in their own DB transaction — this matters: a background task opens its own session and will not see uncommitted data from the request that triggered it, so the request path commits explicitly before dispatch (see backend/api/main.py).

Security model

Two credential problems come up in any tool that touches a customer's AWS account and GitHub repo. Both are handled the same way — short-lived, scoped, per-tenant identity, never a static secret with broad access:

Naive approach (rejected) What DriftGuard does
AWS access Accept aws_access_key_id/secret over HTTP STS AssumeRole with a per-workspace external ID (confused-deputy guarded — DriftGuard actively probes that a role rejects the wrong external ID before trusting it)
GitHub access One static PAT with access to every customer's repo GitHub App installation tokens — JWT-signed, scoped only to repos that installed the app, expire in ~1 hour

Self-hosted single-account deployments can skip the AssumeRole flow entirely — DriftGuard falls back to the ambient credential chain (an IAM role already attached to wherever it's running).

What it does not do

  • Does not modify your infrastructure directly. It reads AWS state and Terraform state; nothing is written to either.
  • Does not auto-apply patches or splice them into your existing .tf files — that's an HCL-aware-parsing problem, and getting it wrong silently corrupts real infrastructure code. Patches land as new files under driftguard-remediations/ in a PR, for human review.
  • Does not support multi-cloud yet. AWS only — Azure and GCP collectors aren't built.
  • Is not a replacement for terraform plan. Plan tells you what will change. DriftGuard tells you what already changed outside your control.

Resource coverage

Resource Tracked attributes
aws_instance instance_type, vpc_security_group_ids, iam_instance_profile
aws_s3_bucket versioning, server_side_encryption_configuration, acl
aws_security_group ingress, egress
aws_db_instance instance_class, publicly_accessible, storage_encrypted
aws_iam_role assume_role_policy
aws_iam_policy policy document

Extending coverage means adding a method to AWSStateCollector and a rule entry to SECURITY_RULES in backend/engines/drift.py.

Quick start

Run the engine locally — no AWS account needed

git clone https://github.com/EdwinJdevops/driftguard.git
cd driftguard
pip install -r requirements-dev.txt
pytest backend/tests/ -v

45 tests, synthetic state and moto-mocked AWS/STS — nothing leaves your machine, no credentials required.

Full API + dashboard

pip install -r requirements.txt
uvicorn backend.api.main:app --reload

API at http://localhost:8000, interactive docs at /docs. Defaults to SQLite if DATABASE_URL is unset — fine for testing, not for a deployed instance (most free hosts wipe the filesystem on restart). Use Postgres in production.

python3 -m http.server 3000 --directory frontend

CLI

pip install -e ./cli   # not yet on PyPI
driftguard signup --org-name "Acme" --org-slug acme
driftguard workspace create prod --region us-east-1
driftguard scan trigger <workspace-id> --wait

Full command reference: cli/README.md.

VS Code extension

Findings sidebar, one-click scan triggers, jump straight to a remediation PR. Built and type-checked; not yet published to the Marketplace. Package it locally:

cd vscode-extension
npm install && npx @vscode/vsce package
# Install the resulting .vsix via "Extensions: Install from VSIX..." in VS Code

Details: vscode-extension/README.md.

API

Every authenticated route expects Authorization: Bearer dg_live_....

curl -X POST $API/signup -d '{"org_name": "Acme", "org_slug": "acme"}'

curl -X POST $API/workspaces -H "Authorization: Bearer $KEY" \
  -d '{"name": "production", "region": "us-east-1"}'

curl -X POST $API/workspaces/$WS_ID/scan -H "Authorization: Bearer $KEY" \
  -d @terraform.tfstate

curl $API/scans/$SCAN_ID -H "Authorization: Bearer $KEY"

Full schema at /docs.

Known limitations

Stated plainly, not buried:

  • Scheduling isn't enforced. scan_interval_minutes is stored on a workspace but nothing dispatches on it — scans are triggered manually or via API/CLI/extension today. Celery is a listed dependency with an empty workers/ package; wiring it up is the next real gap, not a hidden one.
  • Cross-account AssumeRole flow is built and unit-tested, not live-validated — it needs a real AWS account acting as the trusted principal, which isn't provisioned yet. Self-hosted single-account mode (ambient credentials) is the supported path today.
  • No Alembic migrations. init_db() runs Base.metadata.create_all. Fine pre-launch; a real gap once there's production data to migrate around.
  • No multi-tenant load testing. Built for correctness, not yet load-tested under concurrent orgs/workspaces.
  • ruff doesn't gate the build — CI runs it with --exit-zero, so lint failures are visible but don't fail the pipeline.

Testing

pytest backend/tests/ -v    # 45 tests: drift engine, AWS auth (STS + confused-deputy), GitHub PR automation
cd cli && pytest tests/ -v  # CLI client tests

GitHub PR automation tests use httpx.MockTransport — real request routing and serialization, only the socket is faked, so a wrong URL or method fails the test rather than being silently accepted. AWS auth tests mix moto (mechanics) with direct ClientError mocking for the confused-deputy check specifically, because moto doesn't enforce IAM trust-policy conditions and would otherwise give false confidence.

Contributing

Issues and PRs welcome. If you're adding resource coverage, a new AWSStateCollector method plus a SECURITY_RULES entry and matching test in backend/tests/test_drift_engine.py is the expected shape of the change.

License

MIT.

Author

Edwin Jonathan Chibuike. Cloud and DevOps Engineer, Lagos, Nigeria.

github.com/EdwinJdevops · linkedin.com/in/edwin-jonathan-1094093b0

About

Open-source Terraform drift detection with auto-PR fixes, security scoring and cost delta

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors