mopyle4/cfn-drift-extended
GitHub: mopyle4/cfn-drift-extended
检测 CloudFormation 原生漂移检测遗漏的增量配置漂移和未被堆栈管理的孤立资源的 IaC 治理工具。
Stars: 1 | Forks: 1
# cfn-drift-extended
检测 CloudFormation 原生漂移检测遗漏的**增量漂移**和**孤立资源**。
[](LICENSE)
[](https://www.python.org/)
[](#-development)
[](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, 云计算, 无后门, 规则引擎, 逆向工具