安全扫描器集成

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

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

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

作业定义#

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

名称#

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

  1. _dependency_scanning
  2. _container_scanning
  3. _dast
  4. _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 关键字声明与其执行的扫描类型对应的报告。有效的报告包括:

  1. dependency_scanning
  2. container_scanning
  3. dast
  4. api_fuzzing
  5. coverage_fuzzing
  6. sast

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

yaml
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 变量以指示应该跳过给定扫描。您可以通过查找以下变量来检查此情况:

  1. DEPENDENCY_SCANNING_DISABLED
  2. CONTAINER_SCANNING_DISABLED
  3. SAST_DISABLED
  4. DAST_DISABLED

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

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

策略检查示例#

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

yaml
1mysec_dependency_scanning: 2 rules: 3 - if: $DEPENDENCY_SCANNING_DISABLED == 'true' 4 when: never 5 - if: $GITLAB_FEATURES =~ /\bdependency_scanning\b/ 6 exists: 7 - '**/*.java'

任何其他作业策略应仅由用户根据其需要进行配置。例如,预定义的策略不应触发特定分支的扫描作业或在特定文件集发生更改时触发扫描作业。

Docker 镜像#

Docker 镜像是一个自包含的环境,将扫描器与其依赖的所有库和工具结合在一起。将扫描器打包到 Docker 镜像中,使其依赖项和配置始终存在,无论扫描器运行的机器如何。

镜像大小#

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

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

为了保持镜像大小较小,可以考虑使用 dive 分析 Docker 镜像中的层,以识别可能产生额外膨胀的地方。

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

镜像标签#

如 Docker 官方镜像项目中所记录的,强烈建议版本号标签提供别名,以便用户可以轻松地引用特定系列的“最新”版本。

权限#

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

  1. 用户 gitlab,用户 ID 为 1000
  2. 群组 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 消息。接受的值如下,从最高到最低列出:

  1. fatal
  2. error
  3. warn
  4. info
  5. debug

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

在执行命令行时,扫描器应使用 debug 级别记录命令行及其输出。如果命令行失败,则应使用 error 日志级别记录此信息;这使得可以在不更改日志级别为 debug 并重新运行扫描作业的情况下调试问题。

通用 logutil#

如果您使用 gocommon,建议您使用 Logrus 和 common 的 logutil配置 Logrus 的格式化程序。

报告#

报告是一个 JSON 文档,其中结合了漏洞和可能的修复措施。

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

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

报告验证#

History
    • 引入于极狐GitLab 15.0。

您必须确保扫描器生成的报告通过声明的架构版本的验证。不通过验证的报告不会被极狐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> 验证报告。
shell
1$ gem install gitlab-security_report_schemas -v 0.1.2.min15.0.0.max15.2.1 2Successfully installed gitlab-security_report_schemas-0.1.2.min15.0.0.max15.2.1 3Parsing documentation for gitlab-security_report_schemas-0.1.2.min15.0.0.max15.2.1 4Done installing documentation for gitlab-security_report_schemas after 0 seconds 51 gem installed 6 7$ security-report-schemas 8SecurityReportSchemas 0.1.2.min15.0.0.max15.2.1. 9Supported 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"] 10 11Usage: security-report-schemas REPORT_FILE_PATH [options] 12 -r, --report_type=REPORT_TYPE Override the report type 13 -w, --warnings Prints the warning messages 14 15$ security-report-schemas ~/Downloads/gl-dependency-scanning-report.json 16Validating dependency_scanning v15.0.0 against schema v15.0.0 17Content is invalid 18* root is missing required keys: dependency_files

报告字段#

版本#

此字段指定您使用的安全报告架构版本。有关要使用的版本的信息,请参阅发布

极狐GitLab 根据此值指定的架构版本验证您的报告。极狐GitLab 支持的版本可以在gitlab/ee/lib/ee/gitlab/ci/parsers/security/validators/schemas中找到。

漏洞#

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

ID#

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

类别#

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

  1. dependency_scanning
  2. container_scanning
  3. sast
  4. dast
扫描#

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

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

扫描主标识符#

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

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

填充后,当其主标识符不包括在内时,Rails 应用程序可以自动解决先前检测到的漏洞,因为它们不再相关。

名称、消息和描述#

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

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

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

Example Vulnerability

例如,由依赖扫描报告的漏洞的 message 提供有关易受攻击依赖项的信息,这与漏洞的 location 字段重复。优先使用 name 字段,但当无法从漏洞的标题中删除上下文/位置时,使用 message 字段。

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

json
1{ 2 "location": { 3 "dependency": { 4 "package": { 5 "name": "debug" 6 } 7 } 8 }, 9 "name": "Regular Expression Denial of Service", 10 "message": "Regular Expression Denial of Service in debug", 11 "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." 12}

description 可能会解释漏洞的工作原理或提供有关利用的背景信息。它不应重复漏洞对象的其他字段。特别是,description 不应重复 location(受影响的是什么)或 solution(如何减轻风险)。

解决方案#

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

标识符#

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

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

标识符类型示例值示例名称
CVEcveCVE-2019-10086CVE-2019-10086
CWEcwe1026CWE-1026
ELSAelsaELSA-2020-0085ELSA-2020-0085
OSVDosvdbOSVDB-113928OSVDB-113928
OWASPowaspA01:2021A01:2021 - Broken Access Control
RHSArhsaRHSA-2020:0111RHSA-2020:0111
USNusnUSN-4234-1USN-4234-1
GHSAghsaGHSA-38jh-8h67-m7mjGHSA-38jh-8h67-m7mj
HACKERONEhackerone698789HACKERONE-698789

上述通用标识符在 common 库中定义,该库由极狐GitLab 维护的一些分析器共享。分析器还可以生成不属于 common 库的供应商特定或产品特定的标识符。

identifiers 数组的第一个项目称为主标识符,它用于在将新提交推送到存储库时跟踪漏洞

并非所有漏洞都有 CVE,CVE 可能会多次被识别。因此,CVE 不是一个稳定的标识符,您不应假设它是用于跟踪漏洞的标识符。

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

详细信息#

details 字段是一个对象,支持许多不同的内容元素,这些内容元素在查看漏洞信息时显示。有关各种数据元素的示例,请参阅安全报告存储库

位置#

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

极狐GitLab 内部提取 location 的某些属性以生成 location fingerprint,用于跟踪漏洞,随着新提交推送到存储库。用于生成 location fingerprint 的属性也取决于扫描的类型。

依赖扫描#

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

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

json
1{ 2 "file": "client/package.json", 3 "dependency": { 4 "package": { 5 "name": "handlebars" 6 }, 7 "version": "4.0.11" 8 } 9}

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

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

容器扫描#

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

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

json
1{ 2 "dependency": { 3 "package": { 4 "name": "glib2.0" 5 }, 6 }, 7 "version": "2.50.3-2+deb9u1", 8 "operating_system": "debian:9", 9 "image": "registry.gitlab.com/example/app:latest" 10}

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

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

SAST#

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

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

json
1{ 2 "file": "src/main/java/com/gitlab/example/App.java", 3 "start_line": 41, 4 "end_line": 41, 5 "class": "com.gitlab.security_products.tests.App", 6 "method": "generateSecretToken1" 7}

SAST 漏洞的 location fingerprint 结合了 filestart_lineend_line,因此这些属性是必需的。所有其他属性都是可选的。

跟踪和合并漏洞#

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

  1. 如果漏洞不适用于他们的项目,他们可以消除漏洞
  2. 如果存在潜在威胁,他们可以为漏洞创建议题

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

  1. 报告类型
  2. 主标识符
  3. 位置指纹
  4. 项目 ID

目前,极狐GitLab 无法跟踪位置随着新提交推送而更改的漏洞,从而导致用户反馈丢失。例如,当受影响文件重命名或受影响行向下移动时,SAST 漏洞的用户反馈会丢失。

另请参阅去重过程

严重性#

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

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

Unknown 值表示没有足够的数据来确定其实际值。因此,可能是 highmediumlow,需要进行调查。

修复#

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

以下是包含修复的报告示例。

json
1{ 2 "vulnerabilities": [ 3 { 4 "category": "dependency_scanning", 5 "name": "Regular Expression Denial of Service", 6 "id": "123e4567-e89b-12d3-a456-426655440000", 7 "solution": "Upgrade to new versions.", 8 "scanner": { 9 "id": "gemnasium", 10 "name": "Gemnasium" 11 }, 12 "identifiers": [ 13 { 14 "type": "gemnasium", 15 "name": "Gemnasium-642735a5-1425-428d-8d4e-3c854885a3c9", 16 "value": "642735a5-1425-428d-8d4e-3c854885a3c9" 17 } 18 ] 19 } 20 ], 21 "remediations": [ 22 { 23 "fixes": [ 24 { 25 "id": "123e4567-e89b-12d3-a456-426655440000" 26 } 27 ], 28 "summary": "Upgrade to new version", 29 "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95Y==" 30 } 31 ] 32}
概述#

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

修复的漏洞#

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

差异#

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