Как читать авторский курс и нормативные материалы
Это учебные материалы, а не заключение о соответствии конкретного продукта. Организационные модели курса отделяйте от терминов стандарта: ППК в опубликованном проекте — машиночитаемый перечень, а живой реестр его версий является практикой сопровождения. Термин OSA в разных материалах имеет разный охват. Статус и обязательность нормативного документа проверяются отдельно; проект КАПО опубликован как проект первой редакции.
П34. SBOM quality gate
Контекст практики Мусорный SBOM ломает всё, что идёт после него: сканер не сопоставит компонент без PURL/CPE, юрист не оценит лицензию без поля
licenses, аудитор не докажет полноту без графа зависимостей. Quality gate ставится перед загрузкой SBOM в анализатор и считает измеримые показатели покрытия. Опора — обязательные поля компонента по BSI TR-03183-2 v2.1.0 (раздел 5.2.2). Контролируемый результат — gate, который валит CI, если покрытие ниже порога, ещё до того как SBOM попадёт в сервис аудита.
Цель
Получить скрипт + CI-шаг, который по CycloneDX-файлу считает:
- PURL coverage — доля компонентов с непустым purl;
- license coverage — доля компонентов с заполненным licenses (id / name / expression);
- наличие dependency graph (секция dependencies непуста);
- число компонентов без версии и без идентификатора (worklist).
Gate падает, если PURL coverage < 90% или license coverage < 80% (пороги настраиваются). Проверяемый результат: на «хорошем» SBOM gate зелёный, на намеренно урезанном — красный с числами.
Команда или действие
Скрипт sbom-quality.sh (нужен только jq):
#!/usr/bin/env bash
set -euo pipefail
SBOM="${1:-sbom.cdx.json}"
MIN_PURL=90
MIN_LIC=80
total=$(jq '.components | length' "$SBOM")
with_purl=$(jq '[.components[] | select(.purl and .purl != "")] | length' "$SBOM")
with_lic=$(jq '[.components[] | select((.licenses|length)>0)] | length' "$SBOM")
with_ver=$(jq '[.components[] | select(.version and .version != "")] | length' "$SBOM")
deps=$(jq '.dependencies | length' "$SBOM")
purl_pct=$(( with_purl * 100 / total ))
lic_pct=$(( with_lic * 100 / total ))
printf 'components=%s purl=%s versioned=%s deps_nodes=%s\n' \
"$total" "$purl_pct" "$lic_pct" "$with_ver" "$deps"
# worklist: компоненты без PURL
jq -r '.components[] | select((.purl // "")=="") | " no-purl: \(.name) \(.version // "?")"' "$SBOM"
fail=0
[ "$purl_pct" -lt "$MIN_PURL" ] && { echo "FAIL: purl coverage < ${MIN_PURL}%"; fail=1; }
[ "$lic_pct" -lt "$MIN_LIC" ] && { echo "FAIL: license coverage < ${MIN_LIC}%"; fail=1; }
[ "$deps" -eq 0 ] && { echo "FAIL: no dependency graph"; fail=1; }
exit $fail
CI-шаг (GitLab):
sbom_quality:
stage: test
image: alpine:3.20
before_script: [ "apk add --no-cache jq bash" ]
script: [ "bash sbom-quality.sh sbom.cdx.json" ]
Что вы должны увидеть
На качественном SBOM — строка метрик и exit 0:
components=147 purl=98% license=91% versioned=145 deps_nodes=147
На урезанном (часть компонентов без purl, типичный симптом C/C++ SBOM, где нет пакетного менеджера) — worklist и exit 1:
components=63 purl=71% license=44% versioned=58 deps_nodes=0
no-purl: libfoo 2.3.1
no-purl: vendored-zlib ?
FAIL: purl coverage < 90%
FAIL: license coverage < 80%
FAIL: no dependency graph
Признак готовности: gate отделяет «годен к загрузке в анализатор» от «сначала дозаполнить координаты». Числа purl%/license% совпадают с тем, что вы посчитаете вручную на маленьком SBOM из 5 компонентов.
Для C/C++ legacy низкий PURL coverage — это норма и сигнал Без пакетного менеджера у статически слинкованных и вендоренных библиотек PURL часто пуст. Не «подгоняйте» порог под такой SBOM — gate должен вывести worklist (что дораспознать через CPE/build evidence), а не молча пропустить. Это вход в П25. CPE alias worklist.
Если не получилось
jq: error: Cannot iterate over null. В SBOM нет секцииcomponentsилиdependencies, либо это не CycloneDX (а, например, SPDX). Проверьтеjq '.bomFormat, .specVersion' sbom.cdx.json— должно быть"CycloneDX".- License coverage = 0%, хотя лицензии видны глазами. Лицензия лежит не там, где ищет скрипт: в CycloneDX это
licenses[].license.id,licenses[].license.nameилиlicenses[].expression. Скрипт считает поlength, но если структура другая — проверьтеjq '.components[0].licenses'. - Деление на ноль / пустой вывод.
total = 0— SBOM без компонентов (сгенерировался по пустой директории или не той цели). Это сам по себе fail полноты. - Gate зелёный на явно плохом SBOM. Пороги слишком низкие или сравнение в bash идёт как со строкой. Убедитесь, что
$purl_pct— целое число и сравнение-ltчисловое.
Критерий зачёта
Показать преподавателю: (1) скрипт + CI-шаг; (2) зелёный прогон на качественном SBOM с метриками; (3) красный прогон на урезанном SBOM с worklist no-purl и exit 1; (4) совпадение посчитанных метрик с ручным подсчётом на маленьком примере. Устно — почему quality gate ставится до загрузки в анализатор и какие обязательные поля BSI TR-03183-2 (раздел 5.2.2) он проверяет.
Нормативная связка
- М03. Форматы SBOM: CycloneDX, SPDX, SWID — родительский модуль (критерии качества BSI).
- П02. Чтение CycloneDX JSON руками · П25. CPE alias worklist · П16. GitLab CI security gate · П24. PURL нормализация и private qualifiers
- Чек-лист качества CycloneDX · Чек-лист качества SPDX · Соглашение о качестве данных SBOM
- BSI TR-03183-2 v2.1.0 — обязательные поля компонента (раздел 5.2.2); CISA Minimum Elements for SBOM · OWASP SCVS.