Технологический блог Netflix, 9 февраля
Авторы: члены команды AIM Karen Casella, Travis Nelson, Sunny Singh;
графика и участие: Justin Ryan, Satyajit Thadeshwar
Перевод @middlejava
Как может подтвердить большинство разработчиков, работа с протоколами безопасности и идентификационными токенами, также, как и с аутентификацией пользователей и устройств, может быть сложной задачей. Представьте, что у вас есть несколько протоколов, несколько токенов, более 200 миллионов пользователей и тысячи типов устройств, и проблема при этом может расширяться. Несколько лет назад мы решили разобраться с этой проблемой, запустив новую инициативу, и в итоге создали новую команду, чтобы перенести сложную обработку аутентификации пользователей и устройств, а также различных протоколов безопасности и токенов на границу сети, управляемой набором централизованных сервисов и одной командой. В процессе мы изменили end-to-end передачу идентификаторов внутри сети сервисов, чтобы использовать криптографически верифицируемый объект идентификатора, не использующий токены.
Далее вы узнаете больше об этом путешествии и о том, как мы смогли:
Уменьшить сложность для владельцев сервисов, которым больше не нужно знать и отвечать за терминацию протоколов безопасности и работу со множеством токенов безопасности,
Повысить безопасность путем делегирования процесса управления токенами сервисам и командам, обладающим опытом в этой области,
Улучшить возможности аудита и криминалистического анализа.
Как мы здесь оказались
Netflix начинался как веб-сайт, который позволял участникам управлять DVD очередями. Позже он был расширен возможностью потоковой передачи контента. Стриминговые устройства появились немного позже, но первые устройства были ограничены в возможностях. Со временем возможности устройств расширились, и функции, которые когда-то были доступны только на веб-сайте, стали доступны через стриминговые устройства. Масштабы сервисов Netflix быстро росли, при этом поддерживалось более 2000 типов устройств.
Сервисы, поддерживающие эти функции, теперь столкнулись с возросшей нагрузкой, связанной с возможностью работы со множеством токенов и протоколов безопасности для идентификации пользователей и устройств, а также авторизации доступа к этим функциям. Вся система была довольно сложной и начинала становиться хрупкой. Кроме того, архитектура слоя Edge эволюционировала в модель PaaS (платформа как услуга), и нам предстояло принять несколько сложных решений о том, как и где выполнять обработку токенов идентификации.
Сложность: несколько сервисов, обрабатывающих токены аутентификации
Чтобы продемонстрировать сложность системы, ниже приведено описание того, как работал процесс входа пользователя в систему до изменений, описанных в этой статье:
На самом верхнем уровне шаги, связанные с этим (сильно упрощенным) процессом, заключаются в следующем:
Пользователь вводит свои учетные данные, и клиент Netflix передает их вместе с ESN устройства на шлюз Edge, он же Zuul
Zuul перенаправляет пользовательский вызов на эндпоинт API /login.
Сервер API организует работу бэкенд систем для аутентификации пользователя.
После успешной аутентификации предоставленных клэймов сервер API отправляет в ответ куки, включая customerId (Long), ESN (String) и срок жизни.
Zuul отправляет куки обратно клиенту Netflix.
У этой модели были некоторые проблемы, например:
Токены, валидные для внешних потребителей, генерировались в самом начале стека, и им надо было пройти по всей цепочке, что открывало возможности для их ненадлежащего логирования или потенциально неправильного использования.
Вышестоящим системам приходилось повторно вскрывать токены для идентификации пользователя, входящего в систему, и потенциально работать с несколькими параллельными структурами данных, представляющих идентификаторы, которые легко могли рассинхронизироваться.
Множество протоколов и токенов
В приведенном выше примере показан один процесс, работающий с одним протоколом (HTTP/S) и одним типом токенов (куки). В стриминговых продуктах Netflix используется несколько протоколов и токенов, как показано ниже:
Эти токены использовались и потенциально изменялись несколькими системами в стриминговой экосистеме Netflix, например:
Чтобы еще больше усложнить ситуацию, существовало множество способов передачи этих токенов или содержащихся в них данных из системы в систему. В некоторых случаях токены вскрывались, и элементы идентификационных данных извлекались в виде простых примитивов или строк для использования в вызовах API или передавались из системы в систему через заголовки контекста запроса или даже в качестве параметров URL. Не было никаких проверок целостности токенов или содержащихся в них данных.
В масштабах Netflix
Тем временем масштабы деятельности Netflix росли в геометрической прогрессии. В настоящее время у Netflix более 200 миллионов подписчиков с миллионами активных устройств в месяц. Мы обслуживаем более 2,5 миллионов запросов в секунду, большой процент из которых требует той или иной формы аутентификации. В старой архитектуре каждый из этих запросов приводил к вызову API для проверки подлинности клэймов, представленных в запросе, как показано ниже:
На сцену выходит EdgePaas
Чтобы еще больше усложнить ситуацию, команда инженеров Edge находилась в процессе перехода от старой серверной архитектуры API к новому подходу на основе PaaS. Когда мы перешли на EdgePaaS, фронтенд-сервисы были перенесены c Java API на BFF (бэкенд для фронтенд), он же NodeQuark, как показано ниже:
Эта модель позволяет фронтенд инженерам владеть и управлять своими сервисами за пределами ядра фреймворка API. Однако это создало еще один уровень сложности - как сервисы NodeQuark будут работать с токенами идентификации? Сервисы NodeQuark написаны на JavaScript, и терминация такого сложного протокола, как MSL, была бы сложной и накладной, как и репликация всей логики управления токенами.
Итак, на чем мы остановились?
Подводя итог, мы столкнулись со сложным и неэффективным решением для обработки токенов аутентификации и идентификации на больших масштабах. У нас было несколько типов и источников идентификационных токенов, каждый из которых требовал специальной обработки, логика которой повторялась в разных системах. Критически важные идентификационные данные распространялись по всей экосистеме сервера в произвольной форме (inconsistent fashion).
На помощь приходит Аутентификация Edge
Мы поняли, что для решения этой проблемы необходима единая модель идентификации. Нам нужно обработать токены аутентификации (и протоколы) вверху цепочки. Мы сделали это, перенеся аутентификацию и терминацию протокола на границу сети, и создали новый объект идентификации с защищенной целостностью, не использующий токены, для распространения по всей экосистеме сервера.
Перенос аутентификации в Edge
Учитывая наши цели по повышению безопасности и снижению сложности и в конечном счете обеспечению лучшего пользовательского опыта, мы разработали стратегию централизации операций аутентификации устройств и идентификации пользователей, а также управления токенами идентификации на границах сервисов.
На верхнем уровне Zuul (облачный шлюз) должен был стать конечной точкой для проверки токенов и шифрования/дешифрования полезной нагрузки. В случае, если Zuul не сможет обработать эти операции (небольшой процент), например, если токены отсутствуют, нуждаются в обновлении или иным образом недействительны, Zuul делегирует эти операции новому набору сервисов аутентификации Edge для обработки обмена криптографическими ключами и создания или обновления токенов.
Сервисы аутентификации Edge
Сервисы аутентификации Edge (EAS) - это:
и архитектурная концепция переноса аутентификации и идентификации устройств и пользователей выше по стеку на границу облака,
и набор сервисов, разработанных для обработки каждого типа токенов.
EAS функционально представляет собой серию фильтров, запущенных в Zuul, которые могут обращаться к внешним сервисам для поддержки своих функциональных областей, например, к одному сервису - для обработки токенов MSL, к другому - для обработки куки. EAS также охватывает обработку токенов, предполагающую только чтение, для создания Паспортов (подробнее об этом позже).
Основной паттерн того, как EAS обрабатывает запросы, выглядит следующим образом:
Для каждого запроса, поступающего в сервис Netflix, Inbound Filter (входящий фильтр) EAS в Zuul проверяет токены, предоставленные клиентом устройства, и либо передает запрос в Passport Injection Filter (фильтр вставки паспортов), либо передает обработку в один из сервисов аутентификации Edge. Passport Injection Filter генерирует идентификатор, не использующий токены, для распространения далее по остальной части экосистемы сервера. При отправке ответа работает Outbound Filter (исходящий фильтр) EAS, обращаясь при необходимости к сервисам аутентификации Edge, генерирует токены, необходимые для отправки обратно на клиентское устройство.
Архитектура системы теперь принимает следующий вид:
Обратите внимание, что токены никогда не пересекают границу шлюза Edge/EAS. Протокол безопасности MSL терминируется на Edge, все токены вскрываются, а идентификационные данные расходятся по экосистеме сервера без использования токенов.
Примечание об отказоустойчивости
При удачном стечении обстоятельств Zuul сможет обработать большой процент валидных и неистекших токенов, а сервисы аутентификации Edge обработают оставшиеся запросы.
Сервисы EAS спроектированы быть отказоустойчивыми, например, когда Zuul определяет, что куки валидны, но срок их действия истек, а запрос на обновление к EAS зафейлился или потерялся:
В этом неуспешном сценарии фильтр EAS в Zuul не будет строгим и отправит обработанный идентификатор дальше и укажет, что при следующем запросе должен быть повторно запланирован вызов обновления.
Идентификатор, не использующий токены (Паспорт)
Легко изменяемая структура идентификаторов не подходила, потому что это означало бы передачу менее надежных идентификаторов от сервиса к сервису. Требовалась структура идентификаторов, не использующая токены.
Мы создали структуру идентификатора под названием «Паспорт», которая позволила нам единообразно передавать идентификационную информацию о пользователе и устройстве. Паспорт также является своего рода токеном, но есть много преимуществ в использовании внутренней структуры, которая отличается от внешних токенов. Однако нижестоящим системам по-прежнему необходим доступ к идентификатору пользователя и устройства.
Паспорт - это короткоживущая структура идентификатора, созданная в Edge для каждого запроса, таким образом он ограничен временем жизни запроса и полностью находится внутри экосистемы Netflix. Он генерируется в Zuul с помощью набора Identity Filters (фильтров идентификаторов). Паспорт содержит идентификаторы как пользователя, так и устройства, имеет формат protobuf и защищен HMAC.
Структура Паспорта
Как отмечалось выше, модель Паспорта имеет формат Protocol Buffer. На самом верхнем уровне определение Паспорта выглядит следующим образом:
message Passport {
Header header = 1;
UserInfo user_info = 2;
DeviceInfo device_info = 3;
Integrity user_integrity = 4;
Integrity device_integrity = 5;
}
Элемент Header содержит имя сервиса, создавшего Паспорт. Но более интересно то, что связано с пользователем и устройством.
Информация о пользователе и устройстве
Элемент UserInfo содержит всю информацию, необходимую для идентификации пользователя, от имени которого выполняются запросы, а элемент DeviceInfo содержит всю информацию, необходимую для устройства, с которого пользователь посещает Netflix:
message UserInfo {
Source source = 1;
int64 created = 2;
int64 expires = 3;
Int64Wrapper customer_id = 4;
… (другие внутренние вещи) …
PassportAuthenticationLevel authentication_level = 11;
repeated UserAction actions = 12;
}
message DeviceInfo {
Source source = 1;
int64 created = 2;
int64 expires = 3;
StringValue esn = 4;
Int32Value device_type = 5;
repeated DeviceAction actions = 7;
PassportAuthenticationLevel authentication_level = 8;
… (другие внутренние вещи) …
}
И UserInfo
, и DeviceInfo
содержат Source
и PassportAuthenticationLevel
для запроса. Список Source
представляет собой виды клэймов с использованным протоколом и сервисами, использованными для валидации клэймов. PassportAuthenticationLevel
- это уровень доверия, установленный нами клэйму аутентификации.
enum Source {
NONE = 0;
COOKIE = 1;
COOKIE_INSECURE = 2;
MSL = 3;
PARTNER_TOKEN = 4;
…
}
enum PassportAuthenticationLevel {
LOW = 1; // недоверенный транспорт
HIGH = 2; // токены безопасности поверх TLS
HIGHEST = 3; // MSL или учетные данные пользователя
}
Приложения, находящиеся ниже по потоку, могут использовать эти значения для принятия решений по авторизации и/или пользовательском опыте.
Целостность Паспорта
Целостность Паспорта защищена с помощью HMAC (кода аутентификации сообщений на основе хэша), который представляет собой особый тип MAC, включающий криптографическую хэш-функцию и секретный криптографический ключ. Он может использоваться для одновременной проверки как целостности данных, так и подлинности сообщения.
Целостность пользователя и устройства определяется как:
message Integrity {
int32 version = 1;
string key_name = 2;
bytes hmac = 3;
}
Версия 1 элемента Integrity использует SHA-256 для HMAC, который кодируется как ByteArray. В будущих версиях Integrity может использоваться другая хэш-функция или кодировка. В версии 1 поле HMAC содержит 256 бит из MacSpec.SHA_256.
Защита целостности гарантирует, что поля Паспорта не будут изменены после его создания. Клиентские приложения могут использовать Passport Introspector (Интроспектор Паспорта) для проверки целостности Паспорта перед использованием любого из содержащихся в нем значений.
Интроспектор Паспорта (Passport Introspector)
Сам объект Passport непрозрачен; клиенты могут использовать Интроспектор Паспорта для извлечения Паспорта из заголовков и содержимого из него самого. Интроспектор Паспорта - это обертка над двоичными данными Паспорта. Клиенты создают Интроспектор с помощью фабрики, после чего обращаются к основным методам доступа:
public interface PassportIntrospector {
Long getCustomerId();
Long getAccountOwnerId();
String getEsn();
Integer getDeviceTypeId();
String getPassportAsString();
…
}
Действия Паспорта (Passport Actions)
В protobuf-определении Паспорта, указанном выше, задаются Действия Паспорта:
message UserInfo {
repeated UserAction actions = 12;
…
}
message DeviceInfo {
repeated DeviceAction actions = 7;
…
}
Действия Паспорта - это явные сигналы, посылаемые сервисами, находящимися ниже по потоку, когда выполняется обновление идентификатора пользователя или устройства. Сигнал используется EAS для создания или обновления соответствующего типа токена.
Обновленный процесс входа в систему
Закончим примером того, как все эти решения работают вместе.
С переносом аутентификации и терминации протокола в Edge и введением Паспорта в качестве идентификатора, описанный ранее процесс входа в систему превратился в следующее:
Пользователь вводит свои учетные данные и клиент Netflix передает их вместе с ESN устройства на шлюз Edge, он же Zuul.
Фильтры идентификаторов, запущенные в Zuul, генерируют Паспорт, привязанный к устройству, и передают его в эндпоинт API /login.
Сервер API рассылает Паспорт промежуточным сервисам, ответственным за аутентификацию пользователя.
После успешной аутентификации предоставленных клэймов эти сервисы создают Passport Action и отправляют его вместе с оригинальным Паспортом вверх по потоку в API и Zuul.
Zuul вызывает сервис Куки для получения Паспорта и Действий Паспорта и отправляет Куки обратно клиенту Netflix.
Основные преимущества и извлеченные уроки
Упрощенная авторизация
Одна из причин, по которой внешние токены попадали в нижестоящие системы, заключалась в том, что решения об авторизации часто зависят от аутентификационных клэймов в токенах и доверия, сопоставленного каждому типу токена. В нашей структуре Паспорта мы присвоили уровни этому доверию, что означает, что системы, требующие решений об авторизации, могут устанавливать разумные правила для Паспорта вместо того, чтобы дублировать правила доверия в коде во многих сервисах.
Четкая и расширяемая модель идентификатора
Наличие структуры, являющейся каноническим идентификатором, очень полезно. Альтернативы, в которых циркулируют примитивы идентификаторов, являются хрупкими и трудными для отладки. Если идентификатор клиента изменился от сервиса A к сервису D в цепочке вызовов, кто его изменил? Как только структура идентификатора пройдет через все ключевые системы, относительно легко добавить новые типы внешних токенов, новые уровни доверия или новые способы представления идентификаторов.
Эксплуатационные проблемы и видимость
Наличие такой структуры, как Паспорт, позволяет определить сервисы, которые могут сформировать Паспорт, а другие сервисы могут его свалидировать. Когда Паспорт рассылается, и мы видим его в логах, его можно открыть, свалидировать и узнать, что это за идентификатор. Мы также знаем источник Паспорта и можем отследить его до того места, где он появился в системе. Это значительно упрощает отладку любых аномалий, связанных с идентификаторами.
Снижение сложности нижестоящих систем и нагрузки
Передача единообразной структуры нижестоящим системам означает, что эти системы могут легко получать идентификаторы устройства и пользователя, используя библиотеку-интроспектор. Вместо того, чтобы иметь отдельный обработчик для каждого типа внешнего токена, можно использовать общую структуру.
За счет переноса обработки токенов из этих систем в сервисы аутентификации Edge (EAS) нижестоящие системы добились значительных улучшений по ЦПУ, задержкам запросов и метрикам сборки мусора, что помогло снизить нагрузку на кластер и затраты на облако. Следующие примеры этих улучшений получены из основного сервиса API.
В предыдущей реализации было необходимо нести расходы на расшифровку/терминацию дважды за запрос, потому что нам нужна была возможность маршрутизации на границе, но и также требовалась расширенная терминация в нижестоящем сервисе. Некоторое улучшение производительности связано с объединением этих вещей - MSL-запросы теперь нужно обрабатывать только один раз.
Соотношение CPU / RPS
Перенос обработки токенов привел к снижению процессорных затрат на 30% на запрос и снижению средней нагрузки на 40%. На следующем графике показано соотношение CPU к RPS (меньше - лучше):
Время отклика API
Время отклика всех вызовов сервиса API значительно улучшилось: средняя задержка сократилась на 30%, а задержка 99-го перцентиля снизилась на 20%:
Сборка мусора
Сервис API также значительно сократил давление и количество пауз GC, что показано в метриках Stop The World Garbage Collection:
Производительность разработчика
Абстрагирование разработчиков микросервисов от проблем, связанных с аутентификацией и идентификаторами, означает, что разработчики могут сосредоточиться на своих основных задачах. Изменения в этой области теперь вносятся один раз и в одном наборе специализированных сервисов, а не распределяются по нескольким.
Что дальше?
(Еще более) Надежная аутентификация
В настоящее время мы расширяем сервисы аутентификации Edge для поддержки многофакторной аутентификации с помощью нового сервиса под названием «Резистор». Мы выборочно вводим второй фактор для подозрительных соединений, основываясь на моделях машинного обучения. По мере внедрения новых процессов мы вводим новые факторы, например, одноразовые пароли (OTP), отправляемые по электронной почте или на телефон, push-уведомления на мобильные устройства и сторонние приложения для аутентификации. Мы также можем изучить возможность многофакторной аутентификации для пользователей, которые хотят повысить безопасность своих учетных записей.
Гибкая авторизация
Теперь, когда у нас есть проверенный идентификатор, проходящий через систему, мы можем использовать его в качестве надежного сигнала для принятия решений об авторизации. В прошлом году мы начали исследовать новую Стратегию Доступа к Продуктам (Product Access Strategy - PACS) и в настоящее время работаем над ее внедрением в прод, чтобы получить ряд новых возможностей в стриминговом продукте Netflix. На базе PACS недавно запустили систему контроля доступа на Streamfest, неделю бесплатного Netflix в Индии.
Хотите еще?
Члены команды представили эту работу на QCon в Сан-Франциско (и были двумя из трех лучших участников конференции!):
Авторы являются членами команды управления доступом и идентификацией в Netflix. Мы гордимся тем, что являемся экспертами в области разработки распределенных систем, эксплуатации и управления идентификацией. И мы нанимаем сеньоров инженеров-программистов! Если вам интересно, свяжитесь с нами через LinkedIn.
Перевод @middlejava