Инсталляция HashiCorp Vault в HA режиме с integrated storage

В последнее время часто встает вопрос безопасного хранения паролей, ключей и переменных, которые приложения используют для своей работы. Одним из популярных решений в данной среде является Vault от HashiCorp.
Vault позволяет хранить секреты, токены, пароли, сертификаты и т.п., предоставляя к ним ограниченный и безопасный доступ. Естественно, внутри Vault все хранится в зашифрованном виде.
Под катом процедура инсталляция и запуск Vault на трех узлах в отказоустойчивом режиме.

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

HA, либо High Availability позволяет выдержать потерю одного или нескольких узлов (в зависимости от кофигурации), сохранив при этом доступ к данным. Не со всеми бэкэндами, используемыми для хранения, возможно использовать HA механизм.

Для хранения данных Vault может использовать большое количество бэкэндов. Особым преимуществом среди них на мой взгляд обладают те, на которые распространяется техническая поддержка непосредственно от HashiCorp и, наверное, в случае с коммерческой версией это актуально.

Полностью поддерживаемые HC бэкэнды (не все, но основные):
Filesystem – название говорит само за себя. Храним данные на локальной файловой системе, там же, где и установлен Vault. Не подходит для HA конфигурации;

Consul – Так же является разработкой HashiCorp. Использование в качестве бэкэнда позволяет реализовать Vault HA. Однако, данный вариант потребует развертывания дополнительных машин и организации отдельного кластера Consul (ну либо все в одном, но я не любитель такого);

Integrated Storage (Raft) – данные Vault так же хранятся на файловой системе, но при этом реплицируются на другие узлы кластера, позволяя им оставаться доступными даже в случае выхода из строя одного из узлов. Данный вариант поддерживает функционал HA.

Мой выбор в данном случае – Integrated Storage.

Итак, начнем с подготовки машин. В моем случае это три машины (минимальное количество для кворума) под управлением Rocky Linux 8.5:
vault-a.vmik.lab – IP 192.168.100.9
vault-b.vmik.lab – IP 192.168.100.10
vault-c.vmik.lab – IP 192.168.100.11

Установим Vault на всех серверах:

[root@vault-a/b/c ~]# dnf install -y dnf-plugins-core
[root@vault-a/b/c ~]# dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
[root@vault-a/b/c ~]# dnf -y install vault

Убеждаемся, что Vault установлен:

[root@vault-a/b/c ~]# vault -v
Vault v1.9.2 (f4c6d873e2767c0d6853b5d9ffc77b0d297bfbdf)

Открываем необходимые для работы Vault порты:

[root@vault-a/b/c ~]# firewall-cmd --add-port=8200/tcp --permanent
[root@vault-a/b/c ~]# firewall-cmd --add-port=8201/tcp --permanent
[root@vault-a/b/c ~]# firewall-cmd –reload

Далее необходимо обзавестись сертификатами. Если таковые отсутствуют – выпустить свои, самоподписанные. Все операции я выполняю с первой ноды.
Сперва выпустим свой CA сертификат, а затем с его помощью подпишем сертификаты для всех узлов кластера:

[root@vault-a ~]# cd /opt/vault/tls/
[root@vault-a tls]# openssl genrsa 2048 > vault-ca-key.pem
[root@vault-a tls]# openssl req -new -x509 -nodes -days 3650 -key vault-ca-key.pem -out vault-ca-cert.pem

Теперь подготовим конфигурационные файлы, содержащие Subject Alternate Name (SAN) для каждого из узлов. Важно, чтобы в SAN был корректней хостнейм и IP каждого узла:

[root@vault-a tls]# echo "[v3_ca]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vault-a.vmik.lab
IP.1 = 192.168.100.9
IP.2 = 127.0.0.1
" > ./cert-a.cfg
[root@vault-a tls]# echo "[v3_ca]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vault-b.vmik.lab
IP.1 = 192.168.100.10
IP.2 = 127.0.0.1
" > ./cert-b.cfg
[root@vault-a tls]# echo "[v3_ca]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vault-c.vmik.lab
IP.1 = 192.168.100.11
IP.2 = 127.0.0.1
" > ./cert-c.cfg

Теперь для каждого из узлов сформируем CSR файл:

[root@vault-a tls]# openssl req -newkey rsa:2048 -nodes -keyout vault-a-key.pem -out vault-a-csr.pem -subj "/CN=vault-a.vmik.lab"
[root@vault-a tls]# openssl req -newkey rsa:2048 -nodes -keyout vault-b-key.pem -out vault-b-csr.pem -subj "/CN=vault-b.vmik.lab"
[root@vault-a tls]# openssl req -newkey rsa:2048 -nodes -keyout vault-c-key.pem -out vault-c-csr.pem -subj "/CN=vault-c.vmik.lab"

И выпустим сертификаты на основании запросов:

[root@vault-a tls]# openssl x509 -req -set_serial 01 -days 3650 -in vault-a-csr.pem -out vault-a-cert.pem -CA vault-ca-cert.pem -CAkey vault-ca-key.pem -extensions v3_ca -extfile ./cert-a.cfg
[root@vault-a tls]# openssl x509 -req -set_serial 01 -days 3650 -in vault-b-csr.pem -out vault-b-cert.pem -CA vault-ca-cert.pem -CAkey vault-ca-key.pem -extensions v3_ca -extfile ./cert-b.cfg
[root@vault-a tls]# openssl x509 -req -set_serial 01 -days 3650 -in vault-c-csr.pem -out vault-c-cert.pem -CA vault-ca-cert.pem -CAkey vault-ca-key.pem -extensions v3_ca -extfile ./cert-c.cfg

Скопируем сертификаты и ключи на узлы B и C:

[root@vault-a tls]# scp ./vault-b-key.pem ./vault-b-cert.pem ./vault-ca-cert.pem vault-b.vmik.lab:/opt/vault/tls
[root@vault-a tls]# scp ./vault-c-key.pem ./vault-c-cert.pem ./vault-ca-cert.pem vault-c.vmik.lab:/opt/vault/tls

На каждом из узлов установим соответствующие права для доступа к файлам сертификатов и ключам:

[root@vault-a tls]# chown root:root /opt/vault/tls/vault-a-cert.pem /opt/vault/tls/vault-ca-cert.pem
[root@vault-a tls]# chown root:vault /opt/vault/tls/vault-a-key.pem
[root@vault-a tls]# chmod 0644 /opt/vault/tls/vault-a-cert.pem /opt/vault/tls/vault-ca-cert.pem
[root@vault-a tls]# chmod 0640 /opt/vault/tls/vault-a-key.pem
[root@vault-b ~]# chown root:root /opt/vault/tls/vault-b-cert.pem /opt/vault/tls/vault-ca-cert.pem
[root@vault-b ~]# chown root:vault /opt/vault/tls/vault-b-key.pem
[root@vault-b ~]# chmod 0644 /opt/vault/tls/vault-b-cert.pem /opt/vault/tls/vault-ca-cert.pem
[root@vault-b ~]# chmod 0640 /opt/vault/tls/vault-b-key.pem
[root@vault-c ~]# chown root:root /opt/vault/tls/vault-c-cert.pem /opt/vault/tls/vault-ca-cert.pem
[root@vault-c ~]# chown root:vault /opt/vault/tls/vault-c-key.pem
[root@vault-c ~]# chmod 0644 /opt/vault/tls/vault-c-cert.pem /opt/vault/tls/vault-ca-cert.pem
[root@vault-c ~]# chmod 0640 /opt/vault/tls/vault-c-key.pem

Теперь, когда для каждой из нод готовы сертификаты. Перейдем к конфигурации Vault.
Отредактируем конфигурационный файл vault для первой ноды:

[root@vault-a ~]# vi /etc/vault.d/vault.hcl
cluster_addr  = "https://192.168.100.9:8201"
api_addr      = "https://192.168.100.9:8200"
disable_mlock = true

ui = true

listener "tcp" {
  address            = "0.0.0.0:8200"
  tls_ca_cert_file   = "/opt/vault/tls/vault-ca-cert.pem"
  tls_cert_file      = "/opt/vault/tls/vault-a-cert.pem"
  tls_key_file       = "/opt/vault/tls/vault-a-key.pem"

}

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-a.vmik.lab"

  retry_join {
    leader_tls_servername   = "vault-a.vmik.lab"
    leader_api_addr         = "https://192.168.100.9:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-a-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-a-key.pem"
  }
  retry_join {
    leader_tls_servername   = "vault-b.vmik.lab"
    leader_api_addr         = "https://192.168.100.10:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-a-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-a-key.pem"
  }
  retry_join {
    leader_tls_servername   = "vault-c.vmik.lab"
    leader_api_addr         = "https://192.168.100.11:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-a-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-a-key.pem"
  }
}

Интересные параметры:
api_addr – адрес и порт, на котором будет доступен API сервер;
cluster_addr – адрес и порт по которому будут взаимодействовать кластерные сервисы;
disable_mlock – рекомендуемый параметр при использовании Integrated Storage;
ui – включение доступа к веб-интерфейсу Vault;
в секции listener указываются сертификаты, которые будут использованы при сетевом взаимодействии. У каждой ноды они свои, за исключением CA, данный сертификат одинаковый для всех.

Секцию storage стоит так же рассмотреть:
path = “/opt/vault/data” – директория, где будут храниться данные Vault;
node_id = “vault-a.vmik.lab” – id, с которым нода будет участвовать в кластере. У каждой ноды он должен отличаться;
Далее идут несколько секций retry_join, с перечислением всех узлов кластера. Поскольку доподлинно неизвестно, какой из узлов будет активным при запуске служб Vault, будет произведена попытка подключения к каждому из узлов.
Здесь же указываются адреса узлов – leader_api_addr, leader_tls_servername – хостнейм сервера, который должен совпадать с тем, что прописано в сертификате данного сервера. Так же указываются сертификаты, которыми клиент будет подключаться к лидеру (у каждой из нод свои сертификаты, которые мы создавали ранее).

Аналогичным образом готовим конфиг-файлы на узлах B и C:

[root@vault-b ~]# vi /etc/vault.d/vault.hcl
cluster_addr  = "https://192.168.100.10:8201"
api_addr      = "https://192.168.100.10:8200"
disable_mlock = true

ui = true

listener "tcp" {
  address            = "0.0.0.0:8200"
  tls_ca_cert_file   = "/opt/vault/tls/vault-ca-cert.pem"
  tls_cert_file      = "/opt/vault/tls/vault-b-cert.pem"
  tls_key_file       = "/opt/vault/tls/vault-b-key.pem"

}

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-b.vmik.lab"

  retry_join {
    leader_tls_servername   = "vault-a.vmik.lab"
    leader_api_addr         = "https://192.168.100.9:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-b-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-b-key.pem"
  }
  retry_join {
    leader_tls_servername   = "vault-b.vmik.lab"
    leader_api_addr         = "https://192.168.100.10:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-b-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-b-key.pem"
  }
  retry_join {
    leader_tls_servername   = "vault-c.vmik.lab"
    leader_api_addr         = "https://192.168.100.11:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-b-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-b-key.pem"
  }
}
[root@vault-c ~]# vi /etc/vault.d/vault.hcl
cluster_addr  = "https://192.168.100.11:8201"
api_addr      = "https://192.168.100.11:8200"
disable_mlock = true

ui = true

listener "tcp" {
  address            = "0.0.0.0:8200"
  tls_ca_cert_file   = "/opt/vault/tls/vault-ca-cert.pem"
  tls_cert_file      = "/opt/vault/tls/vault-c-cert.pem"
  tls_key_file       = "/opt/vault/tls/vault-c-key.pem"

}

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-c.vmik.lab"

  retry_join {
    leader_tls_servername   = "vault-a.vmik.lab"
    leader_api_addr         = "https://192.168.100.9:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-c-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-c-key.pem"
  }
  retry_join {
    leader_tls_servername   = "vault-b.vmik.lab"
    leader_api_addr         = "https://192.168.100.10:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-c-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-c-key.pem"
  }
  retry_join {
    leader_tls_servername   = "vault-c.vmik.lab"
    leader_api_addr         = "https://192.168.100.11:8200"
    leader_ca_cert_file     = "/opt/vault/tls/vault-ca-cert.pem"
    leader_client_cert_file = "/opt/vault/tls/vault-c-cert.pem"
    leader_client_key_file  = "/opt/vault/tls/vault-c-key.pem"
  }
}

Обратите внимание как меняются поля с адресами, сертификатами, а также node_id. Это важно.

Теперь, когда конфигурационные файлы готовы, возвращаемся на первый узел. Добавляем Vault в автозагрузку и автоматически его запускаем:

[root@vault-a tls]# systemctl enable --now vault.service
[root@vault-a tls]# systemctl status vault.service
● vault.service - "HashiCorp Vault - A tool for managing secrets"
   Loaded: loaded (/usr/lib/systemd/system/vault.service; enabled; vendor preset: disabled)
   Active: active (running)

Проверим статус Vault, предварительно отменив проверку сертификатов, выставив значение специальной переменной:

[root@vault-a tls]# export VAULT_SKIP_VERIFY="true"
[root@vault-a tls]# vault status
Key                Value
---                -----
Seal Type          shamir
Initialized        false
Sealed             true
Total Shares       0
Threshold          0
Unseal Progress    0/0
Unseal Nonce       n/a
Version            1.9.2
Storage Type       raft
HA Enabled         true

Как можно заметить на текущий момент Vault не инициализирован (Initialized false), а также запечатан (Sealed true).

Примечание: добавив наш CA файл, сгенерированный ранее, в список доверенных сертификатов, прибегать к отмене проверки сертификатов будет необязательно.

Инициализируем Vault:

[root@vault-a tls]# vault operator init

Unseal Key 1: jeTvK2x6frIXN6FzPZwA9hl4Hos7ANsq+RhUj
Unseal Key 2: zdg30oMjp0e44FowtgjQIbt/i7KCkmEgvoAoJi
Unseal Key 3: wJ1Jzm6lAObfD/relfKuPLkeTzWoiQEZH
Unseal Key 4: z8lheFwPWvRvY8OtVqmbVe+L4L3O6QkzrHsP
Unseal Key 5: nH2i7cjrEtwOZNmOBL7q+BU5NO2R8Cj7G

Initial Root Token: s.qXp84detWQYLNQ4Fby3

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated master key. Without at least 3 keys to
reconstruct the master key, Vault will remain permanently sealed!

Vault выдаст 5 ключей, которые необходимо использовать при «распечатке». Как сказано выше, после каждой остановки либо перезапуска, Vault будет вновь находиться в запечатанном состоянии и для открытия нужно будет использовать любые три из пяти предоставленных ключей. Терять эти ключи не стоит!
Так же нам предоставляется Root Token для доступа к Vault с максимальными правами.

Распечатаем Vault. Операцию vault operator unseal нужно будет провести 3 раза. Каждый раз с разным ключом:

[root@vault-a tls]# vault operator unseal
Unseal Key (will be hidden):
Key                Value
---                -----
Unseal Progress    1/3

[root@vault-a tls]# vault operator unseal
Unseal Key (will be hidden):
Key                Value
---                -----
Unseal Progress    2/3

[root@vault-a tls]# vault operator unseal
Storage Type            raft
Cluster Name            vault-cluster-8250666f
Cluster ID              057a1274-f9fd-0508-b5cf-6fe0fef0712e
HA Enabled              true

На данном этапе мы настроили одну ноду Vault, а также инициализировали и распечатали ее. Теперь подключим в кластер две других ноды.
Ранее мы уже подготовили и разместили конфигурационные файлы на нодах B и C.
Подключаемся к ноде B, запускаем Vault:

[root@vault-b ~]# systemctl enable --now vault
[root@vault-b ~]# systemctl status vault
● vault.service - "HashiCorp Vault - A tool for managing secrets"
   Loaded: loaded (/usr/lib/systemd/system/vault.service; enabled; vendor preset: disabled)
   Active: active (running)

Теперь, в отличии от первой ноды, инициализировать Vault больше не нужно, но новая нода все еще в запечатанном состоянии. Распечатаем ноду B:

[root@vault-b ~]# export VAULT_SKIP_VERIFY="true"
[root@vault-b ~]# vault operator unseal
[root@vault-b ~]# vault operator unseal
[root@vault-b ~]# vault operator unseal
[root@vault-b ~]# vault status
Key                     Value
---                     -----
Cluster Name            vault-cluster-8250666f
Cluster ID              057a1274-f9fd-0508-b5cf-6fe0fef0712e
HA Enabled              true
HA Cluster              https://192.168.100.9:8201
HA Mode                 standby
Active Node Address     https://192.168.100.9:8200

После распечатки нода будет подключена к кластеру. Можно обратить внимание, что новая нода находится в режиме standby, так же можно определить текущий адрес активной ноды. Это нода A.

Подключимся к ноде C и выполним аналогичные действия:

[root@vault-c ~]# systemctl enable --now vault
[root@vault-c ~]# systemctl status vault
● vault.service - "HashiCorp Vault - A tool for managing secrets"
   Loaded: loaded (/usr/lib/systemd/system/vault.service; enabled; vendor preset: disabled)
   Active: active (running)
[root@vault-c ~]# export VAULT_SKIP_VERIFY="true"
[root@vault-c ~]# vault operator unseal
[root@vault-c ~]# vault operator unseal
[root@vault-c ~]# vault operator unseal

Теперь мы успешно сформировали кластер из трех нод. Вернемся на первую и проверим состояние кластера.

Для начала авторизуемся с токеном, который был получен ранее:

[root@vault-a tls]# vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

И проверим статус хранилища:

[root@vault-a tls]# vault operator raft list-peers
Node                Address             State       Voter
----                -------             -----       -----
vault-a.vmik.lab    192.168.100.9:8201     leader      true
vault-b.vmik.lab    192.168.100.10:8201    follower    true
vault-c.vmik.lab    192.168.100.11:8201    follower    true

По статусу видно 3 сервера, один из которых в статусе лидера, а два других – ведомые.

Попробуем добавить новый секрет в хранилище. Для этого используем механизм kv (key-value):

[root@vault-a ~]# vault secrets enable -path=vmik-secrets/ kv
Success! Enabled the kv secrets engine at: vmik-secrets/

И поместим секрет под именем db:

[root@vault-a ~]# vault kv put vmik-secrets/db password=vmik
Success! Data written to: vmik-secrets/db

Посмотрим список всех секретов в нашем KV хранилище vmik-secrets:

[root@vault-a ~]# vault kv list vmik-secrets
Keys
----
db

А также значение секрета db:

[root@vault-a ~]# vault kv get vmik-secrets/db
====== Data ======
Key         Value
---         -----
password    vmik

Проверим работоспособность механизма HA. Отключим ноду A:

[root@vault-a ~]# shutdown -h now

Подключимся к ноде B и проверим статус:

[root@vault-b ~]# vault status
Key                     Value
---                     -----
HA Enabled              true
HA Cluster              https://192.168.100.10:8201
HA Mode                 active

HA Mode изменено со standby на active. Запросим список узлов raft:

[root@vault-b ~]# vault operator raft list-peers
Node                Address             State       Voter
----                -------             -----       -----
vault-a.vmik.lab    192.168.100.9:8201     follower    true
vault-b.vmik.lab    192.168.100.10:8201    leader      true
vault-c.vmik.lab    192.168.100.11:8201    follower    true

Лидер так же был перенесен на ноду B. В завершении запросим ранее созданный секрет:

[root@vault-a ~]# vault kv get vmik-secrets/db
====== Data ======
Key         Value
---         -----
password    vmik

Vault продолжает функционировать при потере одной активной ноды. Отключим ноду B. Теперь из трех узлов доступен только один. Проверим работоспособность:

[root@vault-b ~]# shutdown -h now

Статус Vault с ноды C:

[root@vault-c ~]# vault status
HA Enabled              true
HA Cluster              https://192.168.100.10:8201
HA Mode                 standby

Активная нода не изменилась. Нода C все так же в режиме standby. Аналогично, не удастся запросить и секрет, поскольку запрос перенаправляется на ранее активную ноду (B):

[root@vault-c ~]# vault kv get vmik-secrets/db
Get "https://192.168.100.10:8200/v1/sys/internal/ui/mounts/vmik-secrets/db": dial tcp 192.168.100.10:8200: connect: no route to host

Запустим обратно ноду A. После запуска, как и ожидается, нода A находится в статусе sealed:

[root@vault-a ~]# vault status
Key                Value
---                -----
Unseal Progress    0/3
Unseal Nonce       n/a

Распечатаем:

[root@vault-a tls]# vault operator unseal
[root@vault-a tls]# vault operator unseal
[root@vault-a tls]# vault operator unseal

Проверим статус:

[root@vault-a ~]# vault status
Key                     Value
---                     -----
HA Enabled              true
HA Cluster              https://192.168.100.11:8201
HA Mode                 standby
Active Node Address     https://192.168.100.11:8200

Работа кластера восстановлена. Нода С теперь активная. Доступ к секретам так же восстановлен:

[root@vault-a ~]# vault kv get vmik-secrets/db
====== Data ======
Key         Value
---         -----
password    vmik

Запустим обратно выключенную ноду B и распечатаем. На этом работа по настройке кластера Vault закончена.

Нерассмотренным остался один вопрос – как подключаться внешним клиентам? Подключение к любой из standby нод перенаправит запрос на active ноду, поэтому знать текущий адрес активной ноды не обязательно.
Однако, клиент может не знать все адреса Vault и в случае отключения известной ноды, доступ клиента к Vault может прекратиться.
Наиболее приемлемые на мой взгляд решения:
1. Внешний балансировщик нагрузки между узлами Vault с заранее известным и постоянным адресом. HAProxy, либо nginx;
2. Общий IP-адрес между узлами кластера Vault, например, на основе keepalived.

Leave a Reply

Your email address will not be published.

Translate »