«1. Обзор

В этом руководстве мы рассмотрим хранилище Hashicorp — популярный инструмент, используемый для безопасного управления конфиденциальной информацией в современных архитектурах приложений.

Основные темы, которые мы рассмотрим, включают:

    Какую проблему Vault пытается решить Архитектура Vault и основные концепции Настройка простой тестовой среды Взаимодействие с Vault с помощью инструмента командной строки

2. Проблема с Конфиденциальная информация

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

Большинству приложений для правильной работы требуется доступ к конфиденциальным данным. Например, приложение электронной коммерции может где-то настроить имя пользователя/пароль для подключения к своей базе данных. Также могут потребоваться ключи API для интеграции с другими поставщиками услуг, такими как платежные шлюзы, логистика и другие деловые партнеры.

Учетные данные базы данных и ключи API — это некоторые примеры конфиденциальной информации, которую нам необходимо хранить и делать доступной для наших приложений безопасным способом.

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

Мы можем немного усложнить задачу, зашифровав эти файлы. Однако такой подход мало что добавит с точки зрения общей безопасности. В основном потому, что наше приложение должно иметь доступ к мастер-ключу. Шифрование, используемое таким образом, создаст только «ложное» ощущение безопасности.

Современные приложения и облачные среды, как правило, добавляют дополнительную сложность: распределенные службы, несколько баз данных, системы обмена сообщениями и т. д. — все они содержат конфиденциальную информацию, разбросанную повсюду, что увеличивает риск нарушения безопасности.

Итак, что мы можем сделать? Давайте Хранилище это!

3. Что такое Vault?

Хранилище Hashicorp решает проблему управления конфиденциальной информацией — секретом на языке Vault. «Управление» в этом контексте означает, что Сейф контролирует все аспекты конфиденциальной информации: ее создание, хранение, использование и, что не менее важно, ее отзыв.

Hashicorp предлагает две версии Vault. Версия с открытым исходным кодом, используемая в этой статье, может использоваться бесплатно даже в коммерческих средах. Также доступна платная версия, которая включает техническую поддержку по различным соглашениям об уровне обслуживания и дополнительные функции, такие как поддержка HSM (аппаратный модуль безопасности).

3.1. Архитектура и основные функции

Архитектура Vault обманчиво проста. Его основными компонентами являются:

    Постоянный бэкэнд – хранилище для всех секретов Сервер API, который обрабатывает запросы клиентов и выполняет операции с секретами Несколько механизмов секретов, по одному для каждого типа поддерживаемого типа секрета

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

    Нашим приложениям больше не нужно хранить их — просто запрашивайте Vault, когда это необходимо, и отбрасывайте их. Мы можем использовать краткосрочные секреты, тем самым ограничивая «окно». возможности», где злоумышленник может использовать украденный секрет

Vault шифрует все данные с помощью ключа шифрования, прежде чем записывать их в хранилище. Этот ключ шифрования зашифрован еще одним ключом — мастер-ключом, который используется только при запуске.

Ключевым моментом в реализации Vault является то, что он не хранит главный ключ на сервере. Это означает, что даже Vault не может получить доступ к своим сохраненным данным после запуска. В этот момент говорят, что экземпляр Vault находится в «запечатанном» состоянии.

Позже мы рассмотрим шаги, необходимые для создания главного ключа и распечатывания экземпляра Vault.

После распечатывания Vault будет готов принимать запросы API. Эти запросы, конечно, требуют аутентификации, что приводит нас к тому, как Vault аутентифицирует клиентов и решает, что они могут или не могут делать.

3.2. Аутентификация

«Чтобы получить доступ к секретам в Vault, клиенту необходимо пройти аутентификацию одним из поддерживаемых методов. В самом простом методе используются токены, которые представляют собой просто строки, отправляемые при каждом запросе API с использованием специального заголовка HTTP.

При первоначальной установке Vault автоматически создает «корневой токен». Этот токен эквивалентен привилегированному суперпользователю в системах Linux, поэтому его использование должно быть сведено к минимуму. Лучше всего использовать этот корневой токен только для создания других токенов с меньшими привилегиями, а затем отозвать его. Однако это не проблема, так как позже мы можем сгенерировать другой корневой токен, используя распечатанные ключи.

Vault также поддерживает другие механизмы аутентификации, такие как LDAP, JWT, сертификаты TLS и другие. Все эти механизмы основаны на базовом механизме токенов: как только Vault проверит наш клиент, он предоставит токен, который мы затем сможем использовать для доступа к другим API.

Токены имеют несколько связанных с ними свойств. Основными свойствами являются:

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

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

Обратное неверно: мы можем — и обычно так и делаем — создать дочерний токен с ограничительными политиками. признан недействительным.

3.3. Политики

Политики точно определяют, к каким секретам клиент может получить доступ и какие операции он может с ними выполнять. Давайте посмотрим, как выглядит простая политика:

path "secret/accounting" {
    capabilities = [ "read" ]
}

Здесь мы использовали синтаксис HCL (язык конфигурации Hashicorp) для определения нашей политики. Vault также поддерживает JSON для этой цели, но в наших примерах мы будем придерживаться HCL, так как его легче читать.

Политики в Vault «по умолчанию запрещены». Токен, прикрепленный к этому образцу политики, получит доступ к секретам, хранящимся в разделе секрет/учет, и ни к чему другому. Во время создания токен можно прикрепить к нескольким политикам. Это очень полезно, поскольку позволяет нам создавать и тестировать меньшие политики, а затем применять их по мере необходимости.

Другим важным аспектом политик является то, что они используют ленивую оценку. Это означает, что мы можем обновить данную политику, и все токены будут затронуты немедленно.

Политики, описанные до сих пор, также называются политиками списка управления доступом или политиками ACL. Vault также поддерживает два дополнительных типа политик: политики EGP и RGP. Они доступны только в платных версиях и расширяют базовый синтаксис политики за счет поддержки Sentinel.

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

Более подробную информацию о синтаксисе политики можно найти в документации Vault.

4. Типы секретов

Vault поддерживает ряд различных типов секретов, предназначенных для различных вариантов использования:

    Ключ-значение: простые статические пары \»ключ-значение\» Динамически генерируемые учетные данные: генерируются Vault по запросу клиента Криптографические ключи: используются для выполнения криптографических функций с данными клиента

Каждый секретный тип определяется следующими атрибутами:

    Точка монтирования, определяющая его префикс REST API Набор операций, доступных через соответствующий API Набор параметров конфигурации

Доступ к заданному секретному экземпляру осуществляется по пути, подобно дереву каталогов в файловой системе. Первый компонент этого пути соответствует точке монтирования, где находятся все секреты этого типа.

Например, строка secret/my-application соответствует пути, по которому мы можем найти пары ключ-значение для my-application.

4.1. Секреты ключ-значение

«Секреты ключ-значение, как следует из названия, представляют собой простые пары, доступные по заданному пути. Например, мы можем хранить пару foo=bar по пути /secret/my-application.

Позже мы используем тот же путь для извлечения одной и той же пары или пар — несколько пар могут храниться по одному и тому же пути.

Vault поддерживает три типа секретов \»ключ-значение\»:

    неверсионные пары ключей, в которых обновления заменяют существующие значения; пары ключей с версионированием, значения которых привязаны к заданному токену доступа (подробнее об этом позже).

Секреты Key-Value являются статическими по своей природе, поэтому с ними не связано понятие срока действия. Основным вариантом использования такого секрета является хранение учетных данных для доступа к внешним системам, например ключей API.

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

4.2. Динамически генерируемые секреты

Динамические секреты генерируются Vault «на лету» по запросу приложения. Vault поддерживает несколько типов динамических секретов, в том числе следующие:

    Учетные данные базы данных Пары ключей SSH Сертификаты X.509 Учетные данные AWS Учетные записи Google Cloud Учетные записи Active Directory

Все они используют один и тот же шаблон использования. Во-первых, мы настраиваем секретный механизм с данными, необходимыми для подключения к связанной службе. Затем мы определяем одну или несколько ролей, которые описывают фактическое создание секрета.

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

Затем мы создаем одну или несколько ролей (ролей хранилища, а не ролей базы данных), содержащих фактические операторы SQL, используемые для создания нового пользователя. Обычно они включают не только оператор создания пользователя, но и все необходимые операторы предоставления доступа, необходимые для доступа к объектам схемы (таблицам, представлениям и т. д.).

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

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

4.3. Криптографические ключи

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

Связанный API позволяет клиентам отправлять данные Vault в виде обычного текста и получать их зашифрованную версию. Возможно и обратное: мы можем отправить зашифрованные данные и получить исходный текст.

В настоящее время существует только один движок этого типа: движок Transit. Этот движок поддерживает популярные типы ключей, такие как RSA и ECDSA, а также поддерживает конвергентное шифрование. При использовании этого режима данное значение открытого текста всегда приводит к одному и тому же результату зашифрованного текста, свойство, которое очень полезно в некоторых приложениях.

Например, мы можем использовать этот режим для шифрования номеров кредитных карт в таблице журнала транзакций. При конвергентном шифровании каждый раз, когда мы вставляем новую транзакцию, значение зашифрованной кредитной карты будет одинаковым, что позволяет использовать обычные SQL-запросы для отчетов, поиска и т. д.

5. Настройка хранилища

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

«Развертывание Vault простое: просто скачайте пакет, соответствующий нашей операционной системе, и извлеките его исполняемый файл (vault или vault.exe в Windows) в какой-нибудь каталог в нашем PATH. Этот исполняемый файл содержит сервер, а также является стандартным клиентом. Также доступен официальный образ Docker, но мы не будем его здесь рассматривать.

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

Вместо этого мы будем использовать постоянное хранилище на основе файлов и настроим HTTPS, чтобы мы могли изучить некоторые детали реальной конфигурации, которые могут быть источником проблем.

5.1. Запуск Vault Server

Vault использует файл конфигурации в формате HCL или JSON. Следующий файл определяет всю конфигурацию, необходимую для запуска нашего сервера с использованием хранилища файлов и самозаверяющего сертификата:

storage "file" {
  path = "./vault-data"
}
listener "tcp" {
  address = "127.0.0.1:8200"
  tls_cert_file = "./src/test/vault-config/localhost.cert"
  tls_key_file = "./src/test/vault-config/localhost.key"
}

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

$ vault server -config ./vault-test.hcl

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

5.2. Инициализация Vault

Теперь наш сервер Vault запущен, но поскольку это его первый запуск, нам необходимо его инициализировать.

Давайте откроем новую оболочку и выполним для этого следующие команды:

$ export VAULT_ADDR=https://localhost:8200
$ export VAULT_CACERT=./src/test/vault-config/localhost.cert
$ vault operator init

Здесь мы определили несколько переменных среды, поэтому нам не нужно каждый раз передавать их в Vault в качестве параметров: ~~ ~ VAULT_ADDR: базовый URI, по которому наш API-сервер будет обслуживать запросы. VAULT_CACERT: Путь к открытому ключу сертификата нашего сервера.

    В нашем случае мы используем VAULT_CACERT, чтобы мы могли использовать HTTPS для доступа к Vault API. Нам это нужно, потому что мы используем самозаверяющие сертификаты. В этом нет необходимости для производственных сред, где у нас обычно есть доступ к сертификатам, подписанным ЦС.

После выполнения приведенной выше команды мы должны увидеть следующее сообщение:

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

Unseal Key 1: <key share 1 value>
Unseal Key 2: <key share 2 value>
Unseal Key 3: <key share 3 value>
Unseal Key 4: <key share 4 value>
Unseal Key 5: <key share 5 value>

Initial Root Token: <root token value>

... more messages omitted

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

Теперь, когда мы его инициализировали, посмотрим на состояние нашего сервера с помощью следующей команды: ~~ ~

$ export VAULT_TOKEN=<root token value> (Unix/Linux)

Мы видим, что Хранилище все еще запечатано. Мы также можем следить за ходом распечатывания: «0/3» означает, что Vault нужно три доли, но пока нет ни одной. Давайте двигаться вперед и обеспечить его нашими акциями.

$ vault status
Key                Value
---                -----
Seal Type          shamir
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    0/3
Unseal Nonce       n/a
Version            0.10.4
HA Enabled         false

5.3. Vault Unseal

Теперь мы распечатываем Vault, чтобы начать пользоваться его секретными службами. Нам нужно указать любые три из пяти ключевых общих ресурсов, чтобы завершить процесс распечатывания:

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

$ vault operator unseal <key share 1 value>
$ vault operator unseal <key share 2 value>
$ vault operator unseal <key share 3 value>

Свойство «Sealed» в данном случае равно «false», что означает, что Vault готов принимать команды.

Key             Value
---             -----
Seal Type       shamir
Sealed          false
... other properties omitted

6. Тестирование Vault

В этом разделе мы протестируем нашу настройку Vault, используя два поддерживаемых типа секретов: ключ/значение и базу данных. Мы также покажем, как создавать новые токены с прикрепленными к ним определенными политиками.

6.1. Использование секретов ключ/значение

Во-первых, давайте сохраним секретные пары ключ-значение и прочитаем их обратно. Предполагая, что командная оболочка, используемая для инициализации Vault, все еще открыта, мы используем следующую команду для сохранения этих пар по пути secret/fakebank:

«

$ vault kv put secret/fakebank api_key=abc1234 api_secret=1a2b3c4d

«Теперь мы можем восстановить эти пары в любое время с помощью следующей команды:

$ vault kv get secret/fakebank
======= Data =======
Key           Value
---           -----
api_key       abc1234
api_secret    1a2b3c4d

Этот простой тест показывает, что Vault работает должным образом. Теперь мы можем протестировать некоторые дополнительные функции.

6.2. Создание новых токенов

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

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

$ vault token create -ttl 1m
Key                  Value
---                  -----
token                <token value>
token_accessor       <token accessor value>
token_duration       1m
token_renewable      true
token_policies       ["root"]
identity_policies    []
policies             ["root"]

Давайте протестируем этот токен, используя его для чтения пар ключ/значение, которые мы создали. до:

$ export VAULT_TOKEN=<token value>
$ vault kv get secret/fakebank
======= Data =======
Key           Value
---           -----
api_key       abc1234
api_secret    1a2b3c4d

Если мы подождем минуту и ​​попытаемся повторить эту команду, мы получим сообщение об ошибке:

$ vault kv get secret/fakebank
Error making API request.

URL: GET https://localhost:8200/v1/sys/internal/ui/mounts/secret/fakebank
Code: 403. Errors:

* permission denied

Сообщение указывает, что наш токен больше не действителен, чего мы и ожидали .

6.3. Политики тестирования

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

Например, давайте определим политику, которая разрешает доступ только для чтения к пути secret/fakebank, который мы использовали ранее:

$ cat > sample-policy.hcl <<EOF
path "secret/fakebank" {
    capabilities = ["read"]
}
EOF
$ export VAULT_TOKEN=<root token>
$ vault policy write fakebank-ro ./sample-policy.hcl
Success! Uploaded policy: fakebank-ro

Теперь мы создадим токен с этой политикой с помощью следующей команды:

$ export VAULT_TOKEN=<root token>
$ vault token create -policy=fakebank-ro
Key                  Value
---                  -----
token                <token value>
token_accessor       <token accessor value>
token_duration       768h
token_renewable      true
token_policies       ["default" "fakebank-ro"]
identity_policies    []
policies             ["default" "fakebank-ro"]

Как мы делали раньше, давайте прочитаем наши секретные значения, используя этот токен:

$ export VAULT_TOKEN=<token value>
$ vault kv get secret/fakebank
======= Data =======
Key           Value
---           -----
api_key       abc1234
api_secret    1a2b3c4d

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

$ vault kv put secret/fakebank api_key=foo api_secret=bar
Error writing data to secret/fakebank: Error making API request.

URL: PUT https://127.0.0.1:8200/v1/secret/fakebank
Code: 403. Errors:

* permission denied

Поскольку наша политика явно не разрешает запись, Vault возвращает код состояния 403 — доступ запрещен.

6.4. Использование учетных данных динамической базы данных

В качестве последнего примера в этой статье давайте воспользуемся секретным механизмом базы данных Vault для создания динамических учетных данных. Здесь мы предполагаем, что у нас есть сервер MySQL, доступный локально, и что мы можем получить к нему доступ с привилегиями «root». Мы также будем использовать очень простую схему, состоящую из одной таблицы — account.

Сценарий SQL, используемый для создания этой схемы и привилегированного пользователя, доступен здесь.

Теперь давайте настроим Vault для использования этой базы данных. Секретный механизм базы данных не включен по умолчанию, поэтому мы должны исправить это, прежде чем продолжить:

$ vault secrets enable database
Success! Enabled the database secrets engine at: database/

Теперь мы создаем ресурс конфигурации базы данных:

$ vault write database/config/mysql-fakebank \
  plugin_name=mysql-legacy-database-plugin \
  connection_url="{{username}}:{{password}}@tcp(127.0.0.1:3306)/fakebank" \
  allowed_roles="*" \
  username="fakebank-admin" \
  password="Sup&rSecre7!"

Префикс пути database/config — это место, где находится вся база данных. конфигурации должны быть сохранены. Мы выбираем имя mysql-fakebank, чтобы мы могли легко выяснить, к какой базе данных относится эта конфигурация. Что касается ключей конфигурации:

    plugin_name: Определяет, какой плагин базы данных будет использоваться. Доступные имена подключаемых модулей описаны в документации Vault. connection_url: это шаблон, используемый подключаемым модулем при подключении к базе данных. Обратите внимание на заполнители шаблонов {{username}} и {{password}}. При подключении к базе данных Vault заменит эти заполнители фактическими значениями allow_roles: определите, какие роли Vault (обсуждаемые далее) могут использовать эту конфигурацию. В нашем случае мы используем «*», поэтому он доступен для всех ролей: имя пользователя и пароль: это учетная запись, которую Vault будет использовать для выполнения операций с базой данных, таких как создание нового пользователя и отзыв его привилегий

База данных Vault Настройка роли

Последней задачей настройки является создание ресурса роли базы данных Vault, который содержит команды SQL, необходимые для создания пользователя. Мы можем создать столько ролей, сколько необходимо, в соответствии с нашими требованиями безопасности.

Здесь мы создаем роль, которая предоставляет доступ только для чтения ко всем таблицам схемы fakebank:

$ vault write database/roles/fakebank-accounts-ro \
    db_name=mysql-fakebank \
    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON fakebank.* TO '{{name}}'@'%';"

Механизм базы данных определяет префикс пути к базе данных/ролям как место для хранения ролей. fakebank-accounts-ro — это имя роли, которое мы позже будем использовать при создании динамических учетных данных. Мы также предоставляем следующие ключи:

    db_name: Имя существующей конфигурации базы данных. Соответствует последней части пути, который мы использовали при создании ресурса конфигурации create_statements: список шаблонов операторов SQL, которые Vault будет использовать для создания нового пользователя

Создание динамических учетных данных

«Когда у нас есть роль базы данных и ее соответствующая конфигурация, мы генерируем новые динамические учетные данные с помощью следующей команды:

$ vault read database/creds/fakebank-accounts-ro
Key                Value
---                -----
lease_id           database/creds/fakebank-accounts-ro/0c0a8bef-761a-2ef2-2fed-4ee4a4a076e4
lease_duration     1h
lease_renewable    true
password           <password>
username           <username>

Префикс database/creds используется для создания учетных данных для доступных ролей. Поскольку мы использовали роль fakebank-accounts-ro, возвращенное имя пользователя/пароль будет ограничено для выбора операций.

Мы можем убедиться в этом, подключившись к базе данных, используя предоставленные учетные данные, а затем выполнив некоторые команды SQL:

$ mysql -h 127.0.0.1 -u <username> -p fakebank
Enter password:
MySQL [fakebank]> select * from account;
... omitted for brevity
2 rows in set (0.00 sec)
MySQL [fakebank]> delete from account;
ERROR 1142 (42000): DELETE command denied to user 'v-fake-9xoSKPkj1'@'localhost' for table 'account'

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

7. Заключение

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

Попутно мы создали простую, но функциональную тестовую среду, которую будем использовать в последующих статьях.

В следующей статье будет рассмотрен очень специфический вариант использования Vault: его использование в контексте приложения Spring Boot. Быть в курсе!