praneethkoti/port-monitoring
GitHub: praneethkoti/port-monitoring
跨 AWS 账户检测并可选修复全网开放的敏感端口安全组规则,默认仅报告并支持基于 Rule ID 的精准撤销与验证。
Stars: 0 | Forks: 0
# port_monitoring
**检测并选择性修复跨 AWS 账户的对全网开放的安全组规则 — 默认仅生成报告。**
[](https://github.com/praneethkoti/port-monitoring/actions/workflows/ci.yml)
[](https://www.python.org/)
[](LICENSE)
[](tests/)
[](#testing)
查找将敏感端口(SSH、RDP、MySQL、PostgreSQL、Redis、MongoDB 等)暴露给整个互联网的 EC2 安全组 ingress 规则 — 遍及组织内的每个区域和每个账户,并且可以精准撤销这些规则,同时保持其他所有规则不变。
## 演示
完全基于 [moto](https://github.com/getmoto/moto) 运行 — 无需 AWS 账户、无需凭证、无需网络:
```
python demo.py
```

它会在三个区域中初始化一个模拟环境,报告高风险规则,应用修复,并展示前后的 diff,以证明合法规则得以保留。
大约在五秒内完成。
**无需运行任何代码即可查看输出:**
[检查结果报告](samples/findings-report.md) ·
[JSON 变更日志](samples/change-log.json) ·
[终端记录](samples/terminal-transcript.txt)
## 快速入门
```
# 安装
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# 无需 AWS 账户即可查看其运行效果
pip install -r requirements-dev.txt
python demo.py
# 报告当前账户的所有已启用区域。不作任何更改。
python -m port_monitoring.cli
# 缩小扫描范围并保存报告
python -m port_monitoring.cli --regions us-east-1,eu-west-1 --report-file findings.md
# 通过 assume-role 跨成员账户
python -m port_monitoring.cli --accounts 111122223333,444455556666 --external-id SECRET
# 确认您*可以*撤销,但实际不撤销(DryRun 预检)
python -m port_monitoring.cli --preflight
# 实际撤销。需要两个标志;写入可审计的变更日志。
python -m port_monitoring.cli --mode remediate --apply --change-log changes.json
```
安装软件包 (`pip install -e .`) 后还会提供一个 `port-monitoring` 控制台命令,等效于 `python -m port_monitoring.cli`。
在报告模式下发现高风险规则时,退出码为 `1`,因此它可以作为 CI 或合规性检查的拦截门。
## 工作原理
```
flowchart LR
SCHED["EventBridge
Scheduler"] -->|invoke| LAMBDA subgraph MASTER["Master account"] LAMBDA["Lambda
port-monitoring"] ROLE["Execution role
sts:AssumeRole only"] LAMBDA --> ROLE end subgraph MEMBERS["Member accounts"] ROLE_A["Security_group_access"] EC2["describe_security_group_rules
paginated, per region"] ROLE_A --> EC2 end ROLE -->|"sts:AssumeRole
+ ExternalId"| ROLE_A EC2 --> CLASSIFY{"world-open CIDR
AND sensitive port?"} CLASSIFY -->|no| SKIP["not a finding"] CLASSIFY -->|yes| FINDING["finding"] FINDING --> REPORT["report / alert
no mutation"] FINDING -.->|"--apply only"| REVOKE["revoke by
SecurityGroupRuleId"] REVOKE --> VERIFY["re-read live state
to confirm"] classDef safe fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20 classDef danger fill:#ffebee,stroke:#c62828,color:#b71c1c class REPORT,VERIFY safe class REVOKE danger ``` 有三个设计决策发挥了核心作用: **发现过程使用 `describe_security_group_rules`,而不是 `describe_security_groups`。** 它为每条规则返回一条带有稳定 `SecurityGroupRuleId` 的原子记录行,因此分类过程变成了简单的逐行判断,而修复操作可以通过 ID 精准定位单条规则。旧的 `IpPermissions` 模型会将多个 CIDR 捆绑在一个权限中,迫使调用者必须*重构*一部分权限才能撤销其中一个 CIDR —— 这是一整类 bug(丢失 IPv6 范围、前缀列表和组对;以及误报的 `-1` 端口)的根源。 **只有当以下两个条件同时满足时,规则才会被标记** — 源地址是 `0.0.0.0/0` 或 `::/0`,**并且**端口范围覆盖了已配置的敏感端口。对 `10.0.0.0/8` 开放的数据库端口不会被标记。对全网开放的 `80`/`443` 端口仅记录为 `INFO` 级别,绝对不会被撤销。 **成功是经过验证的,绝非假设。** 即使权限不匹配,`RevokeSecurityGroupIngress` 也会返回 `Return: True` — 它会通过 `UnknownIpPermissions` 报告未匹配项,而不是抛出异常。在每次撤销操作后,该工具都会重新读取实时规则状态,只有当规则确实不存在时才将其计为已撤销;其他任何情况都会被记录为 `unmatched` 并作为警告显示。 详情请参阅 [docs/architecture.md](docs/architecture.md)。关于原始脚本的预构建分析 —— 以及为什么要做出这些决定 —— 请参阅 [docs/engineering-notes.md](docs/engineering-notes.md)。 ## 模式 | 模式 | 扫描 | 通知 | 撤销 | | --- | :---: | :---: | :---: | | `report` *(默认)* | ✅ | — | **否** | | `alert` | ✅ | ✅ SNS | **否** | | `remediate` | ✅ | — | 仅在使用 `--apply` 时 | 建议的采用路径:运行 `report` 一两周,将合法的暴露情况添加到 `exempt_group_ids`,然后切换到 `alert`,只有在检查结果持续保持干净时,才考虑使用 `remediate`。 ## 配置 复制 [`config.example.yaml`](config.example.yaml) 并进行编辑。优先级顺序依次为:默认值 → 配置文件 → `PORT_MONITORING_*` 环境变量 → CLI 标志。 ``` mode: report apply: false accounts: ["111122223333"] assume_role_name: Security_group_access external_id: your-shared-secret sensitive_ports: [22, 23, 21, 445, 1433, 3306, 3389, 5432, 5984, 6379, 9200, 11211, 27017] informational_ports: [80, 443] # reported, never revoked exempt_group_ids: ["sg-0123456789abcdef0"] include_egress: false max_workers: 8 ``` ### 关键标志 | 标志 | 用途 | | --- | --- | | `--mode {report,alert,remediate}` | 操作模式(默认为 `report`) | | `--apply` | 执行撤销操作的必需参数;隐含 `--mode remediate` | | `--config PATH` | YAML/JSON 配置文件 | | `--accounts IDS` | 要代入的成员账户 | | `--regions` / `--exclude-regions` | 限定扫描范围 | | `--sensitive-ports PORTS` | 覆盖默认的高风险端口集 | | `--exempt-groups IDS` | 绝对禁止触碰的安全组 | | `--include-egress` | 同时对 egress 进行分类(默认仅 ingress) | | `--preflight` | 通过 `DryRun` 检查撤销权限,而不实际执行撤销 | | `--output {table,json,markdown}` | 输出格式 | | `--report-file` / `--change-log` | 写入报告 / JSON 审计跟踪文件 | ## 作为 Lambda 部署 Handler 字符串:**`port_monitoring.handler.handler`** - [IAM 与跨账户设置](docs/lambda-cross-account-role.md) — 最小权限策略、`ExternalId` 以及信任关系 - [EventBridge Scheduler](docs/eventbridge-schedule.md) — 每日计划、payload、超时与内存配置指南 ## 输出 单行 JSON 格式,可在 CloudWatch Logs Insights 中查询: ``` {"timestamp":"2026-07-27T02:00:03Z","level":"INFO","message":"revoked","run_id":"73c3d71e20de","account_id":"111122223333","region":"eu-west-1","group_id":"sg-e4a8dc4","rule_id":"sgr-0d4f2","action":"revoke","outcome":"revoked","ports":"3389","source":"::/0"} ``` 摘要将*发现 (found)*、*确认撤销 (confirmed revoked)*、*未匹配 (unmatched)* 和*错误 (errors)* 作为独立的计数器保留,因此该工具绝不会暗示它做出了实际并未做出的更改: ``` Risky rules found (HIGH) : 4 Rules confirmed revoked : 4 Rules unmatched (warning) : 0 Rules left untouched : 3 ``` ## 测试 ``` pip install -r requirements-dev.txt pytest --cov=port_monitoring ``` **153 个测试,95% 的行覆盖率**,全部针对模拟的 AWS 运行 — 无需凭证,也不会发起真实的 API 调用。`remediator.py` 是唯一会更改 AWS 状态的模块,其覆盖率达到 **100%**。 覆盖范围包括分类器的准确性(包含边界情况、`-1` 协议以及绝对*不应*匹配的反例)、dry-run(试运行)安全性、带响应验证的基于 ID 的撤销、分页、包含失败区域的多区域聚合、assume-role 路径、豁免列表以及 Lambda handler。 ## 范围与局限性 - **v1 版本仅支持 ingress。** Egress 分类已实现并经过测试,但默认处于关闭状态(需设置 `include_egress: true` 以启用)。 - **检查结果持久化与 CloudWatch 指标推迟至 v2 实现。** 目前,检查结果会输出到结构化日志、报告文件以及 JSON 变更日志中。目前还没有历史趋势存储。 - **Slack 通知仅限配置层面。** `alert` 模式会发布到 SNS;虽然接受配置 `slack_webhook_url` 字段,但尚未接入任何实际的发送器。 - **账户发现是静态的。** 目标账户来自配置项;未实现 AWS Organizations 的自动发现功能。 - 对全网开放的检测仅匹配精确的 CIDR `0.0.0.0/0` 和 `::/0`。如果规则的范围是一个极大但非通用的范围(例如 `0.0.0.0/1`),则不会被标记 — 这是有意为之,以保持判定条件的明确性。 ## 项目结构 ``` port_monitoring/ classifier.py risky-rule predicate (pure, no AWS calls) scanner.py account/region orchestration, bounded concurrency remediator.py revoke by rule ID + outcome verification aws.py client injection, assume-role, pagination config.py configuration precedence and validation handler.py Lambda entrypoint cli.py command-line interface docs/ IAM, scheduling, architecture samples/ committed example output assets/ demo recording demo.py self-contained moto demo ``` ## 贡献与安全 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 和 [SECURITY.md](SECURITY.md)。请私下报告安全漏洞,而不是在公开的 issue 中提出。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。
Scheduler"] -->|invoke| LAMBDA subgraph MASTER["Master account"] LAMBDA["Lambda
port-monitoring"] ROLE["Execution role
sts:AssumeRole only"] LAMBDA --> ROLE end subgraph MEMBERS["Member accounts"] ROLE_A["Security_group_access"] EC2["describe_security_group_rules
paginated, per region"] ROLE_A --> EC2 end ROLE -->|"sts:AssumeRole
+ ExternalId"| ROLE_A EC2 --> CLASSIFY{"world-open CIDR
AND sensitive port?"} CLASSIFY -->|no| SKIP["not a finding"] CLASSIFY -->|yes| FINDING["finding"] FINDING --> REPORT["report / alert
no mutation"] FINDING -.->|"--apply only"| REVOKE["revoke by
SecurityGroupRuleId"] REVOKE --> VERIFY["re-read live state
to confirm"] classDef safe fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20 classDef danger fill:#ffebee,stroke:#c62828,color:#b71c1c class REPORT,VERIFY safe class REVOKE danger ``` 有三个设计决策发挥了核心作用: **发现过程使用 `describe_security_group_rules`,而不是 `describe_security_groups`。** 它为每条规则返回一条带有稳定 `SecurityGroupRuleId` 的原子记录行,因此分类过程变成了简单的逐行判断,而修复操作可以通过 ID 精准定位单条规则。旧的 `IpPermissions` 模型会将多个 CIDR 捆绑在一个权限中,迫使调用者必须*重构*一部分权限才能撤销其中一个 CIDR —— 这是一整类 bug(丢失 IPv6 范围、前缀列表和组对;以及误报的 `-1` 端口)的根源。 **只有当以下两个条件同时满足时,规则才会被标记** — 源地址是 `0.0.0.0/0` 或 `::/0`,**并且**端口范围覆盖了已配置的敏感端口。对 `10.0.0.0/8` 开放的数据库端口不会被标记。对全网开放的 `80`/`443` 端口仅记录为 `INFO` 级别,绝对不会被撤销。 **成功是经过验证的,绝非假设。** 即使权限不匹配,`RevokeSecurityGroupIngress` 也会返回 `Return: True` — 它会通过 `UnknownIpPermissions` 报告未匹配项,而不是抛出异常。在每次撤销操作后,该工具都会重新读取实时规则状态,只有当规则确实不存在时才将其计为已撤销;其他任何情况都会被记录为 `unmatched` 并作为警告显示。 详情请参阅 [docs/architecture.md](docs/architecture.md)。关于原始脚本的预构建分析 —— 以及为什么要做出这些决定 —— 请参阅 [docs/engineering-notes.md](docs/engineering-notes.md)。 ## 模式 | 模式 | 扫描 | 通知 | 撤销 | | --- | :---: | :---: | :---: | | `report` *(默认)* | ✅ | — | **否** | | `alert` | ✅ | ✅ SNS | **否** | | `remediate` | ✅ | — | 仅在使用 `--apply` 时 | 建议的采用路径:运行 `report` 一两周,将合法的暴露情况添加到 `exempt_group_ids`,然后切换到 `alert`,只有在检查结果持续保持干净时,才考虑使用 `remediate`。 ## 配置 复制 [`config.example.yaml`](config.example.yaml) 并进行编辑。优先级顺序依次为:默认值 → 配置文件 → `PORT_MONITORING_*` 环境变量 → CLI 标志。 ``` mode: report apply: false accounts: ["111122223333"] assume_role_name: Security_group_access external_id: your-shared-secret sensitive_ports: [22, 23, 21, 445, 1433, 3306, 3389, 5432, 5984, 6379, 9200, 11211, 27017] informational_ports: [80, 443] # reported, never revoked exempt_group_ids: ["sg-0123456789abcdef0"] include_egress: false max_workers: 8 ``` ### 关键标志 | 标志 | 用途 | | --- | --- | | `--mode {report,alert,remediate}` | 操作模式(默认为 `report`) | | `--apply` | 执行撤销操作的必需参数;隐含 `--mode remediate` | | `--config PATH` | YAML/JSON 配置文件 | | `--accounts IDS` | 要代入的成员账户 | | `--regions` / `--exclude-regions` | 限定扫描范围 | | `--sensitive-ports PORTS` | 覆盖默认的高风险端口集 | | `--exempt-groups IDS` | 绝对禁止触碰的安全组 | | `--include-egress` | 同时对 egress 进行分类(默认仅 ingress) | | `--preflight` | 通过 `DryRun` 检查撤销权限,而不实际执行撤销 | | `--output {table,json,markdown}` | 输出格式 | | `--report-file` / `--change-log` | 写入报告 / JSON 审计跟踪文件 | ## 作为 Lambda 部署 Handler 字符串:**`port_monitoring.handler.handler`** - [IAM 与跨账户设置](docs/lambda-cross-account-role.md) — 最小权限策略、`ExternalId` 以及信任关系 - [EventBridge Scheduler](docs/eventbridge-schedule.md) — 每日计划、payload、超时与内存配置指南 ## 输出 单行 JSON 格式,可在 CloudWatch Logs Insights 中查询: ``` {"timestamp":"2026-07-27T02:00:03Z","level":"INFO","message":"revoked","run_id":"73c3d71e20de","account_id":"111122223333","region":"eu-west-1","group_id":"sg-e4a8dc4","rule_id":"sgr-0d4f2","action":"revoke","outcome":"revoked","ports":"3389","source":"::/0"} ``` 摘要将*发现 (found)*、*确认撤销 (confirmed revoked)*、*未匹配 (unmatched)* 和*错误 (errors)* 作为独立的计数器保留,因此该工具绝不会暗示它做出了实际并未做出的更改: ``` Risky rules found (HIGH) : 4 Rules confirmed revoked : 4 Rules unmatched (warning) : 0 Rules left untouched : 3 ``` ## 测试 ``` pip install -r requirements-dev.txt pytest --cov=port_monitoring ``` **153 个测试,95% 的行覆盖率**,全部针对模拟的 AWS 运行 — 无需凭证,也不会发起真实的 API 调用。`remediator.py` 是唯一会更改 AWS 状态的模块,其覆盖率达到 **100%**。 覆盖范围包括分类器的准确性(包含边界情况、`-1` 协议以及绝对*不应*匹配的反例)、dry-run(试运行)安全性、带响应验证的基于 ID 的撤销、分页、包含失败区域的多区域聚合、assume-role 路径、豁免列表以及 Lambda handler。 ## 范围与局限性 - **v1 版本仅支持 ingress。** Egress 分类已实现并经过测试,但默认处于关闭状态(需设置 `include_egress: true` 以启用)。 - **检查结果持久化与 CloudWatch 指标推迟至 v2 实现。** 目前,检查结果会输出到结构化日志、报告文件以及 JSON 变更日志中。目前还没有历史趋势存储。 - **Slack 通知仅限配置层面。** `alert` 模式会发布到 SNS;虽然接受配置 `slack_webhook_url` 字段,但尚未接入任何实际的发送器。 - **账户发现是静态的。** 目标账户来自配置项;未实现 AWS Organizations 的自动发现功能。 - 对全网开放的检测仅匹配精确的 CIDR `0.0.0.0/0` 和 `::/0`。如果规则的范围是一个极大但非通用的范围(例如 `0.0.0.0/1`),则不会被标记 — 这是有意为之,以保持判定条件的明确性。 ## 项目结构 ``` port_monitoring/ classifier.py risky-rule predicate (pure, no AWS calls) scanner.py account/region orchestration, bounded concurrency remediator.py revoke by rule ID + outcome verification aws.py client injection, assume-role, pagination config.py configuration precedence and validation handler.py Lambda entrypoint cli.py command-line interface docs/ IAM, scheduling, architecture samples/ committed example output assets/ demo recording demo.py self-contained moto demo ``` ## 贡献与安全 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 和 [SECURITY.md](SECURITY.md)。请私下报告安全漏洞,而不是在公开的 issue 中提出。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。
标签:AWS, DevSecOps, DPI, Python, 上游代理, 安全合规, 无后门, 网络代理, 逆向工具