翻译自Edge Authentication and Token-Agnostic Identity Propagation。通过本文可以了解到Netflix是如何通过将认证转移到边缘设备来降低系统内容内部的认证流程,以及如何使用统一的认证结构支持系统对身份信息的需求。
正如大多数开发人员认为的那样,对安全协议和身份令牌,以及用户和设备身份验证的处理可能会充满挑战。假设有很多协议,令牌,200M+的用户,以及上千个设备,问题可能随时会在范围内爆发。几年前,我们决定通过发起一个新的计划,组建一个新的团队来解决这种复杂性,将用户和设备身份验证以及各种安全协议和令牌的复杂处理移至(由一组集中式服务和一个团队管理的)边缘网络上。在这个过程中,我们更改了身份在服务之间的传播方式,转而使用支持加密验证且令牌无关的身份对象。
通过本文可以了解到:
- 如何降低服务所有者的复杂度,服务所有者不需要再了解并负责终结安全协议,以及处理无数的安全令牌;
- 通过将令牌管理委派给在该领域具有专业知识的服务和团队来提高安全性;
- 提高审计能力和取证分析。
我们是如何做到的
Netflix最初是一个允许会员管理其DVD队列的网站。该网站后来提供了流内容,流设备稍晚一些,但是这些初始设备的功能受到了限制。随着时间的推移,流设备的功能越来越强大,曾经只能通过网站访问的功能也拓展到了流设备上。Netflix服务的扩展非常快速,目前已经支持多达2000种设备类型。
支持这些功能的服务识别多种令牌以及安全协议(用于验证用户和设备,并授权访问这些功能)的负担也越来越重。整个系统变得相当复杂,开发也变得脆弱。加上边缘层的架构已经演化到PaaS模型,我们需要确定如何,以及在哪里处理身份令牌。
复杂度:多个服务处理认证令牌
为了展示流的复杂度,下面描述了在架构修改前,用户是如何登录的:
从最高层面看,此流程(大大简化)涉及的步骤如下:
- 用户输入凭据,然后Netflix客户端将凭据以及设备的ESN传输到边缘网关,即Zuul;
- Zuul将用户调用重定向到API/登录终端;
- API服务编排后端系统,验证用户身份。
- 成功验证请求提供的声明后,API服务会返回cookie给上游,包括customerId 和ESN,以及一个到期指令;
- Zuul发送Cookies到NetFlix客户端。
该模型有一些问题,如:
- 外部有效的令牌被深深地嵌入到调用栈中,因此需要一直向上游传播,可能会导致记录不合理的日志或导致潜在的管理问题。
- 上游系统必须重新打开令牌来识别用户登录,并可能管理多个并行的身份数据结构,很可能导致数据不同步。
多协议&令牌
本例展示了处理一个协议(HTTP/S)以及一个令牌类型(Cookies)的流程。在Netflix的流产品中使用了一些协议和令牌,概括如下:
Netflix 的流生态系统会消费(有可能会更改)这些令牌,如:
更复杂的是,可以通过多种方法在系统之间传输这些令牌或令牌中包含的数据。在某些情况下会不断打开令牌,从中抽取身份数据元素,作为API调用使用的简单基元或字符串,或通过请求上下文首部或URL参数在系统间传递。整个过程中并不会检查令牌或令牌中包含的数据的完整性。
Netflix 的规模
同时,Netflix 的规模在以指数增长。现在,Netflix 已经有200M+的订阅,以及每月上百万个活动的设备。我们每秒要服务超过2.5百万个请求,相当大一部分用于某种格式的认证。在老的架构中,每一个请求都会触发一个API调用,用来验证请求中声明的内容,如下所示:
加入EdgePaaS
后续业务的变动使得情况变得更复杂,边缘工程团队正在将老的API服务架构迁移到一个新的基于PaaS的方式。在我们迁移到EdgePaaS的同时,前后端服务也从基于Java的API迁移到了BFF(backend for frontend),即NodeQuark:
该模型可以在不依赖核心API框架的前提下让前后端工程拥有和操作各自的服务。但这也引入了另一层复杂性,即这些NodeQuark服务如何处理身份令牌?NodeQuark服务是用JavaScript编写的,废除了像MSL这样复杂又浪费的协议,这些协议会复制所有令牌管理的逻辑。
做个总结,在大规模场景下,发现我们使用了一个复杂且低效的方案来处理认证和身份令牌。我们有多种身份令牌类型和资源,每种身份令牌又需要不同的处理,各个处理逻辑被复制到了多个系统中。关键身份数据以不一致的方式在整个服务器生态系统中传播。
使用边缘认证解决问题
我们意识到,为了解决这个问题,需要一个统一的身份模型,在上游进一步处理身份验证令牌(和协议)。我们通过将认证和协议终结转移到边缘网络,然后创建一个新的完整性保护的且令牌无关的对象,使该对象在整个服务器生态系统中传播。
将认证转移到边缘
注意,我们的目标是提升安全性,并降低复杂度,进而提供更好的用户体验,我们就如何将设备身份验证操作以及用户标识和身份验证令牌管理集中到服务边缘制定了相应的策略。
从上层看,Zuul(云网关)作为令牌检查和载荷加密/解密的终结点。这种情况下,Zuul可以处理这些操作(一小部分),例如,如果没有出现令牌,则需要更新,否则视为无效。Zuul会将这些操作委派给一组新的边缘身份验证服务,用来处理加密密钥交换以及令牌的创建或更新。
边缘认证服务
边缘认证服务(EAS)是一个架构理念,包含将设备和用户的认证和身份验证从栈转移到云边缘,以及用于处理令牌类型而开发的服务套件。
EAS是运行在Zuul中的一系列过滤器,可能会调用外部服务来支持域(domain),如调用一个服务来处理MSL 令牌或Cookies的其他令牌。EAS涵盖了为只读令牌创建"Passport"(稍后会涉及到)。
EAS处理请求的基本模式如下:
对于每个进入Netflix 服务的请求,Zuul中的EAS入站过滤器会检查设备客户端提供的令牌,然后将请求转发到"Passport"检查过滤器(Passport Injection Filter),或某个认证服务进行处理。Passport Injection Filter会生成一个令牌无关的身份,然后使用该身份在剩余的服务生态系统中传播。在响应路径上,在边缘认证服务的协助下,EAS出站过滤器会生成需要发送到客户端设备的令牌。
现在系统架构的格式如下:
注意令牌永远不会越过边缘网关/EAS边界。MSL安全协议会在边缘网关上终结,且所有的令牌会在网关上打开,然后以一种令牌无关的方式在服务生态系统中传播身份数据。
在新的处理路径上,Zuul能够处理大量有效且未过期的令牌,边缘认证服务处理剩余的请求。
EAS服务具有容错性,例如在Zuul标识Cookies有效但已过期,且对EAS的续约调用失败或某些潜在的错误情况下:
这种失败场景下,Zuul中的EAS过滤器将会容忍这种错误,并允许解析后的身份继续传播,并在下一次请求时重新调度续约调用。
令牌无关的身份(Passport)
使用简单的可变身份结构是远远不够的,因为这样会导致服务到服务间传递的身份缺少足够的信任。此时需要令牌无关的身份结构。
我们引入了一个称为"Passport"的身份结构,它允许以统一的方式传播用户和设备身份信息。Passport也是一种令牌,但相比使用外部令牌,使用内部结构能带来很多好处。然而,下游系统仍然需要访问用户和设备身份。
Passport 是一种由边缘网关为每个请求创建的短生命的身份结构,即它的生存时间取决于请求的生命周期,且仅在Netflix生态系统内部有效。Passport由Zuul通过一组身份过滤器生成。一个Passport包含用户&设备身份,格式为protobuf,其完整性由HMAC保证。
Passport 结构
如上所述,Passport 模型为一个Protocol Buffer。从高层看,Passport 的定义如下:
message Passport { Header header = 1; UserInfo user_info = 2; DeviceInfo device_info = 3; Integrity user_integrity = 4; Integrity device_integrity = 5; }
Header元素传达了创建Passport的服务的名称。更有意义的是传播的与用户和设备有关的内容。
用户&设备信息
UserInfo 元素包含识别发起请求的用户所需的所有信息,DeviceInfo 元素包含用户访问Netflix的设备所需的所有信息:
message UserInfo { Source source = 1; int64 created = 2; int64 expires = 3; Int64Wrapper customer_id = 4; … (some internal stuff) … 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; … (some more internal stuff) … }
UserInfo
和DeviceInfo
包含了请求的Source
和PassportAuthenticationLevel
。Source
是一个声明类型列表,为使用的协议以及用于验证声明的服务。PassportAuthenticationLevel
为放到认证声明中的信任的级别。
enum Source { NONE = 0; COOKIE = 1; COOKIE_INSECURE = 2; MSL = 3; PARTNER_TOKEN = 4; … } enum PassportAuthenticationLevel { LOW = 1; // untrusted transport HIGH = 2; // secure tokens over TLS HIGHEST = 3; // MSL or user credentials }
下游应用可以使用这些值来进行授权或决定用户体验。
Passport 的完整性
Passport 的完整性由HMAC保证(基于哈希的消息认证码),HMAC是一种特定类型的MAC,涉及密码哈希函数和密钥,可以同时用于校验数据完整性和消息的真实性。
用户和设备的完整性定义如下:
message Integrity { int32 version = 1; string key_name = 2; bytes hmac = 3; }
当Integrity的version为1表示为HMAC使用SHA-256,编码为ByteArray。未来Integrity的version可能使用一个不同的哈希函数或编码。在version为1时,HMAC字段包含MacSpec.SHA_256中的256位。
完整性防护保证Passport 字段在Passport创建之后不会改变。客户端应用可以在使用其中包含的任何值之前,通过Passport Introspector检查Passport的完整性。
Passport Introspector
Passport对象本身是不透明的。客户端可以使用Passport Introspector从首部抽取Passport,并检索其中的内容。Passport Introspector是Passport二进制数据的包装器。客户端可以通过一个工厂创建一个Introspector,然后就访问基本的访问器方法:
public interface PassportIntrospector { Long getCustomerId(); Long getAccountOwnerId(); String getEsn(); Integer getDeviceTypeId(); String getPassportAsString(); … }
Passport Actions
下面定义了Passport 协议缓冲,以及Passport Actions 的定义:
message UserInfo { repeated UserAction actions = 12; … } message DeviceInfo { repeated DeviceAction actions = 7; … }
当对用户或设备身份进行更新时,下游服务会显示地发送一个Passport Actions。EAS 会使用该信号来创建或更新对应类型的令牌。
重新审视登录流程
让我们总结一下所有这些解决方案一起工作的例子。
在将认证和协议终结转移到边缘,并引入Passports作为身份之后,前面所述的登录流程演变为了以下内容:
- 用户输入凭据,Netflix客户端将设备ESN和凭据传送到边缘网关,即Zuul;
- Zuul上运行的身份过滤器会生成一个绑定设备的Passport,然后将其传送到API/登录终端;
- API服务将Passport传播到负责认证用户的中间层服务;
- 在成功认证提供的声明之后,这些服务会创建并发送一个Passport Action(伴随原始Passport),同时将流备份到API和Zuul;
- Zuul会调用Cookie服务来解析Passport和Passport Actions,然后将Cookies返回Netflix客户端。
主要的好处
简化授权
存在外部令牌流入下游系统的原因是,授权决策经常会依赖令牌中的认证声明,且信任与各种令牌类型相关联。在我们的Passport结构中为信任分配了不同的级别,意味着,需要授权决策的系统可以围绕Passport编写合理的规则,而无需在很多服务的代码中重复信任规则。
显示的,可扩展的身份模型
具有规范身份的结构非常有用。传递身份原始数据的方式比较脆弱且难以调试。如果在一个调用声明中,用户的身份从服务A切换到了服务D,那么谁会发生改变?一旦身份结构通过所有关键系统,一种相对简单的方式是添加一个新的外部令牌类型,新的信任级别,以及新的方式来表示该身份。
操作问题和可见性
拥有一个像Passport的结构,可以允许定义一个使用Passport定义的服务,并且可以被其他服务校验。当传播Passport且在日志中看到该Passport时,我们可以打开、校验、了解其身份内容。也可以了解到Passport的来历,并跟踪到它是如何进入系统的。这使得调试异常身份问题变得更加容易。
降低下游系统的复杂度&负载
传递一个统一的结构到下游系统,意味着这些系统可以使用内省库查看设备和用户身份(由于使用了相同的结构,因此无需单独处理各个类型的外部令牌)
通过将令牌处理从这些系统卸载到中央边缘认证服务上,下游系统在CPU、请求延迟、垃圾回收指标当方面获得了可观的收益,所有这些都可以帮助降低集群占用空间以及云支出。下面例子中的受益都来源于主要的API服务。
在前面的实现中,每个请求必须承担两次解密/终止开销,因为我们需要在边缘具有路由的能力,且需要在下游服务中具有丰富的终止请求的能力。某些性能的提高归因于这些功能的合并-现在仅需要处理一次MSL请求。
CPU的RPS占比
卸载令牌的处理使得每个请求的CPU开销降低了30%,并降低了40%的平均负载。下图展示了CPU的RPS比率,越低越好:
API响应时间
API服务的响应时间有了很大提升,降低了30%的平均延迟,并使99%的延迟降低20%:
垃圾回收
显著降低了API服务的垃圾回收的压力,以及GC等待值时间,下图展示了垃圾回收的STW指标:
开发者速度
将微服务开发人员和身份验证和身份相关的问题剥离开来,意味着他们可以专注于其核心领域。现在仅在一组专门的服务中完成一次对身份认证的更改即可,而无需将变更散布到多个服务中。
下一步
更强大的认证
我们目前正在扩展边缘认证服务来通过一个新的服务"Resistor"支持多因子认证。基于机器学习模型选择性地为可疑的连接引入第二个因素。随着加入了新的流程,我们引入了新的因素,例如使用一次性密码(OTP)来发送邮件或电话,给移动设备推送通知,以及使用第三方认证应用等。
我们还可能为希望在其帐户上增加安全性的用户引入可选择的多重身份验证。
灵活的授权
现在我们已经有一个系统层面的身份验证流,在授权决策中我们可以使用该身份验证流作为一个信号。去年,我们开始探索一个新的产品访问策列(PACS),现在正在将其引入Netflix流产品中。PACS最近为Streamfest( a weekend of free Netflix in India)的体验访问控制提供了支持。