При настройке CI/CD для приложений мы в компании, как правило, используем Vault от HashiCorp. К тому же сами приложения зачастую используют Vault для хранения секретных данных. Но для того, чтобы начать использовать Vault, сначала необходимо произвести его настройку. Раньше нам приходилось вручную производить подобные манипуляции для каждого проекта, что отнимало немало времени. Найти готовый модуль, который подошел бы нам, не удалось. Поэтому мы написали свой terraform-модуль, который позволяет автоматизировать данный процесс.
На текущий момент созданный нами terraform-модуль покрывает все наши требования, но в дальнейшем он будет дорабатываться. Модуль позволяет создавать политики доступа и хранилища секретов, включать необходимые способы авторизации (certs, jwt, oidc, kubernetes и т.д.), создавать токены, jwt/oidc/token-роли и идентификационные группы. Также при помощи него можно генерировать TLS-сертификаты и jwt ключи в определенной директории.
Модуль можно найти в нашем GitHub-репозитории. Чтобы использовать его, потребуется terraform >= 1.0.0 и Vault свежей версии, так как в старых версиях не поддерживаются некоторые функции.
Описание переменных
Кому интересно описание переменных, можно посмотреть в спойлере ниже. Полное описание переменных доступно в README.
переменные
output_files_path: путь до директории, в которой сохраняются сгенерированные TLS-сертификаты и jwt ключи. По умолчанию: "./files".
vault_addr: адрес Vault сервера.
vault_admin_token: токен с административными правами.
vault_skip_tls_verify(опционально): установите для этого параметра значение true, чтобы отключить проверку TLS-сертификата для сервера Vault. По умолчанию: "false".
policies: список политик доступа, которые будут созданы.
name: имя политики.
paths: список путей до хранилищ секретов.
capabilities: список разрешений для политики.
secrets_engines: список хранилищ секретов, которые необходимо создать.
path: путь для хранилища.
type: тип хранилища.
options(опционально): опции для хранилища.
auth_backends: список способов для авторизации, которые необходимо включить.
type: тип бэкенда авторизации.
path(опционально): путь до бэкенда. Значение по умолчанию равно полю type.
tune(опционально): настройки для бэкенда.
Значение переменной type не должно равняться "kubernetes", "jwt", "oidc". Данные способы авторизации описаны ниже.
jwt_oidc_auth_backend: список jwt/oidc бэкендов авторизации.
path: путь для бэкенда.
type(опционально): тип авторизации. По умолчанию: "jwt".
Остальные переменные можно посмотреть в README. Описание переменных соответствует описанию в документации.
Для одного бэкэнда должна быть установлена только одна из переменных (jwks_url, oidc_discovery_url, jwt_validation_pubkeys).
certs_auth_backend: список бэкендов авторизации через сертификаты.
name: имя сертификата.
policies: список политик, применяемых для сертификата.
path(опционально): путь до бэкенда авторизации. По умолчанию: "cert".
tls_self_signed_cert: информация о сертификате.
common_name(опционально): общее имя сертификата.
organization(опционально): организация.
allowed_uses: список разрешений для выданного сертификата.
convert_to_pkcs12: необходимо ли сгенерировать сертификат в формате PKCS12. По умолчанию: "false".
Авторизация через сертификаты должна быть включена в auth_backends с соответствующим путем path.
Чтобы использовать этот метод авторизации, параметр "tls_disable" должен иметь значение false в конфигурации Vault. Это связано с тем, что сертификаты передаются через TLS-соединение.
kubernetes_auth_backends: список бэкендов авторизации для kubernetes.
path(опционально): путь до бэкенда авторизации. По умолчанию: "kubernetes".
kubernetes_host: переменная должна быть строкой хоста, парой host:port или URL адресом Kubernetes API.
kubernetes_ca_cert(опционально): CA сертификат в кодировке PEM для использования клиентом TLS-соединения с Kubernetes API.
token_reviewer_jwt(опционально): JWT сервисного аккаунта, используемый для доступа к TokenReview API для проверки других JWT во время входа в систему. Если не установлено, то для доступа к API будет использоваться JWT, используемый для входа.
issuer(опционально): издатель токена.
roles(опционально): список ролей, привязываемых к бэкенду.
role_name: название роли.
bound_service_account_names: список сервисных аккаунтов, которые могут получить доступ к этой роли. Если установлено значение ["*"], разрешены все аккаунты. Данный параметр и bound_service_account_namespaces не могут быть равны "*" одновременно.
bound_service_account_namespaces: список пространств имен, которым разрешен доступ к этой роли. Если установлено значение ["*"], все пространства имен разрешены. Для данного параметра и для bound_service_account_names нельзя установить значение "*" одновременно.
token_policies(опционально): список политик, которые привязываются к сгенерированным токенам.
tune(опционально): настройки для бэкенда авторизации.
vault_tokens: список токенов.
display_name: отображаемое имя токена.
policies: политики, привязываемые к токену.
Остальные переменные можно посмотреть в README. Описание переменных соответствует описанию в документации.
vault_token_roles: список ролей для токенов.
role_name: имя роли.
Остальные переменные можно посмотреть в README. Описание переменных соответствует описанию в документации.
jwt_oidc_roles: список jwt/oidc ролей.
name: имя роли.
path: путь до бэкенда авторизации.
type(опционально): тип роли.
Остальные переменные можно посмотреть в README. Описание переменных соответствует описанию в документации.
Если бэкенд авторизации не был создан при первом запуске из-за каких-то ошибок, его необходимо удалить вручную и создать заново. Проблема описана на форуме.
identity_groups: список идентификационных групп.
group_id: имя группы.
type(опционально): тип группы, "internal" или "external". По умолчанию: "internal".
policies(опционально): список политик, которые применяются к группе.
mount_type: тип бэкенда авторизации.
mount_path: путь до бэкенда.
metadata(опционально): словарь дополнительных метаданных для группы.
Пример использования модуля
Рассмотрим конкретный пример по использованию модуля:
module "vault-init" {
source = "../vault-init"
output_files_path = "./files"
vault_addr = "https://127.0.0.1:8200"
vault_admin_token = "token"
vault_skip_tls_verify = true
policies = [
{
name = "policy-1"
paths = ["kv-1", "kv-1/*"]
capabilities = ["create", "read", "update", "list"]
},
{
name = "policy-2"
paths = ["kv-2", "kv-2/*"]
capabilities = ["read", "list"]
}
]
secrets_engines = [
{
path = "kv-1"
type = "kv"
options = {
version = "2"
}
},
{
path = "kv-2"
type = "kv-v2"
}
]
auth_backends = [
{
type = "cert"
path = "cert"
}
]
jwt_oidc_auth_backend = [
{
path = "gitlab_jwt"
type = "jwt"
jwks_url = "https://gitlab.example.com/-/jwks/"
bound_issuer = "gitlab.example.com"
},
{
path = "oidc"
type = "oidc"
oidc_discovery_url = "https://login.microsoftonline.com/12324436534/v2.0"
oidc_client_id = "123"
oidc_client_secret = "secret"
default_role = "azure_users"
},
{
type = "jwt"
path = "jwt-pubkeys"
jwt_validation_pubkeys = ["jwt-keys-1", "jwt-keys-2"]
tune = {
default_lease_ttl = "10m"
max_lease_ttl = "10m"
}
}
]
certs_auth_backend = [
{
name = "cert1"
policies = ["policy-1"]
path = "cert"
tls_self_signed_cert = {
common_name = "Cert1"
organization = "Organization1"
allowed_uses = ["key_encipherment", "digital_signature", "server_auth", "client_auth", "cert_signing"]
convert_to_pkcs12 = true
}
}
]
kubernetes_auth_backends = [
{
path = "kubernetes"
kubernetes_host = "https://example.com"
kubernetes_ca_cert = "-----BEGIN CERTIFICATE-----\nexample\n-----END CERTIFICATE-----"
token_reviewer_jwt = "ZXhhbXBsZQo="
issuer = "kubernetes.io/serviceaccount"
roles = [
{
role_name = "role1"
bound_service_account_names = ["deployer"]
bound_service_account_namespaces = ["*"]
token_policies = ["policy-1"]
},
{
role_name = "role2"
bound_service_account_names = ["*"]
bound_service_account_namespaces = ["test"]
token_policies = ["policy-2"]
}
]
tune = {
max_lease_ttl = "90000s"
}
}
]
vault_tokens = [
{
policies = ["policy-1"]
display_name = "token-1"
role_name = "role-1"
}
]
vault_token_roles = [
{
role_name = "role-1"
allowed_policies = ["policy-1"]
}
]
jwt_oidc_roles = [
{
name = "jwt-1"
path = "gitlab_jwt"
type = "jwt"
token_policies = ["policy-1"]
user_claim = "user_email"
bound_claims = { "project_id" : "111" }
},
]
identity_groups = [
{
group_id = "23454355467"
type = "external"
policies = ["policy-1"]
metadata = {
responsibility = "kv-1"
}
mount_type = "oidc"
mount_path = "oidc"
}
]
}
Этот пример можно найти в GitHub-репозитории. В примере создаются 2 policy, например, policy-1 выглядит следующим образом:
path "kv-1" {
capabilities = ["create", "read", "update", "list"]
}
path "kv-1/*" {
capabilities = ["create", "read", "update", "list"]
}
Также создаются 2 хранилища секретов, например, kv-1:
Дополнительно включаются необходимые бэкенды авторизации:
И наконец генерируется токен и роль для него, затем создаются jwt роли и идентификационные группы. Обратите внимание, что необходимо прописать правильные переменные, в противном случае некоторые ресурсы не будут созданы.
Что планируется дальше
В будущем планируется добавить следующий функционал:
Возможность выбирать output для сгенерированных файлов.
Добавить модуль в terraform registry, чтобы было удобнее его использовать.
Доработка модуля в зависимости от новых требований.
Выводы
До написания модуля нам приходилось вручную настраивать (создавать политики, хранилища секретов и т.д.) Vault. Модуль же помог автоматизировать данный процесс и повысить нашу эффективность. Как правило, для разных приложений настройки похожи, отличия только в названиях. Поэтому при помощи модуля можно унифицировать процесс настройки, то есть повторно использовать заранее созданные файлы с заполненными переменными, заменив некоторые.
P.S.: На данный момент модуль не прошел обкатку на большом количестве проектов, поэтому возможны какие-то баги. Если у вас возникнет какой-то баг, пожалуйста, создайте issue на GitHub, мы постараемся оперативно обработать его.
Еще больше информации о мире DevOps в нашем телеграм-канале DevOps FM.