Как реализовать поворот ключа с нулевым временем простоя - PullRequest
0 голосов
/ 22 января 2019

У меня есть несколько микро-сервисов, работающих в 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, расшифровка не удастся.

Здесь я вижу несколько подходов:

  1. Разделите на два секрета с отдельными схемами вращения и вращайте по одному, как описано выше. Это добавляет накладные расходы с точки зрения дополнительных секретов для обработки, с идентичными значениями (за исключением того, что они вращаются с некоторым временем между ними)

  2. Пусть расшифровка заставит освежить секрет при неудаче:

    • Шифрование всегда использует AWSCURRENT (J1 или J2 в зависимости от того, обновляется)
    • Дешифрование будет пытаться использовать AWSCURRENT, затем AWSPREVIOUS, и, если оба сбоя (из-за шифрования другим экземпляром, который использует J2 и [J1, J0] сохранен), запросит обновление секрета вручную (теперь сохранено [J2, J1]), и затем снова попробуйте AWSCURRENT и AWSPREVIOUS.
  3. Используйте три ключа в окне ключей и всегда шифруйте со средним ключом, поскольку он всегда должен находиться в окне всех остальных экземпляров (если он не был повернут несколько раз, быстрее, чем интервал обновления). Это добавляет сложности.

Какие еще есть варианты? Это похоже на такой стандартный вариант использования, но я все еще изо всех сил пытался найти лучший подход.

РЕДАКТИРОВАТЬ ------------------

Исходя из ответа ДжоБа, алгоритм, который я до сих пор придумал, таков: Допустим, изначально секрет имеет значение CURRENT K1 и значение PENDING null.

Нормальная работа

  • Все службы периодически (каждые T секунд) запрашивают SecretsManager для AWSCURRENT, AWSPENDING и пользовательскую метку ROTATING и принимают их все (если они существуют) -> Все службы принимают [AWSCURRENT = K1]
  • Все клиенты используют AWSCURRENT = K1

Вращение ключа

  1. Установите новое значение K2 для этапа ОЖИДАНИЯ
  2. wait T секунд -> Все сервисы теперь принимают [AWSCURRENT = K1, AWSPENDING = K2]
  3. Добавить ROTATING в версию K1 + переместить AWSCURRENT в версию K2 + удалитьAWSPENDING метка из К2 (похоже, нет атомной перестановки меток).До тех пор, пока не пройдет T секунд, некоторые клиенты будут использовать K2, а некоторые K1, но все службы принимают как
  4. wait T секунд -> Все службы все еще принимают [AWSCURRENT = K2, AWSPENDING = K1] и всеклиенты используют AWSCURRENT = K2
  5. Удалить этап ROTATING из K1.Обратите внимание, что у K1 все еще будет этап AWSPREVIOUS.
  6. Через T секунд все службы будут принимать только [AWSCURRENT = K2], а K1 фактически мертв.

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

К сожалению, я не знаю, как использовать для этого встроенный механизм ротации, поскольку для этого требуется несколько шагов с задержками между,Одна из идей состоит в том, чтобы придумать несколько пользовательских шагов и заставить шаг setSecret создать событие cron CloudWatch, которое снова вызовет функцию через T секунд, вызывая ее с шагами swapPending и removePending.Было бы замечательно, если бы SecretsManager мог поддерживать это автоматически, например, поддерживая, что функция возвращает значение, указывающее, что следующий шаг должен быть вызван через T секунд.

1 Ответ

0 голосов
/ 23 января 2019

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

  1. Сохранить новые учетные данные в диспетчере секретов с меткой этапа AWSPENDING (если вы проходите этап создания секрета, он не помечается как AWSCURRENT).Также используйте токен идемпотентности, предоставленный лямбде, при создании секрета, чтобы не создавать дубликаты при повторной попытке.
  2. Возьмите секрет, хранящийся в диспетчере секретов на этапе AWSPENDING, и добавьте его в качестве учетных данных в службу B.
  3. Убедитесь, что вы можете войти в службу B с учетными данными AWSPENDING.
  4. Измените этап учетных данных AWSPENDING на AWSCURRENT.

Это те же секреты шаговМенеджер берет, когда создает RDS ротацию многопользовательской лямбды.Обязательно используйте метку AWSPENDING, потому что менеджер секретов обрабатывает это специально.Если служба B не поддерживает два активных учетных данных или совместное использование данных несколькими пользователями, возможно, нет способа сделать это.См. документы по ротации диспетчера секретов .

Кроме того, механизм ротации диспетчера секретов является асинхронным и повторяет попытки после сбоев (именно поэтому каждый лямбда-шаг должен быть идемпотентным).Существует первоначальный набор повторов (порядка 5), а затем несколько ежедневных повторов.Вы можете воспользоваться этим, провалив третий шаг (проверка секрета) через исключение, пока не будут выполнены условия распространения.В качестве альтернативы вы можете увеличить время выполнения лямбды до 15 минут и поспать соответствующее время в ожидании завершения распространения.Однако у спящего метода есть недостаток, заключающийся в ненужной привязке ресурсов.

Имейте в виду, как только вы удалите стадию ожидания или переместите AWSCURRENT на стадию ожидания, двигатель вращения остановится.Если приложение B принимает текущее и ожидающее обработки (или текущее, ожидающее и предыдущее, если вы хотите быть в большей безопасности), четыре шага, приведенные выше, будут работать, если вы добавите задержку, которую вы описали.Вы также можете взглянуть на образец AWS Secrets Manager Lambdas , где приведены примеры того, как манипулировать этапами для вращения базы данных.

Что касается вашего вопроса о шифровании, лучший способ, который я видел, - этохранить идентификатор ключа шифрования с зашифрованными данными.Поэтому, когда вы шифруете данные D1 ключом J1, вы либо сохраняете, либо иным образом передаете в нисходящее приложение что-то вроде секретного ARN и версии (скажем, V) в приложение.Если служба A отправляет зашифрованные данные службе B в сообщении M (...), она будет работать следующим образом:

  1. A выбирает ключ J1 для этапа AWSCURRENT (идентифицируется ARN и версией V1).
  2. A шифрует данные D1 как E1, используя ключ J1, и отправляет их в сообщении M1 (ANR, V1, E1) на B.
  3. Позже J1 поворачивается к J2, а J2 помечается как AWSCURRENT..
  4. A выбирает ключ J2 для этапа AWSCURRENT (идентифицируемый ARN и V2).
  5. A шифрует данные D2 как E2 с помощью ключа J2 и отправляет их в сообщении M2 (ANR, V2,E2) до B.
  6. B получает M1 и выбирает ключ (J1), указав ARN, V1 и расшифровывает E1, чтобы получить D1.
  7. B получает M2 и выбирает ключ (J2) с помощьюуказав ARN, V2 и расшифровывая E2, чтобы получить D2.

Обратите внимание, что ключи могут кэшироваться как A, так и B. Если зашифрованные данные должны храниться в течение длительного времени, вам необходимо убедиться, чтоключ не удаляется до тех пор, пока зашифрованные данные больше не исчезнут или не будут повторно зашифрованы с использованием текущего ключа.у.Вы также можете использовать несколько секретов (вместо версий), передавая разные ARN.

Другой альтернативой является использование KMS для шифрования. Служба A отправляет зашифрованный ключ данных KMS вместо идентификатора ключа вместе с зашифрованной полезной нагрузкой. Зашифрованный ключ данных KMS можно расшифровать с помощью B, вызвав KMS, а затем использовать ключ данных для расшифровки полезной нагрузки.

...