mopyle4/cfn-drift-extended

GitHub: mopyle4/cfn-drift-extended

检测 CloudFormation 原生漂移检测遗漏的增量配置漂移和未被堆栈管理的孤立资源的 IaC 治理工具。

Stars: 1 | Forks: 1

# cfn-drift-extended 检测 CloudFormation 原生漂移检测遗漏的**增量漂移**和**孤立资源**。 [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue.svg)](https://www.python.org/) [![Tests](https://img.shields.io/badge/Tests-242%20passing-brightgreen.svg)](#-development) [![PyPI](https://img.shields.io/badge/PyPI-v1.1.0-blue.svg)](https://pypi.org/project/cfn-drift-extended/) ## 🔍 问题描述 CloudFormation 漂移检测只能捕获对其管理的资源的修改或删除。它完全遗漏了**增量变更** —— 例如,手动将 IAM policy 附加到由 CDK 管理的 role、添加额外的 security group ingress rule,或者未经授权的 SNS subscription。此工具正是为了填补这一空白。 **真实案例:** 一个对账任务在 QA 环境中失败,但在 Dev 环境中正常运行。根本原因:有人手动将一个权限更广的 IAM policy 附加到了 Dev 环境的 orchestrator role 上。CloudFormation 显示为 "IN_SYNC",因为这种手动添加并不是修改 —— 而是 CFN 不知道的额外 policy。 ## 🎯 支持的服务 ### 增量漂移检测 (`audit`) | 服务 | 检测到的漂移 | 严重程度 | |---------|---------------|----------| | **IAM Roles** | 额外的 inline policy、额外的 managed policy、被修改的 policy document | HIGH | | **Security Groups** | 额外的 ingress rule(攻击面)、额外的 egress rule(数据泄露) | HIGH / MEDIUM | | **SNS Topics** | 额外的 policy statement、额外的 subscription | HIGH / MEDIUM | | **SQS Queues** | 额外的 resource policy statement | HIGH | | **EventBridge** | CFN 管理的 event bus 上额外的 rule | MEDIUM | | **Lambda** | 额外的环境变量、额外的 layer、额外的 resource policy | MEDIUM | | **S3** | 额外的 bucket policy statement、额外的 lifecycle rule、额外的 CORS rule | MEDIUM | | **DynamoDB** | 额外的 GSI、额外的 scaling target、额外的 scaling policy | MEDIUM | ### 孤立资源检测 (`orphans`) | 服务 | 检测目标 | 严重程度 | |---------|----------------|----------| | **IAM Roles** | 未由任何活跃 CFN stack 管理的 Role | HIGH | | **Security Groups** | 未由任何活跃 CFN stack 管理的 SG | MEDIUM | | **Lambda Functions** | 未由任何活跃 CFN stack 管理的 Function | MEDIUM | | **SQS Queues** | 未由任何活跃 CFN stack 管理的 Queue | MEDIUM | | **SNS Topics** | 未由任何活跃 CFN stack 管理的 Topic | MEDIUM | ## 📦 安装说明 ``` pip install cfn-drift-extended ``` **环境要求:** Python 3.11+ ## 🚀 快速开始 ``` # 审计所有以 "my-app" 开头的 stack cfn-drift-extended audit --stack-prefix my-app --region us-east-1 # 按名称审计特定 stack cfn-drift-extended audit --stack-name my-stack-prod --region us-east-1 # 按 tag 过滤 cfn-drift-extended audit --stack-prefix my-app --tag Environment=Production --region us-east-1 # 为 CI/CD 生成 JSON 报告 cfn-drift-extended audit --stack-prefix my-app --output-json report.json # 检测到 drift 不报错(仅报告) cfn-drift-extended audit --stack-prefix my-app --no-fail-on-drift # 用于调试的 verbose 模式 cfn-drift-extended audit --stack-prefix my-app -v # 控制 concurrency(默认:10 个 parallel worker) cfn-drift-extended audit --stack-prefix my-app --max-workers 5 # 仅审计特定 service cfn-drift-extended audit --stack-prefix my-app --services iam,sg # 仅审计 SNS/SQS 和 EventBridge cfn-drift-extended audit --stack-prefix my-app --services sns,sqs,eventbridge ``` ## 🔎 孤立资源检测 (v1.1) 检测您的 AWS 账户中存在但未由任何 CloudFormation stack 管理的资源 —— 例如已删除 stack 遗漏的资源、在控制台创建且从未纳入 IaC 的资源,或在突发事件期间创建后被遗忘的资源。 ``` # 检测跨所有 service 的 orphaned resource cfn-drift-extended orphans --region us-east-1 # 限定于具有特定前缀的 stack cfn-drift-extended orphans --stack-prefix my-app --region us-east-1 # 限制大型 account 的 deleted-stack 扫描数量(默认:200) cfn-drift-extended orphans --stack-prefix my-app --max-deleted-stacks 500 # 单独设置 CFN API concurrency 以避免 throttling(默认:5) cfn-drift-extended orphans --stack-prefix my-app --max-cfn-workers 3 # 仅扫描特定 service cfn-drift-extended orphans --services iam,sg,lambda --region us-east-1 # 用于 CI/CD 的 JSON 输出 cfn-drift-extended orphans --stack-prefix my-app --output-json orphans.json ``` ### 孤立资源来源分类 每个孤立资源都会按来源进行分类: | 来源 | 含义 | 严重程度 | |------------|---------|----------| | `cfn_orphan_deleted_stack` | 由 CFN 创建,但 stack 随后已被删除 (DeletionPolicy: Retain) | HIGH | | `unknown` | CloudFormation 无相关记录 —— 可能是非 IaC 资源,或扫描已达上限 | MEDIUM | ### 规模优化 (v1.1) 对于拥有数千个已删除 stack 的账户,`--max-deleted-stacks` 标志(默认值:200)会对已删除 stack 的来源扫描设置上限。这能在防止 API throttling 的同时,依然解析出最近删除的 stack 的来源。未解析的孤立资源会被分类为 `unknown`,而不会被静默错误分类。 `--max-cfn-workers` 标志(默认值:5)用于控制 CloudFormation API 调用的独立并发度,它与检测器线程池(`--max-workers`)相互独立。 ## 🔎 所需的 IAM 权限 (最小权限原则) 此工具仅使用**只读**的 AWS API 调用。不执行任何写操作。 ``` { "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": "*" } ] } ``` 如需更严格的范围限制,可将 `Resource` 限制为特定的 stack ARN、role ARN、security group ID、queue ARN、topic ARN 以及 event bus ARN。 ## ⚙️ 退出代码 | 代码 | 命令 | 含义 | |------|---------|---------| | 0 | `audit` | 未检测到漂移(或使用了 `--no-fail-on-drift`) | | 1 | `audit` | 检测到增量漂移 | | 0 | `orphans` | 未发现孤立资源 | | 1 | `orphans` | 检测到孤立资源 | | 2 | Both | 发生错误(权限被拒绝、输入无效、意外故障) | ## 🚀 输出示例 ``` ════════════════════════════════════════════════════════════════ 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 报告格式 ``` { "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 用法 ``` # 增量 drift 检测 - 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 检测 - 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" ``` ## 🏗️ 架构设计 ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ 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** 负责收集状态(预期状态来自 CFN 模板,实际状态来自 AWS API) - **Comparators** 使用集合运算对预期与实际状态进行差异比对 (O(n)) - **Reporters** 负责将结果格式化为不同的输出目标(控制台、JSON、GitHub Checks) - **Auditor** 通过并行执行来编排整个流水线 ## ⚡ 设计原则 | 原则 | 实现方式 | |-----------|---------------| | **最小权限** | 仅进行只读 API 调用;无写操作 | | **SOLID** | 每个模块承担单一职责;通过构造函数进行依赖注入 | | **不可变模型** | 冻结的 Pydantic model 和 frozen dataclass 可防止数据突变 | | **优雅降级** | 单个资源失败不会导致审计崩溃 | | **高性能** | 通过 ThreadPoolExecutor 进行并行审计;使用集合运算实现 O(n) 的比较 | | **自适应重试** | 带有抖动的指数退避(boto3 自适应模式,最大尝试 5 次) | | **适配 CI/CD** | 提供退出代码、JSON 输出以及 `--fail-on-drift` 标志 | ## ⚡ 性能特征 - **时间复杂度:** O(S × R),其中 S = 扫描的 stack 数,R = 每个 stack 中的资源数 - **比较:** 每个资源使用基于集合的 O(n) 差异运算 - **并发:** 可配置的线程池(默认 10 个 worker)用于并行资源审计 - **内存:** 带有 `__slots__` 的 frozen dataclass 以实现最小的内存占用 - **网络:** 带有指数退避的自适应重试可防止 throttling ## 🛠️ 故障排除 | 症状 | 原因 | 修复方法 | |---------|-------|-----| | 退出代码为 2 且提示 "Permission denied" | 缺少 IAM 权限 | 添加上方 policy 中所述的必需权限 | | 未找到任何 stack | Prefix 不匹配或 stack 处于非终止状态 | 使用 `aws cloudformation list-stacks` 检查 stack 名称 | | 执行缓慢 | 多个 stack 中包含大量 role | 增加 `--max-workers` 或缩小 `--stack-prefix` 范围 | | CDK stack 出现误报 | CDK 会单独生成 `AWS::IAM::Policy` 资源 | 已处理 —— 外部 policy 会与其目标 role 关联 | | 孤立资源来源全为 "unknown" | 对于您的账户而言 `--max-deleted-stacks` 设置过低 | 增加 `--max-deleted-stacks`(默认为 200) | | 孤立资源检测期间发生 CFN API throttling | 并发的 CFN 调用过多 | 减少 `--max-cfn-workers`(默认为 5) | | 由于 service-linked role 导致出现大量孤立资源 | AWS 管理的 role 显示为孤立资源 | 已过滤 —— service-linked role 和 AWS 保留的 role 将被排除 | ## 🧪 开发指南 ``` # 克隆并以 dev 模式安装 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]" # 运行测试(242 个测试) pytest --cov=cfn_drift_extended --cov-report=term-missing # Lint ruff check src/ tests/ # Type check mypy src/ ``` ## 🤝 贡献指南 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 了解指南。 ## 📄 许可证 MIT —— 详情请参阅 [LICENSE](LICENSE)。
标签:AWS, CloudFormation, DPI, Python, 云计算, 无后门, 规则引擎, 逆向工具