Как управлять чувствительными данными (секретами) в CI/CD пайплайне GitLab?

«Как управлять чувствительными данными (секретами) в CI/CD пайплайне GitLab?» — вопрос из категории CI/CD, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Управление секретами — критически важная часть безопасного CI/CD. В GitLab я использую многоуровневый подход, чтобы токены, ключи и пароли никогда не попадали в исходный код.

1. GitLab CI/CD Variables (основной метод): Это встроенный и самый простой способ. В настройках проекта (Settings > CI/CD > Variables) создаю переменные:

  • Тип Variable: Для строковых значений (например, DOCKER_REGISTRY_PASSWORD, AWS_ACCESS_KEY_ID).
    • Обязательно ставлю галочку Mask variable, чтобы значение скрывалось в логах заданий.
    • Для секретов, связанных с защищенными ветками (prod), также ставлю Protect variable. Тогда переменная будет доступна только в пайплайнах, запущенных из этих веток или тегов.
  • Тип File: Для целых файлов (например, сертификаты .pem, JSON-ключи сервисного аккаунта). GitLab сохранит содержимое в файл, путь к которому будет доступен через переменную.

Пример использования в .gitlab-ci.yml:

deploy_to_production:
  stage: deploy
  rules:
    - if: $CI_COMMIT_TAG  # Запускать только для тегов
  script:
    # Переменная File type будет доступна как файл
    - export GOOGLE_APPLICATION_CREDENTIALS=${SERVICE_ACCOUNT_KEY_JSON}
    # Использование обычной masked переменной
    - echo "Deploying with user $(echo $DEPLOY_USER | sed 's/@/ at /')"  # Маскировка в логах
    - kubectl set image deployment/my-app app=my-registry/app:$CI_COMMIT_TAG --token=$K8S_SA_TOKEN
  variables:
    KUBECONFIG: "$KUBE_CONFIG_FILE"  # KUBE_CONFIG_FILE — переменная типа File

2. HashiCorp Vault (для enterprise-среды): Для централизованного хранения секретов многих проектов интегрирую GitLab с Vault.

  • Настраиваю аутентификацию JWT: GitLab CI/CD предоставляет CI_JOB_JWT, который пайплайн может использовать для запроса секретов из Vault.

Пример этапа получения секрета из Vault:

.get_secret_from_vault:
  script:
    - |
      # Аутентификация в Vault и получение секрета
      VAULT_TOKEN=$(curl -s --request POST 
        --data "{"jwt": "$CI_JOB_JWT", "role": "$VAULT_ROLE"}" 
        $VAULT_ADDR/v1/auth/jwt/login | jq -r .auth.client_token)

      DATABASE_PASSWORD=$(curl -s --header "X-Vault-Token: $VAULT_TOKEN" 
        $VAULT_ADDR/v1/secret/data/myproject/db | jq -r .data.data.password)

      # Экспортируем в переменную, но не логируем
      export DATABASE_PASSWORD
  artifacts:
    reports:
      dotenv: vault.env  # Можно передать секреты между джобами через артефакт типа dotenv

3. External Secrets Operator (для Kubernetes): Если конечная цель — деплой в Kubernetes, то вместо передачи секретов через пайплайн я часто использую паттерн, когда пайплайн ничего о секретах не знает.

  • Секреты хранятся в Vault или облачном менеджере (AWS Secrets Manager).
  • В кластере Kubernetes установлен External Secrets Operator, который автоматически синхронизирует секреты из внешнего хранилища в Kubernetes Secrets.
  • Пайплайн GitLab просто применяет манифесты приложения, которые ссылаются на эти уже созданные Secrets.

4. Критически важные практики безопасности:

  • Никогда не использовать set -x (режим отладки bash) в скриптах, работающих с секретами.
  • Использовать rules:if или workflow:rules для строгого ограничения этапов, где используются защищенные переменные, только определенными ветками.
  • Регулярно ротировать секреты (токены, ключи) и использовать автоматические процессы для их обновления в GitLab Variables.