В 2023 году исследователи обнаружили в публичных GitHub-репозиториях более 10 миллионов секретов: API-ключей, токенов, паролей от баз данных и облачных сервисов. Большинство из них были залиты туда случайно — разработчик закоммитил .env файл, забыл удалить переменную с токеном из конфига, оставил credentials в скрипте деплоя.
Последствия предсказуемы: несанкционированный доступ к production-среде, утечка данных клиентов, компрометация облачной инфраструктуры. И всё это — из-за одного неосторожного коммита.
Хранение секретов — одна из самых болезненных тем в DevOps. Давление на разработчика огромное: нужно быстро, пайплайн должен работать, дедлайн горит. В таких условиях .env в репозитории кажется быстрым решением. И это решение регулярно приводит к инцидентам.
В этой статье разбираем, почему типичные подходы не работают, какие инструменты решают задачу правильно и как выстроить безопасное управление секретами в CI/CD без значительных накладных расходов на разработку.
Что такое секреты в контексте CI/CD
Секреты — это любые данные, которые предоставляют доступ к системам или ресурсам и не должны быть доступны посторонним. В контексте CI/CD это:
Учётные данные для доступа к инфраструктуре — пароли от баз данных, SSH-ключи для подключения к серверам, учётные данные для доступа к Kubernetes-кластеру.
API-ключи и токены — ключи для доступа к внешним сервисам: платёжные системы, сервисы отправки почты, аналитические платформы, облачные провайдеры.
Ключи шифрования и сертификаты — ключи для шифрования данных, SSL-сертификаты, подписи для JWT-токенов.
Конфигурационные данные — строки подключения к базам данных, URL внутренних сервисов, параметры облачных окружений.
Все эти данные нужны пайплайну в момент сборки, тестирования или деплоя. Задача — передать их в пайплайн так, чтобы они не оказались в репозитории, в логах и не стали доступны людям, которым они не нужны.
Почему типичные подходы не работают
Секреты в коде и конфигах
Самый распространённый антипаттерн. Пароль от базы данных прямо в config.py, токен API в docker-compose.yml, ключ AWS в скрипте деплоя. Быстро, удобно — и катастрофически небезопасно.
Проблема не только в том, что это попадает в репозиторий. Даже если репозиторий приватный, любой разработчик с доступом к коду видит все секреты. При смене сотрудника нужно менять все секреты, к которым он имел доступ. История коммитов хранит старые значения навсегда — даже если вы удалили файл в следующем коммите, секрет останется в истории.
Переменные окружения без управления
Переменные окружения лучше, чем секреты в коде, но не решают проблему полностью. Кто устанавливал эти переменные? Когда? На каких серверах они актуальны? Кто имеет к ним доступ? Как они передаются новому разработчику или новому серверу?
Без централизованного управления переменные окружения превращаются в хаос: одни актуальны, другие устарели, третьи дублируются с разными значениями на разных серверах. При инциденте невозможно понять, откуда утёк конкретный секрет.
Встроенные секреты CI/CD платформ
GitHub Secrets, GitLab CI Variables, Bitbucket Secured Variables — это лучше, чем предыдущие варианты. Секреты хранятся отдельно от кода, не отображаются в логах, доступны только через переменные окружения во время выполнения пайплайна.
Но у этого подхода есть ограничения. Секреты привязаны к конкретной платформе — при миграции с GitHub на GitLab всё нужно переносить вручную. Нет централизованного аудита — невозможно увидеть, кто и когда обращался к секрету. Нет ротации — секреты живут до тех пор, пока их не обновят вручную. Нет разграничения доступа на уровне отдельных секретов.
Для небольших команд и некритичных пайплайнов это приемлемо. Для production-инфраструктуры с требованиями к аудиту — недостаточно.
Правильные подходы к хранению секретов
Принцип минимальных привилегий для секретов
Каждый пайплайн должен иметь доступ только к тем секретам, которые необходимы именно ему. Пайплайн сборки фронтенда не должен видеть ключи от production-базы данных. Пайплайн тестирования должен работать с тестовыми учётными данными, а не с production.
На практике это означает: создавайте отдельные сервисные аккаунты для каждого пайплайна или группы пайплайнов с минимально необходимыми правами. Не используйте один «суперключ» для всего.
Ротация секретов
Долгоживущие секреты — это риск. Чем дольше токен действует, тем выше вероятность, что он попадёт не туда или будет скомпрометирован незаметно.
Выстраивайте процесс регулярной ротации: меняйте секреты по расписанию (например, раз в 90 дней) и немедленно при подозрении на компрометацию. Хорошие инструменты управления секретами поддерживают автоматическую ротацию.
Аудит доступа к секретам
Любое обращение к секрету должно фиксироваться: какой пайплайн, когда, с какого хоста. Это позволяет обнаружить аномалии — например, обращение к production-секрету из тестового пайплайна или необычно частые запросы к конкретному ключу.
Для серьёзной работы с секретами нужен специализированный инструмент. Он решает задачи хранения, разграничения доступа, аудита и ротации в одном месте.
HashiCorp Vault — де-факто стандарт в западной практике. Мощный инструмент с поддержкой динамических секретов, множеством бэкендов хранения и гибкой моделью доступа. Сложен в эксплуатации, требует отдельного специалиста для поддержки.
Корпоративный менеджер паролей с API. Для многих компаний полноценный Vault — избыточное решение. Корпоративный менеджер паролей с документированным API закрывает большинство задач: централизованное хранение, RBAC, аудит доступов, интеграция с пайплайнами. При этом тот же инструмент используют все остальные сотрудники для обычных паролей — одна система вместо двух.
Как интегрировать BearPass с CI/CD пайплайном
BearPass предоставляет документированный REST API с JWT-авторизацией, который позволяет получать секреты из менеджера паролей напрямую в пайплайне — без хранения значений в переменных окружения платформы или в коде.
Общая схема работы:
Создаёте в BearPass отдельную папку для секретов конкретного пайплайна или проекта. Создаёте сервисную учётную запись с доступом только к этой папке. Пайплайн в начале выполнения запрашивает секреты через API, используя токен сервисной учётки. Секреты передаются в переменные окружения только для текущего запуска — нигде не сохраняются.
Пример на Python — получение секрета из BearPass:
python
import requests
import os
def get_secret(item_name: str) -> str:
"""
Получить значение пароля из BearPass по названию элемента.
Токен передаётся через переменную окружения BEARPASS_TOKEN.
"""
base_url = os.environ["BEARPASS_URL"] # например https://bearpass.company.ru
token = os.environ["BEARPASS_TOKEN"]
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
# Поиск элемента по названию
response = requests.get(
f"{base_url}/api/items",
headers=headers,
params={"search": item_name},
)
response.raise_for_status()
items = response.json().get("data", [])
if not items:
raise ValueError(f"Секрет '{item_name}' не найден в BearPass")
# Получение значения пароля
item_id = items[0]["id"]
secret_r = requests.get(
f"{base_url}/api/items/{item_id}/password",
headers=headers,
)
secret_r.raise_for_status()
return secret_r.json()["password"]
# Использование в пайплайне
if __name__ == "__main__":
db_password = get_secret("Production DB Password")
api_key = get_secret("Payment Gateway API Key")
# Устанавливаем в переменные окружения для текущего процесса
os.environ["DB_PASSWORD"] = db_password
os.environ["PAYMENT_KEY"] = api_key
print("Секреты загружены успешно")
Пример на Bash — для shell-скриптов деплоя:
bash
#!/bin/bash
# Получение секрета из BearPass через API
# Переменные BEARPASS_URL и BEARPASS_TOKEN передаются через CI/CD платформу
get_secret() {
local item_name="$1"
local search_url="${BEARPASS_URL}/api/items?search=${item_name}"
export DB_PASSWORD=$(get_secret "Production DB Password")
export SSH_KEY=$(get_secret "Deploy SSH Key")
echo "Секреты загружены, начинаем деплой"
Пример шага в GitLab CI:
yaml
# .gitlab-ci.yml
deploy_production:
stage: deploy
image: python:3.11-slim
before_script:
- pip install requests
script:
- python scripts/load_secrets.py # загружает секреты из BearPass в env
- ./deploy.sh
variables:
# Только токен и URL — никаких реальных секретов в репозитории
BEARPASS_URL: "https://bearpass.company.ru"
BEARPASS_TOKEN: $BEARPASS_SERVICE_TOKEN # добавлен в GitLab CI Variables
only:
- main
Таким образом в репозитории нет ни одного реального секрета — только URL и сервисный токен с ограниченными правами. Все фактические значения хранятся в BearPass и загружаются только в момент выполнения пайплайна.
Практические правила безопасного управления секретами
Никогда не коммитить секреты. Установите pre-commit hook с инструментом типа detect-secrets или trufflehog — он проверяет каждый коммит на наличие паролей, ключей и токенов до того, как они попадут в репозиторий. Это простая и эффективная первая линия защиты.
Разделяйте окружения. У каждого окружения — development, staging, production — должны быть отдельные секреты. Пайплайн для тестирования никогда не должен иметь доступ к production-учётным данным.
Используйте короткоживущие токены там, где это возможно. Вместо долгосрочного API-ключа используйте токены с ограниченным временем жизни. Если такой токен утечёт — его ценность быстро истекает.
Не выводите секреты в логи. Проверьте, что ваш пайплайн не логирует значения переменных окружения. Большинство CI/CD платформ маскируют переменные, помеченные как секретные, — убедитесь, что эта опция включена.
Документируйте, какие секреты используются в каком пайплайне. При смене секрета вы должны знать, какие пайплайны нужно обновить. Без документации это становится угадыванием.
Проверяйте секреты на компрометацию. Регулярно проверяйте используемые токены и API-ключи по базам публичных утечек. BearPass делает это автоматически — мониторинг даркнета включён в функционал.
Что делать если секрет уже попал в репозиторий
Если вы обнаружили, что секрет закоммичен в репозиторий — даже в приватный — действуйте немедленно.
Первым делом отзовите скомпрометированный секрет: сбросьте пароль, деактивируйте API-ключ, выпустите новый токен. Делайте это прежде чем что-либо другое — пока вы читаете следующий шаг, кто-то уже может сканировать репозитории в поисках активных ключей.
Затем очистите историю репозитория. Простое удаление файла в новом коммите не помогает — секрет остаётся в истории. Используйте git filter-branch или инструмент BFG Repo-Cleaner для удаления секрета из всей истории. После этого сделайте force push.
Проверьте логи доступа к системе, к которой был секрет. Если есть подозрение на использование — проведите полноценное расследование инцидента.
Итог
Безопасное управление секретами в CI/CD — это не разовая настройка, а постоянный процесс. Минимальный набор правил: секреты никогда не попадают в код и репозиторий, каждый пайплайн имеет доступ только к нужным секретам, все обращения к секретам фиксируются, секреты регулярно ротируются.
Централизованный менеджер паролей с API — оптимальный инструмент для компаний, которым нужно закрыть и задачу управления секретами в DevOps, и задачу корпоративного управления паролями в одном решении. Не нужно разворачивать и поддерживать отдельный Vault — тот же BearPass, которым пользуется вся команда, через API обслуживает и пайплайны.