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

性能调优和测试速度

执行 API 模糊测试的安全工具(如 API Fuzzing)通过向运行中的应用实例发送请求来执行测试。我们的模糊测试引擎会对这些请求进行变异,以触发应用中可能存在的意外行为。API 模糊测试的速度取决于以下因素:

  • 我们的工具每秒能向您的应用发送多少请求
  • 您的应用响应请求的速度有多快
  • 需要发送多少请求来测试应用
    • 您的 API 包含多少操作
    • 每个操作有多少字段(如 JSON 主体、请求头、查询字符串、cookies 等)

如果在遵循本性能指南的建议后,API Fuzzing 测试作业仍然耗时超出预期,请联系支持团队获取进一步帮助。

诊断性能问题

解决性能问题的第一步是了解是什么因素导致了测试时间慢于预期。我们常见的一些问题包括:

  • API Fuzzing 在低 vCPU 的 runner 上运行
  • 应用部署在慢速/单核 CPU 实例上,无法跟上测试负载
  • 应用包含一个影响整体测试速度的慢速操作(> 1/2 秒)
  • 应用包含一个返回大量数据的操作(> 500K+)
  • 应用包含大量操作(> 40)

应用包含一个影响整体测试速度的慢速操作(> 1/2 秒)

API Fuzzing 作业输出包含有关测试速度、每个被测操作的响应速度以及摘要信息的有用信息。让我们来看一些示例输出,了解如何用它来追踪性能问题:

API Fuzzing: Loaded 10 operations from: assets/har-large-response/large_responses.har
API Fuzzing:
API Fuzzing: Testing operation [1/10]: 'GET http://target:7777/api/large_response_json'.
API Fuzzing:  - Parameters: (Headers: 4, Query: 0, Body: 0)
API Fuzzing:  - Request body size: 0 Bytes (0 bytes)
API Fuzzing:
API Fuzzing: Finished testing operation 'GET http://target:7777/api/large_response_json'.
API Fuzzing:  - Excluded Parameters: (Headers: 0, Query: 0, Body: 0)
API Fuzzing:  - Performed 767 requests
API Fuzzing:  - Average response body size: 130 MB
API Fuzzing:  - Average call time: 2 seconds and 82.69 milliseconds (2.082693 seconds)
API Fuzzing:  - Time to complete: 14 minutes, 8 seconds and 788.36 milliseconds (848.788358 seconds)

这个作业控制台输出片段首先告诉我们找到了多少个操作(10 个),然后通知我们测试已在特定操作上开始,并完成了该操作的摘要。摘要是此日志输出中最有趣的部分。在摘要中,我们可以看到 API Fuzzing 花了 767 个请求才完全测试了这个操作及其相关字段。我们还可以看到平均响应时间为 2 秒,而这个操作完成测试耗时 14 分钟。

2 秒的平均响应时间是一个很好的初步指标,表明这个特定操作需要很长时间来测试。此外,我们可以看到响应体相当大。大的响应体是这里的罪魁祸首,每次请求传输这么多数据花费了这 2 秒中的大部分时间。

对于这个问题,团队可能会决定:

假设您团队的要求在 5-7 分钟范围内,可能的解决方案是结合使用这些方法以达到可接受的测试时间。

解决性能问题

以下章节记录了针对 API Fuzzing 性能问题的各种解决方案:

使用更大的 runner

使用 API Fuzzing 和 更大的 runner 可以获得最简单的性能提升。此表显示了在 Java Spring Boot REST API 基准测试期间收集的统计数据。在此基准测试中,目标和 API Fuzzing 共享一个 runner 实例。

Linux 托管 runner 标签 每秒请求数
saas-linux-small-amd64(默认) 255
saas-linux-medium-amd64 400

从这张表我们可以看出,增加 runner 的大小和 vCPU 数量可以对测试速度/性能产生重大影响。

以下是 API Fuzzing 的示例作业定义,它添加了一个 tags 部分来使用 Linux 上的中等 SaaS runner。该作业扩展了通过 API Fuzzing 模板包含的作业定义。

apifuzzer_fuzz:
  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

排除慢速操作

对于一两个慢速操作的情况,团队可能会决定跳过对这些操作的测试。排除操作是使用 FUZZAPI_EXCLUDE_PATHS 配置 变量完成的,如本节所述。

在此示例中,我们有一个返回大量数据的操作。该操作是 GET http://target:7777/api/large_response_json。要排除它,我们提供 FUZZAPI_EXCLUDE_PATHS 配置变量,并使用我们操作 URL 的路径部分 /api/large_response_json

要验证操作是否被排除,请运行 API Fuzzing 作业并查看作业控制台输出。它包含测试结束时包含和排除的操作列表。

apifuzzer_fuzz:
  variables:
    FUZZAPI_EXCLUDE_PATHS: /api/large_response_json

从测试中排除操作可能会导致某些漏洞未被检测到。

将测试拆分为多个作业

API Fuzzing 通过使用 FUZZAPI_EXCLUDE_PATHSFUZZAPI_EXCLUDE_URLS 支持将测试拆分为多个作业。当拆分测试时,一个好的模式是禁用 apifuzzer_fuzz 作业,并用两个具有标识性名称的作业替换它。在此示例中,我们有两个作业,每个作业都在测试 API 的一个版本,因此我们的名称反映了这一点。然而,此技术可以应用于任何情况,而不仅仅是 API 版本。

我们在 apifuzzer_v1apifuzzer_v2 作业中使用的规则是从 API Fuzzing 模板 复制的。

# Disable the main apifuzzer_fuzz job
apifuzzer_fuzz:
  rules:
    - if: $CI_COMMIT_BRANCH
      when: never

apifuzzer_v1:
  extends: apifuzzer_fuzz
  variables:
    FUZZAPI_EXCLUDE_PATHS: /api/v1/**
  rules:
    - if: $API_FUZZING_DISABLED == 'true' || $API_FUZZING_DISABLED == '1'
      when: never
    - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
            $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
      when: never
    - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
            $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
      when: never
    - if: $CI_COMMIT_BRANCH &&
          $CI_GITLAB_FIPS_MODE == "true"
      variables:
          FUZZAPI_IMAGE_SUFFIX: "-fips"
    - if: $CI_COMMIT_BRANCH

apifuzzer_v2:
  variables:
    FUZZAPI_EXCLUDE_PATHS: /api/v2/**
  rules:
    - if: $API_FUZZING_DISABLED == 'true' || $API_FUZZING_DISABLED == '1'
      when: never
    - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH &&
            $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
      when: never
    - if: $CI_COMMIT_BRANCH &&
          $CI_GITLAB_FIPS_MODE == "true"
      variables:
          FUZZAPI_IMAGE_SUFFIX: "-fips"
    - if: $CI_COMMIT_BRANCH

在功能分支中排除操作,但不排除默认分支

对于一两个慢速操作的情况,团队可能会决定跳过对这些操作的测试,或者从功能分支测试中排除它们,但在默认分支测试中包含它们。排除操作是使用 FUZZAPI_EXCLUDE_PATHS 配置 变量完成的,如本节所述。

在此示例中,我们有一个返回大量数据的操作。该操作是 GET http://target:7777/api/large_response_json。要排除它,我们提供 FUZZAPI_EXCLUDE_PATHS 配置变量,并使用我们操作 URL 的路径部分 /api/large_response_json。我们的配置禁用了主要的 apifuzzer_fuzz 作业,并创建了两个新作业 apifuzzer_mainapifuzzer_branchapifuzzer_branch 设置为排除长时间运行的操作,并且只在非默认分支(例如功能分支)上运行。apifuzzer_main 设置为只在默认分支(在此示例中为 main)上执行。apifuzzer_branch 作业运行得更快,允许快速的开发周期,而只在默认分支构建上运行的 apifuzzer_main 作业则需要更长时间运行。

要验证操作是否被排除,请运行 API Fuzzing 作业并查看作业控制台输出。它包含测试结束时包含和排除的操作列表。

# Disable the main job so we can create two jobs with
# different names
apifuzzer_fuzz:
  rules:
    - if: $CI_COMMIT_BRANCH
      when: never

# API Fuzzing for feature branch work, excludes /api/large_response_json
apifuzzer_branch:
  extends: apifuzzer_fuzz
  variables:
    FUZZAPI_EXCLUDE_PATHS: /api/large_response_json
  rules:
    - if: $API_FUZZING_DISABLED == 'true' || $API_FUZZING_DISABLED == '1'
      when: never
    - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH &&
            $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
      when: never
    - if: $CI_COMMIT_BRANCH &&
          $CI_GITLAB_FIPS_MODE == "true"
      variables:
          FUZZAPI_IMAGE_SUFFIX: "-fips"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: never
    - if: $CI_COMMIT_BRANCH

# API Fuzzing for default branch (main in our case)
# Includes the long running operations
apifuzzer_main:
  extends: apifuzzer_fuzz
  rules:
    - if: $API_FUZZING_DISABLED == 'true' || $API_FUZZING_DISABLED == '1'
      when: never
    - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH &&
            $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
      when: never
    - if: $CI_COMMIT_BRANCH &&
          $CI_GITLAB_FIPS_MODE == "true"
      variables:
          FUZZAPI_IMAGE_SUFFIX: "-fips"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH