Пиков / КАПО
Как читать авторский курс и нормативные материалы

Это учебные материалы, а не заключение о соответствии конкретного продукта. Организационные модели курса отделяйте от терминов стандарта: ППК в опубликованном проекте — машиночитаемый перечень, а живой реестр его версий является практикой сопровождения. Термин OSA в разных материалах имеет разный охват. Статус и обязательность нормативного документа проверяются отдельно; проект КАПО опубликован как проект первой редакции.

П16. GitLab CI security gate

Контекст практики Цель CI security gate — не «прогнать сканер», а остановить pipeline на заранее оговорённом пороге: критичность уязвимостей и запрещённые лицензии. Здесь вы соберёте gate из трёх шагов: генерация SBOM (Syft) → сканирование (Grype с --fail-on) → отдельный fail по лицензиям. Ключевая идея — gate валит сборку кодом возврата задачи, а не текстом в логе, который никто не читает.

Цель

Получить .gitlab-ci.yml, в котором: - стадия sca падает (job статус failed), если в зависимостях есть уязвимость уровня High или Critical с известным фиксом; - стадия license падает, если встречена лицензия из стоп-листа (например, GPL-3.0-only); - артефакты (SBOM, отчёт сканера) сохраняются и доступны на вкладке Job artifacts.

Проверяемый результат: один намеренно уязвимый коммит → pipeline красный; после фикса версии → зелёный.

Команда или действие

.gitlab-ci.yml (раннер с Docker executor):

stages:
  - sca

variables:
  SYFT_VERSION: "v1.18.1"
  GRYPE_VERSION: "v0.85.0"

sca_scan:
  stage: sca
  image: alpine:3.20
  before_script:
    - apk add --no-cache curl
    - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin "${SYFT_VERSION}"
    - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin "${GRYPE_VERSION}"
  script:
    # 1. SBOM из исходников проекта
    - syft dir:. -o cyclonedx-json=sbom.cdx.json
    # 2. Gate по критичности: задача падает на High/Critical
    - grype sbom:sbom.cdx.json --fail-on high --only-fixed -o table | tee grype-report.txt
  artifacts:
    when: always
    paths:
      - sbom.cdx.json
      - grype-report.txt
    expire_in: 1 week

Стоп-лист лицензий — отдельный шаг (CycloneDX кладёт лицензии в components[].licenses):

# license gate: падаем, если в SBOM есть запрещённая лицензия
FORBIDDEN='GPL-3.0-only|AGPL-3.0|GPL-2.0-only'
if jq -r '.components[].licenses[]?.license.id // .components[].licenses[]?.expression' sbom.cdx.json \
     | grep -E "$FORBIDDEN"; then
  echo "FORBIDDEN LICENSE FOUND"; exit 1
fi

Что вы должны увидеть

Лог grype при срабатывании gate (флаг --only-fixed оставляет только уязвимости с доступным исправлением):

NAME       INSTALLED  FIXED-IN  TYPE  VULNERABILITY   SEVERITY
lodash     4.17.15    4.17.21   npm   GHSA-p6mc-m468  High
minimist   1.2.0      1.2.6     npm   GHSA-xvch-5gv4  Critical

1 error occurred:
    * discovered vulnerabilities at or above the severity threshold

Статус задачи в GitLab — failed, exit code 1. На вкладке Job artifacts лежат sbom.cdx.json и grype-report.txt. После обновления lodash/minimist до фикс-версий повторный pipeline — зелёный, grype выводит No vulnerabilities found, exit code 0.

Сначала visibility, потом блокировка На первом проходе по репозиторию запустите gate в режиме allow_failure: true (или grype без --fail-on), соберите статистику и согласуйте порог. Жёсткий --fail-on high включают, когда команда уже разобрала текущий долг — иначе первый же pipeline встанет колом и gate отключат административно.

Если не получилось

Критерий зачёта

Показать преподавателю: (1) .gitlab-ci.yml с двумя gate-условиями (критичность + лицензии); (2) скриншот/лог красного pipeline на уязвимом коммите с видимым exit code 1 и строкой Grype; (3) скриншот зелёного pipeline после фикса; (4) сохранённые артефакты SBOM и отчёта. Устно — почему gate валит по коду возврата, а не по тексту лога, и зачем нужен этап allow_failure перед жёсткой блокировкой.

Нормативная связка