<<TableOfContents()>>
= Vault =
== AppRole ==
Для интеграции сервисов и инструментов (напр. Ansible) с Vault можно использовать метод доступа AppRole. Суть сводится к созданию политики, роли и секрета на стороне Vault, а на сторону сервиса передается идентификаторы роли и секрета.
Шаг 1 - создаем политику, напр. ''ansible-policy''
{{{#!highlight hcl
path "kv/*" {
capabilities = ["read"]
}
}}}
Шаг 2 - активируем метод доступа и создаем роль (нам потребуется ид-р роли)
{{{#!highlight bash
vault auth enable approle
vault write auth/approle/role/ansible token_policies="ansible-policy"
vault read auth/approle/role/ansible/role-id
# Key Value
# --- -----
# role_id 39aef9d2-...
}}}
Шаг 3 - создаем секрет
{{{#!highlight bash
vault write -f auth/approle/role/ansible/secret-id
# Key Value
# --- -----
# secret_id 469f6928-...
}}}
Шаг 4 - авторизуемся на клиенте с использованием `role_id` и `secret_id`
{{{#!highlight bash
# --- получаем токен
vault write auth/approle/login \
role_id="39aef9d2-..." \
secret_id="469f6928-..."
# Key Value
# --- -----
# token hvs.CAESIO...
}}}
Используем токен для работы
=== Ansible ===
Пример получения секретов из Vault в Ansible
{{{#!highlight yaml
# ...
- name: Read secret from vault
vars:
path: "{{ config_dir }}/path/to/secret"
ansible.builtin.set_fact:
response: >
{{ lookup('community.hashi_vault.vault_kv2_get',
path,
engine_mount_point='kv',
url=vault_url,
auth_method='approle',
role_id=vault_role_id,
secret_id=vault_secret_id) }}
- name: Create env file from secret
ansible.builtin.copy:
content: "{{ response.secret['env'] }}"
dest: "{{ project_dir }}/.env"
mode: "0664"
}}}
=== Terraform ===
Пример получения секретов в Terraform.
Предварительно необходимо получить токен
{{{#!highlight bash
vault write auth/approle/login role_id="7e59..." secret_id="efd2..."
}}}
Далее полученный токен переносим куда-нибудь в tfvars и используем в конфигурации
{{{#!highlight tf
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 3.0"
}
}
}
provider "vault" {
address = "https://vault.example.com"
token = var.vault_token
}
resource "vault_mount" "kv" {
path = "kv"
type = "kv"
options = { version = "2" }
description = "KV Version 2 secret engine mount"
}
data "vault_kv_secret_v2" "db_creds" {
mount = vault_mount.kv.path
name = "test/database"
}
# ...
resource "yandex_mdb_postgresql_user" "user" {
cluster_id = yandex_mdb_postgresql_cluster.my_database.id
name = data.vault_kv_secret_v2.db_creds.data["username"]
password = data.vault_kv_secret_v2.db_creds.data["password"]
}
}}}
== JWT и Gitlab CI ==
Секреты можно забирать из Vault при выполнении линии сборки Gitlab CI через механизм JWT. Пример конфигурации:
Активируем метод доступа
{{{#!highlight bash
vault auth enable jwt
}}}
Конфигурация метода доступа
{{{#!highlight bash
vault write auth/jwt/config -<<EOF
{
"jwks_url": "https://gitlab.example.com/oauth/discovery/keys",
"bound_issuer": "https://gitlab.example.com"
}
EOF
}}}
Создаем роль для метода доступа
{{{#!highlight bash
# --- для проекта
vault write auth/jwt/role/ci-backend - <<EOF
{
"role_type": "jwt",
"policies": ["some-policy"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_audiences": ["https://vault.example.com"],
"bound_claims": {
"project_path": "project/path"
}
}
EOF
# --- для группы проектов
vault write auth/jwt/role/ci-backend - <<EOF
{
"role_type": "jwt",
"policies": ["some-policy"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_audiences": ["https://vault.example.com"],
"bound_claims": {
"namespace_path": "project_group/path"
}
}
EOF
}}}
Конфигурация джобы в ''.gitlab-ci.yml''
{{{#!highlight yaml
test_vault:
stage: test
image:
name: hashicorp/vault:1.19
id_tokens:
JWT:
aud: https://vault.example.com
script:
- export VAULT_ADDR=https://vault.example.com
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=ci-backend jwt=$JWT)"
- export MY_SECRET="$(vault kv get -field=my_secret kv/test/app)"
- echo $MY_SECRET
}}}
== Kubernetes аутентификация и External Secrets ==
Крайне полезно иметь прозрачный механизм доставки секретов из Vault в Kubernetes, например [[ https://external-secrets.io ]].
Ниже приводится инструкция создания секретов в пространстве имен external-secrets, но для практического использования лучше использовать пространство имен приложения.
Подразумевается:
. Vault развернут отдельно от Kubernetes и имеет адрес https://vault.example.com
. в Vault существует политика dev
. в Vault секреты смонтированы по пути kv
. в Kubernetes установлен контроллер ExternalSecrets
Создаем сервисную учетную запись
{{{#!highlight bash
kubectl create serviceaccount vault-auth -n external-secrets
kubectl apply -n external-secrets -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: external-secrets
EOF
kubectl apply -n external-secrets -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: vault-auth-token
annotations:
kubernetes.io/service-account.name: vault-auth
type: kubernetes.io/service-account-token
EOF
}}}
Настраивем Kubernetes аутентификацию на стороне Vault. Учитывая, что Vault может обслуживать произвольное количество кластеров, явно задаем путь монтирования - ''k8s-v2''
{{{#!highlight bash
vault auth enable -path=k8s-v2 kubernetes
kubectl get secret vault-auth-token -n external-secrets -o jsonpath='{.data.token}' | base64 -d > token.jwt
kubectl get secret vault-auth-token -n external-secrets -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
KUBE_API=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
vault write auth/k8s-v2/config \
token_reviewer_jwt=@token.jwt \
kubernetes_host="$KUBE_API" \
kubernetes_ca_cert=@ca.crt
vault write auth/k8s-v2/role/v2-dev \
bound_service_account_names=vault-auth \
bound_service_account_namespaces=external-secrets \
policies=dev \
ttl=1h
}}}
Создаем сущности ExternalSecrets
{{{#!highlight bash
kubectl apply -n external-secrets -f - <<EOF
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: vault-store
namespace: external-secrets
spec:
provider:
vault:
server: "https://vault.examle.com"
path: "kv" # <---- точка монтирования секретов
version: "v2"
auth:
kubernetes:
mountPath: "k8s-v2"
role: "v2-dev"
serviceAccountRef:
name: vault-auth
EOF
kubectl apply -n external-secrets -f - <<EOF
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: example-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-store
kind: SecretStore
target:
name: my-secret # <--- имя создаваемого секрета в Kubernetes
dataFrom:
- extract:
key: foo/bar # <----- путь ключа в Vault
EOF
}}}
== OIDC аутентификация ==
В качестве OIDC провайдера можно использовать Keycloak. Там создаем Realm роли и назначаем из пользователю. Также необходимо сделать так, чтобы назначенные роли попали в id_token, который выдается со стороны Keycloak после аутентификации (информация есть здесь [[ https://stackoverflow.com/questions/68741412/grafana-generic-oauth-role-assignment ]]).
Создаем oidc метод доступа (обязательно указываем роль по умолчанию)
{{{#!highlight bash
vault auth enable oidc
}}}
Создаем роль для oidc
{{{#!highlight bash
vault write auth/oidc/role/default -<<EOF
{
"allowed_redirect_uris": ["https://vault.example.com/ui/vault/auth/oidc/oidc/callback"],
"user_claim":"email",
"groups_claim":"roles",
"bound_audiences": "vault",
"oidc_scopes": "openid roles",
"ttl": "8h"
}
EOF
}}}
Команда выше подразумевает, что ''id_token'' из Keycloak содержит поле ''roles'' с массивом Realm ролей.
Уже можно пробовать зайти через OIDC и получить доступ в объеме ''default'' политики.
Допустим уже существует политика ''admin'', которая предоставляет административный доступ к Vault. Чтобы к ней ''привязаться'' ролью ''vault-admin'' из Keycloak нам потребуется создать т.н. `identity/group` и `identity/group-alias`.
Кроме того, нам потребуется получить т.н. `mount_accessor` для oidc
{{{#!highlight bash
vault auth list -detailed
Path Plugin Accessor
------- ------ --------
oidc/ oidc auth_oidc_123 <------------ mount_accessor
...
}}}
Создаем `identity/group` (идентификатор группы пригодится в следующей команде)
{{{#!highlight bash
vault write identity/group \
name="vault-admin-group" \
type="external" \
policies="admin"
Key Value
--- -----
id 123123
name vault-admin-group
}}}
Создаем `identity/group-alias`
{{{#!highlight bash
vault write identity/group-alias \
name="vault-admin" \
mount_accessor="auth_oidc_123" \
canonical_id="123123"
}}}
Здесь значения для `mount_accessor` и `cacnonical_id` определяются в командах выше.
В данной конфигурации, пользователю с Realm ролью ''vault-admin'' будет назначена политика ''admin'' в Vault.
Для добавления новых ролей необходимо выполнить:
. на стороне Keycloak создаем роль и назначаем ее пользователю
. на стороне Vault создаем политику для этой роли, а также соответствующие `identity/group` и `identity/group-alias`