Автоматическое распечатывание волта

При инициализации волт кластера мы получаем root токен и 5 ключей, они необходимы для распечатывания кластера в случае его падения, делать это можно в ручную, но делать это ночью, если вдруг поды упали, не совсем удобно…

Для автоматичечкого распечатывания можно ипользовать различные KMS сервисы от облачных провайдеров, подробнее почитать об этом можно в официальной документации hashicorp

image

В этой статье мы настроим автоматический unseal “боевого” кластера с ипользованием промежуточного волта в реалиях Yandex Cloud

Проблема которую мы будем решать

Яндекс предоставляет KMS который можно использовать для автоматического распечатывания волта в случае падения, в таком случае приходится использовать не официальный контейнер, а тот который предоставляет Яндекс, если вас это устраивает, на этом можно остановиться

В таком случае вы сможете использовать только ту версию волта которую предоставляет Яндекс и зависить от их политики обновлений

Мы будем использовать transit engine, как это работает:

  • Будем использовать два кластера волта
  • Первый будет использоваться только для распечатывания второго (боевого), сам по себе он будет распечатываться с помощью Yandex KMS
  • Второй кластер боевой, который вы используете в проде (обновляя и конфигурируя его по своему усмотрению, без зависимости от Yandex Cloud)

image

Настройка первого (unwrap) кластера

Unwrap кластер ипользуется для распечатывания боевого кластера, а сам распечатывается средствами Яндекса

Получаем креды для распечатывания unwrap волта

yc iam service-account create --name vault-unwrap-sa
yc iam key create \
   --service-account-name vault-unwrap-sa \
   --output key.json

yc kms symmetric-key create \
   --name vault-unwrap \
   --default-algorithm aes-256 \
   --deletion-protection \
   --description "Ключ для vault-unwrap разворачивающего основной vault в prod"

# Сохраните идентификатор ключа (id) — он понадобится при установке приложения.

yc kms symmetric-key add-access-binding \
   --name prod-vault-unwrap \
   --service-account-name prod-vault-unwrap-sa \
   --role kms.keys.encrypterDecrypter

Деплоим волт так как вы привыкли, хельм чарт и все параметры доступны на Github

Используем image от Yandex Cloud, что бы корректно работал KMS

  image:
    repository: "cr.yandex/yc/vault"
    tag: "1.11.5_yckms"
    pullPolicy: IfNotPresent

Готовим базу данных

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

При выборе PostgreSQL в качестве бекенда, таблички не будут созданы автоматически, создать их можно в ручную

CREATE TABLE vault_kv_store (
  parent_path TEXT COLLATE "C" NOT NULL,
  path        TEXT COLLATE "C",
  key         TEXT COLLATE "C",
  value       BYTEA,
  CONSTRAINT pkey PRIMARY KEY (path, key)
);

CREATE INDEX parent_path_idx ON vault_kv_store (parent_path);

Табличка для HA мода

CREATE TABLE vault_ha_locks (
  ha_key                                      TEXT COLLATE "C" NOT NULL,
  ha_identity                                 TEXT COLLATE "C" NOT NULL,
  ha_value                                    TEXT COLLATE "C",
  valid_until                                 TIMESTAMP WITH TIME ZONE NOT NULL,
  CONSTRAINT ha_key PRIMARY KEY (ha_key)
); 

Если используется PostgreSQL до 9.5, создайте функцию

CREATE FUNCTION vault_kv_put(_parent_path TEXT, _path TEXT, _key TEXT, _value BYTEA) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE vault_kv_store
          SET (parent_path, path, key, value) = (_parent_path, _path, _key, _value)
          WHERE _path = path AND key = _key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO vault_kv_store (parent_path, path, key, value)
              VALUES (_parent_path, _path, _key, _value);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- Do nothing, and loop to try the UPDATE again.
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

Конфиг кластера должен выглядеть следующим образом

  extraconfig-from-values.hcl: |-
    disable_mlock = true
    ui = true

    listener "tcp" {
      tls_disable = 1
      address = "[::]:8200"
      cluster_address = "[::]:8201"
    }

    storage "postgresql" {
      connection_url = "postgres://<USER>:<PASSWORD>@<HOSTS>:6432/vault?sslmode=require"
      table = "vault_kv_store"
      ha_enabled = true
      ha_table = "vault_ha_locks"
      tls_ca_file = "/etc/ssl/certs/ca-yandex.pem"
    }

    seal "yandexcloudkms" {
      kms_key_id = ""
      service_account_key_file = "/vault/ssl/key.json"
    }

tls_ca_file - публичный сертификат для подлючения к managed сервисам с SSL kms_key_id - id ключа который мы получили в первом шаге service_account_key_file - получаем при создании аккаунта в первом шаге

Инициализируем кластер

Для инициализации достаточно запустить vault operator init в любом поде, если все настроенно верно, вывод будет следующий

Recovery Key 1: iz1XWxe4CM+wrOGqRCx8ex8kB2XvGJEdfjhXFC+MA6Rc
Recovery Key 2: rKZETr6IAy686IxfO3ZBKXPDAOkkwkpSepIME+bjeUT7
Recovery Key 3: 4XA/KJqFOm+jzbBkKQuRVePEYPrQe3H3TmFVmdlUjRFv
Recovery Key 4: lfnaYoZufP0uhooO3mHDAKGNZB5HLP9HYYb+LAfKkUmd
Recovery Key 5: L169hHj3DMpphGsOnS8TEz3Febvdx3vsG3Xr8kGWdUtW

Initial Root Token: s.AWnDagUkKNNbvkENiL72wysn

Success! Vault is initialized

Recovery key initialized with 5 key shares and a key threshold of 3. Please
securely distribute the key shares printed above.

Для автоматического распечатывания, первый раз кластер нужно распечатать вручную vault operator unseal <key> Вводим 3 ключа, кластер будет распечатан Теперь при падении подов кластер будет распечатываться автоматически

Включаем transit engine

Все настройки описанные ниже можно выполнить с помощью terraform, я опишу более простой способ (через консоль), все команды достаточно выполнить в одном поде

  • Включаем transit
vault secrets enable transit
  • Создаем ключ шифрования
vault write -f transit/keys/autounseal
  • Создаем полиси
vault policy write autounseal -<<EOF
path "transit/encrypt/autounseal" {
   capabilities = [ "update" ]
}

path "transit/decrypt/autounseal" {
   capabilities = [ "update" ]
}
EOF

Если все сделанно правильно вывод должен быть следующим

Success! Uploaded policy: autounseal
  • Создаем orphan periodic токен с политикой autounseal и TTL 120 сек, использовать его мы будем когда будет готов боевой прод волт, поэтому создать его можно и позже

#Логинимся 
$ vault login
#Генерим токен в соответствии с политикой
$ vault token create -policy="autounseal" -wrap-ttl=120 -orphan -period=30m

Key                              Value
---                              -----
wrapping_token:                  s.AO55UYmKpT4TmPVn2AZ0UkNT
wrapping_accessor:               7N3PtIwhuAtjJZ2GvA9RM0iV
wrapping_token_ttl:              2m
wrapping_token_creation_time:    2019-06-25 19:24:46.345501602 +0000 UTC
wrapping_token_creation_path:    auth/token/create
wrapped_accessor:                B8kWfoLfXYOAxFI2P2TIduaI

#Получаем токен для распечатывания
$ VAULT_TOKEN="s.AO55UYmKpT4TmPVn2AZ0UkNT" vault unwrap

Key                  Value
---                  -----
token                s.LvLFn4InVdIorAFS5E9j6xd3
......

Настройка второго (prod) кластера

Деплоим волт

Все точно так же как для unwrap кластера, только в другой неймспейс

Готовим базу данных

Все точно так же как для unwrap кластера

Инициализируем кластер

Здесь все тоже без изменений, так же как для unwrap

Конфиг кластера должен выглядеть следующим образом(добавляем seal transit)

seal "transit" {
  address = "http://vault.vault-unwrap-system:8200"
  token = "s.LvLFn4InVdIorAFS5E9j6xd3"
  disable_renewal = "false"
  key_name = "autounseal"
  mount_path = "transit/"
  tls_skip_verify = "true"
  }

Если все сделано правильно волт перейдет в режим миграции ( это видно в логах )

core: entering seal migration mode; Vault will not automatically unseal even if using an autoseal: from_barrier_type=shamir to_barrier_type=transit

Распечатываем волт в режиме миграции ( достаточно проделать это с одним подом )

vault operator unseal -migrate <key>

Вводим три ключа для распечатывания продового вольта, после этого все поды поднимутся и будут распечатаны.

Таким образом unwrap vault распечатывается с помощью Яндекс kms, а боевой с помощью vault unwrap Проверяем - убиваем под в промежуточном и основном вольте и проверяем что они автоматически распечатались.

Если по какой-то причине боевой волт не может достучаться до unwrap волта в течении 30 минут, токен не будет обновлен и волт упадет с ошибкой

  | Error making API request.
  |
  | URL: PUT http://vault.vault-unwrap-system:8200/v1/auth/token/renew-self
  | Code: 403. Errors:
  |
  | * permission denied

Время в 30 минут устанавливается здесь:

vault token create -policy="autounseal" -wrap-ttl=120 -orphan -period=30m

В таком случае необходимо обновить токен в unwrap волте и подложить его в конфигмап боевого волта, либо увеличить -period до удобного вам значения.