vegazbabz/CISAzureBenchmark-PS
GitHub: vegazbabz/CISAzureBenchmark-PS
该工具是基于 PowerShell 的 Azure 云环境安全合规审计器,用于自动化执行 CIS Azure Foundations Benchmark 检查并生成可视化报告。
Stars: 1 | Forks: 0
# CIS Microsoft Azure Foundations Benchmark v6.0.0 — 审计工具 (PowerShell)
[](https://www.powershellgallery.com/packages/CISAzureFoundationsBenchmark)
[](https://www.powershellgallery.com/packages/CISAzureFoundationsBenchmark)
[](LICENSE)
[](https://www.cisecurity.org/benchmark/azure)
[](https://learn.microsoft.com/en-us/powershell/)
[](https://github.com/vegazbabz/CISAzureBenchmark-PS/actions/workflows/ci.yml)

**版本:** 2.4.0
**Benchmark:** [CIS Microsoft Azure Foundations Benchmark v6.0.0](https://www.cisecurity.org/benchmark/azure) (2026年4月)
**覆盖范围:** 涵盖 7 个部分的 93 项自动化控制 · 输出中注明的 34 项手动控制(共 127 项)
## 概述
这是一款 PowerShell 工具,用于根据 **[CIS Microsoft Azure Foundations Benchmark v6.0.0](https://www.cisecurity.org/benchmark/azure)** —— 由[互联网安全中心 (CIS)](https://www.cisecurity.org/) 发布的 Azure 环境行业标准强化指南,对 Azure 租户进行审计。
所有审计检查均使用 Az PowerShell 模块 —— 无需 Azure CLI。
可选的权限预检会在并行 runspace 中使用 `Get-AzRoleAssignment`,以在审计开始前验证运行者账户是否持有所需的角色。
如果您已确认权限无误,可以使用 `-NoPermissionCheck` 跳过此步骤。
只需一条 `Install-Module CISAzureFoundationsBenchmark` 命令即可引入该工具及其所需的所有 Az 模块;运行 `Connect-AzAccount` 进行身份验证。
每个订阅完成后,结果都会作为检查点保存,因此如果运行失败或中断,可以恢复执行而无需重新运行已完成的工作。输出是一个独立的 HTML 报告,包含过滤、合规性评分、图表以及针对各项发现的修复指南。
## 环境要求
### 运行时
| 要求 | 详情 |
| --- | --- |
| PowerShell | [7.0 或更高版本](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell) |
| Az.Accounts | `Install-Module Az.Accounts` —— 身份验证、上下文、REST 调用 |
| Az.ResourceGraph | `Install-Module Az.ResourceGraph` —— 资源预取查询 |
| Az.Monitor | `Install-Module Az.Monitor` —— 日志配置文件、诊断设置、活动日志警报 |
| Az.Network | `Install-Module Az.Network` —— 网络观察程序流日志 |
| Az.Storage | `Install-Module Az.Storage` —— 存储账户枚举 |
| Az.KeyVault | `Install-Module Az.KeyVault` —— 密钥轮换策略 |
| Az.Resources | `Install-Module Az.Resources` —— 角色定义 |
| Az.Security | `Install-Module Az.Security` —— Defender 计划、安全联系人 |
| Azure 登录 | 运行前需完成 `Connect-AzAccount` |
### Azure 权限
| 范围 | 角色 | 用途 |
| --- | --- | --- |
| 每个订阅 | Reader | 枚举所有资源 |
| 每个订阅 | Security Reader | Defender 计划、安全联系人 |
| Microsoft Entra ID (租户) | Global Reader | 身份检查 (5.x) |
| Key Vaults (可选) | Key Vault Reader | 列出用于 8.3.x 检查的密钥、机密、证书 |
## 快速开始
```
# 1. 安装(一次性) — 所需的 Az modules 会自动安装
Install-Module CISAzureFoundationsBenchmark -Scope CurrentUser
# 2. 登录到 Azure
Connect-AzAccount
# 3. 运行审计(审计您的 tenant 中所有已启用的 subscriptions)
Invoke-CISAzureAudit -TenantId (Get-AzContext).Tenant.Id
```
审计完成后,报告会自动在浏览器中打开。该工具将枚举租户中所有已启用的订阅,运行所有检查,并保存报告。
(需要显式指定 `-TenantId` 或 `-Subscriptions` 范围 —— 工具不会自动假定范围。)
如果是通过克隆的代码库运行?可以使用脚本入口点,其行为完全相同:
`.\Invoke-CISAzureAudit.ps1 -TenantId (Get-AzContext).Tenant.Id`
### 作为 PowerShell 模块使用
该工具也是一个正规的模块 —— 导入它并处理返回的摘要对象,而无需解析控制台输出:
```
Import-Module CISAzureFoundationsBenchmark # from a clone: Import-Module .\CISAzureFoundationsBenchmark.psd1
$audit = Invoke-CISAzureAudit -TenantId (Get-AzContext).Tenant.Id -NoOpen
$audit.Score # e.g. 62.1
$audit.Counts.FAIL # failed control count
$audit.Results # every per-resource result object
$audit.ReportPath # path to the generated HTML report
$audit.ExitCode # 0 | 1 (setup error) | 2 (failures found, with -ExitCode)
```
`.\Invoke-CISAzureAudit.ps1` 脚本依然是此函数的轻量级封装,因此现有的命令行和 CI pipeline 可保持不变地继续工作。
## 详细入门步骤
### 步骤 1 — 安装 PowerShell 7+
### 步骤 2 — 安装所需的 Az PowerShell 模块
**如果您是通过 PowerShell Gallery 安装的,请跳过此步骤**(步骤 3,选项 A)—— Az 模块是声明的依赖项,会自动附带安装。
对于克隆或 ZIP 下载的代码,请在 PowerShell 7 终端中运行一次此命令:
```
Install-Module Az.Accounts, Az.ResourceGraph, Az.Monitor, Az.Network, Az.Storage, Az.KeyVault, Az.Resources, Az.Security -Scope CurrentUser
```
如果提示从不受信任的存储库安装,请输入 `Y` 并按 Enter。
### 步骤 3 — 获取工具
**选项 A — 从 PowerShell Gallery 安装**(推荐):
```
Install-Module CISAzureFoundationsBenchmark -Scope CurrentUser
```
**选项 B — 使用 Git 克隆**:
```
git clone https://github.com/vegazbabz/CISAzureBenchmark-PS.git
cd CISAzureBenchmark-PS
```
**选项 C — 以 ZIP 格式下载**(无需 Git):
1. 在 [GitHub 代码库页面](https://github.com/vegazbabz/CISAzureBenchmark-PS),
点击绿色的 **Code** 按钮 → **Download ZIP**。
2. 解压 ZIP 文件并在解压后的文件夹中打开终端。
### 步骤 4 — 登录 Azure
```
Connect-AzAccount
```
将打开一个浏览器窗口供您登录。您使用的账户至少需要对要审计的订阅拥有 **Reader** 和 **Security Reader** 权限。
### 步骤 5 — 运行审计
```
Invoke-CISAzureAudit -TenantId (Get-AzContext).Tenant.Id
```
(从克隆的代码库运行时,`.\Invoke-CISAzureAudit.ps1 -TenantId (Get-AzContext).Tenant.Id` 的作用完全相同。)该工具将枚举所有已启用的订阅,运行所有检查,并在完成后自动在浏览器中打开 HTML 报告。
## 项目结构
```
CISAzureFoundationsBenchmark.psd1 Module manifest (version, exports, Gallery metadata)
CISAzureFoundationsBenchmark.psm1 Module loader — dot-sources Private/, Checks/, Public/
Invoke-CISAzureAudit.ps1 Script entry point (thin shim around the module function)
Public/
Invoke-CISAzureAudit.ps1 The audit orchestrator — the module's exported command
Private/
AzureClient.ps1 PS-based API client: Resource Graph (Search-AzGraph)
and ARM REST (Invoke-AzRestMethod)
CheckHelpers.ps1 Prefetch data lookups, error formatting,
New-ErrorResult / New-InfoResult / New-ManualResult
Checkpoint.ps1 Save/resume audit state
Config.ps1 Timeouts, constants, PASS/FAIL labels
Helpers.ps1 Logging, utilities
History.ps1 Run history tracking
Identity.ps1 Subscription enumeration, permission checks
Models.ps1 New-CISResult (the result object contract)
ModuleManifest.ps1 Single source of truth for the dot-source load order
Report.ps1 HTML report generation
Sarif.ps1 SARIF 2.1.0 export for code scanning
Checks/
Section2.ps1 Databricks checks (12 controls)
Section3.ps1 Compute checks (1 manual control)
Section5.ps1 Identity & access checks (15 controls)
Section6.ps1 Logging & monitoring checks (24 controls)
Section7.ps1 Networking checks (16 controls)
Section8.ps1 Security services checks (38 controls)
Section9.ps1 Storage checks (21 controls — 127 total)
Tests/
Section*.Tests.ps1 Pester tests per benchmark section (314 tests total)
Helpers.Tests.ps1 Pure-helper tests (factories, catalog, scoring, classifiers)
Pipeline.Tests.ps1 Report/summary pipeline tests (SARIF, suppressions, history)
TestHelpers.ps1 Shared bootstrap: fixtures + hermetic default mocks
Run-Tests.ps1 Test runner
scripts/
New-SampleReport.ps1 Generate sample report with synthetic data
docs/
sample_report.html Pre-generated sample report
suppressions.json.example Suppression template with annotated examples
```
## 用法
```
.\Invoke-CISAzureAudit.ps1 [options]
```
### 所有参数
| 参数 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `-Subscriptions` | string[] | all | 要审计的一个或多个订阅名称或 ID |
| `-Output` | string | `cis_audit_report.html` | HTML 报告路径 |
| `-Parallel` | int | 3 | 并发订阅 worker 数 |
| `-Level` | 1 \| 2 \| both | both | CIS 级别过滤器 |
| `-Fresh` | switch | | 清除所有检查点并开始完整的重新审计 |
| `-ReportOnly` | switch | | 从现有检查点重新生成报告 —— 不进行 API 调用 |
| `-NoCheckpoint` | switch | | 禁用检查点保存 |
| `-SkipTenantChecks` | switch | | 跳过租户级别的检查(第 3 和第 5 部分) |
| `-NoPermissionCheck` | switch | | 跳过预检权限检查 |
| `-NoOpen` | switch | | 不在浏览器中自动打开报告 |
| `-ExitCode` | switch | | 发现 FAIL 或 ERROR 结果时以代码 2 退出(用于 CI/CD) |
| `-SuppressionsFile` | string | `suppressions.json` | 抑制文件的路径(参见 *抑制发现*) |
| `-CompareWith` | string | | 用于进行差异比较的前一次运行的 `.json`,或设为 `auto` 以选取输出目录中最新的报告 JSON。这会在 HTML 报告中添加“与上一次运行的变化”部分,并写入 `.diff.json` |
| `-DebugMode` | switch | | 详细的调试日志 |
| `-LogFile` | string | | 将日志写入文件 |
### 配置文件
在脚本旁边创建一个 `cis_audit.json` 文件以设置持久化默认值。CLI 参数始终会覆盖配置文件中的值。设置 `CIS_AUDIT_CONFIG` 环境变量可使用自定义路径。
有关所有可用设置,请参见 [`cis_audit.json.example`](cis_audit.json.example)。
```
{
"audit": {
"parallel": 3,
"level": "both",
"exit_code": true,
"no_open": true
},
"timeouts": {
"default": 60,
"storage_list": 90
}
}
```
### 示例
```
# 审计所有 subscriptions
.\Invoke-CISAzureAudit.ps1
# 使用并行机制审计特定 subscriptions
.\Invoke-CISAzureAudit.ps1 -Subscriptions "sub-id-1","sub-id-2" -Parallel 5
# 仅执行 Level 1 检查,自定义输出路径
.\Invoke-CISAzureAudit.ps1 -Level 1 -Output report.html
# 完全重新开始,忽略之前的 checkpoints
.\Invoke-CISAzureAudit.ps1 -Fresh
# 重新生成 HTML 报告,无需重新运行任何检查
.\Invoke-CISAzureAudit.ps1 -ReportOnly
# 将 Trace 级别的诊断信息写入日志文件
.\Invoke-CISAzureAudit.ps1 -DebugMode -LogFile cis_audit.log
# 跳过 tenant 级别的身份检查
.\Invoke-CISAzureAudit.ps1 -SkipTenantChecks
# 运行中断了?只需重新运行 — 它会自动恢复
.\Invoke-CISAzureAudit.ps1
# 显示当前被抑制的发现结果
.\Invoke-CISAzureAudit.ps1 -ReportOnly # suppressions applied during report generation
# 与上一次运行进行 Diff(regressions、improvements、新增/移除的结果)
.\Invoke-CISAzureAudit.ps1 -CompareWith auto
```
## 工作原理
### 数据收集 —— 三种方法
#### 1. Azure Resource Graph(批量预取 —— 每次审计一次)
在任何针对特定订阅的工作开始之前,Kusto 查询会通过一次往返获取整个租户内的所有相关资源:
- 网络安全组 (NSG) 和安全规则
- 存储账户和安全属性
- Key Vault —— 访问配置和网络设置
- 虚拟网络、子网和 NSG 关联
- 应用程序网关和 WAF 设置
- Databricks 工作区
- Bastion Host
- 网络观察程序和资源位置
- 角色分配(Owner 和 User Access Administrator)
- WAF 策略
#### 2. 每个订阅的 Az PowerShell 模块调用
用于获取 Resource Graph 无法公开的实时服务配置和数据:
- `Get-AzSecurityPricing` —— Defender 计划状态 (8.1.x)
- `Get-AzActivityLogAlert` —— 所有 11 项警报检查 (6.1.2.x)
- `Get-AzDiagnosticSetting` —— Key Vault 和应用服务诊断日志 (6.1.1.4, 6.1.1.6)
- `Get-AzLogProfile` —— 通过日志配置文件保留活动日志 (6.1.1.3)
- `Get-AzNetworkWatcherFlowLog` —— 流日志保留期 (7.5, 7.8)
- `Get-AzKeyVaultKey` / `Get-AzKeyVaultSecret` / `Get-AzKeyVaultCertificate` —— 过期日期 (8.3.x)
- `Get-AzKeyVaultKeyRotationPolicy` —— 自动轮换配置 (8.3.9)
- `Get-AzStorageBlobServiceProperty` —— 软删除、版本控制、日志记录 (9.2.x)
- `Get-AzStorageFileServiceProperty` —— 文件软删除和 SMB 设置 (9.1.x)
- `Get-AzStorageAccount` —— 存储安全属性 (9.3.x)
- `Get-AzResourceLock` —— CanNotDelete / ReadOnly 锁 (9.3.9, 9.3.10)
- `Get-AzRoleDefinition` —— 自定义管理员角色检测 (5.4)
#### 3. 通过 `Invoke-AzRestMethod` 进行 ARM / Microsoft Graph REST 调用
用于 Az 模块 cmdlet 未公开的租户级身份检查和 API:
- `graph.microsoft.com/v1.0/policies/identitySecurityDefaultsEnforcementPolicy` —— 安全默认值 (5.1.1)
- `graph.microsoft.com/v1.0/identity/conditionalAccess/policies` —— 条件访问回退 (5.1.1)
- `graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails` —— 每用户 MFA 注册 (5.1.3)
- `management.azure.com/.../diagnosticSettings` —— 订阅级活动日志路由 (6.1.1.x)
- 用于安全联系人 (8.1.12–8.1.15)、WDATP 集成 (8.1.3.3) 和攻击路径通知 (8.1.15) 的 ARM REST
#### 4. 权限预检
审计开始前,会在并行 runspace 中(最多同时 8 个)调用 `Get-AzRoleAssignment`,以验证运行者账户在每个订阅上是否持有所需的角色。无需 Azure CLI。使用 `-NoPermissionCheck` 可完全跳过预检。
### 权限预检
审计开始前,该工具会检查运行者账户是否在每个订阅上持有所需的角色。
### 检查点与恢复
每个订阅完成后,结果会写入 `cis_checkpoints/.json`。
如果脚本在运行中途停止或崩溃,重新运行它将跳过已完成的订阅,并从上次中断的地方继续。使用 `-Fresh` 可放弃所有检查点并重新开始。
### 并行执行
订阅通过 PowerShell runspace 并发运行。默认值为 3 个并行 worker(可通过 `-Parallel` 配置)。Resource Graph 预取始终在并行循环开始前运行一次。
内置了自适应并发:如果 Azure 返回限制响应 (HTTP 429),工具会自动减少并发 worker 数量。在连续两批没有被限制后,worker 数量会增加回原始值。
## HTML 报告
生成的报告是一个独立的 HTML 文件,没有外部依赖。
- **摘要卡片** —— 合规性评分(PASS / (PASS + FAIL + ERROR) —— 无法读取的资源会扣分;INFO、MANUAL 和 SUPPRESSED 状态会被排除),以及每种状态的计数。
- **合规性圆环图** —— 三个环状图,分别显示总体、Level 1 和 Level 2 的 PASS/FAIL/ERROR 比例。
**部分细分** —— 每个 CIS 部分的水平堆叠条形图,从最差到最好排序。
- **按订阅汇总** —— 堆叠条形图表格,显示每个订阅的通过/失败/错误计数;点击某一行可按该订阅过滤结果表。
- **可过滤表格** —— 可同时通过自由文本搜索、订阅、状态和级别 (L1/L2) 进行过滤。当所有结果都被过滤掉时,部分标题会折叠。
- **按资源的结果** —— 每个 NSG、存储账户、Key Vault、子网和 Databricks 工作区都会单独报告,而不是汇总为单个控制的通过/失败。
- **修复提示** —— 每个 FAIL 结果都包含修复问题的 Azure 门户导航路径。ERROR 结果包含关于缺少哪些访问权限的可操作说明。
- **审计错误行** —— 如果订阅上下文切换、整个检查组或租户级别的运行崩溃,将生成一个合成的 ERROR 行(控制项为位于 *0 - Audit Errors* 部分下的 `CONTEXT`、`GROUP`、`TENANT` 或 `FATAL`),以便在报告中显示失败,而不是让控制项静默消失。
- **合规性趋势** —— 经过两次或多次完整租户审计运行后,将出现一个显示合规性评分随时间变化的图表,该图表可折叠。
- **返回顶部** —— 长报告右下角的固定按钮。
### 状态类型
| 状态 | 含义 |
| --- | --- |
| PASS | 控制项合规 |
| FAIL | 控制项不合规 —— 提供修复提示 |
| ERROR | 检查无法完成 —— 审计缺口(缺少权限、超时或 API 错误)。合规性**未知**;不应视为合规。 |
| INFO | 不适用 —— 资源类型不存在或账户类型不支持该功能 |
| MANUAL | 无法自动化 —— 需要根据 CIS PDF 进行手动验证 |
| SUPPRESSED | 已接受的风险 —— 已确认并附带理由的发现 |
### `-ReportOnly`:在不重新审计的情况下重新生成报告
```
.\Invoke-CISAzureAudit.ps1 -ReportOnly
```
### 输出文件
每次运行都会在 HTML 报告旁边生成四个文件:
| 文件 | 格式 | 用途 |
| --- | --- | --- |
| `cis_audit_report.html` | HTML | 交互式可视化报告(除非使用 `-NoOpen`,否则自动打开) |
| `cis_audit_report.json` | JSON | 供下游工具使用的机器可读结果 |
| `cis_audit_report.csv` | CSV | 适合合规团队的电子表格格式 |
| `cis_audit_report.sarif` | SARIF 2.1.0 | 用于 GitHub 代码扫描 / Defender for DevOps 的发现(参见 *CI/CD pipeline*) |
使用 `-Output report.html` 更改基础路径 —— `.json`、`.csv` 和 `.sarif` 扩展名将自动派生。
SARIF 日志仅包含发现:FAIL 映射为 `error`,ERROR 映射为 `warning`(无法审计的控制项也是一种发现,映射反映在评分中),而 SUPPRESSED 结果会带有 SARIF 抑制对象,以便代码扫描 UI 将其显示为已驳回。PASS/INFO/MANUAL 将被忽略。
## 抑制发现(已接受的风险)
在项目根目录下创建一个 `suppressions.json` 文件,以将特定发现标记为已接受的风险。
被抑制的发现将在报告中重新分类为 **SUPPRESSED**,并从 FAIL/ERROR 计数中排除。
[`suppressions.json.example`](suppressions.json.example) 中提供了一个带有注释示例的模板
(包含由 WAF 架构和 B2B 来宾管理员账户的示例)。
将其复制到 `suppressions.json` 并删除不适用的条目。
### suppressions.json 格式
```
[
{
"control_id": "9.5",
"justification": "Legacy storage account — migration planned for Q3",
"expires": "2025-09-30",
"resource": "/subscriptions/.../storageAccounts/legacy01",
"subscription": "Production"
}
]
```
### 抑制规则
- `control_id`(必填)—— 要抑制的 CIS 控制 ID(例如 `"9.5"`)。
- `justification`(必填)—— 抑制的人类可读理由。
- `expires`(必填)—— 以 `YYYY-MM-DD` 格式表示的过期日期。距离今天最多 365 天。
- `resource`(可选)—— 要匹配的资源 ID。省略此选项可抑制此控制项的所有资源。
- `subscription`(可选)—— 订阅名称或 ID。省略此选项可跨所有订阅进行抑制。
过期的条目将被静默忽略。摘要横幅会显示处于活动状态的抑制计数。
## 覆盖的控制项
### 第 2 部分 —— Azure Databricks(6 项自动化 · 6 项手动)
| 控制项 | 标题 | 级别 |
| --- | --- | --- |
| 2.1.1 | 部署在客户管理的 VNet 中的 Databricks | L1 |
| 2.1.2 | 为 Databricks 子网配置了 NSG | L1 |
| 2.1.3 | 集群工作节点之间的流量已加密(手动) | L2 |
| 2.1.4 | 从 Microsoft Entra ID 同步的用户/组(手动) | L1 |
| 2.1.5 | 配置了 Unity Catalog(手动) | L1 |
| 2.1.6 | PAT 使用受限且强制执行过期(手动) | L1 |
| 2.1.7 | 配置了诊断日志传送 | L1 |
| 2.1.8 | 关键数据使用客户管理的密钥加密(手动) | L2 |
| 2.1.9 | 启用了 No Public IP | L1 |
| 2.1.10 | 禁用了 Allow Public Network Access | L1 |
| 2.1.11 | 使用专用终结点访问工作区 | L2 |
| 2.1.12 | 定期审查 Databricks 组(手动) | L1 |
### 第 3 部分 —— 计算服务(1 项手动)
| 控制项 | 标题 | 级别 | 注意 |
| --- | --- | --- | --- |
| 3.1.1 | 只有启用了 MFA 的身份才能访问特权虚拟机 | L2 | **手动** —— 需要将角色分配与 MFA 状态相关联 |
### 第 5 部分 —— 身份服务(8 项自动化 · 7 项手动)
| 控制项 | 标题 | 级别 | 注意 |
| --- | --- | --- | --- |
| 5.1.1 | 在 Microsoft Entra ID 中启用了“安全默认值” | L1 | |
| 5.1.2 | 注册或加入设备需要 MFA | L1 | **手动** |
| 5.1.3 | 为所有用户启用了“多重身份验证” | L1 | |
| 5.1.4 | 在受信任的设备上禁用“记住 MFA” | L1 | **手动** |
| 5.3.1 | Azure 管理员账户不用于日常操作 | L1 | **手动** |
| 5.3.2 | 定期审查来宾用户 | L1 | **手动** —— 当不存在来宾时通过;通过 Graph 提取来宾清单 |
| 5.3.3 | 限制使用“User Access Administrator”角色 | L1 | |
| 5.3.4 | 定期审查特权角色分配 | L1 | **手动** |
| 5.3.5 | 已禁用的账户没有 Read/Write/Owner 权限 | L1 | |
| 5.3.6 | 定期审查“Tenant Creator”角色分配 | L1 | **手动** |
| 5.3.7 | 定期审查非特权角色分配 | L1 | **手动** |
| 5.4 | 没有自定义订阅管理员角色 | L1 | |
| 5.5 | 分配了用于管理资源锁的自定义角色 | L2 | |
| 5.6 | 订阅离开/进入租户设置为“Permit no one” | L2 | |
| 5.7 | 订阅所有者数量在 2 到 3 之间 | L1 | |
### 第 6 部分 —— 日志记录与监控(15 项自动化 · 9 项手动)
| 控制项 | 标题 | 级别 |
| --- | --- | --- |
| 6.1.1.1 | 为订阅活动日志提供了诊断设置 | L1 |
| 6.1.1.2 | 诊断设置捕获了相应的类别 | L1 |
| 6.1.1.3 | 包含 activity-logs 容器的存储账户使用 CMK 加密(手动) | L2 |
| 6.1.1.4 | 启用了 Azure Key Vault 的日志记录 | L1 |
| 6.1.1.5 | NSG 流日志被捕获并发送到 Log Analytics(手动) | L2 |
| 6.1.1.6 | 虚拟网络流日志被捕获并发送到 Log Analytics(手动) | L2 |
| 6.1.1.7 | 用于 Microsoft Graph 活动日志的 Entra 诊断设置(手动) | L2 |
| 6.1.1.8 | 用于 Microsoft Entra 活动日志的 Entra 诊断设置(手动) | L2 |
| 6.1.1.9 | Intune 日志被捕获并发送到 Log Analytics(手动) | L2 |
| 6.1.2.1 | 活动日志警报:创建策略分配 | L1 |
| 6.1.2.2 | 活动日志警报:删除策略分配 | L1 |
| 6.1.2.3 | 活动日志警报:创建或更新 NSG | L1 |
| 6.1.2.4 | 活动日志警报:删除 NSG | L1 |
| 6.1.2.5 | 活动日志警报:创建或更新安全解决方案 | L1 |
| 6.1.2.6 | 活动日志警报:删除安全解决方案 | L1 |
| 6.1.2.7 | 活动日志警报:创建或更新 SQL 防火墙规则 | L1 |
| 6.1.2.8 | 活动日志警报:删除 SQL 防火墙规则 | L1 |
| 6.1.2.9 | 活动日志警报:创建或更新公共 IP | L1 |
| 6.1.2.10 | 活动日志警报:删除公共 IP | L1 |
| 6.1.2.11 | 活动日志警报:服务运行状况 | L1 |
| 6.1.3.1 | 配置了 Application Insights | L2 |
| 6.1.4 | 为所有受支持的服务启用了 Azure Monitor 资源日志记录(手动) | L1 |
| 6.1.5 | 生产工件不使用 Basic/Free/Consumption SKU(手动) | L2 |
| 6.2 | 为关键任务资源设置了资源锁(手动) | L2 |
### 第 7 部分 —— 网络服务(13 项自动化 · 3 项手动)
| 控制项 | 标题 | 级别 |
| --- | --- | --- |
| 7.1 | RDP (3389) 未对 Internet 开放 | L1 |
| 7.2 | SSH (22) 未对 Internet 开放 | L1 |
| 7.3 | 限制了来自 Internet 的 UDP 端口访问 | L1 |
| 7.4 | 评估并限制了来自 Internet 的 HTTP/HTTPS (80/443) | L1 |
| 7.5 | NSG 流日志保留期 >= 90 天 | L2 |
| 7.6 | 为所有正在使用的区域启用了网络观察程序 | L2 |
| 7.7 | 定期评估公共 IP 地址(手动) | L1 |
| 7.8 | VNet 流日志保留期 >= 90 天 | L2 |
| 7.9 | VPN Gateway P2S 身份验证类型 = 仅限 Entra ID(手动) | L2 |
| 7.10 | 在 Azure Application Gateway 上启用了 WAF | L2 |
| 7.11 | 子网与 NSG 关联 | L1 |
| 7.12 | 应用网关 SSL 策略最低 TLS 1.2+ | L1 |
| 7.13 | 在 Application Gateway 上启用了 HTTP2 | L1 |
| 7.14 | 启用了 WAF 请求正文检查 | L2 |
| 7.15 | 启用了 WAF 机器人防护 | L2 |
| 7.16 | 为 PaaS 资源使用了网络安全边界(手动) | L2 |
### 第 8 部分 —— 安全服务(30 项自动化 · 8 项手动)
| 控制项 | 标题 | 级别 |
| --- | --- | --- |
| 8.1.1.1 | Microsoft Defender CSPM | L2 |
| 8.1.2.1 | Microsoft Defender for APIs | L2 |
| 8.1.31 | Microsoft Defender for Servers | L2 |
| 8.1.3.2 | 计算机组件的漏洞评估(手动) | L2 |
| 8.1.3.3 | 终结点保护 (WDATP) 组件 | L2 |
| 8.1.3.4 | 计算机的无代理扫描组件(手动) | L2 |
| 8.1.3.5 | 文件完整性监控组件(手动) | L2 |
| 8.1.4.1 | Microsoft Defender for Containers | L2 |
| 8.1.5.1 | Microsoft Defender for Storage | L2 |
| 8.1.5.2 | 监控存储账户的 ATP 警报(手动) | L2 |
| 8.1.6.1 | Microsoft Defender for App Services | L2 |
| 8.1.7.1 | Microsoft Defender for Azure Cosmos DB | L2 |
| 8.1.7.2 | Microsoft Defender for Open-Source Relational DBs | L2 |
| 8.1.7.3 | Microsoft Defender for SQL (托管实例) | L2 |
| 8.1.7.4 | Microsoft Defender for SQL Servers on Machines | L2 |
| 8.1.8.1 | Microsoft Defender for Key Vault | L2 |
| 8.1.9.1 | Microsoft Defender for Resource Manager | L2 |
| 8.1.10 | Defender 配置为检查 VM 操作系统更新 | L1 |
| 8.1.11 | 未禁用非废弃的 MCSB 策略(手动) | L1 |
| 8.1.12 | 安全警报通知订阅所有者 | L1 |
| 8.1.13 | 安全联系人的其他电子邮件地址 | L1 |
| 8.1.14 | 配置了警报严重性通知 | L1 |
| 8.1.15 | 配置了攻击路径通知 | L1 |
| 8.1.16 | Defender 外部攻击面监控 (EASM)(手动) | L2 |
| 8.2.1 | Microsoft Defender for IoT Hub(手动) | L2 |
| 8.3.1 | 设置了密钥过期时间 —— 使用 RBAC 的 Key Vault | L1 |
| 8.3.2 | 设置了密钥过期时间 —— 访问策略(遗留) | L1 |
| 8.3.3 | 设置了机密过期时间 —— 使用 RBAC 的 Key Vault | L1 |
| 8.3.4 | 设置了机密过期时间 —— 访问策略(遗留) | L1 |
| 8.3.5 | 启用了 Key Vault 清除保护 | L1 |
| 8.3.6 | 启用了 Key Vault RBAC 授权 | L2 |
| 8.3.7 | 禁用了 Key Vault 公共网络访问 | L1 |
| 8.3.8 | 使用专用终结点访问 Key Vault | L2 |
| 8.3.9 | 启用了自动密钥轮换 | L2 |
| 8.3.10 | 需要时使用 Azure Key Vault 托管 HSM(手动) | L2 |
| 8.3.11 | 证书有效期 <= 12 个月 | L1 |
| 8.4.1 | 存在 Azure Bastion Host | L2 |
| 8.5 | 在 VNet 上启用了 DDoS 网络防护 | L2 |
### 第 9 部分 —— 存储服务(21 项自动化)
| 控制项 | 标题 | 级别 |
| --- | --- | --- |
| 9.1.1 | 启用了 Azure 文件共享的软删除 | L1 |
| 9.1.2 | SMB 协议版本 >= 3.1.1 | L1 |
| 9.1.3 | SMB 通道加密为 AES-256-GCM 或更高 | L1 |
| 9.2.1 | 启用了 Blob 软删除 | L1 |
| 9.2.2 | 启用了容器软删除 | L1 |
| 9.2.3 | 启用了 Blob 版本控制 | L2 |
| 9.3.1.1 | 启用了密钥轮换提醒 | L1 |
| 9.3.1.2 | 访问密钥在 90 天内重新生成 | L1 |
| 9.3.1.3 | 禁用了存储账户密钥访问 | L1 |
| 9.3.2.1 | 使用专用终结点访问存储账户 | L2 |
| 9.3.2.2 | 禁用了公共网络访问 | L1 |
| 9.3.2.3 | 默认网络访问规则为拒绝 | L1 |
| 9.3.3.1 | 在 Azure 门户中默认使用 Microsoft Entra 授权 | L1 |
| 9.3.4 | 需要安全传输 (HTTPS) | L1 |
| 9.3.5 | 允许受信任的 Microsoft 服务访问存储 | L2 |
| 9.3.6 | 最低 TLS 版本 1.2 | L1 |
| 9.3.7 | 禁用了跨租户复制 | L1 |
| 9.3.8 | 禁用了 Blob 匿名访问 | L1 |
| 9.3.9 | 存储账户应用了 ARM 删除锁 | L1 |
| 9.3.10 | 考虑为存储账户应用 ARM ReadOnly 锁 | L2 |
| 9.3.11 | 冗余设置为异地冗余 (GRS) | L2 |
## 测试
测试套件使用 **Pester 5** —— 所有测试均模拟 Az 模块调用,因此不需要真实的 Azure 连接。
### 运行所有测试
```
# 需要 Pester 5.0+
Install-Module Pester -Force -Scope CurrentUser
# 运行所有测试
.\Tests\Run-Tests.ps1
```
### 运行单个检查
```
.\Tests\Run-Tests.ps1 -Test "5_27"
```
### 包含代码覆盖率
```
.\Tests\Run-Tests.ps1 -Coverage
# 当覆盖率低于下限时失败(退出代码为 1) — 这正是 CI 所强制执行的
.\Tests\Run-Tests.ps1 -Coverage -MinCoverage 80
```
覆盖率测量 `Private/*.ps1` 和 `Checks/*.ps1` 并写入 `coverage.xml` (JaCoCo)。在 GitHub Actions 中,该百分比也会发布到作业摘要中。
### 持续集成
GitHub Actions pipeline 会在每次推送和拉取请求时运行:
- Pester 测试(Ubuntu + Windows)
- 代码覆盖率底线为 80%(Ubuntu 环境,发布在作业摘要中)
- PSScriptAnalyzer lint 检查
工作流文件位于 `.github/workflows/ci.yml`。
#### 在您自己的 CI/CD pipeline 中使用此工具
添加 `-ExitCode`,以便在发现任何 FAIL 或 ERROR 结果时使工具以代码 2 退出。
这使得 Azure DevOps、GitHub Actions 和类似系统能够在出现合规性倒退时使构建失败:
```
# GitHub Actions 示例
- name: Azure foundations audit
shell: pwsh
run: |
.\Invoke-CISAzureAudit.ps1 `
-Subscriptions "Production" `
-NoOpen `
-ExitCode
# Step fails (exit code 2) if any controls are non-compliant
```
```
# Azure DevOps 示例
- task: PowerShell@2
displayName: 'Azure Foundations audit'
inputs:
targetType: inline
pwsh: true
script: |
.\Invoke-CISAzureAudit.ps1 -Subscriptions $(SUB_NAME) -NoOpen -ExitCode
# Marks the pipeline stage as failed when FAIL/ERROR results are found
```
#### 将发现上传到 GitHub 代码扫描
每次运行还会在报告旁边写入一个 SARIF 2.1.0 日志。将其上传以在代码库的 **Security → Code scanning**(代码扫描)选项卡中显示发现(私有代码库需要 GitHub Advanced Security;公共代码库免费):
```
- name: Azure foundations audit
shell: pwsh
run: |
.\Invoke-CISAzureAudit.ps1 `
-Subscriptions "Production" `
-Output reports/cis.html `
-NoOpen
# No -ExitCode: let the upload step run even when there are findings
- name: Upload SARIF to code scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: reports/cis.sarif
category: cis-azure-benchmark
```
警报由控制项 + 订阅 + 资源作为键值,因此在多次运行中持续存在的发现将保留为单个警报,而消失的发现将被自动关闭。
#### 使用 OIDC 计划审计(无需存储 secret)
对于定期审计,请使用 GitHub 的 OIDC 联合身份验证:工作流在运行时将短期的 GitHub token 换取为 Azure token,因此代码库中永远不会存储任何 client secret 或证书。
一次性 Azure 设置 —— 带有联合凭据的应用注册:
```
# 1. App registration + service principal
az ad app create --display-name "cis-azure-audit" # note the appId in the output
az ad sp create --id
# 2. 为此 repository 的 default branch 信任 GitHub 的 OIDC issuer
# (scheduled workflows 始终在 default branch 上运行,因此 subject 必须与之匹配)
az ad app federated-credential create --id --parameters '{
"name": "github-scheduled-audit",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:/:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# 3. 为每个要审计的 subscription 授予读取权限
az role assignment create --assignee --role "Reader" --scope /subscriptions/
az role assignment create --assignee --role "Security Reader" --scope /subscriptions/
```
对于身份检查(第 5.x 部分,Microsoft Graph),还需要为 service principal 分配 **Global Reader** Entra 角色;如果没有它,这些控制项将返回 ERROR 并指出缺少的权限 —— 此时合规性未知,而不是假定为合规。
将 `AZURE_CLIENT_ID`(即 appId)、`AZURE_TENANT_ID` 和 `AZURE_SUBSCRIPTION_ID` 作为代码库的**变量**存储(Settings → Secrets and variables → Actions → Variables)—— 它们都不是 secret。
然后是工作流:
```
name: Weekly CIS Azure audit
on:
schedule:
- cron: '17 6 * * 1' # Mondays 06:17 UTC
workflow_dispatch: {} # allow manual runs too
permissions:
id-token: write # required for the OIDC token exchange
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Install the audit module
shell: pwsh
run: |
# Pulls the Az module dependencies automatically. Pin -Version for
# reproducible scheduled runs if you prefer.
Install-PSResource CISAzureFoundationsBenchmark -TrustRepository -Scope CurrentUser
- name: Azure login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true # the tool uses Az PowerShell, not az CLI
- name: Run audit
shell: pwsh
run: |
Invoke-CISAzureAudit `
-Subscriptions "${{ vars.AZURE_SUBSCRIPTION_ID }}" `
-Output reports/cis.html `
-NoOpen `
-ExitCode
- name: Upload report artifacts
if: always() # keep the reports even when -ExitCode fails the job
uses: actions/upload-artifact@v4
with:
name: cis-audit-report
path: reports/
```
构件包含所有四个报告文件(`cis.html`、`cis.json`、`cis.csv`、`cis.sarif`)。若还要在 Security 选项卡中显示发现,请追加上一节中的 `upload-sarif` 步骤(将 `security-events: write` 添加到工作流的 `permissions` 块中)。
退出代码摘要:
| 代码 | 含义 |
| --- | --- |
| `0` | 审计完成 —— 所有控制项均通过(或所有失败项均已被抑制) |
| `1` | 工具设置错误 —— 缺少 Az PowerShell 模块、未登录 Azure 或未找到可访问的订阅 |
| `2` | 合规性失败 —— 检测到一个或多个 FAIL 或 ERROR 结果(仅在指定了 `-ExitCode` 时发生) |
## 检查点文件
```
cis_checkpoints/
|- xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json <- completed
|- yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy.json <- completed
`- zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz.json <- failed (retried on next run)
```
每个文件包含该订阅的完整结果集、UTC 时间戳和完成状态。删除 `cis_checkpoints/` 文件夹或使用 `-Fresh` 可放弃所有检查点。使用 `-ReportOnly` 可在现有检查点的基础上重新生成 HTML 报告,而无需运行任何检查。
## 已知限制
**只读** —— 该脚本仅进行审计。它不会对您的环境进行任何更改。
**时间点快照** —— 结果反映的是脚本运行那一刻的状态。
**Key Vault 数据平面访问** —— 列出密钥、机密和证书除了需要订阅 Reader 权限外,还需要数据平面权限。请分配 **Key Vault Reader**(RBAC 保管库)或将运行者账户添加到保管库的访问策略中(非 RBAC 保管库)。如果没有此权限,受影响的检查将返回 ERROR 并附带说明消息。报告会清楚地将此与合规结果区分开 —— 此时合规性未知,而不是假定为合规。
**用于身份检查的 Graph API** —— 控制项 5.1.1、5.1.3、5.3.2 和 5.3.5 通过 `Invoke-AzRestMethod` 调用 Microsoft Graph API。如果尚未为运行审计的身份同意所需的 Graph 权限,这些检查将返回 ERROR。控制项 5.6 通过 ARM 读取租户级别的订阅策略,当调用者缺少租户范围的读取访问权限时返回 ERROR。
请使用以下命令进行测试:
```
Invoke-AzRestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/policies/identitySecurityDefaultsEnforcementPolicy"
```
## 故障排除
**身份检查返回 ERROR (AccessDenied)**
您的账户需要在 Entra ID 中具有 Global Reader 权限。要验证 Graph API 访问权限:
```
Invoke-AzRestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/policies/authorizationPolicy"
```
如果返回 403 错误,请要求您的 Entra ID 管理员授予 Global Reader 权限,或同意用于审计的 service principal 所需的 Graph API 权限。
**Key Vault 检查返回 ERROR(审计未完成)**
运行者账户需要 Key Vault 数据平面访问权限。对于启用了 RBAC 的保管库,请分配 **Key Vault Reader** 角色;对于访问策略保管库,请将该账户添加到保管库的访问策略中。报告中的 ERROR 消息会准确指出缺少的内容。
**找不到订阅**
传递给 `-Subscriptions` 的值必须与现有的订阅名称或 ID 完全匹配。
运行 `Get-AzSubscription | Select-Object Name, Id` 以查看可用的订阅。
**运行中断**
重新运行相同的命令以从中断处恢复,或添加 `-Fresh` 以重新开始。
## 免责声明
## 鸣谢
## 许可证
[MIT](LICENSE)
**版本:** 2.4.0
**Benchmark:** CIS Microsoft Azure Foundations Benchmark v6.0.0(2026年4月)
**覆盖范围:** 涵盖 7 个部分的 93 项自动化控制 · 输出中注明的 34 项手动控制(共 127 项)
标签:AI合规, Anthropic, CIS基准, IPv6, Libemu, Microsoft Azure, PowerShell, 云安全审计, 关系图谱, 基线检查, 安全合规, 网络代理