Все статьи

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

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

Читать статью на Хабр.

Commit-time Checks

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

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

SAST

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

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

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

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

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

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

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

Платные Бесплатные
LGTM
Checkmarx SAST
Contrast
Coverity Scan
SonarQube
Semgrep
PVS-Studio
Reshift
Mend SAST
HCL AppScan Source
        
Open Source, которые в основном направлены на конкретные языки:
Bandit
Brakeman
Phpcs-security-audit
Gosec
Security Code Scan
Flawfinder
ESLint security plugin

Free Community Edition:
Contrast Scan
SonarQube
Semgrep
        

SAST в GitLab

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

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

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

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

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

Ниже приведен пример того, как подключенный шаблон этой задачи (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 на вхождение строки с именем инструмента. Таким образом, мы можем выключать определенные инструменты, если они нам не нужны, — это нам пригодится чуть позже.

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

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

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

Поэтому мы пойдем по тому же пути (что и в части 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'

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

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

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

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

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

Выглядит интересно, 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 нам понравился своим удобным интерфейсом: это и удобная визуализация, и возможность быстрой реакции на найденные уязвимости (можно указать, что это корректное поведение, а можно — что это ложное срабатывание).

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

Dependency Scanning

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

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

Во многих языках программирования есть пакетные менеджеры, при помощи которых мы можем выкачивать код, и информация об этом сохраняется в различных файлах (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-версии, но можем использовать его образ, чтобы использовать в бесплатной версии. Ниже приведен пример того, как это выглядит в исходниках:

Попробуем применить. Для этого создаем собственную задачу, чтобы не было конфликта с задачей 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.

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

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

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

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

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

Итоги

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

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

Олег Казаков
Технический директор

Читайте также

Параметризованные сборки в GitLab
Параметризованные сборки в GitLab
Понятие параметризованных сборок очень популярно в Jenkins — это функционал, который позволяет запускать сборки с пользовательскими параметрами. Это значительно расширяет возможности автоматизации и делает процессы более гибкими. Одна из ключевых задач, для которой этот функционал может применяться, — тестирование функционала в разных окружениях. Можно запускать тесты на окружении (например, dev, staging, test), просто задавая нужные параметры. Разбираем эту тему в нашей статье.
Внедряем DevSecOps в процесс разработки. Часть 5. Этап Deploy-time Checks, обзор инструментов
Внедряем DevSecOps в процесс разработки. Часть 5. Этап Deploy-time Checks, обзор инструментов
В предыдущей части рассказали о тестировании функционала на уязвимость до его попадания на продакшн. По итогам предыдущих статей мы можем проверить код на безопасность, собрать безопасные билды, проверить функционал на наличие уязвимостей. Теперь можно разворачивать приложение на продакшне.
Внедряем DevSecOps в процесс разработки. Часть 4. Этап Test-time Checks, обзор инструментов
Внедряем DevSecOps в процесс разработки. Часть 4. Этап Test-time Checks, обзор инструментов
В предыдущих статьях разобрали три этапа DevSecOps — Pre-commit Checks, Commit-time Checks, Post-build Checks. В четвертой статье поговорим о следующем этапе — Test-time Checks.
Внедряем DevSecOps в процесс разработки. Часть 3. Этап Post-build Checks в DevSecOps, обзор инструментов
Внедряем DevSecOps в процесс разработки. Часть 3. Этап Post-build Checks в DevSecOps, обзор инструментов
В этой части материала рассмотрим, что же такое Post-build Checks, и как на этом этапе используется такой класс инструментов, как Container Cheсks