У меня есть несколько микро-сервисов, работающих в AWS, некоторые из которых взаимодействуют друг с другом, некоторые имеют внешних клиентов или являются клиентами для внешних сервисов.
Для реализации моих услуг мне нужен ряд секретов (пары ключей RSA для подписи / проверки токенов, симметричные ключи, ключи API и т. Д.). Я использую AWS SecretsManager для этого, и он отлично работает, но сейчас я нахожусь в процессе реализации надлежащей поддержки ротации клавиш, и у меня есть несколько мыслей.
- Я использую AWS SecretsManager, периодически извлекаю секреты (~ 5 минут) и кэширую их локально.
- Я использую функцию этапов версии AWS SecretsManager для ссылки на версии AWSCURRENT и AWSPREVIOUS, если необходимо.
Допустим, службе A нужен ключ K для службы B:
- Скажем, в начале, K имеет текущее значение K1 и предыдущее значение K0.
- Служба A всегда будет использовать (и локально кешировать) версию K AWSCURRENT для связи с B, поэтому в этом случае K1
- Служба B будет хранить версии AWSCURRENT и AWSPREVIOUS в своем локальном кэше и принимать обе версии [K1, K0]
- При вращении K я сначала проверяю, что секрет, используемый службой B, вращается, так что после истечения интервала обновления все экземпляры службы B принимают [K2, K1] вместо [K1, K0]. Пока интервал обновления не истек, все экземпляры A по-прежнему используют K1.
- Когда интервал обновления истек, то есть все экземпляры B должны получить K2, я поворачиваю ключ для обслуживания, так что A будет использовать K1 или K2 до тех пор, пока не истечет интервал обновления, тогда только K2.
- Это завершает поворот ключа (но если считается, что K1 взломан, мы можем снова повернуть секрет B, чтобы вытолкнуть K1 и получить [K3, K2]).
Это лучший подход или есть другие, которые стоит рассмотреть?
Тогда в некоторых ситуациях у меня есть симметричный ключ J, который используется в той же службе, например, ключ для шифрования некоторого сеанса. Таким образом, в одном запросе к услуге C сеанс шифруется ключом J1, а затем должен быть расшифрован с помощью J1 на более позднем этапе. У меня есть несколько экземпляров службы C.
Проблема здесь в том, что если один и тот же секрет используется как для шифрования, так и для дешифрования, его вращение становится более грязным - если ключ поворачивается, чтобы иметь значение J2, и один экземпляр обновляется, так что он шифруется с помощью J2, тогда как другой экземпляр все еще не видит J2, расшифровка не удастся.
Здесь я вижу несколько подходов:
Разделите на два секрета с отдельными схемами вращения и вращайте по одному, как описано выше. Это добавляет накладные расходы с точки зрения дополнительных секретов для обработки, с идентичными значениями (за исключением того, что они вращаются с некоторым временем между ними)
Пусть расшифровка заставит освежить секрет при неудаче:
- Шифрование всегда использует AWSCURRENT (J1 или J2 в зависимости от того, обновляется)
- Дешифрование будет пытаться использовать AWSCURRENT, затем AWSPREVIOUS, и, если оба сбоя (из-за шифрования другим экземпляром, который использует J2 и [J1, J0] сохранен), запросит обновление секрета вручную (теперь сохранено [J2, J1]), и затем снова попробуйте AWSCURRENT и AWSPREVIOUS.
Используйте три ключа в окне ключей и всегда шифруйте со средним ключом, поскольку он всегда должен находиться в окне всех остальных экземпляров (если он не был повернут несколько раз, быстрее, чем интервал обновления). Это добавляет сложности.
Какие еще есть варианты? Это похоже на такой стандартный вариант использования, но я все еще изо всех сил пытался найти лучший подход.
РЕДАКТИРОВАТЬ ------------------
Исходя из ответа ДжоБа, алгоритм, который я до сих пор придумал, таков:
Допустим, изначально секрет имеет значение CURRENT K1 и значение PENDING null.
Нормальная работа
- Все службы периодически (каждые T секунд) запрашивают SecretsManager для
AWSCURRENT
, AWSPENDING
и пользовательскую метку ROTATING
и принимают их все (если они существуют) -> Все службы принимают [AWSCURRENT
= K1] - Все клиенты используют
AWSCURRENT
= K1
Вращение ключа
- Установите новое значение K2 для этапа ОЖИДАНИЯ
- wait T секунд -> Все сервисы теперь принимают [
AWSCURRENT
= K1, AWSPENDING
= K2] - Добавить
ROTATING
в версию K1 + переместить AWSCURRENT
в версию K2 + удалитьAWSPENDING
метка из К2 (похоже, нет атомной перестановки меток).До тех пор, пока не пройдет T секунд, некоторые клиенты будут использовать K2, а некоторые K1, но все службы принимают как - wait T секунд -> Все службы все еще принимают [
AWSCURRENT
= K2, AWSPENDING
= K1] и всеклиенты используют AWSCURRENT
= K2 - Удалить этап
ROTATING
из K1.Обратите внимание, что у K1 все еще будет этап AWSPREVIOUS
. - Через T секунд все службы будут принимать только [
AWSCURRENT
= K2], а K1 фактически мертв.
Это должно работать как для отдельных секретов, так и для симметричных секретов, используемых как для шифрования, так и для дешифрования.
К сожалению, я не знаю, как использовать для этого встроенный механизм ротации, поскольку для этого требуется несколько шагов с задержками между,Одна из идей состоит в том, чтобы придумать несколько пользовательских шагов и заставить шаг setSecret
создать событие cron CloudWatch, которое снова вызовет функцию через T секунд, вызывая ее с шагами swapPending
и removePending
.Было бы замечательно, если бы SecretsManager мог поддерживать это автоматически, например, поддерживая, что функция возвращает значение, указывающее, что следующий шаг должен быть вызван через T секунд.