oscarmorberg-ops/s3-nist-scanner
GitHub: oscarmorberg-ops/s3-nist-scanner
一款轻量级 Rust AWS 只读安全扫描器,专注检测 S3 与 IAM 访问控制风险并提供可解释的 CIS 对齐发现。
Stars: 0 | Forks: 0
# s3-nist-scanner
一个轻量级的 Rust CLI 工具,用于检查 S3、IAM 和 GuardDuty 中的 AWS 访问控制信号 — 为 AWS 账户中可能存在的最小权限违规提供快速、可解释的初步视图。
它的范围特意做得很窄:作为访问控制审查的实用起点,而不是 CSPM 平台或合规性证明工具。
## 检查内容
**高严重性**
- **S3 Block Public Access** — 账户级别和存储桶级别,涵盖全部四个标志。
- **S3 存储桶策略** — 包含通配符主体(`principal:*`)的 `Allow` 语句。
- **S3 ACL** — 授予 `AllUsers` 或 `AuthenticatedUsers` 访问权限的存储桶。
- **IAM 角色和用户策略** — 通配符 S3 操作(`s3:*`、`s3:Get*` 等)或在 `*` 上的 `iam:PassRole`,适用于内联和附加策略。
- **Root 账户访问密钥** — Root 账户上的活跃访问密钥;绕过所有 IAM 控制且无法进行范围限制。
- **Root 账户 MFA** — 没有配置 MFA 设备的 Root 账户;仅靠密码保护完整的账户访问权限。
- **GuardDuty 发现** — 所有 17 个标准商业区域中的活跃(非存档)发现。
**中严重性**
- **S3 访问日志** — 未启用服务器访问日志的存储桶。
- **S3 强制 HTTPS** — 未对 `aws:SecureTransport: false` 设置 `Deny` 的存储桶。
- **S3 版本控制** — 禁用了版本控制的存储桶(无法防范删除或勒索软件覆盖)。
- **S3 对象所有权** — 仍启用了 ACL 的存储桶(未设置 `BucketOwnerEnforced`)。
- **S3 Object Lock** — 未开启 Object Lock 的版本控制存储桶(任何具有写入权限的人都可以永久删除版本)。
- **IAM 访问密钥期限** — 超过 90 天的活跃访问密钥或在创建后从未使用过的访问密钥 (CIS AWS 1.14)。
## 如何运行
**前置条件:** Rust 工具链,已配置的 AWS 凭证(默认 profile 或 `AWS_PROFILE`)。扫描器需要对 S3、IAM、GuardDuty、CloudTrail、IAM Access Analyzer、KMS 和 CloudWatch 的读取权限 — 请参阅下方的[最小权限角色](#least-privilege-iam-role-recommended)以获取确切的策略。
```
cargo run
```
扫描器使用您的默认 AWS profile 和区域,将人类可读的摘要打印到 stdout,并在当前目录中写入 `scanreport.json`。
**常用选项:**
```
cargo run -- --profile my-profile # use a named AWS profile
cargo run -- --region eu-west-1 # override region
cargo run -- --json # print JSON to stdout instead of human summary
cargo run -- --verbose # show per-resource progress on stderr
cargo run -- --output out.json # write report to a custom path
```
**退出代码:**
| Code | 含义 |
|------|---------|
| `0` | PASS — 未发现问题 |
| `1` | WARN — 低可信度问题 |
| `2` | FAIL — 已确认的发现 |
| `3` | 扫描器错误(凭证、网络等) |
## 示例输出
## 最小权限 IAM 角色(推荐)
使用 `AdministratorAccess` 运行扫描器会导致扫描器标记自身。为避免这种情况并在生产账户中安全运行,请将以下策略附加到专用的 IAM 角色或用户,然后配置您的 AWS profile 来代入它。
扫描器仅进行只读调用。以下策略仅授予这些调用,不包含其他任何权限。
```
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "STS",
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
},
{
"Sid": "S3",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:GetBucketPublicAccessBlock",
"s3:GetAccountPublicAccessBlock",
"s3:GetBucketPolicy",
"s3:GetBucketPolicyStatus",
"s3:GetBucketAcl",
"s3:GetBucketCORS",
"s3:GetBucketLogging",
"s3:GetBucketVersioning",
"s3:GetEncryptionConfiguration",
"s3:GetBucketOwnershipControls",
"s3:GetBucketObjectLockConfiguration"
],
"Resource": "*"
},
{
"Sid": "IAM",
"Effect": "Allow",
"Action": [
"iam:GetAccountSummary",
"iam:ListRoles",
"iam:ListRolePolicies",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListUsers",
"iam:ListUserPolicies",
"iam:GetUserPolicy",
"iam:ListAttachedUserPolicies",
"iam:ListAccessKeys",
"iam:GetAccessKeyLastUsed",
"iam:ListGroupsForUser",
"iam:ListGroupPolicies",
"iam:GetGroupPolicy",
"iam:ListAttachedGroupPolicies"
],
"Resource": "*"
},
{
"Sid": "GuardDuty",
"Effect": "Allow",
"Action": [
"guardduty:ListDetectors",
"guardduty:ListFindings",
"guardduty:GetFindings"
],
"Resource": "*"
},
{
"Sid": "CloudTrail",
"Effect": "Allow",
"Action": [
"cloudtrail:DescribeTrails",
"cloudtrail:GetTrailStatus",
"cloudtrail:GetEventSelectors"
],
"Resource": "*"
},
{
"Sid": "AccessAnalyzer",
"Effect": "Allow",
"Action": [
"access-analyzer:ListAnalyzers",
"access-analyzer:ListFindings"
],
"Resource": "*"
},
{
"Sid": "KMS",
"Effect": "Allow",
"Action": [
"kms:ListKeys",
"kms:DescribeKey",
"kms:ListAliases",
"kms:GetKeyPolicy",
"kms:GetKeyRotationStatus"
],
"Resource": "*"
},
{
"Sid": "CloudWatch",
"Effect": "Allow",
"Action": [
"logs:DescribeMetricFilters",
"cloudwatch:DescribeAlarms"
],
"Resource": "*"
}
]
}
```
将其保存为 `scanner-policy.json`,然后执行:
```
# 创建 policy
aws iam create-policy \
--policy-name S3NistScannerPolicy \
--policy-document file://scanner-policy.json
# 创建 role(替换 ACCOUNT_ID)
aws iam create-role \
--role-name S3NistScannerRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::ACCOUNT_ID:root" },
"Action": "sts:AssumeRole"
}]
}'
# 附加 policy
aws iam attach-role-policy \
--role-name S3NistScannerRole \
--policy-arn arn:aws:iam::ACCOUNT_ID:policy/S3NistScannerPolicy
```
然后配置 AWS profile 以代入该角色,并使用 `--profile` 传递它。使用此角色时,扫描器不会发现有风险的 IAM 主体,因此 `AdministratorAccess` 警告将永久消失。
## 示例输出
**FAIL — 所有类别中的发现:**
```
AWS risk status: FAIL (issues found)
Account : 123456789012
Scope : S3, IAM, GuardDuty, CloudTrail, AccessAnalyzer, KMS
Last scan: 2026-06-17 12:00
Findings
High
- [ROOT CREDS] [Low] Policy:IAMUser/RootCredentialUsage (Access key ASIA0EXAMPLE0RUNWB4B) — The API ListSSHPublicKeys was invoked using root credentials.
- [ROOT CREDS] [Low] Policy:IAMUser/RootCredentialUsage (Access key ASIA0EXAMPLE002R4PK2Q) — The API DescribeRegions was invoked using root credentials.
- account Block Public Access — not fully enabled (missing: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets)
- bucket my-pipeline-bucket — wildcard policy (principal:*, action:s3:*)
- s3-nist-scanner-borderline-role — s3:Get*, s3:Put* on * [BroadReadWriteS3] (BorderlineS3Inline)
Medium
- 3 buckets with access logging disabled — see scanreport.json for full list
- 3 buckets without HTTPS enforcement — plaintext requests are allowed
- 3 buckets with versioning disabled — no protection against accidental delete or ransomware overwrite
- 3 buckets with ACLs enabled — Object Ownership not set to BucketOwnerEnforced
Why this matters
- GuardDuty detected active findings — possible identity misuse
- IAM role or user has overly broad S3 permissions — violates least-privilege
- S3 bucket policies or Block Public Access expose data to unintended principals
Next actions
1. Triage GuardDuty findings; quarantine affected resources
2. Scope down IAM role and user permissions
3. Enable S3 Block Public Access; review bucket policies
Blind spots
- Does not check S3 Access Points, MFA Delete, bucket replication destination permissions, or S3 Event notification targets
- Does not detect IAM privilege escalation paths
- Does not inspect SCPs, permission boundaries, or non-S3 resource-based policies
- Does not distinguish SSE-KMS from SSE-S3 or validate KMS key grant permissions
- Point-in-time only — does not track configuration drift or historical changes
Summary
Buckets : 7
GuardDuty : 2 findings
IAM : 1 risky principal
S3 : 1 public-like
Report : scanreport.json
```
**WARN — 仅扫描器操作者噪音:**
```
AWS risk status: WARN (issues found)
Account : 123456789012
Scope : S3, IAM, GuardDuty, CloudTrail, AccessAnalyzer, KMS
Last scan: 2026-06-17 12:00
Findings
Medium
- 5 buckets with access logging disabled — see scanreport.json for full list
- 5 buckets without HTTPS enforcement — plaintext requests are allowed
- 5 buckets with versioning disabled — no protection against accidental delete or ransomware overwrite
Info
- user lab-admin — * on * (AdministratorAccess) [expected for scanner operator]
Next actions
1. For production accounts, avoid running scanner with permanent AdministratorAccess
Blind spots
- Does not check S3 Access Points, MFA Delete, bucket replication destination permissions, or S3 Event notification targets
- Does not detect IAM privilege escalation paths
- Does not inspect SCPs, permission boundaries, or non-S3 resource-based policies
- Does not distinguish SSE-KMS from SSE-S3 or validate KMS key grant permissions
- Point-in-time only — does not track configuration drift or historical changes
Summary
Buckets : 5
GuardDuty : 0 findings
IAM : 1 risky principal
S3 : 0 public-like
Report : scanreport.json
```
**PASS — 干净的账户:**
```
AWS risk status: PASS (no issues)
Account : 123456789012
Scope : S3, IAM, GuardDuty, CloudTrail, AccessAnalyzer, KMS
Last scan: 2026-06-17 12:00
Summary
Buckets : 5
GuardDuty : 0 findings
IAM : 0 risky principals
S3 : 0 public-like
Report : scanreport.json
```
## CI
扫描器有一个专门的 GitHub Actions 工作流,它会运行:
- build
- tests
- rustfmt
- clippy
此工作流的作用域仅限于 `tools/s3-nist-scanner/**` 及其工作流文件下的更改。
## JSON 报告 schema
`scanreport.json` 由 [`schema.json`](schema.json) (JSON Schema 2020-12) 完整描述。该 schema 涵盖了每个字段,包括 `findings` 数组(source、resource、rule、severity、confidence、message)和 `correlations` 数组(按资源分组,按严重性排序)。
每个报告中的 `schema_version` 字段目前为 `"4"`。当输出结构发生破坏性更改时,该数字将会增加 — 消费者应拒绝具有无法识别版本的报告。
针对该报告的有用的 jq 查询:
```
# 所有高置信度发现
jq '[.findings[] | select(.confidence == "High")]' scanreport.json
# 发现最多的资源(hotspots)
jq '.correlations[:5]' scanreport.json
# 特定 bucket 的所有发现
jq '[.findings[] | select(.resource == "bucket:my-bucket")]' scanreport.json
# 根据 schema 进行验证(需要 check-jsonschema)
check-jsonschema --schemafile schema.json scanreport.json
```
## 设计说明
- **S3 策略检测** 仅标记具有通配符主体的 `Allow` 语句。仅有 `Deny` 的策略 — 例如带有 `principal:*` 的强制 SSL 语句 — 不会被视为公共暴露。
- **强制 HTTPS 检测** 检查存储桶策略中是否具有针对 `aws:SecureTransport: false` 的 `Bool` 条件的 `Deny` 效果。完全没有策略的存储桶将被标记为未强制执行。
- **对象所有权检测** 为每个存储桶调用 `GetBucketOwnershipControls`。`BucketOwnerEnforced` 表示 ACL 已禁用(无发现)。`BucketOwnerPreferred`、`ObjectWriter` 或 `OwnershipControlsNotFoundError`(未配置规则)均会使 ACL 保持活动状态并被标记。超时和未知错误将被保守处理(无发现)。
- **IAM 服务相关角色** (路径 `/aws-service-role/`) 会被跳过:它们由 AWS 管理且无法携带用户创建的内联策略。
- **扫描器操作者噪音**:如果唯一有风险的 IAM 主体是调用扫描器的 IAM 用户,则状态将从 FAIL 降级为 WARN,并且发现将标记为 `[expected for scanner operator]`。
- **GuardDuty** 使用 `service.archived = false` 进行查询,因此只会显示活跃的发现。即使存档的发现尚未从检测器中过期,也会被排除。
- **并发**:S3、IAM 和 GuardDuty 阶段并行运行(`tokio::try_join!`)。单个存储桶、IAM 主体和 GuardDuty 区域在每个阶段内也会并发运行。
## 范围与盲区
此扫描器检查一组特定且有边界的 AWS 访问控制信号。PASS 意味着**在此范围内未发现问题** — 这并不代表该账户完全安全。
**已检查项**
| 区域 | 检查 |
|------|--------|
| S3 | Block Public Access(账户 + 每个存储桶,全部 4 个标志),具有通配符主体的存储桶策略,ACL 公共授权,CORS 配置错误,跨账户存储桶策略授权,Object Lock(已启用版本控制的存储桶),访问日志,强制 HTTPS,版本控制,对象所有权,服务器端加密状态 |
| IAM | 角色和用户的通配符与管理员策略(内联 + 附加 + 组继承),Root 访问密钥,Root MFA,超过 90 天的陈旧且从未使用的访问密钥 (CIS AWS 1.14) |
| GuardDuty | 所有 17 个标准商业区域中的活跃(非存档)发现;GuardDuty 是否在每个区域启用 |
| CloudTrail | 是否配置并积极记录了带有管理事件的多区域追踪 |
| IAM Access Analyzer | S3、IAM、KMS、Lambda、SQS、SNS、Secrets Manager 的活跃外部访问发现(公共或跨账户);是否启用了分析器 |
| KMS | 具有公共主体或向非 Root 主体授予 kms:* 权限的客户管理密钥策略;对称密钥的自动轮换状态 |
| CloudWatch | 关键 CIS 警报控制(Root 使用、未授权的 API 调用、控制台登录失败、CloudTrail 更改、安全组更改、IAM 策略更改)是否存在 CloudWatch 指标过滤器;每个过滤器是否附加了警报 |
**S3 盲区**
- MFA Delete(要求硬件 MFA 进行版本移除的额外删除保护)
- S3 Access Points 及其策略
- 存储桶复制目标账户和权限
- 限制 S3 访问的 VPC endpoint 策略
- S3 事件通知目标(Lambda、SNS、SQS)— 可能导致数据泄露
- SSE-KMS 与 SSE-S3 的区别(两者都注册为已加密)
- KMS 密钥策略和密钥轮换
**IAM 盲区**
- IAM 提权路径(例如 `iam:CreatePolicy` + `iam:AttachUserPolicy`)
- 权限边界
- AWS Organizations 级别的 Service Control Policies (SCPs)
- 身份提供商(SAML/OIDC 联合身份,`sts:AssumeRoleWithWebIdentity`)
- S3 之外的基于资源的策略(Lambda、SQS、SNS、KMS、Secrets Manager、ECR 等)
**GuardDuty 盲区**
- 可选的保护计划(S3 Protection、EKS、Lambda、RDS、Malware Protection)
**CloudTrail 盲区**
- 日志文件完整性验证(CloudTrail 摘要文件)
- S3 数据事件(对象级别的读/写操作)
- 日志向目标 S3 存储桶的传输(存储桶是否存在、策略、加密)
- AWS Organizations 中的 GuardDuty 成员账户配置
**架构盲区**
- 仅限单个账户 — 不支持多账户或 AWS Organizations 遍历
- CloudTrail 启用、日志完整性和 S3 传输篡改
- AWS Config 规则、Security Hub、Macie 或 Inspector 发现
- 网络级控制(Security Groups、NACLs、VPC Flow Logs)
- 嵌入在 S3 对象内容中的机密或凭证
- 历史跟踪或配置漂移 — 每次运行都是时间点快照
- 没有合规性控制映射(CIS AWS Foundations、NIST 800-53、ISO 27001)
## 计划范围
- 每个存储桶的 SSE-KMS 与 SSE-S3 强制执行
- 适用于控制审计文档的证据导出
- 多账户支持
## 安全
如果在此项目中发现漏洞或安全问题,请私下报告,而不是开启公开的 issue。
[oscarmorberg@gmail.com](mailto:oscarmorberg@gmail.com)
## 许可证
在 MIT 许可证下授权。请参阅 `LICENSE`。
标签:AWS, DPI, LNA, Rust, 云基础设施扫描, 可视化界面, 网络流量审计, 通知系统