advanced-security/monorepo-code-scanning-action
GitHub: advanced-security/monorepo-code-scanning-action
一款针对 GitHub Monorepo 场景的代码扫描优化工具,通过增量检测和并行扫描大幅缩减 SAST 耗时,同时满足分支保护与合规要求。
Stars: 12 | Forks: 4
# Monorepo 代码扫描 Action
利用 GitHub Advanced Security,根据您的定义对 Monorepo 的特定部分进行代码扫描。这可以最大程度地减少 CI 工作量和时间,并允许在计划扫描时并行扫描 Monorepo。
有关如何将其用于 PR 扫描的示例,请参阅本仓库中的 [`./samples/sample-codeql-monorepo-pr-workflow.yml`](./samples/sample-codeql-monorepo-pr-workflow.yml)。有关计划扫描示例,请参阅 [`./samples/sample-codeql-monorepo-whole-repo-workflow.yml`](./samples/sample-codeql-monorepo-whole-repo-workflow.yml)
这些步骤相互传递信息以正常工作,因此您需要使用该工作流中定义的格式,并根据需要更改输入。
请参阅 [替代方案](#alternatives) 了解实现相同目标的其他方法。
## 概述
您必须为仓库定义一个项目结构,其中每个项目都是特定的语言和目录路径的子集,以便使用此工具拆分对 Monorepo 的扫描。
如果您使用 C# 和现有的 [MSBuild 项目文件](https://learn.microsoft.com/en-us/visualstudio/msbuild/walkthrough-creating-an-msbuild-project-file-from-scratch?view=vs-2022),则可以直接使用它来定义项目结构。
对于其他情况,您需要创建一个描述该结构的 JSON 文件,如下所示:
```
{
"":
"projects": {
"": {
"paths":
[
"",
"",
...
]
},
...
},
...
}
```
在 `changes` Action 的描述中,您可以看到更多可以在语言或项目级别设置的可选键。
此项目定义允许工作流使用 `changes` Action 查找已定义项目结构中的更改;然后 `scan` Action 使用 CodeQL(或其他工具)扫描任何已更改的项目;最后 `republish-sarif` 允许项目的未扫描部分通过重新发布 SARIF 来通过所需的 CodeQL 检查。
```
graph TD
PR --> |triggers| workflow[Workflow]
workflow[Workflow] -->|runs| changes[changes]
changes[Changes] -->|triggers| scan[Scan]
changes[Changes] -->|trigger| republish-sarif[Republish SARIF]
scan[Scan] -->|matrix| project-a[Project A]
scan[Scan] -->|matrix| project-b[Project B]
scan[Scan] -->|matrix| ...
```
## 使用 Action
这些步骤相互传递信息以正常工作,因此您需要使用该示例工作流中定义的格式,并根据需要更改输入。
### 分支保护
为了确保覆盖所有代码更改而无需扫描推送到默认分支的代码,请确保使用仓库规则集来限制直接推送到默认分支。
这还需要一条规则,要求分支在合并前必须是最新的。
您可能偶尔希望绕过此规则,因此请记住适当地允许这种情况,并考虑此时是否需要对仓库进行全面扫描以确保更改被扫描。您可以在完整的计划工作流上使用 `workflow_dispatch` 触发器来实现这一点。
### 扫描 PR 中的更改
`changes` Action 查找您定义的项目结构中的更改。
它还设置用于工作流其余部分的 CodeQL 配置。
有关如何将其用于 PR 扫描的示例,请参阅 [`./samples/sample-codeql-monorepo-pr-workflow.yml`](./samples/sample-codeql-monorepo-pr-workflow.yml)
#### 设置项目结构
项目结构既可以在 JSON 文件中定义并通过 `projects-json` 输入中的名称提供,也可以从 `build-xml` 输入中的 MSBuild XML 文件中解析出来。
它有几个目的——它定义了监视更改和检出的路径,还定义了 CodeQL 配置选项。
##### MSBuild 文件解析
您可以在本仓库的 `./samples/build-projects.xml` 中查看此 XML 格式的示例。
目前,解析依赖于项目中定义的格式与示例完全一致——即具有 `ItemGroup` 结构。
如果您使用 `PropertyGroup` 或具有不符合以此方式定义的简单结构的目标,则可能需要编写对 MSBuild 文件的自定义解析,以正确提取和分组成功构建项目所需的文件夹和文件。
使用 `build-xml` 时,您需要在 `variables` 输入中以 YAML 格式字典的形式定义输入文件中使用的任何变量及其具体值。例如,变量看起来像 `$(FolderADir)`。
##### JSON 项目结构
基本所需结构为每种语言包含一个顶级键,命名为 CodeQL 的命名方式(对于非 CodeQL 语言则使用任意名称)。每种语言都有一个 `projects` 键,在其下每个项目是一个包含 `paths` 的命名键,`paths` 是文件夹名称列表(不带前导 `./` - 例如 `src/FolderA`)。
在 JSON 版本中,您可以选择为 CodeQL 指定构建模式(build-mode),如 `none`、`auto` 或 `manual` 来选择该模式,或使用 `other` 以允许使用 CodeQL 以外的代码扫描工具进行扫描。这可以通过在适当级别提供 `build-mode` 键在语言或项目级别完成。如果您不提供,则默认为合适的构建模式,如果您在项目级别使用它,它将覆盖在语言级别设置的任何模式。
您还可以使用项目中的 `files` 键包含单个文件。这些文件*不被监视*更改,但将与 `paths` 键中的文件夹一起包含在稀疏检出中。这是为了防止在顶级构建文件更改时触发每个项目的扫描。如有必要,可以随时使用全仓库工作流手动触发仓库的完全重新扫描。同样,不要使用 `./` 作为文件名的前导。
`paths` 和 `files` 列表由两个不同的引擎评估——一个是 `dorny/paths-filter` Action,另一个是 `actions/checkout` Action。前者使用 [picomatch](https://github.com/micromatch/picomatch),后者使用 [gitignore](https://git-scm.com/docs/gitignore)-样式匹配,因此可以使用通配符,但它们的确切行为可能略有不同——最好尽可能使用精确路径。
您还可以选择使用 `queries` 键指定一组 CodeQL 查询,同样可以在语言或项目级别进行。这是对 `codeql/init` 步骤的 `queries` 输入有效的输入列表,如[此处文档所述](https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#specifying-additional-queries)。
如果您不想以这种方式指定 `queries`,您也可以为 Action 设置全局 `queries` 输入——见下文。
使用 `queries`、`build-mode` 和 `files` 键的 JSON 文件示例如下:
```
{
"":
"build-mode": "none",
"queries": [
"security-extended",
"./local/path/to/a/query.qls",
]
"projects": {
"": {
"build-mode": "auto",
"paths": [
"",
"",
...
],
"files": [
"",
"",
...
]
},
...
},
...
}
```
如果您动态生成项目 JSON 文件,那么最好将其保存在 Actions 工作区之外,例如在 `$RUNNER_TEMP` 中,因为检出可能会覆盖它。
###### 设置自定义语言 Glob
在项目 `paths` 中检查更改的文件不是该路径中的所有文件,而是每个语言定义的子集。每种语言都定义了一组捆绑的 glob,用于在为项目定义的项目路径中搜索。
您可以通过 `changes` Action 的 `extra-globs` 输入添加内容,该输入接受内联 YAML 输入,格式如下:
```
:
-
-
...
```
###### 配置 CodeQL
除了在语言或项目级别设置 `queries` 键外,您还可以全局设置它。
如果您传递全局 `queries` 输入(作为逗号分隔的字符串列表),它允许为所有语言和项目设置要使用的查询。它们使用与 `codeql/init` 步骤的 `queries` 输入相同的格式,如[此处文档所述](https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#specifying-additional-queries),用逗号分隔,就像那种情况一样。
同样,您可以传入全局 `config` 或 `config-file` 输入,它们使用与[此处](https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#using-a-custom-configuration-file)和[此处](https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#specifying-configuration-details-using-the-config-input)文档相同的格式。目前这*不能*在语言或项目级别设置,只能全局设置。
使用的有效配置是从项目 `paths` 创建的输入,以及在此 Action 的单独 `queries` 输入中设置的任何查询的组合,并覆盖 `config` 或 `config-file` 输入的内容。如果从配置输入传入,将使用 `paths-ignore`,否则将应用一组预定的经验性忽略路径。
### 全仓库扫描
与 `changes` Action 相比,`whole-repo` Action 允许在计划扫描中扫描整个 Monorepo。
这对于利用新的 CodeQL 查询或第三方 SAST 改进现有代码非常有用。
进行这种并行扫描可以使在更小的 runner 上扫描整个 Monorepo 成为可能,并将扫描的挂钟时间减少到可接受的水平。
这也是确保所有代码最近都被扫描的一种方式,这可能是合规性目的所要求的。
该 Action 接受与 `changes` Action 大致相同的输入,但不接受 `extra-globs` 输入,因为它不在仓库中查找更改。也不需要将 `republish-sarif` Action 与其一起使用。
在项目输入中定义的每个项目都被并行扫描,结果独立上传。
有关计划扫描示例,请参阅 [`./samples/sample-codeql-monorepo-whole-repo-workflow.yml`](./samples/sample-codeql-monorepo-whole-repo-workflow.yml)
### 扫描
`scan` Action 使用 CodeQL(或其他工具)扫描任何已更改的项目,仅使用对已定义项目的更改。
使用发生更改的项目的稀疏检出可以加快检出速度,并仅针对该项目进行扫描。
扫描可以使用本地自定义代码扫描 Action 来执行手动构建步骤和扫描前所需的任何准备步骤,该 Action 必须位于仓库的 `.github/actions/code-scanning-custom-analysis/` 中。这用于 `manual` 或 `other` 的 `build-mode`。
您可以在本仓库的 [`./samples/code-scanning-custom-analysis/`](./samples/code-scanning-custom-analysis/) 中查看此自定义工作流的示例。
这必须具有条件检查,以便为语言和项目应用正确的构建步骤。
### 项目注释器
`sarif-project-annotator` Action 用于将项目标记信息添加到 CodeQL 分析的 SARIF 输出文件中。在 Monorepo 中扫描多个项目时,此 Action 通过向警报规则添加项目标签来帮助识别哪些警报属于哪个项目。
此 Action 处理 CodeQL SARIF 文件并修改每个规则的 `properties.tags` 数组以包含 `project/{project-name}` 标签。此标签允许您更轻松地在警报中过滤和识别源项目。
项目注释器需要三个输入:
- `project`:要作为标签注释的项目名称(必需)
- `sarif_file`:CodeQL SARIF 结果文件的路径(必需)
- `output_file`:保存修改后的 SARIF 文件的路径(必需)
以下是在工作流中使用该 Action 的示例:
```
# Perform CodeQL scan but do not upload results (for further SARIF processing)
- name: Perform CodeQL Analysis
id: codeql-analyze
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.project.language }};project:${{ matrix.project.name }}"
upload: false
output: sarif-results
# Parse the db-locations output and get the sarif file name from the analysis
- name: Set SARIF file name
id: set-sarif-file-name
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const dbLocations = JSON.parse('${{ steps.codeql-analyze.outputs.db-locations }}');
const language = Object.keys(dbLocations)[0];
const sarifFilePath = `${language}.sarif`;
return sarifFilePath;
# Annotate the SARIF with project tags
- name: Annotate CodeQL Alert SARIF with Project tag
uses: advanced-security/monorepo-code-scanning-action/sarif-project-annotator@main #Recommended: pin to a hash instead of `main`
with:
project: ${{ matrix.project.name }}
sarif_file: ${{ steps.codeql-analyze.outputs.sarif-output }}/${{ steps.set-sarif-file-name.outputs.result }}
output_file: ${{ steps.codeql-analyze.outputs.sarif-output }}/${{ matrix.project.name }}-${{ steps.set-sarif-file-name.outputs.result }}
```
已知限制:
- 按标签过滤 Advanced Security Code Scanning 仪表板的功能是按规则全局实现的——这种设计限制了按唯一标签过滤的能力。查看或通过 API/Webhook 导出 Alert 将包含正确的数据。
### 重新发布
`republish-sarif` Action 允许项目的未扫描部分通过所需的 CodeQL 检查。
SARIF 被重新发布,这意味着一组完整的代码扫描结果会附加到 PR,从目标分支复制,无论项目在 PR 期间是否发生更改。
它还会在合并时将扫描结果重新发布到目标分支。这节省了在目标分支上运行全仓库扫描的时间。
为此,您需要为工作流提供 `pull_request` 触发器上的 `closed` 类型,以便能够在 PR 合并时运行,如示例工作流所示,以及此处:
```
pull_request:
branches: ["main"]
types:
- opened
- reopened
- synchronize
- closed
```
## 限制
如果您将 Monorepo 拆分为较小的组件,CodeQL 可能无法在项目的单个部分中同时看到完成漏洞数据流的源和汇。要解决此问题,您可能需要定义一个包含数据扩展的模型包,用于定义项目入口点的源。
Actions 最多可以创建包含 256 个目标的矩阵作业。这意味着拥有超过 256 个项目的 Monorepo 必须划分为多个工作流,直到此 Action 采取措施处理此问题(如果可能)。
GitHub 的检查功能最多可以包含 1000 个同名检查。这意味着拥有超过 1000 个项目的 Monorepo 无法单独扫描所有项目;因此必须将几个项目分组在一起以低于此 1000 阈值。
自定义 CodeQL/其他工具分析需要在单个工作流中手动控制将哪些构建步骤应用于哪个项目,这与工作流其余部分的声明式设计形成对比。
此工具无法帮助处理无法拆分为较小项目的单体应用。
如果您的 Monorepo 包含同一项目中存在的多种语言,则需要针对每种受影响的语言多次复制该项目结构。如果这会有所帮助,未来的选项可能会采用具有多个路径和语言的单个命名项目——请提出 issue。
## 测试
此工具依赖的脚本的本地测试位于 `tests` 文件夹中。它们使用 `./run.sh` 运行,而不是使用测试框架。
测试还使用私有 [`advanced-security/sample-csharp-monorepo`](https://github.com/advanced-security/sample-csharp-monorepo/) 仓库进行端到端完成。
## 许可证
本项目根据 MIT 开源许可证的条款授权。有关完整条款,请参阅 [LICENSE](LICENSE)。
## 维护者
有关维护者列表,请参阅 [CODEOWNERS](CODEOWNERS)。
## 支持
请参阅 [SUPPORT](SUPPORT.md) 文件。
## 背景和致谢
`changes` Action 依赖于 [`dorny/paths-filter`](https://github.com/dorny/paths-filter/) Action。
有关更多信息,请参阅 [CHANGELOG](CHANGELOG.md)、[CONTRIBUTING](CONTRIBUTING.md)、[SECURITY](SECURITY.md)、[SUPPORT](SUPPORT.md)、[CODE OF CONDUCT](CODE_OF_CONDUCT.md) 和 [PRIVACY](PRIVACY.md) 文件。
### 替代方案
此工具旨在提供一种独立于构建系统的方式来拆分 Monorepo 以进行扫描,但它远不是高效扫描 Monorepo 的唯一方法。
可以使用对 Monorepo 友好的构建工具来定义构建目标和依赖项,仅检出构建目标所需的代码,并在该目标上运行自定义命令。
您可以将 CodeQL 作为自定义命令注入构建工具;对于编译语言,您可以检测构建过程以监视构建命令并在输出上运行 CodeQL;或者您可以在检出代码的构建目标所需子集上运行 CodeQL。
有必要跟踪分配给每个构建目标的 CodeQL 类别,以避免冲突。
请参阅 GitHub 文档 [Using code scanning with your existing CI systemhttps://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/integrating-with-code-scanning/using-code-scanning-with-your-existing-ci-system)
标签:CodeQL, DevSecOps, GitHub Actions, GitHub Advanced Security, Homebrew安装, macOS, MSBuild, SARIF, SAST, 上游代理, 代码安全, 增量扫描, 安全加固, 安全评估工具, 工作流优化, 并行扫描, 漏洞枚举, 盲注攻击, 结构化查询, 自动化安全, 自动笔记, 自定义脚本, 自定义脚本