성능 튜닝 및 테스트 속도

DAST API와 같은 동적 분석 테스트를 수행하는 보안 도구들은 사용 중인 응용 프로그램의 인스턴스로 요청을 보내어 테스트를 수행합니다. 이러한 요청은 응용 프로그램에서 발생할 수 있는 특정 취약점을 테스트하기 위해 설계되었습니다. 동적 분석 테스트의 속도는 다음에 따라 달라집니다:

  • 우리의 도구를 통해 응용 프로그램으로 초당 전송할 수 있는 요청의 수
  • 응용 프로그램이 요청에 응답하는 속도
  • 응용 프로그램을 테스트하기 위해 보내야 하는 요청의 수
    • API가 어떤 작업으로 구성되어 있는지
    • 각 작업에 필드가 몇 개가 있는지 (JSON 본문, 헤더, 쿼리 문자열, 쿠키 등을 고려해보세요.)

이 성능 가이드에서 제안된 조언을 따라도 DAST API 테스트 작업이 여전히 예상보다 오래 걸린다면, 더 많은 지원을 받기 위해 지원팀에 문의하십시오.

성능 문제 진단

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

  • DAST API가 저용량 vCPU 러너에서 실행 중
  • 응용 프로그램이 느린/단일 CPU 인스턴스에 배포되어 있으며 테스트 부하를 따라잡지 못함
  • 응용 프로그램에 전체 테스트 속도에 영향을 미치는 느린 작업이 포함되어 있음 (> 1/2초)
  • 응용 프로그램이 대량의 데이터를 반환하는 작업이 포함되어 있음 (> 500K+)
  • 응용 프로그램에 많은 수의 작업이 포함되어 있음 (> 40)

응용 프로그램에 전체 테스트 속도에 영향을 미치는 느린 작업이 포함되어 있음 (> 1/2초)

DAST API 작업 출력에는 테스트하는 속도, 각 테스트 작업의 응답 시간, 요약 정보에 대한 유용한 정보가 포함되어 있습니다. 성능 문제를 추적하는 데 어떻게 사용될 수 있는지 확인하기 위해 일부 샘플 출력을 살펴봅시다:

DAST API: assets/har-large-response/large_responses.har에서 10개의 작업 로드
DAST API:
DAST API: 작업 테스트 중 [1/10]: 'GET http://target:7777/api/large_response_json'.
DAST API:  - 매개 변수: (헤더: 4, 쿼리: 0, 본문: 0)
DAST API:  - 요청 본문 크기: 0 바이트 (0 바이트)
DAST API:
DAST API: 'GET http://target:7777/api/large_response_json' 작업 테스트 완료.
DAST API:  - 제외된 매개 변수: (헤더: 0, 쿼리: 0, 본문: 0)
DAST API:  - 수행된 요청 수: 767
DAST API:  - 평균 응답 본문 크기: 130MB
DAST API:  - 평균 호출 시간: 2초 82.69밀리초 (2.082693초)
DAST API:  - 완료까지 걸린 시간: 14분 8초 788.36밀리초 (848.788358초)

이 작업 로그 출력 조각은 발견된 작업 수(10개)부터 시작하여 특정 작업에서 테스트가 시작되었음을 알리고 작업의 요약이 완료되었다는 알림이 옵니다. 요약 부분이 가장 흥미로운 부분입니다. 이 요약에서 우리는 DAST API가 이 작업 및 관련 필드를 완전히 테스트하는 데 767개의 요청이 소요되었음을 볼 수 있습니다. 또한 평균 응답 시간이 2초이고 이 한 작업을 위해 14분이 소요되었음을 볼 수 있습니다.

2초의 평균 응답 시간은 해당 작업이 테스트하는 데 많은 시간이 걸린다는 초기 지표입니다. 더구나 응답 본문 크기가 상당히 큽니다. 큰 본문 크기가 주 원인으로, 각 요청마다 그만큼 많은 데이터를 전송하는 것이 2초의 대부분을 차지합니다.

이 문제에 대해 팀은 다음과 같이 결정할 수 있습니다:

팀의 요구 사항이 5-7분의 범위에 있다고 가정하면, 이러한 해결책의 조합을 사용하여 수용 가능한 테스트 시간에 도달할 수 있을 것으로 예상됩니다.

성능 문제 해결

다음 섹션에서는 DAST API의 성능 문제를 해결하기 위한 다양한 옵션에 대해 문서화되어 있습니다:

보다 크거나 러너(runner) 사용

DAST API에 보다 크거나 러너(runner)를 사용하는 가장 쉬운 성능 향상 중 하나입니다. 이 테이블은 Java Spring Boot REST API의 벤치마킹 데이터를 보여줍니다. 이 벤치마킹에서 대상과 DAST API는 단일 러너 인스턴스를 공유합니다.

SaaS Linux 러너 태그 초당 요청 수
saas-linux-small-amd64 (기본값) 255
saas-linux-medium-amd64 400

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

다음은 Linux용 SaaS 러너를 사용하는 DAST API의 예제 작업 정의입니다. 이 작업은 DAST API 템플릿을 통해 포함된 작업 정의를 확장합니다.

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

느린 작업 제외하기

일부 느린 작업이 하나 또는 두 개인 경우 팀은 해당 작업의 테스트를 건너뛰기로 결정할 수 있습니다. 작업을 제외하는 방법은 DAST_API_EXCLUDE_PATHS 구성 변수를 사용하여 수행됩니다. 이 섹션에서 설명된 내용에 따라 작업을 제외합니다.

이 예에서는 대량의 데이터를 반환하는 작업이 있습니다. 해당 작업은 GET http://target:7777/api/large_response_json입니다. 이를 제외하려면 작업 URL의 경로 부분(/api/large_response_json)과 함께 DAST_API_EXCLUDE_PATHS 구성 변수를 제공합니다.

작업이 제외되었는지 확인하려면 DAST API 작업을 실행하고 작업 콘솔 출력을 확인합니다. 테스트의 끝에 포함된 작업과 제외된 작업 디렉터리이 표시됩니다.

dast_api:
  variables:
    DAST_API_EXCLUDE_PATHS: /api/large_response_json

테스트에서 작업을 제외하는 것은 일부 취약성이 감지되지 못할 수 있게 할 수 있습니다.

테스트 분리하기

DAST API를 사용하여 테스트를 여러 작업으로 분리하는 것은 DAST_API_EXCLUDE_PATHSDAST_API_EXCLUDE_URLS를 사용하여 지원됩니다. 테스트를 분리할 때, 좋은 패턴은 dast_api 작업을 비활성화하고 식별 가능한 이름으로 된 두 개의 작업으로 대체하는 것입니다. 이 예에서는 두 개의 작업이 있으며 각 작업은 API의 버전을 테스트하므로 해당 이름이 반영됩니다. 그러나 이 기술은 API의 버전 뿐만 아니라 어떤 상황에도 적용할 수 있습니다.

dast_api_v1dast_api_v2 작업에서 사용하는 규칙은 DAST API 템플릿에서 복사되었습니다.

# 기본 dast_api 작업 비활성화
dast_api:
  rules:
  - if: $CI_COMMIT_BRANCH
    when: never

dast_api_v1:
  extends: dast_api
  variables:
    DAST_API_EXCLUDE_PATHS: /api/v1/**
  rules:
  - if: $DAST_API_DISABLED == 'true' || $DAST_API_DISABLED == '1'
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $CI_COMMIT_BRANCH &&
        $CI_GITLAB_FIPS_MODE == "true"
    variables:
      DAST_API_IMAGE_SUFFIX: "-fips"
  - if: $CI_COMMIT_BRANCH

dast_api_v2:
  variables:
    DAST_API_EXCLUDE_PATHS: /api/v2/**
  rules:
  - if: $DAST_API_DISABLED == 'true' || $DAST_API_DISABLED == '1'
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $CI_COMMIT_BRANCH &&
        $CI_GITLAB_FIPS_MODE == "true"
    variables:
      DAST_API_IMAGE_SUFFIX: "-fips"
  - if: $CI_COMMIT_BRANCH

기능 브랜치에서 작업 제외하기(단, 기본 브랜치에서는 제외하지 않기)

한 두 가지 느린 작업의 경우 팀은 해당 작업의 테스트를 건너뛰거나 기본 브랜치의 테스트에서만 포함하고 기능 브랜치의 테스트에서만 제외하기로 결정할 수 있습니다. 작업을 제외하는 방법은 DAST_API_EXCLUDE_PATHS 구성 변수를 사용하여 수행됩니다. 이 섹션에서 설명된 내용에 따라 작업을 제외합니다.

이 예에서는 대량의 데이터를 반환하는 작업이 있습니다. 해당 작업은 GET http://target:7777/api/large_response_json입니다. 이를 제외하려면 작업 URL의 경로 부분(/api/large_response_json)과 함께 DAST_API_EXCLUDE_PATHS 구성 변수를 제공합니다. 구성에서 기본 dast_api 작업을 비활성화하고 새로운 dast_api_maindast_api_branch 작업을 생성합니다. dast_api_branch는 긴 작업을 제외하고 기본 브랜치 이외(예: 기능 브랜치)에서만 실행하도록 설정되어 있습니다. dast_api_main 브랜치는 기본 브랜치(main 예제)에서만 실행되도록 설정되어 있습니다. dast_api_branch 작업은 빠르게 실행되어 빠른 개발 주기를 허용하고, 기본 브랜치 빌드에서만 실행되는 dast_api_main 작업은 더 오랜 시간이 걸립니다.

작업이 제외되었는지 확인하려면 DAST API 작업을 실행하고 작업 콘솔 출력을 확인합니다. 테스트의 끝에 포함된 작업과 제외된 작업 디렉터리이 표시됩니다.

# 두 개의 작업을 만들기 위해 기본 job 비활성화
dast_api:
  rules:
  - if: $CI_COMMIT_BRANCH
    when: never

# 기능 브랜치 작업, /api/large_response_json을 제외함
dast_api_branch:
  extends: dast_api
  variables:
    DAST_API_EXCLUDE_PATHS: /api/large_response_json
  rules:
  - if: $DAST_API_DISABLED == 'true' || $DAST_API_DISABLED == '1'
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $CI_COMMIT_BRANCH &&
        $CI_GITLAB_FIPS_MODE == "true"
    variables:
      DAST_API_IMAGE_SUFFIX: "-fips"
  - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    when: never
  - if: $CI_COMMIT_BRANCH

# 기본 브랜치용 DAST API(main 예제에서)
# 오랜 시간 실행하는 작업을 포함함
dast_api_main:
  extends: dast_api
  rules:
  - if: $DAST_API_DISABLED == 'true' || $DAST_API_DISABLED == '1'
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
        $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
    when: never
  - if: $CI_COMMIT_BRANCH &&
        $CI_GITLAB_FIPS_MODE == "true"
    variables:
      DAST_API_IMAGE_SUFFIX: "-fips"
  - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH