Help us learn about your current experience with the documentation. Take the survey.

安全扫描器集成

将安全扫描器集成到 GitLab 中,就是为最终用户提供一个 CI/CD 作业定义, 他们可以将其添加到 CI/CD 配置文件中,以扫描他们的 GitLab 项目。 然后,该作业应以 GitLab 指定的格式输出其结果。这些结果会自动在 GitLab 的各个地方展示, 例如流水线视图、合并请求小组件和安全仪表板。

扫描作业通常基于一个 Docker 镜像, 该镜像在独立环境中包含扫描器及其所有依赖项。

本文档介绍了编写实现安全扫描器的 CI/CD 作业的要求和指南, 以及对 Docker 镜像的要求和指南。

作业定义

本节描述了安全扫描器作业定义文件中需要添加的几个重要字段。 这些和其他可用字段的完整文档可以在 CI 文档 中查看。

名称

为了保持一致性,扫描作业应按扫描器命名,使用小写。 作业名称在扫描类型后添加后缀:

  • _dependency_scanning
  • _container_scanning
  • _dast
  • _sast

例如,基于"MySec"扫描器的依赖扫描作业应命名为 mysec_dependency_scanning

镜像

image 关键字用于指定包含安全扫描器的 Docker 镜像

脚本

script 关键字用于指定运行扫描器的命令。 由于 script 条目不能为空,必须设置为执行扫描的命令。 不能依赖 Docker 预定义的 ENTRYPOINTCMD 来自动执行扫描, 而不传递任何命令。

before_script 不应在作业定义中使用,因为用户可能依赖它来在执行扫描前准备他们的项目。 例如,使用 before_script 安装特定项目在执行 SAST 或依赖扫描前需要的系统库是常见做法。

同样,after_script 也不应在作业定义中使用,因为它可能被用户覆盖。

阶段

为了保持一致性,扫描作业在可能时应属于 test 阶段。 stage 关键字可以省略,因为 test 是默认值。

失败保护

默认情况下,扫描作业在失败时不会阻塞流水线, 因此应将 allow_failure 参数设置为 true

工件

扫描作业必须使用 artifacts:reports 关键字 声明与其执行的扫描类型对应的报告。 有效的报告类型有:

  • dependency_scanning
  • container_scanning
  • dast
  • api_fuzzing
  • coverage_fuzzing
  • sast
  • secret_detection

例如,下面是一个生成名为 gl-sast-report.json 文件并将其作为 SAST 报告上传的 SAST 作业定义:

mysec_sast:
  image: registry.gitlab.com/secure/mysec
  artifacts:
    reports:
      sast: gl-sast-report.json

gl-sast-report.json 是示例文件路径,可以使用任何其他文件名。更多详情请参见 输出文件部分。之所以将其处理为 SAST 报告, 是因为它在作业定义中的 reports:sast 键下声明,而不是因为文件名。

策略

某些 GitLab 工作流,如 AutoDevOps, 定义了 CI/CD 变量来指示应跳过哪些扫描。您可以通过查找以下变量来检查:

  • DEPENDENCY_SCANNING_DISABLED
  • CONTAINER_SCANNING_DISABLED
  • SAST_DISABLED
  • DAST_DISABLED

如果根据扫描器类型合适,您应该跳过运行自定义扫描器。

GitLab 还定义了一个 CI_PROJECT_REPOSITORY_LANGUAGES 变量,它提供存储库中的语言列表。 根据此值,您的扫描器可能会执行不同的操作。语言检测当前依赖于 linguist Ruby gem。 请参见 预定义 CI/CD 变量

策略检查示例

此示例展示了如何跳过自定义依赖扫描作业 mysec_dependency_scanning, 除非项目存储库包含 Java 源代码且启用了 dependency_scanning 功能:

mysec_dependency_scanning:
  rules:
    - if: $DEPENDENCY_SCANNING_DISABLED == 'true'
      when: never
    - if: $GITLAB_FEATURES =~ /\bdependency_scanning\b/
      exists:
        - '**/*.java'

任何额外的作业策略都应仅根据用户需求进行配置。 例如,预定义策略不应为特定分支或特定文件集更改时触发扫描作业。

Docker 镜像

Docker 镜像是一个独立的环境,将扫描器及其所有依赖的库和工具组合在一起。 将扫描器打包到 Docker 镜像中,可以确保其依赖项和配置始终存在, 无论扫描器在哪个机器上运行。

镜像大小

根据 CI 基础设施的不同,CI 可能需要在每次作业运行时获取 Docker 镜像。 为了使扫描作业运行快速并避免浪费带宽,Docker 镜像应尽可能小。 您应目标为 50 MB 或更小。如果无法实现,请尝试保持在 1.46 GB 以下, 这是 DVD-ROM 的大小。

如果扫描器需要完全功能的 Linux 环境, 建议使用 Debian “slim” 发行版或 Alpine Linux。 如果可能,建议使用 FROM scratch 指令从头构建镜像, 并编译扫描器及其所需的所有库。 多阶段构建 也有助于保持镜像较小。

为了保持镜像大小较小,可以考虑使用 dive 来分析 Docker 镜像中的层, 以识别额外膨胀可能来自哪里。

在某些情况下,从镜像中删除文件可能很困难。当发生这种情况时,考虑使用 Zstandard 来压缩文件或大型目录。Zstandard 提供了许多不同的压缩级别, 可以在对解压缩速度影响很小的情况下减小镜像大小。 在镜像启动后自动解压缩任何压缩目录可能会有所帮助。 您可以通过向 Docker 镜像的 /etc/bashrc 或特定用户的 $HOME/.bashrc 添加步骤来实现这一点。 如果您选择后者选项,请记住更改入口点以启动 bash 登录 shell。

以下是一些入门示例:

镜像标签

Docker Official Images 项目中所述, 强烈建议为版本号标签提供别名,这使用户可以轻松引用特定系列的"最新"版本。 另请参见 Docker 标签:标记和版本化 Docker 镜像的最佳实践

权限

要使用非 root 权限运行 Docker 容器,容器中必须存在以下用户和组:

  • 用户 gitlab,用户 ID 为 1000
  • gitlab,组 ID 为 1000

命令行

扫描器是一个命令行工具,它接受环境变量作为输入, 并生成一个作为报告上传的文件(基于作业定义)。 它还会在标准输出和标准错误流上生成文本输出,并以状态码退出。

变量

所有 CI/CD 变量都作为环境变量传递给扫描器。 被扫描的项目由 预定义 CI/CD 变量 描述。

SAST 和依赖扫描

SAST 和依赖扫描扫描器必须扫描项目目录中的文件, 该目录由 CI_PROJECT_DIR CI/CD 变量给出。

容器扫描

为了与 GitLab 官方容器扫描保持一致, 扫描器必须扫描名称和标签由 CI_APPLICATION_REPOSITORYCI_APPLICATION_TAG 给出的 Docker 镜像。 如果提供了 DOCKER_IMAGE CI/CD 变量,则忽略 CI_APPLICATION_REPOSITORYCI_APPLICATION_TAG 变量, 而是扫描 DOCKER_IMAGE 变量中指定的镜像。

如果未提供,CI_APPLICATION_REPOSITORY 应默认为 $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG,这是预定义 CI/CD 变量的组合。 CI_APPLICATION_TAG 应默认为 CI_COMMIT_SHA

扫描器应使用 DOCKER_USERDOCKER_PASSWORD 变量登录 Docker 注册表。 如果未定义这些变量,则扫描器应使用 CI_REGISTRY_USERCI_REGISTRY_PASSWORD 作为默认值。

配置文件

虽然扫描器可以使用 CI_PROJECT_DIR 来加载特定的配置文件, 但建议将配置作为 CI/CD 变量暴露,而不是文件。

输出文件

与上传到 GitLab CI/CD 的任何工件一样, 扫描器生成的安全报告必须写在由 CI_PROJECT_DIR CI/CD 变量给出的项目目录中。

建议根据扫描类型命名输出文件,并使用 gl- 作为前缀。 由于所有安全报告都是 JSON 文件,建议使用 .json 作为文件扩展名。 例如,依赖扫描报告的建议文件名是 gl-dependency-scanning.json

作业定义中的 artifacts:reports 关键字 必须与安全报告写入的文件路径一致。 例如,如果依赖扫描分析器将其报告写入 CI 项目目录, 并且该报告文件名为 depscan.json, 则 artifacts:reports:dependency_scanning 必须设置为 depscan.json

退出码

遵循 POSIX 退出码标准,扫描器以 0 表示成功退出,以 1 表示失败。 成功也包括发现漏洞的情况。

当 CI 作业失败时,即使作业 允许失败,安全报告结果也不会被 GitLab 接收。 但是,报告工件仍然会上传到 GitLab,并可在 流水线安全选项卡中下载

日志记录

扫描器应记录错误消息和警告,以便用户可以通过查看 CI 扫描作业的日志 轻松调查配置错误和集成问题。

扫描器可以使用 ANSI 转义码 来为其写入 Unix 标准输出和标准错误流的消息着色。 我们建议使用红色报告错误,黄色表示警告,绿色表示通知。 此外,我们建议错误消息前缀为 [ERRO],警告前缀为 [WARN],通知前缀为 [INFO]

日志级别

如果扫描器的日志级别低于 SECURE_LOG_LEVEL CI/CD 变量中设置的级别, 则扫描器应过滤掉该日志消息。例如,当 SECURE_LOG_LEVEL 设置为 error 时, 应跳过 infowarn 消息。接受的值如下,从高到低列出:

  • fatal
  • error
  • warn
  • info
  • debug

建议使用 debug 级别进行详细日志记录,这在调试时可能很有用。 SECURE_LOG_LEVEL 的默认值应设置为 info

在执行命令行时,扫描器应使用 debug 级别来记录命令行及其输出。 如果命令行失败,则应以 error 日志级别记录; 这样无需将日志级别更改为 debug 并重新运行扫描作业即可调试问题。

常用 logutil

如果您使用 gocommon, 建议您使用 Logruscommon 的 logutil 来配置 Logrus 的格式化程序。 请参见 logutil README

报告

报告是一个 JSON 文档,结合了漏洞和可能的修复方案。

本文档概述了报告 JSON 格式、建议和示例, 以帮助集成器设置其字段。 该格式在 SASTDAST依赖扫描容器扫描 的文档中有详细描述。

您可以在此处找到这些扫描器的模式:

报告验证

您必须确保扫描器生成的报告通过针对报告中声明的模式版本的验证。 未通过验证的报告不会被 GitLab 接收,并且会在相应的流水线上显示错误消息。

使用已弃用的安全报告模式版本生成的报告会被接收,但会在相应的流水线上显示警告消息。 如果您看到此警告,请更新您的分析器以使用最新的可用模式。

在模式版本的弃用期之后,该文件将从 GitLab 中移除。声明了已移除版本的报告会被拒绝, 并且会在相应的流水线上显示错误消息。

如果报告使用的 PATCH 版本与任何提供的模式版本不匹配,它将针对最新的提供的 PATCH 版本进行验证。 例如,如果报告版本是 15.0.23,而最新的提供版本是 15.0.6,则报告将针对版本 15.0.6 进行验证。

GitLab 根据 gitlab-security_report_schemas gem 读取的安全报告 JSON 模式验证报告。您可以通过查看 GitLab 安装中 gem 的版本来查看您的 GitLab 版本支持哪些模式版本。 例如, GitLab 15.4 使用版本 0.1.2.min15.0.0.max15.2.0,这意味着它具有 15.0.015.2.0 范围内的版本。

要查看确切版本,请阅读 本地验证 部分。

本地验证

在 GitLab 中运行您的分析器之前,您应该验证您的分析器生成的报告, 以确保它符合声明的模式版本。

  1. 安装 gitlab-security_report_schemas
  2. 运行 security-report-schemas 查看支持的模式版本。
  3. 运行 security-report-schemas <report.json> 来验证报告。
$ gem install gitlab-security_report_schemas -v 0.1.2.min15.0.0.max15.2.1
Successfully installed gitlab-security_report_schemas-0.1.2.min15.0.0.max15.2.1
Parsing documentation for gitlab-security_report_schemas-0.1.2.min15.0.0.max15.2.1
Done installing documentation for gitlab-security_report_schemas after 0 seconds
1 gem installed

$ security-report-schemas
SecurityReportSchemas 0.1.2.min15.0.0.max15.2.1.
Supported schema versions: ["15.0.0", "15.0.1", "15.0.2", "15.0.4", "15.0.5", "15.0.6", "15.0.7", "15.1.0", "15.1.1", "15.1.2", "15.1.3", "15.1.4", "15.2.0", "15.2.1"]

Usage: security-report-schemas REPORT_FILE_PATH [options]
    -r, --report_type=REPORT_TYPE    Override the report type
    -w, --warnings                   Prints the warning messages

$ security-report-schemas ~/Downloads/gl-dependency-scanning-report.json
Validating dependency_scanning v15.0.0 against schema v15.0.0
Content is invalid
* root is missing required keys: dependency_files

报告字段

版本

此字段指定您正在使用的 安全报告模式 版本。 有关要使用的版本信息,请参见 发布版本

GitLab 根据此值指定的模式版本验证您的报告。

漏洞

报告的 vulnerabilities 字段是漏洞对象的数组。

ID

id 字段是漏洞的唯一标识符。 它用于从 修复对象 引用已修复的漏洞。 我们建议您生成一个 UUID 并将其用作 id 字段的值。

类别

category 字段的值与报告类型匹配:

  • dependency_scanning
  • container_scanning
  • sast
  • dast
扫描

scan 字段是一个对象,嵌入有关扫描本身的元信息:执行扫描的 analyzerscanner,扫描执行的 start_timeend_time, 以及扫描的 status(“success” 或 “failure”)。

analyzerscanner 字段都是对象,嵌入人类可读的 name 和技术性的 idid 不应与其他集成器提供的任何分析器或扫描器冲突。

扫描主要标识符

scan.primary_identifiers 字段是一个可选字段,包含 主要标识符 的数组。 这是分析器执行扫描的所有规则集的完整列表。

即使给定扫描的 Vulnerabilities 数组可能为空, 此可选字段也应包含完整的潜在标识符列表,以告知 Rails 应用程序执行了哪些规则。

当填充时,Rails 应用程序可能会自动解决先前检测到的漏洞, 当它们的主要标识符未包含时,将其标记为不再相关。

名称、消息和描述

namemessage 字段包含漏洞的简短描述。 description 字段提供更多详细信息。

name 字段是上下文无关的,不包含漏洞发现位置的信息, 而 message 可能重复位置信息。

作为视觉示例,此屏幕截图突出显示了在查看漏洞时这些字段的使用位置, 作为流水线视图的一部分。

示例漏洞

例如,依赖扫描报告的漏洞的 message 提供有关易受攻击依赖项的信息, 这与漏洞的 location 字段冗余。 name 字段是首选,但当上下文/位置无法从漏洞标题中移除时, 会使用 message 字段。

举例说明,这是一个由依赖扫描扫描器报告的漏洞对象示例, 其中 message 重复了 location 字段:

{
    "location": {
        "dependency": {
            "package": {
            "name": "debug"
          }
        }
    },
    "name": "Regular Expression Denial of Service",
    "message": "Regular Expression Denial of Service in debug",
    "description": "The debug module is vulnerable to regular expression denial of service
        when untrusted user input is passed into the `o` formatter.
        It takes around 50k characters to block for 2 seconds making this a low severity issue."
}

description 可能解释漏洞的工作原理或提供有关利用的上下文。 它不应重复漏洞对象的其他字段。 特别是,description 不应重复 location(受影响的内容) 或 solution(如何缓解风险)。

解决方案

您可以使用 solution 字段来指导用户如何修复已识别的漏洞或缓解风险。 最终用户与此字段交互,而 GitLab 自动处理 remediations 对象。

标识符

identifiers 数组描述了检测到的漏洞。标识符对象的 typevalue 字段用于判断两个标识符是否相同。 用户界面使用对象的 nameurl 字段来显示标识符。

我们建议您使用 GitLab 扫描器已经定义的标识符:

标识符 类型 示例值 示例名称
CVE cve CVE-2019-10086 CVE-2019-10086
CWE cwe 1026 CWE-1026
ELSA elsa ELSA-2020-0085 ELSA-2020-0085
OSVD osvdb OSVDB-113928 OSVDB-113928
OWASP owasp A01:2021 A01:2021 - Broken Access Control
RHSA rhsa RHSA-2020:0111 RHSA-2020:0111
USN usn USN-4234-1 USN-4234-1
GHSA ghsa GHSA-38jh-8h67-m7mj GHSA-38jh-8h67-m7mj
HACKERONE hackerone 698789 HACKERONE-698789

上面列出的通用标识符定义在 common 库 中, 该库由 GitLab 维护的一些分析器共享。如果需要,您可以贡献 新的通用标识符。分析器也可能产生供应商特定的或产品特定的标识符, 这些标识符不属于 common 库

identifiers 数组的第一个项称为 主要标识符, 它用于 跟踪漏洞, 因为新提交被推送到存储库。

并非所有漏洞都有 CVE,并且一个 CVE 可能被多次识别。因此, CVE 不是稳定的标识符,在跟踪漏洞时不应将其视为如此。

漏洞的最大标识符数量设置为 20。如果漏洞有超过 20 个标识符, 系统只保存前 20 个。流水线安全 选项卡中的漏洞不强制执行此限制,并显示报告工件中存在的所有标识符。

详情

details 字段是一个对象,支持许多不同的内容元素,这些元素在查看漏洞信息时显示。 各种数据元素的示例可以在 security-reports 仓库 中看到。

位置

location 指示漏洞被检测到的位置。 位置的格式取决于扫描类型。

在 GitLab 内部,location 的某些属性被提取以生成位置指纹, 该指纹用于跟踪漏洞, 因为新提交被推送到存储库。 用于生成位置指纹的属性也取决于扫描类型。

依赖扫描

依赖扫描漏洞的 locationdependencyfile 组成。 dependency 对象描述受影响的 package 和依赖项 versionpackage 嵌入受影响的库/模块的 namefile 是声明受影响依赖项的依赖项文件的路径。

例如,这是影响 npm 包 handlebars 版本 4.0.11 的漏洞的 location 对象:

{
    "file": "client/package.json",
    "dependency": {
        "package": {
            "name": "handlebars"
        },
        "version": "4.0.11"
    }
}

此受影响的依赖项列在 client/package.json 中, 这是由 npm 或 yarn 处理的依赖项文件。

依赖扫描漏洞的位置指纹结合了 file 和包 name, 因此这些属性是必需的。 所有其他属性是可选的。

容器扫描

与依赖扫描类似, 容器扫描漏洞的 location 具有 dependencyfile。 它还有一个 operating_system 字段。

例如,这是影响 Debian 包 glib2.0 版本 2.50.3-2+deb9u1 的漏洞的 location 对象:

{
    "dependency": {
        "package": {
            "name": "glib2.0"
        },
    },
    "version": "2.50.3-2+deb9u1",
    "operating_system": "debian:9",
    "image": "registry.gitlab.com/example/app:latest"
}

受影响的包在扫描 Docker 镜像 registry.gitlab.com/example/app:latest 时被发现。 Docker 镜像基于 debian:9(Debian Stretch)。

容器扫描漏洞的位置指纹结合了 operating_system 和包 name, 因此这些属性是必需的。 image 也是必需的。 所有其他属性是可选的。

SAST

SAST 漏洞的 location 必须有一个 file 给出受影响文件的路径, 以及一个包含受影响行号的 start_line 字段。 它还可以有 end_lineclassmethod

例如,这是在 src/main/java/com/gitlab/example/App.java 的第 41 行发现的 安全漏洞的 location 对象, 位于 com.gitlab.security_products.tests.App Java 类的 generateSecretToken 方法中:

{
    "file": "src/main/java/com/gitlab/example/App.java",
    "start_line": 41,
    "end_line": 41,
    "class": "com.gitlab.security_products.tests.App",
    "method": "generateSecretToken1"
}

SAST 漏洞的位置指纹结合了 filestart_lineend_line, 因此这些属性是必需的。 所有其他属性是可选的。

漏洞跟踪和合并

用户可以对漏洞提供反馈:

  • 如果漏洞不适用于他们的项目,他们可以忽略漏洞
  • 如果存在潜在威胁,他们可以为漏洞创建问题

GitLab 跟踪漏洞,以便当新的 Git 提交被推送到存储库时, 用户反馈不会丢失。 漏洞使用 UUIDv5 摘要进行跟踪,该摘要由四个属性的 SHA-1 哈希生成:

目前,如果漏洞的位置随着新的 Git 提交被推送而改变, GitLab 无法跟踪漏洞,这会导致用户反馈丢失。 例如,如果受影响的文件被重命名或受影响的行向下移动, SAST 漏洞的用户反馈就会丢失。 这已在 问题 #7586 中解决。

另请参见 去重过程

严重性

severity 字段描述漏洞对软件的影响程度。 严重性用于对安全仪表板中的漏洞进行排序。

严重性范围从 InfoCritical,但也可能是 Unknown。 有效值为:UnknownInfoLowMediumHighCritical

Unknown 值表示数据不可用以确定其实际值。因此,它可能是 highmediumlow, 需要调查。

修复方案

报告的 remediations 字段是修复对象的数组。 每个修复描述了一个可以应用的补丁,用于 解决 一组漏洞。

这是一个包含修复方案的报告示例。

{
    "vulnerabilities": [
        {
            "category": "dependency_scanning",
            "name": "Regular Expression Denial of Service",
            "id": "123e4567-e89b-12d3-a456-426655440000",
            "solution": "Upgrade to new versions.",
            "scanner": {
                "id": "gemnasium",
                "name": "Gemnasium"
            },
            "identifiers": [
                {
                  "type": "gemnasium",
                  "name": "Gemnasium-642735a5-1425-428d-8d4e-3c854885a3c9",
                  "value": "642735a5-1425-428d-8d4e-3c854885a3c9"
                }
            ]
        }
    ],
    "remediations": [
        {
            "fixes": [
                {
                    "id": "123e4567-e89b-12d3-a456-426655440000"
                }
            ],
            "summary": "Upgrade to new version",
            "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95Y=="
        }
    ]
}
摘要

summary 字段概述了如何修复漏洞。此字段是必需的。

修复的漏洞

fixes 字段是一个对象数组,引用由修复方案修复的漏洞。 fixes[].id 包含已修复漏洞的唯一标识符。此字段是必需的。

差异

diff 字段是 base64 编码的修复代码差异,与 git apply 兼容。此字段是必需的。