性能调优和测试速度
执行动态分析测试的安全工具(如 API 安全测试)通过向运行中的应用实例发送请求来进行测试。这些请求经过精心设计,用于测试应用中可能存在的特定漏洞。动态分析测试的速度取决于以下因素:
- 我们的工具每秒能向您的应用发送多少请求
- 您的应用响应请求的速度有多快
- 必须发送多少请求来测试应用
- 您的 API 包含多少个操作
- 每个操作中有多少字段(如 JSON 主体、请求头、查询字符串、cookies 等)
如果您遵循了本性能指南中的建议,但 API 安全测试作业仍然耗时超出预期,请联系支持团队获取进一步帮助。
诊断性能问题
解决性能问题的第一步是了解是什么因素导致测试时间慢于预期。我们常见的一些问题包括:
- API 安全测试在低 vCPU 的 runner 上运行
- 应用部署在慢速/单核 CPU 实例上,无法跟上测试负载
- 应用包含影响整体测试速度的慢速操作(> 1/2 秒)
- 应用返回大量数据的操作(> 500K+)
- 应用包含大量操作(> 40)
应用包含影响整体测试速度的慢速操作(> 1/2 秒)
API 安全测试作业输出包含有关测试速度、每个被测试操作响应速度以及摘要信息的有用信息。让我们看一些示例输出,了解如何用它来跟踪性能问题:
API SECURITY: Loaded 10 operations from: assets/har-large-response/large_responses.har
API SECURITY:
API SECURITY: Testing operation [1/10]: 'GET http://target:7777/api/large_response_json'.
API SECURITY: - Parameters: (Headers: 4, Query: 0, Body: 0)
API SECURITY: - Request body size: 0 Bytes (0 bytes)
API SECURITY:
API SECURITY: Finished testing operation 'GET http://target:7777/api/large_response_json'.
API SECURITY: - Excluded Parameters: (Headers: 0, Query: 0, Body: 0)
API SECURITY: - Performed 767 requests
API SECURITY: - Average response body size: 130 MB
API SECURITY: - Average call time: 2 seconds and 82.69 milliseconds (2.082693 seconds)
API SECURITY: - Time to complete: 14 minutes, 8 seconds and 788.36 milliseconds (848.788358 seconds)这个作业控制台输出片段首先告诉我们找到了多少个操作(10),然后通知测试已开始对特定操作进行,并完成了该操作的摘要。摘要是此日志输出中最有趣的部分。在摘要中,我们可以看到 API 安全测试需要 767 个请求才能完全测试此操作及其相关字段。我们还可以看到平均响应时间为 2 秒,而这个单一操作完成测试需要 14 分钟。
2 秒的平均响应时间是此特定操作测试时间长的良好初步指标。此外,我们可以看到响应主体大小相当大。大的主体大小是这里的罪魁祸首,每次请求传输这么多数据花费了大部分的 2 秒时间。
对于这个问题,团队可能会决定:
- 使用具有更多 vCPU 的 runner,因为这允许 API 安全测试并行执行工作。这有助于降低测试时间,但由于操作测试时间较长,即使使用更大的 runner,将测试时间控制在 10 分钟以下可能仍然有问题。虽然更大的 runner 成本更高,但如果作业执行更快,您也会为更少的分钟数付费。
- 从此操作中排除 API 安全测试。虽然这是最简单的方法,但其缺点是安全测试覆盖范围存在漏洞。
- 从功能分支 API 安全测试中排除该操作,但在默认分支测试中包含它。
- 将 API 安全测试拆分为多个作业。
可能的解决方案是结合使用这些解决方案以达到可接受的测试时间,假设您团队的要求在 5-7 分钟范围内。
解决性能问题
以下部分记录了针对 API 安全测试解决性能问题的各种选项:
使用更大的 runner
使用 更大的 runner 与 API 安全测试结合,可以轻松实现性能提升。此表显示了在 Java Spring Boot REST API 基准测试期间收集的统计数据。在此基准测试中,目标和 API 安全测试共享同一个 runner 实例。
| Linux 标签上的托管 runner | 每秒请求数 |
|---|---|
saas-linux-small-amd64(默认) |
255 |
saas-linux-medium-amd64 |
400 |
从表中我们可以看到,增加 runner 大小和 vCPU 数量可以对测试速度/性能产生重大影响。
以下是使用 Linux 中等 SaaS runner 的 API 安全测试作业定义示例,它添加了一个 tags 部分。该作业扩展了通过 API 安全测试模板包含的作业定义。
api_security:
tags:
- saas-linux-medium-amd64在 gl-api-security-scanner.log 文件中,您可以搜索字符串 Starting work item processor 来检查报告的最大 DOP(并行度)。最大 DOP 应该大于或等于分配给 runner 的 vCPU 数量。如果无法识别问题,请向支持团队提交工单以获取帮助。
示例日志条目:
17:00:01.084 [INF] <Peach.Web.Core.Services.WebRunnerMachine> Starting work item processor with 4 max DOP
排除慢速操作
对于一两个慢速操作,团队可能会决定跳过对这些操作的测试。排除操作是使用 APISEC_EXCLUDE_PATHS 配置 变量完成的,如本节所述。
在此示例中,我们有一个返回大量数据的操作。该操作是 GET http://target:7777/api/large_response_json。要排除它,我们为 APISEC_EXCLUDE_PATHS 配置变量提供操作 URL 的路径部分 /api/large_response_json。
要验证操作是否被排除,请运行 API 安全测试作业并查看作业控制台输出。它在测试结束时包含包含和排除的操作列表。
api_security:
variables:
APISEC_EXCLUDE_PATHS: /api/large_response_json从测试中排除操作可能会导致某些漏洞无法被发现。
将测试拆分为多个作业
API 安全测试支持通过使用 APISEC_EXCLUDE_PATHS 和 APISEC_EXCLUDE_URLS 将测试拆分为多个作业。拆分测试时,一个良好的模式是禁用 dast_api 作业,并用两个具有标识名称的作业替换它。在此示例中,我们有两个作业,每个作业都在测试 API 的一个版本,因此我们的名称反映了这一点。但是,此技术可以应用于任何情况,而不仅仅是 API 的版本。
我们在 APISEC_v1 和 APISEC_v2 作业中使用的规则是从 API 安全测试模板 复制的。
# 禁用主 dast_api 作业
api_security:
rules:
- if: $CI_COMMIT_BRANCH
when: never
APISEC_v1:
extends: dast_api
variables:
APISEC_EXCLUDE_PATHS: /api/v1/**
rules:
- if: $APISEC_DISABLED == 'true' || $APISEC_DISABLED == '1'
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $CI_COMMIT_BRANCH &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH
APISEC_v2:
variables:
APISEC_EXCLUDE_PATHS: /api/v2/**
rules:
- if: $APISEC_DISABLED == 'true' || $APISEC_DISABLED == '1'
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $CI_COMMIT_BRANCH &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH在功能分支中排除操作,但在默认分支中不排除
对于一两个慢速操作,团队可能会决定跳过对这些操作的测试,或者从功能分支测试中排除它们,但在默认分支测试中包含它们。排除操作是使用 APISEC_EXCLUDE_PATHS 配置 变量完成的,如本节所述。
在此示例中,我们有一个返回大量数据的操作。该操作是 GET http://target:7777/api/large_response_json。要排除它,我们为 APISEC_EXCLUDE_PATHS 配置变量提供操作 URL 的路径部分 /api/large_response_json。我们的配置禁用了主 dast_api 作业,并创建了两个新作业 APISEC_main 和 APISEC_branch。APISEC_branch 设置为排除长操作,并且仅在非默认分支(例如功能分支)上运行。APISEC_main 分支设置为仅在默认分支(在此示例中为 main)上执行。APISEC_branch 作业运行得更快,允许快速开发周期,而仅在默认分支构建上运行的 APISEC_main 作业则需要更长时间运行。
要验证操作是否被排除,请运行 API 安全测试作业并查看作业控制台输出。它在测试结束时包含包含和排除的操作列表。
# 禁用主作业,以便我们可以创建两个具有
# 不同名称的作业
api_security:
rules:
- if: $CI_COMMIT_BRANCH
when: never
# 功能分支工作的 API 安全测试,排除 /api/large_response_json
APISEC_branch:
extends: dast_api
variables:
APISEC_EXCLUDE_PATHS: /api/large_response_json
rules:
- if: $APISEC_DISABLED == 'true' || $APISEC_DISABLED == '1'
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $CI_COMMIT_BRANCH &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: never
- if: $CI_COMMIT_BRANCH
# 默认分支(在我们的情况下为 main)的 API 安全测试
# 包含长时间运行的操作
APISEC_main:
extends: dast_api
rules:
- if: $APISEC_DISABLED == 'true' || $APISEC_DISABLED == '1'
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $CI_COMMIT_BRANCH &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH