HashiCorp Vault Secure Storage Backend в экосистеме Apache APISIX
API7.ai
January 24, 2022
С ростом популярности архитектуры на основе микросервисов обеспечение безопасности стало значительно сложнее, чем раньше. Мы давно прошли тот этап, когда 100 экземпляров серверов бэкенда обращались к серверу базы данных с использованием одного статического секретного ключа, потому что в случае утечки этого ключа вся система оказывается под угрозой, а отзыв этого ключа приводит к масштабному сбою в работе (теперь никто не может получить доступ к чему-либо, пока экземпляры не будут перенастроены). Мы не можем полностью исключить возможность нарушения безопасности, так как иногда случается непредвиденное. Вместо этого, наша задача — контролировать масштаб последствий в таких ситуациях. Для решения подобных сценариев в производственной среде используется популярное решение, такое как HashiCorp Vault, которое выступает в роли системы управления секретами и шифрованием на основе идентификации. В этой статье я покажу, как интегрировать Vault с плагином jwt-auth Apache APISIX (облачного API-шлюза), чтобы эффективно использовать преимущества обоих решений.
Что такое Vault
HashiCorp Vault разработан для помощи организациям в управлении доступом к секретам и их безопасной передаче внутри организации. Секреты определяются как любые формы чувствительных учетных данных, которые необходимо строго контролировать и отслеживать, и которые могут быть использованы для доступа к конфиденциальной информации. Секреты могут быть в виде паролей, API-ключей, SSH-ключей, RSA-токенов или OTP. В реальном мире часто встречается ситуация, когда секреты хранятся в конфигурационных файлах или в виде переменных в программном коде, что иногда приводит к их попаданию в системы контроля версий, такие как GitHub, BitBucket или GitLab, что представляет серьезную угрозу безопасности. Vault решает эту проблему, централизуя хранение секретов. Он предоставляет зашифрованное хранилище для статических секретов, генерацию динамических секретов с ограниченным сроком действия, аутентификацию пользователей (машин или людей) для обеспечения их авторизации доступа к определенным секретам и многое другое. Таким образом, даже в случае нарушения безопасности масштаб последствий будет значительно меньше и локализован.
Vault упрощает управление доступом, предоставляя унифицированный интерфейс для управления каждым секретом в вашей инфраструктуре. Кроме того, он позволяет создавать детальные журналы аудита и отслеживать, кто и к каким данным получил доступ.

О плагине APISIX jwt-auth
Это плагин аутентификации, который может быть подключен к любому маршруту APISIX для выполнения аутентификации JWT (JSON Web Token, подробнее) перед тем, как запрос будет перенаправлен на вышестоящий URI. Вкратце, это безопасный механизм аутентификации, который обеспечивает авторизацию доступа к критически важным ресурсам. Обычно для подписи JWT используется закрытый ключ или текстовый секрет. Получатель JWT проверяет подпись, чтобы убедиться, что токен не был изменен после подписания издателем. Целостность всего механизма JWT зависит от секрета подписи (будь то текстовый секрет или пары ключей RSA). Это затрудняет для неавторизованных источников возможность угадать ключ подписи и попытаться изменить утверждения в JWT.
Поэтому хранение этих ключей в безопасной среде крайне важно. Попадание их в чужие руки может поставить под угрозу безопасность всей инфраструктуры. Хотя мы со стороны APISIX прилагаем все усилия для соблюдения стандартных практик SecOps, в производственной среде вполне естественно иметь централизованное решение для управления ключами, такое как HashiCorp Vault, для создания подробных журналов аудита, периодической ротации ключей, их отзыва и т.д. И было бы довольно проблематично, если бы каждый раз при ротации ключей в инфраструктуре приходилось обновлять конфигурацию Apache APISIX.
Шаги по использованию Vault с Apache APISIX
Для интеграции с Vault, Apache APISIX должен быть настроен с конфигурацией Vault в файле config.yaml.
Внутренне APISIX взаимодействует с сервером Vault через KV secret engine v1 HTTP API. Поскольку большинство корпоративных решений предпочитают использовать KV Secrets Engine - Version 1 в своей производственной среде, на начальном этапе поддержки APISIX-Vault мы ограничились только версией 1. В последующих выпусках мы добавим поддержку K/V версии 2.
Основная идея использования Vault вместо etcd-бэкенда APISIX заключается в обеспечении безопасности в среде с низким уровнем доверия. Мы, разработчики APISIX, серьезно относимся к вашим приоритетам. Именно поэтому мы рекомендуем использовать токены доступа Vault, которые имеют ограниченную область действия и могут предоставлять серверу APISIX ограниченный доступ.
Настройка Vault
Если у вас уже запущен экземпляр Vault с необходимыми привилегиями, можете пропустить этот раздел. В этом разделе описаны лучшие практики использования Vault в экосистеме Apache APISIX. Пожалуйста, следуйте указанным ниже шагам.
Шаг 1: Запуск сервера Vault
Здесь у вас есть несколько вариантов: вы можете выбрать между Docker, предварительно скомпилированным бинарным файлом или сборкой из исходного кода. Поскольку для взаимодействия с сервером Vault вам нужен клиент Vault CLI, я бы предпочел использовать предварительно скомпилированный бинарный файл вместо подхода с Docker. В любом случае, выбор за вами (вы можете обратиться к официальной документации по установке Vault). Чтобы запустить сервер для разработки, выполните следующую команду.
$ vault server -dev -dev-root-token-id=root … WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory and starts unsealed with a single unseal key. The root token is already authenticated to the CLI, so you can immediately begin using Vault. You may need to set the following environment variable: export VAULT_ADDR='http://127.0.0.1:8200' The unseal key and root token are displayed below in case you want to seal/unseal the Vault or re-authenticate. Unseal Key: 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY= Root Token: root Development mode should NOT be used in production installations!
Установите правильные переменные окружения для текущего CLI.
export VAULT_ADDR='http://127.0.0.1:8200' export VAULT_TOKEN='root'
Включите k/v версию 1 движка секретов Vault с подходящим префиксом пути. В этом демо мы выберем путь kv, чтобы избежать конфликта с путем по умолчанию для kv версии 2.
$ vault secrets enable -path=kv -version=1 kv Success! Enabled the kv secrets engine at: kv/ # Для подтверждения статуса выполните $ vault secrets list Path Type Accessor Description ---- ---- -------- ----------- cubbyhole/ cubbyhole cubbyhole_4eeb394c per-token private secret storage identity/ identity identity_5ca6201e identity store kv/ kv kv_92cd6d37 n/a secret/ kv kv_6dd46a53 key/value secret storage sys/ system system_2045ddb1 system endpoints used for control, policy and debugging
Шаг 2: Генерация токена доступа Vault для APISIX
Эта статья посвящена использованию Vault в контексте плагина jwt-auth. Поэтому для потребителя APISIX (если вы не знакомы с концепцией потребителей в экосистеме APISIX, прочитайте документацию о потребителях Apache APISIX) с именем пользователя jack плагин jwt-auth ищет секреты (если включена конфигурация Vault) по пути <vault.prefix из config.yaml>/consumer/<consumer.username>/jwt-auth в хранилище kv Vault. В этом контексте, если вы назначаете пространство имен kv/apisix (путь Vault) как vault.prefix внутри config.yaml для всех данных, связанных с APISIX, мы рекомендуем создать политику для пути kv/apisix/consumer/. Дополнительная звездочка () в конце гарантирует, что политика разрешает чтение для любого пути, имеющего префикс kv/apisix/consumer.
Создайте файл политики на языке HashiCorp Configuration Language (HCL).
$ tee apisix-policy.hcl << EOF path "kv/apisix/consumer/*" { capabilities = ["read"] } EOF
Примените политику к экземпляру Vault.
$ vault policy write apisix-policy apisix-policy.hcl Success! Uploaded policy: apisix-policy
Сгенерируйте токен с новой политикой, которая была настроена с ограниченной областью доступа.
$ vault token create -policy="apisix-policy" Key Value --- ----- token s.KUWFVhIXgoRuQbbp3j1eMVGa token_accessor nPXT3q0mfZkLmhshfioOyx8L token_duration 768h token_renewable true token_policies ["apisix-policy" "default"] identity_policies [] policies ["apisix-policy" "default"]
В этом демо s.KUWFVhIXgoRuQbbp3j1eMVGa — это ваш токен доступа.
Добавление конфигурации Vault в Apache APISIX
Как обсуждалось ранее, Apache APISIX взаимодействует с экземпляром Vault через HTTP API Vault. Необходимая конфигурация должна быть добавлена в config.yaml. Вот краткая информация о различных полях, которые вы можете использовать:
- host: Адрес хоста, на котором запущен сервер Vault.
- timeout: Тайм-аут HTTP для каждого запроса.
- token: Сгенерированный токен из экземпляра Vault, который может предоставить доступ для чтения данных из Vault.
- prefix: Включение префикса позволяет лучше контролировать политики, генерировать токены с ограниченной областью действия и строго контролировать данные, которые могут быть доступны из APISIX. Допустимые префиксы: (
kv/apisix,secretи т.д.)
vault: host: 'http://0.0.0.0:8200' timeout: 10 token: 's.KUWFVhIXgoRuQbbp3j1eMVGa' prefix: 'kv/apisix'
Создание потребителя APISIX
APISIX имеет абстракцию уровня потребителя, которая идет рука об руку с сценариями аутентификации. Чтобы включить аутентификацию для любого маршрута APISIX, необходим потребитель с подходящей конфигурацией для этого конкретного типа службы аутентификации. Только тогда APISIX сможет перенаправить запрос на вышестоящий URI, успешно выполнив аутентификацию в соответствии с конфигурацией потребителя. Потребитель APISIX имеет два поля: одно — это username (обязательное) для идентификации одного потребителя от других, а другое — plugins, которое содержит конфигурации плагинов, специфичные для потребителя.
В этой статье мы создадим потребителя с плагином jwt-auth. Он выполняет аутентификацию JWT для соответствующего маршрута или службы.
Чтобы включить jwt-auth с конфигурацией Vault, выполните запрос:
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jack", "plugins": { "jwt-auth": { "key": "test-key", "vault": {} } } }'
Здесь плагин ищет секретный ключ внутри пути Vault (<vault.prefix из conf.yaml>/consumer/jack/jwt-auth) для потребителя jack, упомянутого в конфигурации потребителя, и использует его для последующей подписи и проверки JWT. Если ключ не найден по этому пути, плагин регистрирует ошибку и не может выполнить аутентификацию JWT.
Настройка тестового вышестоящего сервера
Чтобы протестировать поведение, вы можете создать маршрут для вышестоящего сервера (простой обработчик ping, который возвращает pong). Вы можете настроить его с помощью простого HTTP-сервера на Go.
// простой вышестоящий сервер package main import "net/http" func ping(w http.ResponseWriter, req *http.Request) { w.Write([]byte("secure/pong\n")) } func main() { http.HandleFunc("/secure/ping", ping) http.ListenAndServe(":9999", nil) }
Создание маршрута APISIX с включенной аутентификацией
Создайте маршрут APISIX с этим защищенным HTTP-сервером ping и включенным плагином аутентификации jwt-auth.
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "plugins": { "jwt-auth": {} }, "upstream": { "nodes": { "127.0.0.1:9999": 1 }, "type": "roundrobin" }, "uri": "/secure/ping" }'
Генерация токена из плагина jwt-auth
Теперь подпишите JWT-секрет из APISIX, который можно использовать и передавать для выполнения запросов к прокси-маршруту [http://localhost:9080/secure/ping](http://localhost:9080/secure/ping) на сервер APISIX.
$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i HTTP/1.1 200 OK Date: Tue, 18 Jan 2022 07:50:57 GMT Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/2.11.0 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8
На предыдущем шаге, если вы видите сообщение типа failed to sign jwt, убедитесь, что у вас есть секретный ключ, сохраненный по пути kv/apisix/consumers/jack/jwt-auth в Vault.
# пример $ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3 Success! Data written to: kv/apisix/consumer/jack/jwt-auth
Запрос к серверу APISIX
Теперь выполните запрос к прокси APISIX для маршрута /secure/ping. При успешной проверке запрос будет перенаправлен на наш HTTP-сервер на Go.
$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 12 Connection: keep-alive Date: Tue, 18 Jan 2022 08:00:04 GMT Server: APISIX/2.11.0 secure/pong
Любой запрос без действительного JWT вызовет ошибку HTTP 401 Unauthorized.
$ curl http://127.0.0.1:9080/secure/ping -i HTTP/1.1 401 Unauthorized Date: Tue, 18 Jan 2022 08:00:33 GMT Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/2.11.0 {"message":"Missing JWT token in request"}
Различные сценарии использования, где Vault может быть интегрирован с плагином APISIX jwt-auth
Плагин Apache APISIX jwt-auth может быть настроен для получения как простых текстовых секретных ключей, так и пар открытых-закрытых ключей RS256 из хранилища Vault.
:::note Для ранней версии этой интеграции плагин ожидает, что имя ключа секретов, хранящихся в пути Vault, будет среди [ secret, public_key, private_key] для успешного использования ключа. В будущих выпусках мы добавим поддержку ссылок на ключи с пользовательскими именами. :::
-
Вы сохранили секрет подписи HS256 в Vault и хотите использовать его для подписи и проверки JWT.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jack", "plugins": { "jwt-auth": { "key": "key-1", "vault": {} } } }'Здесь плагин ищет ключ
secretвнутри пути Vault (<vault.prefix из conf.yaml>/consumer/jack/jwt-auth) для потребителя jack, упомянутого в конфигурации потребителя, и использует его для последующей подписи и проверки JWT. Если ключ не найден по этому пути, плагин регистрирует ошибку и не может выполнить аутентификацию JWT. -
Пара ключей RS256 RSA, как открытый, так и закрытый ключи, хранятся в Vault.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jim", "plugins": { "jwt-auth": { "key": "rsa-keypair", "algorithm": "RS256", "vault": {} } } }'Плагин ищет ключи
public_keyиprivate_keyвнутри пути kv Vault (<vault.prefix из conf.yaml>/consumer/jim/jwt-auth) дляjim, упомянутого в конфигурации плагина Vault. Если ключи не найдены, аутентификация не выполняется.Если вы не уверены, как сохранить открытый и закрытый ключи в хранилище kv Vault, используйте эту команду:
# при условии, что в текущем каталоге есть файлы "public.pem" и "private.pem" $ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem Success! Data written to: kv/apisix/consumer/jim/jwt-auth -
Открытый ключ в конфигурации потребителя, а закрытый ключ — в Vault.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "john", "plugins": { "jwt-auth": { "key": "user-key", "algorithm": "RS256", "public_key": "-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----" "vault": {} } } }'Этот плагин использует открытый ключ RSA из конфигурации потребителя и закрытый ключ, полученный непосредственно из Vault.
Отключение Vault в плагине
Теперь, чтобы отключить поиск в Vault в плагине jwt-auth, просто удалите пустой объект Vault из конфигурации плагина потребителя (в данном случае это jack). Это заставит плагин JWT искать секреты подписи (как HS256/HS512, так и пары ключей RS512) в конфигурации плагина для последующих запросов к маршруту URI, где включена конфигурация jwt-auth. Даже если у вас включена конфигурация Vault в config.yaml APISIX, запросы на сервер Vault отправляться не будут.
Плагины APISIX перезагружаются "на лету", поэтому перезапуск APISIX не требуется.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jack", "plugins": { "jwt-auth": { "key": "test-key", "secret": "my-secret-key" } } }'
Итог
Эта статья знакомит вас с предстоящим выпуском интеграции Vault-Apache APISIX и связанными деталями.
Не стесняйтесь начать обсуждение в GitHub Discussions или связаться через список рассылки.