主要结论
- 在评估新技术的过程中进行缺口分析(Gap analysis)
- 可通过持续部署和持续集成模型缓解并预防安全弱点
- 通过为部署流程提供工具实现自动化的安全扫描
- 快速部署模型需要相应范围的测试并鼓励测试驱动的开发(TDD)
- 客户端 Web 框架能跨越 Web、iOS 及 Android 等多种客户端架构提供更一致的 API
2008 年到 2014 年间,LinkedIn 全球用户数从约 1600 万增长至约 3.3 亿,短时间内爆发式增长为基础架构造成了重重压力。随着用户数持续增长,我们需要更深入地了解用户对我们平台的使用方式,以及通过哪些新产品线能更好地满足全球职场专家的需求。
超高速增长过程中的缩放
因此我们开发了多个内部使用的 Web 应用程序,工程师、产品经理、高管、运维团队会使用这些工具执行从 A/B 测试、应用程序开发和生命周期管理,到报表和分析在内的各种任务。随着新应用快速变得充实,我们也开始通过新方法解决技术问题,陆续引入并推广了更多不同类型的语言、框架和工具。这样的增长和实验最终导致不同部门的技术和解决方案间缺乏统一性,随着越来越多新入职工程师组建新团队,造成了不小的压力。
诸如 Python、Ruby、Java、Scala、JavaScript 等语言出现在各种工作任务中,此外还有不停的收购,导致整个生态内包含大量相互无法兼容,但又必不可少的解决方案。别忘了,这种探索的本意是好的,是为了针对面临的问题寻找最棒的长期解决方案。我们鼓励不同团队寻找自己认为能为组织带来价值的技术,而这种探索过程本身也可以帮助我们确定可以在长期范围内依赖的技术,进而在硅谷和全球范围内快速缩放,始终保持领先地位。
2015 年中期,数十个依然处于活跃状态的项目已经使用了不同实现、框架以及库。由于方法各不相同,团队间代码分享很麻烦,需要通过不同代码库和工作成果来体现,并且实现结果本身也缺乏统一性。例如仅就 JavaScript 来说,有些团队同时使用了库和 jQuery 以及 Backbone.js 等微库(Micro-library),一些团队使用了主流框架,一些团队开发了自己的框架。通过何种方法为应用构建前端,开发者如何跨团队共享通用逻辑,从开发者的工效和满意度方面来说如何提供更流畅的最佳实践,这些方面逐渐开始面临不确定性。
你可能已经想到了,从长期角度来看,所用技术的数量不断增长也会造成安全债。由于使用了大量框架、语言和组件,我们越来越难以评估在这基础上开发出的应用程序的安全状况。此外这也削弱了我们发布通用框架级安全解决方案,借此缓解某些类型安全漏洞危害的做法所能实现的效果。
缓慢的部署流程所产生的影响
与此同时,仅 LinkedIn.com 网站本身,复杂的基础架构就已包含超过 3,000 个子模块,代码总量超过 6 百万行,所有这些内容都包含在一个代码库主干(Trunk)中。这个主干通过一种乏味的月度发布周期进行管理,有多个团队的数百位工程师参与其中。每次发布前需要选择一个发布候选版(RC),随后将该版本交给测试团队进行为期四天的手工回归测试。如果发现任何 Bug,会对 RC 版本开发热修复程序,以避免对部署流程产生影响。为避免再等一个月才能向用户提供新功能或 Bug 修复,工程师们往往会“疲于奔命”赶在截止期限前将自己的代码签入。
(点击放大图像)
图 1:我们原本的部署流程
这种循序渐进,对时间要求极为严格的流程要求产品经理和营销合作伙伴必须共同就工作时间做出非常细致的安排,双方必须针对新功能的发布制定一致的规划。但是针对用户反馈进行迭代的过程也变得非常困难,因为我们的发布工作每年只能进行十二次。
此外,为了预防和缓解潜在安全漏洞,也对部署和发布流程提出了更高要求。我们必须做到:一旦确定某个需要的修复程序,必须尽快部署到生产环境。这通常意味着安全问题的修复程序必须独立于发布周期实现热修复。通常来说,在相对隔离的情况下部署安全补丁是一种好的实践,例如在部署安全更新过程中,不同时部署与安全性无关的 Bug 修复程序。这主要是为了降低当非安全更新影响到网站功能而需回滚时,重新暴露出安全漏洞的可能性。
飞快增长伴随着一个不那么明显的副作用:发布节奏变长,不同技术混用造成了“斑驳”不一致的用户体验(UX)。随着 LinkedIn 开始在产品开发过程中进行用户调研,我们发现很多用户觉得网站体验开始变得支离破碎,不同页面看起来感觉截然不同。因为不同团队都在通过各自周期发布,导致有关 UI 变更的反馈环路延迟,进而在长期范围内影响到变更质量。
我们采用了 3×3 方法论
2014 年 ,LinkedIn 移动工程团队进行的一次实验最终催生了目前我们使用的发布模型。这种被称之为 Train Release 的方法将原本每月发布一次的节奏变为一种名叫 3×3,每天发布三次的方法,借此可将提交后的代码在三小时内发布给用户。
这种想法并非仅适用于 Web 相关的开发。我们的最终目标是让包括 iOS、Android、API,以及组成 LinkedIn.com 网站体验,以及其他扩展产品线和服务在内的其他后端服务,所有平台都使用这样的发布节奏。
向这种发布模型的转变过程充满了挑战,需要所有工程人员,尤其是工具和基础架构团队的参与。这也意味着现有的内部应用和工具需要革新,以确保开发者可以针对部署流程的状态变更获得更及时的信息,而他们原本已经通过必要的脚本和系统对流程中的大部分任务实现了自动化,同时需要通过必要的端到端测试确保实现足够的代码覆盖面。
由于新方法的发布周期间时间窗口大幅缩短,短时间内可能无法对所有变更进行测试,这也更进一步增加了对测试技术和自动化技术的依赖。此外结合其他方面的挑战,使得我们需要通过客户端技术加强对开发生命周期内测试工作的重视,毕竟从历史上来看,整个业界都不认为测试工作应该是客户端工程领域需要考虑的,Web 领域更是如此。这个问题与上文提到的其他痛点结合在一起,使得我们决定不仅需要对 LinkedIn.com 体验做出改变,同时也要改变基础架构和应用程序层技术栈的结构。
这意味着平台方面发现的安全问题现在可以在极端时间里修复,同时也意味着随着代码的更快速部署,也会更快速遇到安全问题。当组成 LinkedIn 生态的上百个应用开始采用这种模型后,我们的安全技术也需要更进一步的自动化。
我们的“下一步”措施
为了实现安全的 Web 应用程序,一种简单但强大的方法是选择本身就具备相应安全特性的框架。当然,框架的选择不能只从安全性着手,性能、易用性,以及其他因素同样重要。
我们的基础架构团队与其他合作伙伴团队携手,开始广泛研究并评估不同语言、框架,以及库,针对不同技术进行缺口分析,这些工作完整涵盖了服务器端,以及当前和未来产品线与内部平台中充当各类工具的应用程序。
此外我们的用户体验研究团队也邀请用户参与焦点小组讨论和反馈工作,更好地了解用户对 LinkedIn.com 访问体验的看法和感受。
产品、设计、工程,多方联手努力催生了 Voyager 项目,这是一个移动为先,跨平台(iOS、Android、Web)的一致用户体验(UX)。移动为先,这种思路使得我们有机会随后扩展至桌面应用端,并能沿用相同的界面模式和主题,跨越所有平台提供一致的体验。
(点击放大图像)
图 2:“Voyager”项目
通过这些努力,我们选择用两个框架构建 API 和 Web 客户端:面向 Java 的 Play Framework,以及面向 Web 的 Ember 框架,这两个框架已成为我们构建 Web 应用的事实标准。在设立这个新项目前,LinkedIn 就已在 Play Framework 方面投入了大量精力,我们的安全团队就这些框架目前具备的安全特性,结合我们希望自己的技术栈具备的特性进行了全面的缺口分析。
分析发现 Play Framework 提供了一种“默认安全”的方法,同时还有着负责任的安全团队,活跃的核心开发者社区,丰富的文档,以及稳定的发布周期。
Ember 也具备上述所有特征。作为一种单页应用(SPA)框架,Ember 还提供了:
- 通过 Fastboot 等技术为服务器端渲染(SSR)提供后续支持,
- 侧重于开发者,基于 CLI 的工效,如生成器和蓝图,以及加载项社区,
- 活跃的核心开发者社区,
- 通过清晰强大的抽象基元(Primitive)专注于新兴行业标准的影响和 Polyfill,
- 一个妥善开发,可用于 UI 组件测试的语义学方法。
通过将 Web 转变成一种客户端应用,我们已经可以为所有客户端(iOS、Android、Web)建立一套统一的内部 API,将整个基础架构更好地与不同平台相匹配,减少为了提供数据而需要的应用程序数量。
Ember 对测试工作的专注使得我们实现了更进一步的自动化部署,这有助于 3×3 方法的推广。该框架提供了三种不同类型的测试:集成测试、接受度测试,以及单元测试。集成测试使得我们可以对数据的流动以及应用程序内部不同组件的交互进行测试,接受度测试使得我们可以进行用户交互测试,单元测试为我们提供了测试应用程序逻辑所需的方法。随着开发者逐渐发布新组件,该框架也能生成所需的测试文件,借此开发者可以更专注于编写代码并在测试中验证代码功能,而不像以前只能在浏览器中执行这些操作。
纵深安全
LinkedIn 安全团队执行的纵深设计审查需要对所有面向用户的产品/特性以及功能进行渗透测试。我们还在安全自动化方面进行了巨大的投入,然而对于 3×3 部署架构,我们无法对所有构建进行规模化的手工渗透测试,因此就更有必要采用安全自动化技术。一旦发现可以更自信地检测出某类弱点,我们会为此类弱点构建自动化检查。合作伙伴产品安全工程团队也会帮助我们构建、维护、缩放这种自动化机制。借此可以更加专注于应用程序/底层框架中更重要的领域,并且能获得更多时间深入研究这些领域的安全弱点。
在向应用程序中增加 API 端点后,需要通过安全分析防止出现弱点。以前这一过程在操作上极为笨重,毕竟每个应用程序包含大量路由(指向资源或 URL 的路径),同时系统中存在大量此类应用程序。我们的安全团队构建了一套工具,可以检测应用程序中新的变更并通知我们,按照变更的本质对其进行分解(外部 API 路由的增减、应用程序重要代码的改动等),借此协助对应用程序中此类情况进行评估。因此我们可以通过最新审阅确定应用程序的状态。借此可以进行有针对性的审阅,确保更广泛的覆盖面,同时应用程序的评估可以实现更快速的周转时间。
我们通过纵深防御的原则建立了一套安全评估方法,毕竟谁也不想因为某个特定安全能控制的失败导致整个链条的失败。我们乐于为开发者提供他们需要的工具,帮助他们避免在代码中产生安全弱点。我们的产品安全团队开发的工具可以扫描代码变更中可能存在的弱点,如果发现任何反模式或不建议使用的实践,开发者在提交代码前就可以获得通知,并通过这些工具提供的代码修复建议,妥善解决存在的问题。一旦顺利通过代码审查流程,变更还会被进一步分析,如果发现可能的弱点,会通过自定义的提交前钩子,直接在部署流程的提交前阶段直接拒绝这样的代码。
一旦通过任何渠道检测到平台存在安全问题,随后我们的目标是防止未来继续出现相同问题。因此为了避免问题回归,我们构建了工具和测试用例,并针对已部署的服务持续运行,以检测特定类型的安全弱点是否会再次出现,并会向安全团队发送警报协助他们调查。
(点击放大图像)
图 3:扫描潜在 XSS 的提交前钩子工具
2014 年 1 月,该系统上线之前,我们通过代码扫描发现了超过 5,000 个潜在的 XSS 弱点;2016 年 1 月,这一数量降低至不到 500 个。这段时间里,我们观察到由于不恰当的提交导致的提交前故障的数量也稳步地大幅降低。通过这种系统化的安全自动化方法,潜在安全弱点的产生和存在数量几乎减少了 90%。
(点击放大图像)
图 4:我们目前的(3×3)发布周期
目前我们在 Web 客户端方面平均每天进行大约 50-75 次提交,这些工作是由超过 100 位 UI 工程师通过同一个代码库进行的。每个提交都会经历代码审查系统的检查,需要开发者通过访问控制列表(ACL)进行多次相互独立的批准,借此确保代码始终保持最高质量。同时这些代码还会针对最佳实践风格指南进行评估,并使用不同语言的 Linter 进行扫描,以确保开发者编写的代码能够与同事保持一致。如上所述,这些代码变更还会进行自动化的自定义安全扫描,检查其中可能包含的已知类别弱点,包括但不限于 XSS、CSRF,以及访问控制问题。一旦开发者获得了所需的审批,并解决了任何可能存在的问题,即可通过一系列系统顺利提交自己的代码,在确保代码健康度的同时等待下一次部署。
这些系统会执行一系列不同任务,针对应用程序运行各种测试。如果测试未通过,提交会被拒绝并会通知开发者。如果提交通过,即可进入应用程序主干,随后会运行一系列单独的测试以确保代码不会造成性能退化或超出应用程序负责人所指定的其他阈值。假设所有这一切均能顺利通过,提交过程顺利结束,代码会在下一次部署时进入生产环境。如果两次部署之间没有产生任何提交,那么依然会使用相同版本进行重新部署,这也是 3×3 方法论的一部分,可以确保代码能够进行严格的测试。
借此形成的全新发布模型使得我们可以按照组织规模的增长顺利缩放,同时大幅改进了代码质量、安全性、生产力,以及用户满意度。现在我们可以更好地为用户提供更安全、更快速、更现代化的体验,快速解决发现的问题或 Bug,以更快的速度进行创新。
关于本文作者
James Baker 是 LinkedIn Feed(首页)团队的资深软件工程师,致力于为全球数百万 LinkedIn 用户提供卓越的使用体验。James 从 2009 年开始积极从事 Web 工程方面的工作,而他早在 2004 年就开始投身 IT 行业。他住在硅谷,热衷于开发企业级、高性能、安全、易于访问的 Web 应用程序。
Mira Thambireddy 是 LinkedIn 的信息安全工程师,隶属于 LinkedIn 的应用程序安全和渗透测试团队。之前 Mira 曾作为安全顾问在硅谷工作,她持有卡耐基·梅隆大学(Carnegie Mellon University)信息安全硕士学位。
作者: James Baker 、 Mira Thambireddy , 阅读英文原文 : Developing a Secure and Scalable Web Ecosystem at LinkedIn