Секреты для CI в GitLab: переменные, а не репозиторий

Токен деплоя, пароль базы и ключ реестра образов не живут в .gitlab-ci.yml и не ездят в чате. Положите их в переменные проекта GitLab, включите маскирование и ограничьте защищёнными ветками. Если секрет уже в истории репозитория — сначала отзыв и поиск: секреты в git. Здесь только как хранить, чтобы туда больше не попадал.
Статья опирается на логику направления «Безопасность данных» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Официальные переменные GitLab: docs.gitlab.com/ci/variables. Практика OWASP — не держать секреты в коде: Secrets Management Cheat Sheet. Это не российский закон.
Куда класть
Нужна роль Maintainer проекта (для группы — Owner). Settings → CI/CD → Variables → Add variable:
| Поле | Для секрета |
|---|---|
| Key | Имя вроде DB_PASSWORD, без пробелов |
| Value | Само значение; в статью его не копируйте |
| Type | Variable для пароля; File для закрытого ключа (в job это путь к файлу) |
| Protect variable | Включите, если секрет только для защищённых веток и тегов (продуктив) |
| Visibility | Masked, не Visible. Если в вашей версии GitLab есть Masked and hidden — лучше оно: значение потом не открыть в UI |
| Environment scope | Для продакшена — конкретная среда, не *, если среды разведены |
В .gitlab-ci.yml в блоке variables: оставляйте только несекретное: имена образов, флаги, URL без пароля. Документация прямо говорит: чувствительные значения — в UI, не в YAML. Риск: merge request из форка, запущенный в родительском проекте, получает переменные родителя. Не гоняйте такие pipeline к продуктивным секретам; форкам оставляйте только тестовый контур.
Как использовать в job
Подставляйте имя переменной, не печатайте значение. Включите маскирование: GitLab прячет точное совпадение в журнале, но куски и set -x его показывают. Перед работой с секретом отключите трассировку:
deploy:
stage: deploy
script:
- set +x
- test -n "$DB_PASSWORD"
- echo "password is set, length suppressed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
environment: production
Не пишите echo "$DB_PASSWORD", не передавайте секрет в curl через query string, не кладите его в артефакты job. Проверка «переменная задана» — непустой test -n, без вывода значения. Закрытый ключ типа File читайте как файл: chmod 600 "$SSH_KEY_FILE", не копируйте содержимое в переменную окружения повторно.
Токены SonarQube и Dependency-Track в том же UI, с Masked и Protected — как в статье про GitLab CI. Перед выкладкой команда ещё раз смотрит, что секретов нет в образе: чек-лист релиза, Trivy.
Кто видит и когда менять
Кто может читать и менять переменные проекта — Maintainer. Не раздавайте эту роль всем разработчикам «чтобы самим крутить CI». Заявку на доступ к продуктовому проекту оформляйте как доступ к системе: заявка на доступ. При увольнении или смене подрядчика ротируйте токены, которые этот человек мог увидеть в UI или в незамаскированном журнале — удаление учётки GitLab само пароль базы не меняет.
Отдельный секрет на среду: тестовый пароль не равен боевому. Иначе утечка стенда открывает продуктив. Боевые ПДн на стенде — другая ошибка: тестовые данные.
Vault, облачный менеджер секретов и динамические пароли на каждый job — следующий уровень. Пока его нет, GitLab Variables с Protect и Mask — гигиена. Не держите второй «настоящий» список секретов в таблице Confluence.
Диагностика
| Симптом | Причина и что делать |
|---|---|
Job на feature-ветке не видит DB_PASSWORD |
Включён Protect, ветка не protected. Либо снимите Protect для тестового секрета, либо не требуйте боевой пароль на feature |
| В журнале виден пароль кусками | Маска ловит только полное совпадение. Уберите set -x и echo |
| Секрет в YAML «на время отладки» | Он уже в git. Отзовите, перенесите в UI, дальше — статья про секреты в истории |
| Все разработчики — Maintainer | Любой может прочитать Visible и часть Masked в настройках. Сузьте роли |
Где это в модели зрелости
Материал относится к направлению «Безопасность данных» (хаб: данные, карта: карта зрелости). Для SSDLC это гигиена конвейера: секрет не в репозитории и не в журнале job. Процесс находок сканера — рядом: SAST и SCA как процесс. Это не менеджер паролей сотрудников и не DLP.
Как проверить
- В репозитории нет файлов
.envс значениями; в YAML нет паролей и токенов. - Боевые переменные: Masked (или hidden) и Protected, среда не
*, если среды разделены. - В журнале последнего деплоя нет значения секрета;
set -xперед работой с ним выключен. - Список Maintainer проекта короткий и совпадает с теми, кому можно видеть продуктивные секреты.
- После ухода человека, который был Maintainer, токены деплоя и пароль, который он мог знать, сменены.
Источники
- GitLab Docs, CI/CD variables: docs.gitlab.com/ci/variables.
- OWASP Secrets Management Cheat Sheet: cheatsheetseries.owasp.org.


















