Документация / CI/CD и API
Загрузка отчётов из CI
Если сканер уже работает в вашем CI, отправьте его отчёт в Core напрямую: POST /api/v1/scan/import/. Core примет отчёт, ответит 202 и разберёт его асинхронно - так же, как отчёты из Radar. Загрузка из интерфейса описана на странице Импорт отчётов.
Перед началом
- Сервисный пользователь и токен. Роль с правом "Уязвимости" - "Изменение". Для проверки результата через аудит добавьте "Продукты" - "Просмотр". Токен - "Профиль" → "Токен аутентификации API", см. Пользователи.
- Имя сканера.
scanner_name- точное название сканера в Core: "Глобальные настройки" → "Сканеры". Список встроенных - Справочник сканеров. - Адрес.
https://core.example.ru/api/v1/scan/import/- со слешем на конце. - Продукт. Передайте имя и тип продукта или один ассет, который уже есть в Core. Правила - ниже.
Храните токен в защищённой маскированной переменной CI, а не в файле пайплайна.
Параметры запроса
Запрос - multipart/form-data, заголовок Authorization: Token <токен>.
| Поле | Обязательное | Описание |
|---|---|---|
scanner_name |
да | название сканера в Core |
file |
file или report |
файл отчёта. Core читает его как текст в UTF-8 |
report |
file или report |
текст отчёта вместо файла |
product_name |
вместе с product_type |
продукт. Если его нет, Core создаёт |
product_type |
вместе с product_name |
тип продукта. Если его нет, Core создаёт |
repository |
нет | ассет "Репозиторий" - SSH URL, например git@gitlab.example.ru:team/payments.git |
docker_image |
нет | ассет "Docker-образ", без тега |
domain |
нет | ассет "Домен" |
host |
нет | ассет "Хост" |
cloud_account |
нет | ассет "Облачный аккаунт" |
branch |
нет | ветка находок |
docker_image_tag |
нет | тег образа |
custom_field |
нет | своё поле находок, до 300 символов |
audit_id |
нет | номер существующего аудита. Не задан - Core создаёт новый аудит, если указан хотя бы один ассет |
Переданные ассеты Core создаёт в продукте, если их там нет.
Как определяется продукт
- Переданы
product_nameиproduct_type- используется этот продукт. - Иначе передан ровно один ассет: продукт ассета, если ассет есть в Core; продукт по умолчанию, если ассета нет.
- Ничего не подошло - ответ 400.
Если в "Глобальные настройки" → "Рабочий процесс" включено "Включить уникальные ассеты", Core пропускает product_name и product_type и определяет продукт по ассету.
Ассет - тот же, что у Radar
Передавайте в repository тот же SSH URL, что у ассета в Core. Тогда находки из CI и из Radar попадают в один ассет, а Quality gate и Security checker их видят.
Автозакрытие
Если у сканера в Core включено "Автозакрытие", находки, которых нет в новом отчёте, будут решены или удалены. Отправляйте полный отчёт по ассету, а не по изменённым файлам.
Ответ
Успех - 202:
{
"product_name": "payments",
"product_type": "Common",
"scanner_name": "Gitleaks Scan",
"branch": "main",
"docker_image_tag": "",
"custom_field": "",
"audit_id": 121,
"assets": {
"repository": "git@gitlab.example.ru:team/payments.git",
"docker_image": "",
"domain": "",
"cloud_account": "",
"host": ""
},
"user_name": "ci",
"user_id": 7
}
202 значит, что отчёт принят в очередь разбора. Находки появляются после разбора.
Коды ответа
| Код | Когда | Пример тела |
|---|---|---|
| 202 | отчёт принят | см. выше |
| 400 | неизвестное имя сканера | {"scanner_name": ["\"Gitleaks\" is not a valid choice."]} |
| 400 | нет ни file, ни report |
{"non_field_errors": ["At least one of the 'file' or 'report' fields is required."]} |
| 400 | несуществующий audit_id |
{"audit_id": ["Audit with id 999 does not exist"]} |
| 400 | продукт не определился | ["Specify product name and product type or set default product"] |
| 400 | нет продукта и передано несколько ассетов | {"detail": "Either specify product and product type or a single asset"} |
| 400 | ассет найден в нескольких продуктах | {"detail": "Only one specified asset should exist"} |
| 401 | нет заголовка или токен неверный | {"detail": "No credentials provided"}, {"detail": "Invalid token."} |
| 403 | у роли нет права "Уязвимости" - "Изменение" | {"detail": "You do not have permission to perform this action."} |
Проверка результата
Номер аудита из ответа покажет итог разбора:
curl -s "https://core.example.ru/api/v1/audit/121/" \
-H "Authorization: Token $SF_TOKEN"
Когда разбор закончится, в scanners появится запись сканера: added_findings_count - новые находки, deduplicated_findings_count - совпавшие с известными, report_duplicates_count - повторы внутри отчёта. В интерфейсе - "Аудиты" → аудит.
GitLab CI
Пример: Gitleaks в одной джобе, отправка в Core - в следующей. SF_TOKEN - маскированная переменная CI/CD группы.
variables:
SF_URL: https://core.example.ru
SF_PRODUCT_NAME: payments
SF_PRODUCT_TYPE: Common
SF_REPOSITORY: git@gitlab.example.ru:$CI_PROJECT_PATH.git
stages: [test, report]
gitleaks:
stage: test
image: registry.example.ru/gitleaks:<версия>
script:
- gitleaks detect -s . -r gitleaks.json --exit-code 0
artifacts:
when: always
paths: [gitleaks.json]
.sf-import:
stage: report
image: registry.example.ru/curl:<версия> # любой образ с curl
script:
- |
curl --silent --show-error --fail-with-body \
-H "Authorization: Token $SF_TOKEN" \
-F "scanner_name=$SF_SCANNER" \
-F "product_name=$SF_PRODUCT_NAME" \
-F "product_type=$SF_PRODUCT_TYPE" \
-F "repository=$SF_REPOSITORY" \
-F "branch=$CI_COMMIT_REF_NAME" \
-F "file=@$SF_REPORT" \
"$SF_URL/api/v1/scan/import/"
gitleaks-import:
extends: .sf-import
needs: [gitleaks]
variables:
SF_SCANNER: Gitleaks Scan
SF_REPORT: gitleaks.json
- Шаблон
.sf-importпереиспользуйте для других сканеров: меняйтеSF_SCANNERиSF_REPORT. --fail-with-bodyроняет джобу при ответе не 2xx и печатает тело ошибки. Нужен curl 7.76 или новее.- Не передавайте в
repositoryпеременнуюCI_REPOSITORY_URL: она содержит токен джобы. - Сканер, который возвращает ненулевой код при находках, запускайте так, чтобы отчёт всё равно сохранился:
--exit-code 0у gitleaks илиallow_failure: trueу джобы.
Jenkins
Отправка - тот же запрос curl. Токен храните в Jenkins Credentials как Secret text.
pipeline {
agent any
environment {
SF_URL = 'https://core.example.ru'
}
stages {
stage('Scan') {
steps {
sh 'trivy fs --format json --output trivy.json .'
}
}
stage('Import to SecurityFlow') {
steps {
withCredentials([string(credentialsId: 'sf-core-token', variable: 'SF_TOKEN')]) {
sh '''
curl --silent --show-error --fail-with-body \
-H "Authorization: Token $SF_TOKEN" \
-F "scanner_name=Trivy Scan" \
-F "product_name=payments" \
-F "product_type=Common" \
-F "repository=git@gitlab.example.ru:team/payments.git" \
-F "branch=${GIT_BRANCH#origin/}" \
-F "file=@trivy.json" \
"$SF_URL/api/v1/scan/import/"
'''
}
}
}
}
}
- Скрипт
shв одинарных кавычках: токен подставляет оболочка, а не Groovy, и он не попадает в журнал сборки. GIT_BRANCHплагина Git содержит префиксorigin/- конструкция${GIT_BRANCH#origin/}его убирает.
Типовые ошибки
| Симптом | Причина | Что сделать |
|---|---|---|
400 is not a valid choice |
scanner_name не совпадает с названием сканера |
скопируйте название из "Глобальные настройки" → "Сканеры" |
401 No credentials provided |
нет заголовка или схема не Token |
заголовок строго Authorization: Token <токен> |
| 202, но находок нет | импортёр не разобрал отчёт: другой формат или пустой файл | проверьте формат отчёта для этого сканера, загрузите тот же файл через интерфейс |
| ответ 404 | адрес без слеша на конце | используйте /api/v1/scan/import/ |
| 500 | файл не в UTF-8 | сохраните отчёт в UTF-8 |