성능 튜닝 및 테스트 속도

API Fuzzing과 같은 API 퍼증 테스트를 수행하는 보안 도구는 실행 중인 애플리케이션의 인스턴스로 요청을 보내어 테스트를 수행합니다. 우리의 퍼징 엔진에 의해 변형된 요청은 애플리케이션에 존재할 수 있는 예상치 못한 동작을 유발합니다. API 퍼징 테스트의 속도는 다음에 달려 있습니다:

  • 우리의 도구를 사용하여 애플리케이션으로 초당 전송할 수 있는 요청의 수
  • 애플리케이션이 요청에 얼마나 빨리 응답하는가
  • 애플리케이션을 테스트하기 위해 보내어야 하는 요청의 수
    • API가 구성된 작업의 수
    • 각 작업에 포함된 필드의 수(JSON 본문, 헤더, 쿼리 문자열, 쿠키 등)

만일 이 성능 가이드에서의 권고를 따라도 API Fuzzing 테스트 작업 시간이 여전히 예상보다 길다면, 추가 지원을 받기 위해 지원팀에 문의하십시오.

성능 이슈 진단

성능 이슈를 해결하는 첫 번째 단계는 예상보다 느린 테스트 시간에 기여하는 요소를 이해하는 것입니다. 몇 가지 우리가 발견하는 일반적인 문제는 다음과 같습니다:

  • API Fuzzing이 낮은 vCPU 러너에서 실행 중
  • 애플리케이션이 느리거나 단일 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초의 대부분을 소비합니다.

이 문제에 대해 팀은 아래와 같은 결정을 내릴 수 있습니다:

  • 작업을 병렬화하기 위해 더 많은 vCPU가 있는 러너를 사용합니다. 이렇게 하면 테스트 시간이 줄어들지만, 작업이 테스트하는 데 오랜 시간이 걸리는 문제 때문에 10분 미만의 테스트 시간을 얻는 것은 여전히 문제일 수 있습니다. 큰 러너는 더 비용이 많이 듭니다만, 작업 실행 시간이 빠를수록 적은 분을 지불하게 됩니다.
  • API Fuzzing 테스트에서 이 작업을 제외합니다. 이것은 가장 간단하지만, 보안 테스트 범위에 차질이 생기는 단점이 있습니다.
  • 기능 브랜치의 API Fuzzing 테스트에서 이 작업을 제외하지만, 기본 브랜치 테스트에는 포함합니다.
  • API Fuzzing 테스트를 여러 작업으로 분할합니다.

가능한 해결책은, 팀의 요구 사항이 5-7분 범위에 있다고 가정했을 때, 이러한 해결책을 결합하여 수용 가능한 테스트 시간에 도달하는 것입니다.

성능 이슈 대처

다음 섹션에서는 API Fuzzing의 성능 이슈에 대한 다양한 옵션을 문서화합니다:

더 큰 러너 사용

API Fuzzing에는 보다 큰 러너를 사용하면 가장 쉽게 성능을 향상시킬 수 있습니다. 이 표는 Java Spring Boot REST API를 벤치마킹하는 동안 수집된 통계를 보여줍니다. 이 벤치마킹에서 대상과 API Fuzzing은 하나의 러너 인스턴스를 공유합니다.

Linux의 호스팅된 러너 태그 초당 요청 수
saas-linux-small-amd64 (기본값) 255
saas-linux-medium-amd64 400

이 표에서 볼 수 있듯이 러너 크기와 vCPU 수를 증가시키면 테스트 속도/성능에 큰 영향을 미칠 수 있습니다.

다음은 API Fuzzing에 대한 예제 작업 정의입니다. 이 작업은 Linux의 medium SaaS 러너를 사용하기 위한 tags 섹션을 추가한 것입니다. 이 작업은 API Fuzzing 템플릿을 통해 작업 정의를 확장합니다.

apifuzzer_fuzz:
  tags:
  - saas-linux-medium-amd64

gl-api-security-scanner.log 파일에서 Starting work item processor 문자열을 검색하여 보고된 최대 DOP(병렬성 수준)을 확인할 수 있습니다. 최대 DOP은 러너에 할당된 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 입니다. 이를 제외하기 위해 작업 URL의 경로 부분(/api/large_response_json)으로 FUZZAPI_EXCLUDE_PATHS 구성 변수를 제공합니다.

작업이 제외되었는지 확인하려면 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 템플릿에서 복사되었습니다.

# 주요 apifuzzer_fuzz 작업 비활성화
apifuzzer_fuzz:
  rules:
  - if: $CI_COMMIT_BRANCH
    when: never

apifuzzer_v1:
  extends: apifuzzer_fuzz
  variables:
    FUZZAPI_EXCLUDE_PATHS: /api/v1/**
  rules:
    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:
    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입니다. 이를 제외하기 위해 우리의 구성은 주요 apifuzzer_fuzz 작업을 비활성화하고 apifuzzer_mainapifuzzer_branch라는 두 개의 새 작업을 생성합니다. apifuzzer_branch는 긴 작업을 제외하고 기본 브랜치 이외의 브랜치(예: 피처 브랜치)에서만 실행되도록 설정됩니다. apifuzzer_main 브랜치는 기본 브랜치(main 예시)에서만 실행되도록 설정됩니다. apifuzzer_branch 작업은 빠른 개발 주기를 가능하게 하기 위해 빠르게 실행되고, 기본 브랜치 빌드에만 실행되는 apifuzzer_main 작업은 더 오랜 시간이 걸립니다.

작업이 제외되었는지 확인하려면 API Fuzzing 작업을 실행하고 작업 콘솔 출력을 확인하세요. 테스트의 끝에는 포함된 작업과 제외된 작업 목록이 포함되어 있습니다.

# 두 개의 작업을 만들기 위해 주요 작업을 비활성화함
apifuzzer_fuzz:
  rules:
  - if: $CI_COMMIT_BRANCH
    when: never

# 피처 브랜치 작업에 대한 API Fuzzing, /api/large_response_json을 제외함
apifuzzer_branch:
  extends: apifuzzer_fuzz
  variables:
    FUZZAPI_EXCLUDE_PATHS: /api/large_response_json
  rules:
    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 (이 예시에서는 main)
# 긴 실행 작업을 포함함
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