company_banner

Аутентификация и чтение секретов в HashiCorp's Vault через GitLab CI

Автор оригинала: GitLab Documentation
  • Перевод
  • Tutorial

Доброго времени суток, читатель!


22 апреля в GitLab выпустили релиз 12.10 и сообщили о том, что теперь CI-процесс может авторизовываться в Hashicorp's Vault через JSON Web Token (JWT), и для авторизации нет необходимости хранить токен для доступа к нужным policy в переменных окружения (или где-либо ещё).



Данная фича показалась нам полезной, поэтому предлагаем перевод соотвествующего туториала из официальной документации GitLab:




Этот туториал демонстрирует пример аутентификации, конфигурации и чтения секретов с HashiCorp’s Vault через GitLab CI/CD.


Требования


Туториал предполагает, что вы знакомы с GitLab CI/CD и Vault.


Чтобы ему последовать, вам понадобятся:


  • Аккаунт в GItLab
  • Запущенный Vault сервер и доступ для настройки аутентификации и создания ролей и политик.

Примечание: вам нужно заменить URL vault.example.com на URL вашего Vault сервера и gitlab.example.com на название вашего GitLab инстанса.


Как это работает


Для каждой задачи (job) генерируется свой уникальный токен JSON Web Token (JWT), доступный только как значение переменной окружения CI_JOB_JWT конкретной задачи. Данный JWT может быть использован для аутентификации в Vault при помощи метода JWT Auth.


Пример того, как выглядит JWT в дешифрованном виде:


{
  "jti": "c82eeb0c-5c6f-4a33-abf5-4c474b92b558", # Уникальный идентификатор токена
  "iss": "gitlab.example.com",                   # Issuer, т.е. домен вашего инстанса GitLab
  "iat": 1585710286,                             # Время выдачи 
  "nbf": 1585798372,                             # Не валиден до
  "exp": 1585713886,                             # Не велиден после
  "sub": "22",                                   # Subject (project id)
  "namespace_id": "1",
  "namespace_path": "mygroup",
  "project_id": "22",
  "project_path": "mygroup/myproject",
  "user_id": "42",
  "user_login": "myuser",
  "user_email": "myuser@example.com"
  "pipeline_id": "1212",
  "job_id": "1212",
  "ref": "auto-deploy-2020-04-01",               # Название Git-refs для этой задачи
  "ref_type": "branch",                          # Тип Git-refs, это либо(branch) либо тег(tag)
  "ref_protected": "true"                        # true, если это ветка protected, иначе false
}  

Токен кодируется по стандарту RS256 и подписывается приватным ключом OpenID Connect вашего GitLab инстанса, причем этот ключ периодически изменяется без вашего ведома. И, если приватный ключ был изменен, при cледующем запуске задания новый JWT будет подписан с новым ключом. Срок валидности токена будет устанавливаться в соотвествии с таймаутом вашего задания, а если он не задан, то срок валидности будет 5 минут.


Вы можете использовать этот JWT-токен и URL (https://gitlab.example.com/-/jwks) как конечную точку JWKS для аутентификации на Vault сервере, если для него настроен соответствующий метод JWT-аутентификации.


При настройке ролей в Vault, вы можете задавать значения bound_claims для сопоставления их с полями из JWT и, таким образом, дополнительно ограничить то, к каким секретам какой процесс CI будет иметь доступ.


Для получения доступа к Vault можно использовать либо его CLI, либо выполнять запросы к API (через curl или другой клиент).


Пример


Предположим, у вас есть пароли для ваших баз данных, отличающиеся для production и staging окружений и они хранятся в Vault, который доступен по адресу http://vault.example.com:8200. Ваш пароль для stage это pa$$w0rd и real-pa$$w0rd для prod:


$ vault kv get -field=password secret/myproject/staging/db
pa$$w0rd

$ vault kv get -field=password secret/myproject/production/db
real-pa$$w0rd

Разрешим для нашего Vault-сервера метод аутентификации через JWT:


$ vault auth enable jwt
Success! Enabled jwt auth method at: jwt/

Создаем policy, которые дают доступ на чтение к нужным нам секретам:


$ vault policy write myproject-staging - <<EOF
# Policy name: myproject-staging
#
# Read-only permission on 'secret/data/myproject/staging/*' path
path "secret/data/myproject/staging/*" {
  capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-staging

$ vault policy write myproject-production - <<EOF
# Policy name: myproject-production
#
# Read-only permission on 'secret/data/myproject/production/*' path
path "secret/data/myproject/production/*" {
  capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-production

Теперь нам нужны роли, которые будут связывать созданные политики с JWT-токенами.


Для stage под названием myproject-staging:


$ vault write auth/jwt/role/myproject-staging - <<EOF
{
  "role_type": "jwt",
  "policies": ["myproject-staging"],
  "token_explicit_max_ttl": 60,
  "user_claim": "user_email",
  "bound_claims": {
    "project_id": "22",
    "ref": "master",
    "ref_type": "branch"
  }
}
EOF

И для production под названием myproject-production:


$ vault write auth/jwt/role/myproject-production - <<EOF
{
  "role_type": "jwt",
  "policies": ["myproject-production"],
  "token_explicit_max_ttl": 60,
  "user_claim": "user_email",
  "bound_claims_type": "glob",
  "bound_claims": {
    "project_id": "22",
    "ref_protected": "true",
    "ref_type": "branch",
    "ref": "auto-deploy-*"
  }
}
EOF

В этом примере использовались bound_claims указывающие, что только JWT-токены с соответствующими значениями смогут пройти аутентификацию.


В сочетании с GitLab protected branches, можно разграничивать тех, кто может проходить аутентификацию и читать секреты.


Token_explicit_max_ttl определяет, что выпущенный Vault токен, после аутентификации имеет время жизни 60 секунд.


User_claim определяет имя пользователя, которое будет использовать Vault при успешной авторизации. (Прим. переводчика — В нашем случае "user_claim" = "user_email", то есть имя пользователя будет иметь значение user_email из JWT-токена. То есть определяться как (известный GitLab) email пользователя, запустившего задание.)


Параметр bound_claims_type задает формат значений bound_claims. Если он установлен в “glob”, то значения будут интерпретироваться в формате glob и * будет означать любое количество символов. (Прим. переводчика — Также доступно значение “string”, в этом случае данные будут хранится в формате строк и * будет означать только *.)
Полный лист опций можно посмотреть в Vault’s Create Role documentation.


Предупреждение: обязательно устанавливайте либо project_id либо namespace_id, иначе любой токен, сгенерированный этим инстансом GitLab, сможет использовать эту роль.


Теперь зададим метод аутентификации через JWT:


$ vault write auth/jwt/config \
    jwks_url="https://gitlab.example.com/-/jwks" \
    bound_issuer="gitlab.example.com"

bound_issuer определяет, что только токены, выпущенные от имени домена gitlab.example.com (iss claim) могут использовать этот метод аутентификации, и в качестве точки валидации токена JWKS должна использоваться страница https://gitlab.example.com/-/jwks .


Полный список доступных опций можно посмотреть в Vault’s API documentation.


Теперь создадим джобу для master-ветки, у которой будут права на чтение secret/myproject/staging/, но при этом не будет прав на чтение secret/myproject/production/:


read_secrets:
  script:
    # Проверяем имя ref джобы
    - echo $CI_COMMIT_REF_NAME
    # и является ли она protected
    - echo $CI_COMMIT_REF_PROTECTED
    # Адрес Vault может быть передан через переменную в CI
    - export VAULT_ADDR=http://vault.example.com:8200
    # Проходим аутентификацию и получаем токен. Время истечения жизни токена и другие  
    # его параметры можно задать при конфигурации 
    # JWT Auth - https://www.vaultproject.io/api/auth/jwt#parameters-1
    - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-staging jwt=$CI_JOB_JWT)"
    # Теперь используем VAULT_TOKEN для чтения секретов и сохранения их в перемнных окружения
    - export PASSWORD="$(vault kv get -field=password secret/myproject/staging/db)"
    # Используем секрет
    - echo $PASSWORD
    # Здесь получить секрет не получится, потому что роль myproject-staging не может 
    # читать секреты из secret/myproject/production/*
    - export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"


Следующее задание сможет пройти аутентификацию через роль myproject-production и прочитать секрет в /secret/myproject/production/:


read_secrets:
  script:
    # Проверяем имя ref джобы
    - echo $CI_COMMIT_REF_NAME
    # и является ли она protected
    - echo $CI_COMMIT_REF_PROTECTED
    # Адрес Vault может быть передан через переменную в CI
    - export VAULT_ADDR=http://vault.example.com:8200
    # Проходим аутентификацию и получаем токен. Время истечения жизни токена и другие  
    # его параметры можно задать при конфигурации 
    # JWT Auth - https://www.vaultproject.io/api/auth/jwt#parameters-1
    - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-production jwt=$CI_JOB_JWT)"
    # Теперь используем VAULT_TOKEN для чтения секретов и сохранения их в перемнных окружения
    - export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
    # Используем секрет
    - echo $PASSWORD




На этом все, надеюсь данный туториал окажется вам полезен!


Мы также ждем следующей фичи GitLab, где можно будет авторизовываться в Vault средствами GitLab в версии 13.4, которая выйдет 22 сентября 2020.


Также читайте другие статьи в нашем блоге:


Nixys
Эксперты в DevOps и Kubernetes

Комментарии 19

    +2
    Большое спасибо за статью!
    К сожалению, не так много материалов про волт, в которых бы освещались аспекты его повседневного использования в инфраструктуре, поэтому очень хочется спросить вас, как живого пользователя, а насколько давно и насколько интенсивно вы используете волт? Какие его фичи, кроме секретов используете? Как вы работаете с аудитом волта?
      0
      Алина просто переводчик, думаю вам стоит попробовать самостоятельно интегрировать аудит с вашим решением. Мы сейчас работаем над полноценным внедрением, у нас планируется хранение PKI и секретов, аутентификация на базе АД и несколько вспомогательных механизмов для автоматизации.
        0
        Я понимаю, что Алина переводчик, но она пишет в блоге компании, в которой, насколько я понял, работают девопсы, использующие волт, мои вопросы и были адресованы им — пользователям волта, именно это я имел ввиду под живым пользователем — компанию, которая использует волт :)
        То есть, как я понимаю, в проде волта у вас ещё нет?
        Аудит волта, как мне кажется — не такое простое дело, особенно, если хочется не просто его запустить и сливать логи в какой-нибудь эластик, а реально понимать, что происходит, поэтому мне было бы очень интересно посмотреть на опыт коллег, о чём я, в числе прочего, и поинтересовался.
          +4
          Нет, я инженер.
            0
            Извините, если обидел, перевёл статью — значит переводчик:)
        +1
        Рада, что вам было полезно!

        В нашей компании Vault используется уже достаточно давно, около года назад. В основном используем его, чтобы хранить именно секреты (пароли, ключи, сертификаты) для Kubernetes с возможностью их оперативного изменения и деплоя через GitLab CI. К помощи Vault Audit прибегаем редко, только если когда непосредственно посмотреть лог доступа за определенный период.
          +3
          Вот это было бы очень интересно почитать или послушать:) Нет планов написать про это статью?
            +1
            В планах не было, но идея интересная, возможно, выпустим такую статью в ближайшее время.
          0

          На работе мы перешли на Vault (3 x ECS-контейнера + DynamoDB на AWS) с gopass+gpg2+git чуть больше полугода назад. Vault — хорошая штука по многим аспектам:


          • скорость: судя по нагрузочным тестам, наш довольно скромный setup легко потянет тысячи запросов в секунду, как на чтение, так и на запись
          • интеграция: Terraform (само собой), Ansible, теперь вот и GitLab нативно и мн. др.
          • аутентификация/авторизация: удобная SSO-интеграция, роли и т.п.

          Но вот что реально напрягает, так это отсутствие встроенного поиска. Есть несколько сторонних проектов, реализующих поиск на клиентской стороне путём обхода всего дерева, но, блин, это как-то тупо. Понятно, что поиск — это не просто так, что каждая policy задаёт ограничения на то, где и что можно искать и показывать. Но, всё-таки, без поиска жить очень неудобно.

            0
            А у вас нет планов напиать про это статю?:)
              0

              Нет, извините :). Чем мог, уже поделился в комментарии :))

          –3

          Что-то как-то не очень понятно — а от чего защищаемся-то? Что и от кого пытаемся скрыть?
          А то пока что выглядит как театр безопасности.

          • НЛО прилетело и опубликовало эту надпись здесь
              0

              То есть по сути дела мы защищаем пароли от production-а от своих собственных сотрудников?

              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Например, хакеры могут угнать учётку сотрудника.
              0
              Кстати после этой статьи может возникнуть вопрос как передавать секреты между стейджами.
              Кроме как использовать кэш не нашел ничего.
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  можно и так, но надо пакет vault ставить каждый раз
                  было бы удобнее один раз всё определить

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое