Skip to main content

Detect additive drift in CloudFormation-managed resources that native drift detection misses

Project description

cfn-drift-extended

Detect additive drift and orphaned resources in CloudFormation-managed AWS accounts that native drift detection misses.

License: MIT Python 3.11+ Tests PyPI

๐Ÿ” The Problem

CloudFormation drift detection only catches modifications or deletions to resources it manages. It completely misses additive changes โ€” for example, a manually attached IAM policy on a CDK-managed role, an extra security group ingress rule, or an unauthorized SNS subscription. This tool fills that gap.

Real-world example: A reconciliation job failed in QA but worked in Dev. Root cause: someone had manually attached a broader IAM policy to the orchestrator role in Dev. CloudFormation showed "IN_SYNC" because the manual addition wasn't a modification โ€” it was an extra policy CFN didn't know about.

๐ŸŽฏ Supported Services

Additive drift detection (audit)

Service Drift Detected Severity
IAM Roles Extra inline policies, extra managed policies, modified policy documents HIGH
Security Groups Extra ingress rules (attack surface), extra egress rules (exfiltration) HIGH / MEDIUM
SNS Topics Extra policy statements, extra subscriptions HIGH / MEDIUM
SQS Queues Extra resource policy statements HIGH
EventBridge Extra rules on CFN-managed event buses MEDIUM
Lambda Extra environment variables, extra layers, extra resource policies MEDIUM
S3 Extra bucket policy statements, extra lifecycle rules, extra CORS rules MEDIUM
DynamoDB Extra GSIs, extra scaling targets, extra scaling policies MEDIUM

Orphan detection (orphans)

Service What It Detects Severity
IAM Roles Roles not managed by any active CFN stack HIGH
Security Groups SGs not managed by any active CFN stack MEDIUM
Lambda Functions Functions not managed by any active CFN stack MEDIUM
SQS Queues Queues not managed by any active CFN stack MEDIUM
SNS Topics Topics not managed by any active CFN stack MEDIUM

๐Ÿ“ฆ Installation

pip install cfn-drift-extended

Requirements: Python 3.11+

๐Ÿš€ Quick Start

# Audit all stacks starting with "my-app"
cfn-drift-extended audit --stack-prefix my-app --region us-east-1

# Audit specific stacks by name
cfn-drift-extended audit --stack-name my-stack-prod --region us-east-1

# Filter by tags
cfn-drift-extended audit --stack-prefix my-app --tag Environment=Production --region us-east-1

# Write JSON report for CI/CD
cfn-drift-extended audit --stack-prefix my-app --output-json report.json

# Don't fail on drift (just report)
cfn-drift-extended audit --stack-prefix my-app --no-fail-on-drift

# Verbose mode for debugging
cfn-drift-extended audit --stack-prefix my-app -v

# Control concurrency (default: 10 parallel workers)
cfn-drift-extended audit --stack-prefix my-app --max-workers 5

# Audit only specific services
cfn-drift-extended audit --stack-prefix my-app --services iam,sg

# Audit only SNS/SQS and EventBridge
cfn-drift-extended audit --stack-prefix my-app --services sns,sqs,eventbridge

๐Ÿ”Ž Orphan Detection (v1.1)

Detect resources that exist in your AWS account but aren't managed by any CloudFormation stack โ€” leaked resources from deleted stacks, console-created resources never brought under IaC, or resources created during incidents and forgotten.

# Detect orphaned resources across all services
cfn-drift-extended orphans --region us-east-1

# Scope to stacks with a specific prefix
cfn-drift-extended orphans --stack-prefix my-app --region us-east-1

# Cap deleted-stack scan for large accounts (default: 200)
cfn-drift-extended orphans --stack-prefix my-app --max-deleted-stacks 500

# Separate CFN API concurrency to avoid throttling (default: 5)
cfn-drift-extended orphans --stack-prefix my-app --max-cfn-workers 3

# Scan only specific services
cfn-drift-extended orphans --services iam,sg,lambda --region us-east-1

# JSON output for CI/CD
cfn-drift-extended orphans --stack-prefix my-app --output-json orphans.json

Orphan provenance classification

Each orphan is classified by origin:

Provenance Meaning Severity
cfn_orphan_deleted_stack Created by CFN, stack since deleted (DeletionPolicy: Retain) HIGH
unknown CloudFormation has no record โ€” could be non-IaC or scan was capped MEDIUM

Scale optimization (v1.1)

For accounts with thousands of deleted stacks, the --max-deleted-stacks flag (default: 200) caps the deleted-stack provenance scan. This prevents API throttling while still resolving provenance for the most recently deleted stacks. Unresolved orphans are classified as unknown rather than silently misclassified.

The --max-cfn-workers flag (default: 5) controls separate concurrency for CloudFormation API calls, independent from the detector thread pool (--max-workers).

๐Ÿ”’ Required IAM Permissions (Least Privilege)

This tool uses read-only AWS API calls exclusively. No write operations are performed.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CfnDriftExtendedReadOnly",
      "Effect": "Allow",
      "Action": [
        "cloudformation:ListStacks",
        "cloudformation:GetTemplate",
        "cloudformation:DescribeStacks",
        "cloudformation:DescribeStackResource",
        "cloudformation:ListStackResources",
        "iam:GetRole",
        "iam:GetRolePolicy",
        "iam:ListRolePolicies",
        "iam:ListAttachedRolePolicies",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSecurityGroupRules",
        "sqs:GetQueueAttributes",
        "sns:GetTopicAttributes",
        "sns:ListSubscriptionsByTopic",
        "events:DescribeEventBus",
        "events:ListRules",
        "events:ListTargetsByRule",
        "sts:GetCallerIdentity",
        "tag:GetResources",
        "iam:ListRoles",
        "lambda:ListFunctions",
        "lambda:GetPolicy",
        "ec2:DescribeVpcs",
        "sqs:ListQueues",
        "sns:ListTopics"
      ],
      "Resource": "*"
    }
  ]
}

For tighter scoping, restrict Resource to specific stack ARNs, role ARNs, security group IDs, queue ARNs, topic ARNs, and event bus ARNs.

โš™๏ธ Exit Codes

Code Command Meaning
0 audit No drift detected (or --no-fail-on-drift used)
1 audit Additive drift detected
0 orphans No orphans found
1 orphans Orphaned resources detected
2 Both Error (permission denied, invalid input, unexpected failure)

๐Ÿš€ Example Output

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  cfn-drift-extended โ€” Additive Drift Report
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  Stacks scanned:    2
  Resources scanned: 5
  Resources drifted: 1

โš  Found 1 drift finding(s) across 1 resource(s):

  [HIGH] tax-reconciliation-tool-orchestrator (tax-reconciliation-tool-dev)
         Managed policy 'arn:aws:iam::123456789012:policy/ManualBroadAccess'
         is attached to role but is not declared in the CloudFormation template
         + arn:aws:iam::123456789012:policy/ManualBroadAccess

โš™๏ธ JSON Report Format

{
  "tool_version": "0.1.0",
  "account_id": "123456789012",
  "region": "us-east-1",
  "timestamp": "2026-05-19T14:30:00+00:00",
  "stacks_scanned": 3,
  "resources_scanned": 12,
  "resources_with_drift": 2,
  "findings": [
    {
      "resource_type": "AWS::IAM::Role",
      "resource_id": "my-role",
      "stack_name": "my-stack",
      "drift_type": "managed_policy_attached",
      "severity": "high",
      "description": "Managed policy 'arn:...' is attached but not in template",
      "expected": ["arn:aws:iam::aws:policy/AWSLambdaBasicExecutionRole"],
      "actual": ["arn:aws:iam::aws:policy/AWSLambdaBasicExecutionRole", "arn:aws:iam::aws:policy/AdministratorAccess"],
      "extra": "arn:aws:iam::aws:policy/AdministratorAccess"
    }
  ],
  "errors": []
}

GitHub Action Usage

# Additive drift detection
- uses: mopyle4/cfn-drift-extended@v1.1
  with:
    command: "audit"
    stack-prefix: "my-app"
    region: "us-east-1"
    services: "iam,sg,sns,sqs,eventbridge"
    fail-on-drift: "true"
    output-json: "drift-report.json"

# Orphan detection
- uses: mopyle4/cfn-drift-extended@v1.1
  with:
    command: "orphans"
    stack-prefix: "my-app"
    region: "us-east-1"
    max-deleted-stacks: "200"
    output-json: "orphan-report.json"

๐Ÿ—๏ธ Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CLI (Click)โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚   Auditor    โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  Reporters  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                   โ”‚
       โ”‚          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚          โ–ผ        โ–ผ        โ–ผ
       โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚   โ”‚Collectorsโ”‚ โ”‚Collectorsโ”‚ โ”‚Comparatorsโ”‚
       โ”‚   โ”‚(expected)โ”‚ โ”‚ (actual) โ”‚ โ”‚  (diff)   โ”‚
       โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Orphan Auditor  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Provenance       โ”‚
โ”‚ (parallel detectโ”‚     โ”‚ Resolver         โ”‚
โ”‚  per service)   โ”‚     โ”‚ (tag + CFN API)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Managed Index   โ”‚ โ† Active + deleted stacks (capped)
โ”‚ (cfn_managed)   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  • Collectors gather state (expected from CFN templates, actual from AWS APIs)
  • Comparators diff expected vs actual using set operations (O(n))
  • Reporters format results for different output targets (console, JSON, GitHub Checks)
  • Auditor orchestrates the pipeline with parallel execution

โšก Design Principles

Principle Implementation
Least Privilege Read-only API calls only; no write operations
SOLID Single responsibility per module; dependency injection via constructor
Immutable Models Frozen Pydantic models and frozen dataclasses prevent mutation
Graceful Degradation Individual resource failures don't crash the audit
Performance Parallel auditing via ThreadPoolExecutor; set operations for O(n) comparison
Adaptive Retry Exponential backoff with jitter (boto3 adaptive mode, 5 max attempts)
CI/CD Ready Exit codes, JSON output, and --fail-on-drift flag

โšก Performance Characteristics

  • Time complexity: O(S ร— R) where S = stacks scanned, R = resources per stack
  • Comparison: O(n) set-based diff operations per resource
  • Concurrency: Configurable thread pool (default 10 workers) for parallel resource auditing
  • Memory: Frozen dataclasses with __slots__ for minimal memory footprint
  • Network: Adaptive retry with exponential backoff prevents throttling

๐Ÿ› ๏ธ Troubleshooting

Symptom Cause Fix
Exit code 2 with "Permission denied" Missing IAM permissions Add the required permissions from the policy above
No stacks found Prefix doesn't match or stacks are in non-terminal state Check stack names with aws cloudformation list-stacks
Slow execution Many roles across many stacks Increase --max-workers or narrow --stack-prefix
False positives on CDK stacks CDK generates AWS::IAM::Policy resources separately Already handled โ€” external policies are associated with their target roles
Orphan provenance all "unknown" --max-deleted-stacks too low for your account Increase --max-deleted-stacks (default 200)
CFN API throttling during orphans Too many concurrent CFN calls Decrease --max-cfn-workers (default 5)
Many orphans from service-linked roles AWS-managed roles show as orphans Already filtered โ€” service-linked and AWS-reserved roles are excluded

๐Ÿงช Development

# Clone and install in dev mode
git clone https://github.com/mopyle4/cfn-drift-extended.git
cd cfn-drift-extended
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# Run tests (242 tests)
pytest --cov=cfn_drift_extended --cov-report=term-missing

# Lint
ruff check src/ tests/

# Type check
mypy src/

๐Ÿค Contributing

See CONTRIBUTING.md for guidelines.

๐Ÿ“„ License

MIT โ€” see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cfn_drift_extended-1.1.0.tar.gz (104.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cfn_drift_extended-1.1.0-py3-none-any.whl (89.5 kB view details)

Uploaded Python 3

File details

Details for the file cfn_drift_extended-1.1.0.tar.gz.

File metadata

  • Download URL: cfn_drift_extended-1.1.0.tar.gz
  • Upload date:
  • Size: 104.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for cfn_drift_extended-1.1.0.tar.gz
Algorithm Hash digest
SHA256 fa4b6e491807cee2974c553e6e12bdd17ef54d941ca7c4a3caa39969471fb305
MD5 5177cbb6790ff390a9ec4b220a634f72
BLAKE2b-256 7943bfb79951e2e14a47ab38c96d221f37badd3baa10da53c2e9cf4c7d8b69e7

See more details on using hashes here.

File details

Details for the file cfn_drift_extended-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for cfn_drift_extended-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cc19a6e7486fa7f8ca9c9be34e8d5d749938ec346b2aac12825d53622c601e88
MD5 d42f88afcb1bc650aa42491376466343
BLAKE2b-256 b9360e3623ffdb5e2fa1adf3376c1f992ac7c18ac9728ded49a30d8353b07e49

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page