성능 튜닝 및 테스트 속도

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초는 이 특정 작업이 오래 걸리는 것으로 보이는 초기 지표입니다. 더불어, 응답 본문 크기가 상당히 크다는 것을 알 수 있습니다. 여기서 큰 본문 크기가 가장 많은 시간을 소요하는 것입니다.

이 문제를 해결하기 위해 팀은 아마도 다음 결정을 할 수 있습니다:

  • DAST API가 수행하는 작업을 병렬화할 수 있게 해주는 더 많은 vCPU를 가진 러너를 사용합니다. 이렇게 하면 테스트 시간을 단축할 수 있지만, 작업이 오래 걸리는 경우 높은 CPU 머신으로 이동하지 않으면 10분 미만으로 테스트를 완료하는 것이 여전히 문제일 수 있습니다. 더 큰 러너는 더 비용이 많이 들지만, 작업 실행 시간이 더 짧으면 덜한 분을 지불하게 됩니다.
  • DAST API 테스트에서 이 작업을 제외합니다. 이것은 가장 간단하지만, 보안 테스트 범위에 구멍을 낼 수 있는 단점이 있습니다.
  • 기능 브랜치 DAST API 테스트에서 이 작업을 제외하지만, 기본 브랜치 테스트에는 이를 포함합니다.
  • DAST API 테스트를 여러 작업으로 분할합니다.

팀의 요구 사항이 5-7분 범위 안에 있는 경우, 이러한 솔루션들의 조합을 사용하여 수용 가능한 테스트 시간을 달성하는 것이 가능할 것으로 예상됩니다.

성능 이슈 해결

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

더 큰 러너 사용

가장 쉬운 성능 향상 중 하나는 DAST API와 함께 보다 큰 러너를 사용하여 달성할 수 있습니다. 이 테이블은 Java Spring Boot REST API의 벤치마킹 중 수집된 통계를 보여줍니다. 이 벤치마킹에서 대상과 DAST API는 단일 러너 인스턴스를 공유합니다.

Linux용 SaaS 러너 태그 초당 요청 수
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 파일에서 보고된 최대 DOP(병렬성 정도)를 검사하려면 문자열 Starting work item processor를 검색할 수 있습니다. 최대 DOP는 러너에 할당된 vCPU 수와 동일하거나 그보다 큰 값을 가져야 합니다. 문제를 식별할 수 없는 경우 지원팀에 지원을 요청하십시오.

로그 항목의 예:

17:00:01.084 [INF] <Peach.Web.Core.Services.WebRunnerMachine> Starting work item processor with 4 max DOP

느린 작업 제외

한 두 개의 느린 작업의 경우, 팀은 해당 작업의 테스트를 건너뛰기로 결정할 수 있습니다. 작업을 제외하는 것은 DAST_API_EXCLUDE_PATHS 구성 변수를 사용하여 작업 URL 부분을 제외하는 방법으로 수행됩니다.

이 예에서는 대량의 데이터를 반환하는 작업이 있습니다. 해당 작업은 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 구성 변수를 사용하여 수행됩니다. [설정(customizing_analyzer_settings.md#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 작업을 실행하고 작업 콘솔 출력을 검토하세요. 테스트의 끝에 포함된 작업과 제외된 작업의 목록이 표시됩니다.

# 메인 작업을 비활성화하여
# 서로 다른 이름의 두 작업을 생성합니다
dast_api:
  rules:
  - if: $CI_COMMIT_BRANCH
    when: never

# 기능 브랜치 작업용 DAST API, /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