Внедряем DevSecOps в процесс разработки. Часть 2. Обзор инструментов, Commit-time Checks

Привет! На связи Олег Казаков из Spectr. 

Мы продолжаем публикацию цикла статей, где делимся опытом и наработками и рассказываем, из чего состоит DevSecOps и как его внедрить в процесс разработки. 

В предыдущей части статьи я рассказал о том, что представляет собой процесс DevSecOps в целом, из каких этапов он состоит, и подробно остановился на первом этапе — Pre-commit Checks. Сегодня пришло время для обзора стадии Commit-time Checks и ее инструментов. Поговорим о каждом инструменте отдельно и расскажем, на чем мы все-таки остановили свой выбор.

Commit-time Checks

f4eea7a47021ca949d633844bf62f4af.png

Суть этапа: проверить код на предмет корректности и безопасности в GIT-репозитории.

Рассмотрим известные классы инструментов.

SAST

SAST (static application security testing) — это процесс тестирования приложения на наличие ошибок и уязвимостей в исходном коде.

По этой ссылке доступен список различных видов SAST. Как видите, их довольно много, но практически у каждого вида есть сложности в использовании. Чаще всего это ложное срабатывание.

Этапы работы SAST-инструментов

4c87e597f7dc172238457aa4a3cfd7b4.png

Конечно, есть нюансы и различия, но в общем случае этапы следующие:

  • Построение модели (Modeled Code). На этом этапе инструмент SAST использует исходный код и преобразует его в формат, полезный для выполнения анализа. Одни инструменты компилируют код, другие используют абстрактное синтаксическое дерево для построения модели, третьи преобразовывают их в произвольный формат по своему выбору. Наиболее популярный формат — абстрактное синтаксическое дерево. Большинство инструментов SAST поддерживают несколько языков программирования, и этот шаг необходим для того, чтобы преобразовать код на любом языке в единый формат.

  • Поиск дефектов (List of Defects). На этом этапе инструменты SAST применяют различные правила к смоделированному коду. Эти правила могут быть определены поставщиком инструмента или написаны пользователем инструмента. Происходит семантический, структурный и прочие анализы, и на выходе мы получаем список дефектов

Виды SAST-инструментов

Среди возможных видов SAST есть платные и бесплатные инструменты, они перечислены ниже:  

SAST в GitLab

Посмотрим, что нам предлагает GitLab. У GitLab есть SAST во всех версиях.

Глядя на эту таблицу, мы видим, какие языки поддерживаются и какие инструменты используются, и видим, что их довольно много. 

434e2a39e16128bd3b1eb7baf3fe0638.jpeg436db9960fe899591ad7e9c54f8ff52c.jpeg

GitLab предлагает возможность использования версий разных Open Source или Free Community Edition. Их можно включить простым кодом, представленным ниже:

include:
  - template: Security/SAST.gitlab-ci.yml

sast:
  tags:
    - docker

То есть мы просто включаем SAST, добавляем файлики, например PHP-файл и Go-файл, таким образом добавляются стадии проверки при помощи phpcs-security-audit, Semgrep и Gosec (последние 2 на Go):

064309ef6de9f33925f69dba1c7f6744.png

Ниже представлен результат сканирования phpcs-security-audit:

f864ccc4a322a6fb87f1355253883c17.jpeg

Можем посмотреть исходники — что здесь есть?  

Ниже приведен пример того, как подключенный шаблон этой задачи (Security/Secret-SAST.gitlab-ci.yml) выглядит в исходниках:

sast:
  stage: test
  artifacts:
    reports:
      sast: gl-sast-report.json
  rules:
    - when: never
  variables:
    SEARCH_MAX_DEPTH: 4
  script:job
    - echo "$CI_JOB_NAME is used for configuration only, and its script should not be executed"
    - exit 1

.sast-analyzer:
  extends: sast
  allow_failure: true
  # `rules` must be overridden explicitly by each child job
  # see https://gitlab.com/gitlab-org/gitlab/-/issues/218444
  script:
    - /analyzer run

В самом начале все по аналогии с тем, что мы видели в Secret Detection в рамках предыдущей статьи: есть сама задача SAST, генерация отчета (только уже с другим именем — gl-sast-report.json). Но отличия все же есть, т. к. GitLab под разные ЯП предлагает различные инструменты SAST, то ест для каждого из этих инструментов есть свое описание.

semgrep-sast:
  extends: .sast-analyzer
  image:
    name: "$SAST_ANALYZER_IMAGE"
  variables:
    SEARCH_MAX_DEPTH: 20
    SAST_ANALYZER_IMAGE_TAG: 4
    SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/semgrep:$SAST_ANALYZER_IMAGE_TAG$SAST_IMAGE_SUFFIX"
  rules:
    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'
      when: never
    - if: $SAST_EXCLUDED_ANALYZERS =~ /semgrep/
      when: never
    - if: $CI_COMMIT_BRANCH
      exists:
        - '**/*.py'
        - '**/*.js'
        - '**/*.jsx'
        - '**/*.ts'
        - '**/*.tsx'
        - '**/*.c'
        - '**/*.go'
        - '**/*.java'
        - '**/*.cs'
        - '**/*.html'
        - '**/*.scala'
        - '**/*.sc'

Ниже приведен пример описания задачи для semgrep. Нас тут интересует раздел rules, а если конкретнее, то:

  • блок exists, в котором идет перечисление масок, то есть для каких файлов применяется инструмент;

  • проверка переменной $SAST_EXCLUDED_ANALYZERS на вхождение строки с именем инструмента. Таким образом, мы можем выключать определенные инструменты, если они нам не нужны, — это нам пригодится чуть позже.

Проверим, как работают инструменты, добавим различные файлы с уязвимостями, например, воспользовавшись данной коллекцией.

9c1ad562e7af3fbe73cc18b52c7ccb2e.png

Видим аналогичную картину, как и в Secret Detection в предыдущей статье. Задачи отработали успешно, но в отчете есть уязвимости.

da52b3e4cae5514381ff5d98ff92f7fd.png

Опять же, стоит заметить, что в бесплатной версии очень мало функционала. 

d81c1d983877817e1c1cd1cea44fb0f4.jpeg

Поэтому мы пойдем по тому же пути (что и в части 1) и немного допишем скрипт, кот

#!/bin/bash

vulnerability_count=$(cat $FILE_REPORT | jq --raw-output '.vulnerabilities | length')
if [ ${vulnerability_count} -gt 0 ];  then
  echo "|     severity     |     name     |     location     |     scanner     |"
  echo "|------------------|--------------|------------------|-----------------|"
  _jq() {
   echo ${row} | base64 --decode | jq -r ${1}
  }
  for row in $(cat $FILE_REPORT | jq -r '.vulnerabilities[] | @base64'); do
    vulnerability_name=$(_jq ".name")
    if [ "$vulnerability_name" == "null" ]; then vulnerability_name=$(_jq ".message"); fi
    echo '|' $(_jq ".severity") '|' $vulnerability_name '|' $(_jq ".location.file")':'$(_jq ".location.start_line") '|' $(_jq ".scanner.name") '|'
  done
fi

exit $vulnerability_count

Дорабатываем задачу SAST, выносим в отдельный файл для удобства (можно в любой момент включать/выключать задачу).

В самом .gitlab-ci.yml подключаем этот отдельный файл с задачей.

stages:
  - test

.analyzer_run:
  script:
    - apk add jq bash coreutils
    - /analyzer run
    - bash .gitlab/scripts/$NAME_OF_CI_SCRIPT.sh

include:
  - local: '/.gitlab/templates/sast.gitlab-ci.yml'

Те же задачи, в которых найдены уязвимости, теперь падают:

5b75259e040f5afcd5f77b381e99c85e.png

Вывод уязвимостей в задаче Bandit (только Python):

bd64b445292f421c4460f085ac07676b.png

Вывод уязвимостей в задаче Flawfinder (только C/C++):

8192607adb0419d8d78063ed101edf5e.png

Вывод уязвимостей в задаче Gosec (только Go):

57ac59990f1d1a399e2ec3840d5c4350.png

Вывод уязвимостей в задаче Semgrep (поддерживает много языков, в т. ч. Python, Go, C/C++):

002328acd898a0722a9700cf34991e35.png

Выглядит интересно, SAST в Gitlab включается и настраивается легко. Есть много инструментов, которые могут дополнять друг друга.

Сложности при использовании SAST в Gitlab

  • Зоопарк технологий. Под каждый ЯП — свой набор инструментов, каждый из которых может работать по-своему. Например, phpcs-security-audit выполняется не под рутом, а это значит, что мы не можем установить свои либы (jq, bash и т. д.).

  • Ложные срабатывания — одна из главных проблем SAST-инструментов. А тут еще много инструментов, что кратно увеличивает вероятные проблемы.

Как решить эти проблемы 

Один из вариантов облегчения — исключить все лишние инструменты. Например, мы видим, что Semgrep покрывает достаточно много ЯП и в целом очень активно развивается. В этом нам поможет переменная SAST_EXCLUDED_ANALYZERS, которая позволяет исключать анализаторы.

sast:
  variables:
    SAST_EXCLUDED_ANALYZERS: "bandit,gosec,flawfinder" # можно исключать различные инструменты
    FILE_REPORT: gl-sast-report.json
    NAME_OF_CI_SCRIPT: "sast"
  tags:
    - docker

Тут мы исключаем Bandit, GoSec и Flawfinder, и выполняется только Semgrep. Таким образом, мы можем оставить какой-то один комплексный инструмент, который будет закрывать много языков, например тот же Semgrep. 

В целом и сам Gitlab потихоньку двигается в сторону уменьшения инструментов, объявляя, что перестает поддерживать некоторые из них (например, Bandit, ESLint, GoSec).

Подключение инструментов DevSecOps напрямую

До сих пор мы использовали только встроенные в GitLab-инструменты. В этом есть плюс — настройка разных шагов в CI/CD получается довольно похожей.

Но есть и минусы:  

  • GitLab может ограничивать возможности инструмента;

  • GitLab может не так активно актуализировать версии инструментов.

Поэтому иногда имеет смысл подключить инструмент самостоятельно. Покажу на примере все того же Semgrep.

У Semgrep есть официальный образ в Docker Hub, и его можно использовать с минимальными доработками. Для этого:

  1. Добавляем новый шаблон.

semgrep:
 stage: test
 image: semgrep/semgrep
 variables:
   FILE_REPORT: gl-sast-report.json
   SEMGREP_RULES: >-
     p/security-audit
     p/secrets
     p/python
     p/django
     p/phpcs-security-audit
 tags:
   - docker
 script:
   - semgrep ci --gitlab-sast > $FILE_REPORT || true
   - apk add jq bash coreutils
   - bash .gitlab/scripts/sast.sh
 artifacts:
   reports:
     sast: $FILE_REPORT
  • SEMGREP_RULES — это список правил, на соответствие которым проверяется весь исходный код. Для поиска правил можно использовать сервис: https://semgrep.dev/r 

  • --gitlab-sast — это специальный флаг, который формирует вывод в таком же формате, как в GitLab.

  • || true — по умолчанию при наличии ошибок данная утилита возвращает ошибку и от этого job завершается. Нам же нужно обработать файл с уязвимостями, поэтому подавляем эту ошибку.

  1. Включаем данный шаблон в .gtilab-ci.yml:

include:
  - local: '/.gitlab/templates/sast-semgrep.gitlab-ci.yml'

Остановились на SonarQube

Если говорить о нашей компании, то мы в итоге остановились на другом инструменте — SonarQube. Этот инструмент уже не является частью GitLab ни в каком виде, но умеет интегрироваться с ним. 

SonarQube нам понравился своим удобным интерфейсом: это и удобная визуализация, и возможность быстрой реакции на найденные уязвимости (можно указать, что это корректное поведение, а можно — что это ложное срабатывание).

59fda889c3fbe02d6062b5ced931ba95.png

По данной ссылке содержится информация о том, как установить SonarQube и интегрировать его с GitLab.

Dependency Scanning

Другой класс инструментов Commit-time Checks — Dependency Scanning (это процесс автоматического обнаружения уязвимостей в зависимостях).

Как работают Dependency Scanning-инструменты

deba278492a0521b60ca9546dd9b579e.png

Во многих языках программирования есть пакетные менеджеры, при помощи которых мы можем выкачивать код, и информация об этом сохраняется в различных файлах (go.sum, composer.lock, package-lock.json, yarn.lock, Gemfile.lock, requirements.txt и т. д.). Dependency Scanning-инструменты сканируют эти файлы, смотрят, какие пакеты были выкачаны и какие были версии, далее пакеты проверяют в базе скомпрометированного ПО и, если их там находят, выдают ошибку.

Виды Dependency Scanning-инструментов

Dependency Scanning в GitLab

В GitLab есть инструмент Dependency Scanning, но, к сожалению, в бесплатной версии GitLab этот инструмент уже недоступен ни в каком виде.

Если просто включим шаблон, то ничего не произойдет, задача не будет даже стартовать. 

В GitLab используется некий инструмент Gemnasium. Gemnasium — это собственное решение GitLab, при этом оно открытое и его можно использовать. Вернее, с одной стороны, мы его не можем использовать, потому что он в Ultimate-версии, но можем использовать его образ, чтобы использовать в бесплатной версии. Ниже приведен пример того, как это выглядит в исходниках:  

1788243319c89b9a53295aad89be149d.png5cfdc0275017fee79cfd87dcd59ace53.png91f484960806c9518fb4d9cfc9188cf9.png

Попробуем применить. Для этого создаем собственную задачу, чтобы не было конфликта с задачей GitLab, указываем найденный образ в image, а далее все применяется как и везде.

dependency_scanning_custom:
  stage: test
  variables:
    FILE_REPORT: gl-dependency-scanning-report.json
    NAME_OF_CI_SCRIPT: "dependency_scanning"
  image: registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium:3
  tags:
    - docker
  artifacts:
    reports:
      dependency_scanning: gl-dependency-scanning-report.json
  script:
    - !reference [.analyzer_run, script]

Создаем скрипт обработки артефакта

#!/bin/bash

vulnerability_count=$(cat $FILE_REPORT | jq --raw-output '.vulnerabilities | length')
if [ ${vulnerability_count} -gt 0 ];  then
  echo "|     severity     |     name     |     file     |     package     |"
  echo "|------------------|--------------|--------------|-----------------|"
  _jq() {
   echo ${row} | base64 --decode | jq -r ${1}
  }
  for row in $(cat $FILE_REPORT | jq -r '.vulnerabilities[] | @base64'); do
    echo '|' $(_jq ".severity") '|' $(_jq ".name") '|' $(_jq ".location.file") '|' $(_jq ".location.dependency.package.name") '|'
  done
fi

exit $vulnerability_count

Закидываем какие-то пакеты, например, в composer и go.

cb80187a6979a50e59a5fc517e883f68.png

Видим отчет Dependency Scanning: обнаружены 2 пакета, уязвимостей нет. 

У Gemnasium есть отдельный сайт с поиском по БД-уязвимостей. Можно найти какой-то скомпрометированный пакет, выбрать его и включить в пакетный менеджер.

da65c8b9b0ea2433b2113aaedf760f8b.png

Ниже приведен пример уязвимости:  

53427fbf28f68c95efa2ff3fa53363e5.png

Добавляем несколько пакетов с уязвимостями — уязвимости в задаче найдены, выводится информация об этом в задаче:

135f92d094ede807fba77ecf2f1d4375.png

По итогу единственное, что пришлось сделать нестандартно, — найти образ в открытом доступе и указать явно образ, который должен использоваться для сканирования. В остальном все как и в предыдущих этапах.

Итоги

Итак, мы разобрали еще один этап в процессе DevSecOps — Commit-time Checks. Все наработки по коду лежат в этом репозитории.

Следующая часть статьи будет посвящена стадии Post-build Checks.

© Habrahabr.ru