Syntax highlighting of devops/vault

<<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...
}}}

Используем токен для работы

== 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`