Vault with k8s v2

Материал из noname.com.ua
Перейти к навигацииПерейти к поиску

Авторизация K8S POD в Vault

Постановка задачи

  • Получать "секреты" из Vault при этом не передавая в POD пароли, токены или другую секретную информацию

Предварительны условия

  • Установлен и настроен Vault

(базовая настройка - https://noname.com.ua/mediawiki/index.php/Vault_Basic_Setup)

  • кластер Kubernetes установлен и настроен
  • kubectl настроен для работы с кластером

Принципы работы

  1. Создается один или несколько Service Account
  2. POD запускается в определенном Service Account
  3. POD может воспользоваться токеном этого Service Account
  4. В качестве входных данных POD получает НЕ СЕКРЕТНУЮ информацию
    1. Адрес Vault в переменной окружения VAULT_ADDR
    2. Имя роли в переменной окружения VAULT_K8S_ROLE
  5. POD авторизуется в VAULT под ролью переданной в переменной VAULT_K8S_ROLE используя для этого JWT принадлежащий Service Account
  6. Vault настроен на проверку прав токена PODs в API K8S (Сам процесс VAULT использует ДРУГОЙ сервисный аккаунт, отличный от того под которым запускается POD для проверки токена PODa)
  7. После успешной авторизации POD может читать "секреты" из VAULT согласно политик которые привязаны к роле (политики и роли тут - это объекты в Vault)


Настройка Vault

Настройка со стороны k8s

Namespace

  • Создать пространство (имен если не создано)
  • Имя пространства имен выбрано произвольно (kilda в этом примере)
  • kubectl apply -f kilda-namespace.yaml
  • Содержимое kilda-namespace.yaml:
---
apiVersion: v1
kind: Namespace
metadata:
  name: kilda
  • проверка: kubectl get namespaces
NAME              STATUS   AGE
default           Active   56d
kilda             Active   7s
kube-public       Active   56d
kube-system       Active   56d
metallb-system    Active   56d
...

Сервисный аккаунт

  • Это аккаунт с которым Vault подключается к API k8s для валидации JWT
  • Этот аккаунт отличается от того который используется для запуска POD
  • Создается в правильном пространстве имен (в частном случае может использоваться default)
  • Имя аккаунта выбрано произвольно
  • kubectl apply -f kilda-vault-token-review-service-account.yaml
  • Содержимое файла kilda-vault-token-review-service-account.yaml:
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kilda-vault-token-review-service-account
  namespace: kilda
  • Проверка: kubectl get serviceAccount --namespace kilda
NAME                                       SECRETS   AGE
default                                    1         9m12s
kilda-vault-token-review-service-account   1         3m37s

Права сервисного аккаунта

  • Для того что б аккаунт мог проверить JWT у других сервисных аккаунтов ему назначается ClusterRole system:auth-delegator

Важно: Назначить права нужно в 2 пространствах имен
Если назначить только в kilda-namespace то прав для проверки JWT недостаточно (что совершенно не очевидно из конфигурации)

kilda-namespace

  • Права назначаются на аккаунт созданный ранее: kilda-vault-token-review-service-account в kilda-namespace
  • kubectl apply -f kilda-vault-token-review-service-account-role-binding-in-kilda-namespace.yaml
  • Содержимое файла kilda-vault-token-review-service-account-role-binding-in-kilda-namespace.yaml:
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kilda-vault-token-review-service-account-role-binding-in-kilda-namespace
  namespace: kilda-namespace
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: kilda-vault-token-review-service-account
  namespace: kilda-namespace


default-namespace

  • Права назначаются на аккаунт созданный ранее: kilda-vault-token-review-service-account в default-namespace
  • kubectl apply -f kilda-vault-token-review-service-account-role-binding-in-default-namespace.yaml
  • Содержимое файла kilda-vault-token-review-service-account-role-binding-in-default-namespace.yaml:


---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kilda-vault-token-review-service-account-role-binding-in-default-namespace
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: kilda-vault-token-review-service-account
  namespace: kilda-namespace

Проверка

  • Проверить: kubectl get clusterRoleBinding
NAME                                                                         ROLE
...
kilda-vault-token-review-service-account-role-binding-in-default-namespace   ClusterRole/system:auth-delegator
kilda-vault-token-review-service-account-role-binding-in-kilda-namespace     ClusterRole/system:auth-delegator
...

Собственно настройка Vault

Доступ к Vault

Для настройки Vault потребуется токен с рутовыми правами.

  • Токен для примера (отличается для каждой инсталляции Vault)
  • В случае https - требуется добавить CA в доверенные (если не добавлен до того)
export VAULT_TOKEN="s.pRFenxR9CANXqLtGI0b6fvy3"
export VAULT_ADDR="https://vault.domain.tld:8200"

Включить поддержку k8s в Vault

  • Предполагается использование нескольких кластеров с одним Vault, для этого указывается отдельный путь -path kilda-fred для каждого кластера kubernetes.
  • Имя пути (в примере kilda-fred) может быть выбрано произвольно
vault auth enable -path kilda-fred  kubernetes
Success! Enabled kubernetes auth method at: kilda-fred/
  • проверка
vault auth list
Path           Type          Accessor                    Description
----           ----          --------                    -----------
kilda-fred/    kubernetes    auth_kubernetes_34db5104    n/a
...

Получение необходимых секретов из K8S

  • В примере все объекты находятся в отдельном пространстве имен - kilda

Получение имени "секрета"

  • Получить имя "секрета" для сервисного аккаунта kilda-vault-token-review-service-account
  • kilda-vault-token-review-service-account это аккаунт с которым Vault подключается к API k8s для валидации JWT (создан выше)
kubectl \
    --namespace kilda \
    get \
    serviceaccount \
    kilda-vault-token-review-service-account \
   -o json
{
  "apiVersion": "v1",
  "kind": "ServiceAccount",
  "metadata": {
    "annotations": {
      "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"annotations\":{},\"name\":\"kilda-vault-token-review-service-account\",\"namespace\":\"kilda\"}}\n"
    },
    "creationTimestamp": "2022-02-09T10:17:40Z",
    "name": "kilda-vault-token-review-service-account",
    "namespace": "kilda",
    "resourceVersion": "8768345",
    "uid": "33b39f30-ba7e-4ef3-a12d-59e0cee9abee"
  },
  "secrets": [
    {
      "name": "kilda-vault-token-review-service-account-token-sdz49"
    }
  ]
}

Тут имя секрета =.secrets[0].name, его можно получить одной командой непосредственно используя jq или встроенную в kubectl фильтрацию

kubectl \
    --namespace kilda \
    get \
    serviceaccount \
    kilda-vault-token-review-service-account \
   -o jsonpath='{.secrets[0].name}'

Результат - kilda-vault-token-review-service-account-token-sdz49


Получение данных из "секрета"

  • имя секрета получено на предыдущем шаге - kilda-vault-token-review-service-account-token-sdz49
  • не забывать что используется отдельное пространство имен - kilda
Токен

Зная имя секрета можно получить его токен:

kubectl \
    --namespace kilda  \
    get \
    secret kilda-vault-token-review-service-account-token-sdz49 \
    -o json
  • Пример вывода (длинные поля заменены описанием для простоты):
{
  "apiVersion": "v1",
  "data": {
    "ca.crt": "<тут base64-encoded CA>=",
    "namespace": "a2lsZGE=",
    "token": "<тут тут base64-encoded tocken>"
  },
  "kind": "Secret",
  "metadata": {
    "annotations": {
      "kubernetes.io/service-account.name": "kilda-vault-token-review-service-account",
      "kubernetes.io/service-account.uid": "33b39f30-ba7e-4ef3-a12d-59e0cee9abee"
    },
    "creationTimestamp": "2022-02-09T10:17:40Z",
    "name": "kilda-vault-token-review-service-account-token-sdz49",
    "namespace": "kilda",
    "resourceVersion": "8768344",
    "uid": "e509252c-3e36-4344-930f-9e988156442c"
  },
  "type": "kubernetes.io/service-account-token"
}

Здесь интересует поле .data - можно получить одной командой, не сохраняя промежуточные данные

kubectl --namespace kilda get secret kilda-vault-token-review-service-account-token-sdz49 -o jsonpath='{.data.token}' | base64 -d

Токен выглядит примерно так:

eyJhbGciOiJSU <тут вырезано >MT3cuTbiL7rZr8oiBM331iCpZ6BK-nybCpptg3lnKOnjJbs26HoMGIY2_DvunNo_TiHRzq03h3z344F5SAqgEe27LWtaiGD1xA
CA
  • В том же секрете содержится сертификат СА
  • Этот же сертификат содержится в конфиге kubectl
kubectl \
    --namespace kilda \
    get \
    secret kilda-vault-token-review-service-account-token-sdz49 \
    -o jsonpath="{.data['ca\.crt']}" \
    | base64 --decode;
-----BEGIN CERTIFICATE-----
MIIDADCCAeigAwIBAgIUXvcRvJKO5x/sOIXOxVD2VIQXY3QwDQYJKoZIhvcNAQEL
BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMTEyMTQxNjQ5MDBaFw0z
<skipped>
eTh8bGZMYcccstj+sPLyprG8bueXzo6hDgaKkz4NXKlgP/b1gB/uycVKuS351mxF
qOY4fQ==
-----END CERTIFICATE-----

Настройка доступа Vault в K8S

  • ACCOUNT_TOKEN - получен на предыдущем шаге
  • API - адрес API kubernetes
  • SERVICE_ACCOUNT_CA_CRT - CA (получен на предыдущем шаге или взят из конфигурации kubectl - это один и тот же CA)
  • kilda-fred - это путь указаный ранее
#!/bin/bash

ACCOUNT_TOKEN="eyJhbGci<skipped>uZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImtpbGRhLXZhd
API="https://https://vault.domain.tld::6443"

SERVICE_ACCOUNT_CA_CRT="-----BEGIN CERTIFICATE-----
MIIDADCCAeigAwIBAgIUXvcRvJKO5x/sOIXOxVD2VIQXY3QwDQYJKoZIhvcNAQEL
<Skipped>
PVGSuNobYcdns4Z+E1O2QLYbnoDsLd+sqf0r9aaFSZQZry8bP783VCuBXg6Zy0BR
eTh8bGZMYcccstj+sPLyprG8bueXzo6hDgaKkz4NXKlgP/b1gB/uycVKuS351mxF
qOY4fQ==
-----END CERTIFICATE-----
"

vault \
    write \
    auth/kilda-fred/config \
    kubernetes_host="${API}" \
    kubernetes_ca_cert="${SERVICE_ACCOUNT_CA_CRT}" \
    token_reviewer_jwt="${ACCOUNT_TOKEN}"
Success! Data written to: auth/kilda-fred/config
11111

Обратить внимание: Service Account - это аккаунт с которым будет деплоиться POD. Это аккаунт в k8s а не в Vault. Роль существует только в пределах Vault (а не в K8S) При авторизации POD запрашивает токен для роли На токен будут назначены политики из привязки Привязка сервисных аккаунтов K8S к ролям Vault[править] export VAULT_K8S_SERVICE_ACCOUNT="k8s-test-service-account" export VAULT_K8S_ROLE="k8s-test-role" Здесь VAULT_K8S_SERVICE_ACCOUNT - это сервисный аккаунт в k8s который можно создать например так: --- apiVersion: v1 kind: ServiceAccount metadata:

 name: k8s-service-account
 namespace: my-namespace-name

Важно: Это сервисный аккаунт для запуска PODов, и это НЕ ТОТ сервисный аккаунт который используется Vault Создать привязку vault \

   write \
   auth/kubernetes/role/${VAULT_K8S_ROLE} \
   bound_service_account_names=${VAULT_K8S_SERVICE_ACCOUNT} \
   bound_service_account_namespaces=${NAMESPACE} \
   policies=policy1,policy2,default \
   ttl=1h

Обратить внимание - политики policy1,policy2 уже существуют (созданы до этого). Если политик нет их необходимо создать.


111

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kilda-service-account
  namespace: kilda
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: common-service-account
  namespace: kilda



kubectl apply -f kilda-service-account.yaml
serviceaccount/kilda-service-account created

kubectl apply -f common-service-account.yaml
serviceaccount/common-service-account created

kubectl get serviceAccount --namespace kilda
NAME                                       SECRETS   AGE
common-service-account                     1         15s
default                                    1         160m
kilda-service-account                      1         23s
kilda-vault-token-review-service-account   1         155m

Тестирование

Создание POD

apiVersion: v1
kind: Pod
metadata:
  name: test-service-acc
  labels:
    role: test
spec:
  serviceAccountName: "kilda-service-account"
  containers:
    - name: web
      image: ubuntu:20.04
      imagePullPolicy: Always
      command: ["sleep", "3600000"]
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
      env:
      - name: VAULT_K8S_ROLE
        value: "k8s-test-role"
      - name: VAULT_ADDR
        value: "https://:8200"