在前几章中,我们讨论了如何构建从代码提交到发布的快速工作流,以及反向的快速反馈流。我们还探索了加强组织学习和放大微弱故障信号的文化惯例,有助于创造更安全的工作系统。
在第六部分中,我们会进一步扩展这些活动,不仅实现开发和运维目标,还要同时实现信息安全目标,保障提高服务和数据的保密性、完整性和可用性。
我们不是在开发流程结束时才进行产品的安全性检查,而是要把安全控制整合到开发和运维团队的日常工作中,让安全成为所有人日常工作的一部分(可能这就是所说的DevSecOps吧 )。理想情况下,这项工作将以自动化的形式集成到部署流水线里。此外,我们还将通过自动化控制来优化手动操作、验收和审批流程,逐渐降低对职责分立和变更审批流程等控制的依赖。
我们不仅要提高安全性,而且还要创建更易于审计、能证明控制有效性的流程,以遵从监管义务和合同义务。相关的举措如下
当我们将安全工作整合到所有人的日常工作中,并使之成为每个人的责任时,组织就会获得更好的安全性。更好的安全性意味着我们可以防护数据、理智地对待数据。这又意味着可靠性和业务持续性,因为可用性更好,能更容易地从故障中恢复。我们能在灾难性后果发生之前解决安全问题,并且增加系统的可预测性。最重要的也许是,我们可以在系统和数据的防护方面做得比以往任何时候都好。
一直以来,实施 DevOps 原则和模式的最大阻碍就是“信息安全和合规性不允许”。然而,在技术价值流中,要将信息安全更好地集成到每个人日常工作中去,DevOps 可能是最佳的一个方式。
贯穿本书,我们探讨了如何在技术价值流中全面整合 QA 和运维目标。本章将介绍如何类似地将信息安全目标整合到日常工作中,从而在提高开发和运维人员效率的同时,增加系统安全程度,提高信息安全性
我们的一个目标是让特性团队尽早与信息安全团队协作,而不是等到项目结束阶段才开展相关工作。这样做的一种方法是:在每次开发间隔的最后,邀请信息安全人员参加产品演示,使他们能够在组织目标背景下更好地理解开发团队的目标,观察开发团队的实施和构建过程,并在项目的最早期阶段就提出指导和反馈,从而为问题修复留出更多的时间和自由度。
当信息安全人员变成团队的一部分时,即使参与方式只是收到通知和观察流程,他们也会获得所需要的业务环境信息,用于做出更好的安全风险决策。此外,信息安全人员还能够帮助特性团队理解需要什么才能达到安全和合规性目标。
如果可能,我们希望使用开发和运维团队的问题跟踪系统来管理所有已知的安全问题,以确保安全工作的可视性,以及能够将它与其他工作放到一起来安排优先级。这与信息安全管理的传统工作方式完全不同。以前,所有的安全漏洞都存储在只有信息安全人员才能够访问的 GRC(治理、风险和合规性)工具中;而现在,我们将把所有要做的工作都放在开发和运维使用的系统中。
“每当出现安全问题的时候,我们都会召开事后分析会议,因为它可以更好地教育工程师如何防止问题复发,也是将安全知识传递给工程团队的绝佳机制。”
在第 20 章中,我们创建了一个共享的源代码库,方便任何人轻松地找到和重用组织的集体知识——不仅仅是代码,还有工具链、部署流水线、标准等。这让每个人都可以受益于组织中所有人累积起来的集体经验。现在,我们要把任何有助于确保应用程序和环境安全性的机制和工具都添加到共享源代码库中。我们将添加用来满足特定信息安全目标的受安全保护的程序库,例如身份验证和加密库及服务。
如果有一个集中的共享服务组织,也可以与之协作,创建和运行与安全相关的共享平台服务,例如认证、授权、日志记录,以及开发和运维所需要的其他安全和审计服务。当工程师开发的一些应用模块使用了这些预定义库或服务时,他们就不需要再安排单独的安全设计评审了,而是会使用我们所创建的关于配置加固、数据库安全设置、密钥长度等的安全指导原则。
为使所提供的服务和程序库尽可能得到正确使用,我们可以向开发和运维团队提供安全培训,帮他们评审项目产品,以确保安全目标的正确实施,特别是在团队第一次使用这些工具的时候。
我们的终极目标是为所有现代应用程序或环境提供所需的安全类库或服务,例如启用用户身份验证、授权、密码管理、数据加密等。此外,也可以为开发和运维团队应用程序栈中用到的组件提供安全方面的有效配置,例如用于日志记录、身份验证和加密。还可能包括以下相关内容:
我们还应该与运维团队合作,创建基础的配置手册或构建操作系统、数据库和其他基础设施(例如,NGINX、Apache、Tomcat)的镜像,确保它们都处在已知、安全和低风险的状态。共享源代码库不仅是获得最新版本的地方,而且也是与其他工程师协作的地方,是对安全敏感模块变更进行监视和报警的地方。
以前,为了对应用程序进行安全加固,我们会在开发工作完成之后开始进行安全审查。通常,开发和运维可能会收到长达数百页的 PDF 文档,这些审查结果描述了各种安全漏洞。由于在整个软件生命周期中发现得太晚,已经错失了轻易修复的机会,或迫于项目期限的压力,这些问题最终很难得到修复
现在,我们将在这一步尽可能地自动化信息安全测试。这样,(在理想情况下)每当开发或运维人员代码提交时,甚至在软件项目的最早期阶段,就可以在部署流水线中与其他所有测试一起运行安全测试。
我们的目标是为开发和运维人员提供快速反馈,以便在他们提交有安全隐患的变更时,及时发出通知。这样就可以快速地检测和修复安全问题,并将这部分工作融入日常工作中,在学习的同时杜绝问题复发。
理想情况下,在部署流水线中,这些自动化安全测试将与其他静态代码分析工具同步运行。
诸如 Gauntlt
这样的工具可以集成到部署流水线中,针对应用程序、应用程序依赖、环境等进行自动化安全测试。值得注意的是,Gauntlt 所有的安全测试都使用 Gherkin 语法格式的测试脚本,而后者被开发人员广泛地用在单元测试和功能测试中。
通常,开发阶段的测试关注功能的正确性,着眼点是正确的逻辑流程。这种类型的测试通常称为愉快路径(happy path),它验证的是用户的正常操作流程(有时候存在几个可选的路径)——一切都按预期执行,没有例外或出错状况。另一方面,QA 人员、信息安全人员和欺诈者其实经常关注不愉快路径(sad path),它在事情出错时发生,尤其与安全相关的错误状况有关。(这类安全特定状况常被戏称为坏路径。
例如,假设有一个电子商务网站,客户下单时要在表单里输入信用卡号码。我们想要定义所有的不愉快路径和坏路径,以确保拒绝无效的信用卡,从而防止欺诈和安全漏洞,如 SQL 注入、缓冲区溢出和其他不良后果。
理想情况下,我们将其作为自动化单元或功能测试的一部分生成,而不是手动执行,以便在部署流水线中持续运行这些测试。我们期望包含以下内容作为测试的一部分。
此外,我们应该定义设计模式,帮助开发人员编写防止滥用的代码,例如为服务设置速率限制,将按下的提交按钮变为无法点击的状态。OWASP 发布了大量有用的指导,如 Cheat Sheet 系列,包括如下内容:
Josh Corman 指出,作为开发人员,“我们不再编写自定义软件,而是组装所需要的开源组件,这已经成为我们非常依赖的软件供应链了”。换句话说,当我们在软件中使用各种(商业或开源的)组件或库时,不仅继承了它们的功能,而且包括任何相关的安全漏洞。
在选择软件时,我们检测软件项目是否依赖有已知漏洞的组件或库,并帮助开发人员慎重地选择要使用的组件——选择那些曾经得到验证、软件漏洞可以快速修复的组件(例如开源项目)。我们还要找出在生产环境中所用库的多个版本,特别是存在已知漏洞的旧版本
在这个步骤里,任何有助于加固环境、降低风险的工作都应该做。虽然我们可能已经建立了已知的良好配置,但还是必须通过监控的手段确保所有生产服务器都与这些已知的良好状态匹配。我们利用自动化测试的方式来保证已正确应用了所有必要的设置,包括安全加固配置、数据库安全设置、密钥长度等。此外,我们将使用测试来扫描环境中的已知漏洞。
另一类安全性验证方法是理解实际环境(即“实际的状态”)。此类工具包括用来确保只开放预期端口的 Nmap,以及用来确保特定已知漏洞已经充分加固的 Metasploit
,例如使用 SQL 注入攻击进行扫描。应该将这些工具的输出放入工件库中,并与以前的版本进行比较,从而作为功能测试过程的一部分。这样,只要发生任何不良变化,就可以立即检测到。
内部安全控制通常无法及时、成功地检测到违规行为。这是由于监控中存在盲点,或者组织中没有人在日常工作中检查相关的遥测。
我们部署必要的监控、日志记录和告警系统,以在应用程序和环境中全面实现信息安全目标,并确保其充分集中化,以便进行简单、有意义的分析和响应。
通过将安全遥测集成到开发、QA 以及运维使用的工具中,价值流中的每个人都可以看到应用程序和环境在恶意威胁入侵中的表现,包括:攻击者不断尝试利用漏洞,获得未经授权的访问,植入后门,执行欺诈,拒绝服务等破坏活动。
公开将服务在生产环境中遭遇攻击的过程展示给所有人,能迫使每个人考虑安全风险,并且在日常工作中设计对策。
有问题的用户行为可以揭示或引发欺诈和未授权的访问,为了进行检测,必须在应用程序中创建相关的遥测系统。这样的例子包括:
除了完善应用程序,还需要在环境中创建全面的遥测系统,以便能够尽早检测未授权访问的迹象,特别是对于运行在非受控基础设施上(例如,托管环境、云端)的组件。
我们需要对某些事件做监控和告警,包括:
我们还要确认已正确配置了日志记录,以便将所有遥测信息发送到正确的地方。在监测攻击时,除了记录事件,还可以选择拦截访问,并保存访问来源和目标的信息,以帮助我们选择最佳的规避措施
支持持续集成和持续部署流程的基础设施,也成了易受攻击的新领域。例如,如果有人攻陷了运行部署流水线的服务器,而且该部署流水线保存着版本控制系统的登录账户信息,他们就能窃取程序的源代码。更糟的是,如果部署流水线里的账户具有写访问权限,攻击者还可能将恶意更改注入版本控制系统,从而将恶意更改注入应用程序和服务中。
为了充分保护应用程序和环境的完整性,还必须减少针对部署流水线的攻击向量。风险包括开发人员引入导致未授权访问的代码(通过代码测试、代码评审和渗透测试等控制措施缓解),以及未经授权的用户获取应用或者环境的访问权限(通过以下控制措施减缓:确保配置
始终处于已知和良好的状态,及时有效地打补丁)
然而,为了保护持续构建、集成或部署流水线,缓解风险的措施还可能包括:
本章介绍了将信息安全目标融入日常工作中所有阶段的方法。那就是通过将安全控制集成到已经创建的机制中,确保所有按需环境都处于加固的低风险状态,以及通过将安全测试集成到部署流水线中,确保在预生产和生产环境中创建安全遥测系统。这样,我们可以在提高开发和运维效率的同时,提高整体安全性。接下来,我们将保护部署流水线。
本章将探讨如何保护部署流水线,以及如何在控制环境里实现安全和合规性目标,包括变更管理和职责分离。
拥有一定规模的 IT 组织几乎都有自己的变更管理流程,这是减少运维和安全风险的主要控制手段。合规经理和安全经理依赖变更管理流程来满足合规性需求,并且通常需要证据表明所有变更都已得到了适当授权。
如果正确构建了部署流水线来降低部署风险,那么大部分变更就不需要经过手动审批流程了,因为我们将依赖于自动化测试和主动生产环境监控等控制措施。
在这个步骤里,我们将采取措施确保将安全和合规性成功地整合到任何已有的变更管理流程中。有效的变更管理政策将认识到不同的类型变更会带来不同的风险,并且有不同的方式来处理不同的变更。ITIL 定义了这些流程,将变更分为如下 3 种类型。
理想情况下,通过建立可靠的部署流程,我们已经因快速、可靠和非剧烈的部署而名声在外了。在此基础之上,应该让运维部门和相关变更机构认同我们所做的变更风险很低,可以归类为变更顾问委员会预审批的标准变更。这样,无需进一步的审批就可以直接部署到生产环境,不过当然还是应该正确记录这些变更
为了证明变更的风险较低,一种参考做法是:展示相当长时间里(例如,几个月或一个季度)的变更历史记录,并且整理出同期生产环境里的完整问题清单。如果能够证实变更成功率高,而且平均故障恢复时间(MTTR)短,那么就能断言我们处在能有效预防部署错误的可控环境中,同时证明了能够快速检测和纠正任何问题。
即使将这些变更归类为标准变更,出于可视化管理的需求,仍然需要记录在变更管理系统中(例如,Remedy 或 ServiceNow)。理想情况下,将使用配置管理工具和部署流水线工具(例如,Puppet、Chef、Jenkins)自动执行部署,并且自动记录部署结果。这样,组织中的每个人(不论是否为 DevOps)都能了解到我们的变更,并且也能了解组织中发生的所有其他变更了。
建立这种可追溯性和上下文联系应该很容易,不应该为工程师造成太繁重或耗时的工作负担。链接到用户故事、需求或缺陷几乎已经完全够用了,更多细节(例如,为版本控制系统中的每次代码提交都建立一个票单)可能没意义、也没必要,因为会显著增加日常工作的阻力。
不能归类为标准变更的变更属于常规变更,在部署前至少需要得到变更顾问委员会部分成员的批准。在这种情况下,即使部署工作并不是完全自动化的,我们的目标仍然是保障快速部署。
在这种情况下,必须确保提交的变更请求单尽可能完整、准确,为变更顾问委员会提供正确评估所需的一切信息。如果变更请求的格式不正确或信息不完整,就会被退回,这除了会增加进入生产环境所需的时间之外,还会让我们对自己是否真正理解了变更管理流程的真实目标产生怀疑
我们基本上一定能自动化创建信息完整和准确的变更请求单,在票单中填入正确的详细变更信息。例如,可以自动化创建一个 ServiceNow 变更票单,并在里面包含 JIRA 中用户故事的链接,来自部署流水线工具的构建清单和测试输出,以及将要执行的 Puppet/Chef 脚本的链接。
提交变更请求单后,变更顾问委员会的相关成员将审核、处理和批准变更。所有变更请求单的处理方式都与此相同。如果一切顺利,变更机构会赞赏变更单的周密程度和丰富细节,因为我们让他们可以快速地校验我们提交的这些信息的正确性(例如,查看部署流水线工具链接到的工件)。然而,我们的目标应该是持续展示成功变更的样板记录,从而最终使其认同能将自动化变更安全地归类为标准变更
几十年来,我们将职责分离用作减少软件开发过程中欺诈或犯错风险的主要控制手段之一。大多数软件开发生命周期都已经接受的做法是:要求将开发人员变更提交给代码库管理员接受审查和批准变更,然后由 IT 运维将变更部署到生产环境
当生产环境部署不太频繁(如每年一次)且工作尚不复杂时,工作划分和工作交接是可行的业务方式。然而,随着复杂性和部署频率的提升,成功地执行生产环境部署就愈发要求价值流中的所有人都能迅速看到工作的执行结果。
职责分离会减慢和减少工程师在工作中获得反馈,可能对上述要求造成阻碍。这妨碍了工程师对工作质量承担全部责任,降低了企业创建组织学习的能力。
因此,在可能的情况下,应避免使用职责分离作为控制手段。我们应该选择结对编程、持续检查代码签入和代码审查等,它们能为工作质量提供必要的保障。此外,实施这些控制手段之后,如果需要分离职责,我们也能证明已经创建的控制手段能实现同样的结果。
随着技术组织越来越多地应用 DevOps 模式,IT 和审计之间的关系变得比以往任何时候都更加紧张。这些新的 DevOps 模式挑战了有关审计、控制和风险规避的传统思维。
本章讨论了让每个人都承担信息安全责任的实践,将所有的信息安全目标都纳入价值流中每个人的日常工作。这样显著提高了控制的有效性,能更好地预防安全漏洞,更快地检测到安全漏洞并从中恢复。此外,还大大减少了准备和通过合规审计的工作时间。
第六部分探讨了如何将 DevOps 原则应用于信息安全,帮助我们实现目标,并确保安全是所有人日常工作的一部分。更好的安全性确保了我们能防护数据、理智地对待数据,能在安全问题酿成灾难以前恢复。最重要的是,我们可以让系统和数据的安全性变得比以往更好。
我们已经对 DevOps 的原理和技术实践进行了详细的探讨。在这个安全漏洞频发、交付周期不断缩短、技术大规模转型的时代,在技术领导者们要同时应对安全性、可靠性和灵活性的挑战时,DevOps 应运而生。希望本书能对读者深入理解问题并找到解决问题的方案有所帮助。
正如本书一直强调的,若管理不当,开发人员与运维人员之间存在的冲突会日益恶化,导致新产品和功能上线时间长,质量不如人意,损耗增加,技术债越积越多,生产力低下,员工的不满和倦怠情绪也会越来越严重。
DevOps 的原则和模式能够化解这个核心冲突。在阅读完本书后,希望读者能明白 DevOps转型如何能创建学习型组织,如何加快流程、打造一流可靠性和安全性的产品,如何提高企业竞争力和员工满意度。
DevOps 的践行需要新的企业文化和管理规范,同时会变革技术实践和架构。跨部门协作至关重要,包括管理层、产品管理部门、开发团队、质量保证团队、IT 运维、信息安全甚至市场营销人员在内,所有部门通力协作,才能有效构建出一个安全的工作系统,从而帮助小团队快速、自主地开发和验证,并安全地部署客户服务相关的代码,也才有可能有技术创新。这种方式可以最大程度地提高开发人员的生产力、学习积极性、满意度以及提高组织赢得市场的能力。
我们知道固步自封、不思进取的危害以及改变日常工作习惯的不易,也理解组织引进新工作方式所带来的风险和成本。我们也清楚,DevOps 也只是历史长河中的一沙,很快就会被新的流行方法取代。
但我们坚信,DevOps 对技术行业带来的转变,就如同精益在 20 世纪 80 年代对制造业的变革一样。那些拥抱 DevOps 的组织将在市场上赢得胜利,拒绝 DevOps 的组织将为此付出代价。拥抱 DevOps 的组织将打造出充满激情并持续进步的学习型组织,并通过创新的方式表现得比竞争对手更加出色。
因此,DevOps 不仅仅是技术层面的当务之急,也是组织层面的迫切要务。最关键的一点,DevOps 普适,尤其适用于这样的组织:必须通过技术手段改进工作流程,同时保证产品的高质量、可靠性和安全性
不要为此愤世嫉俗。参与变革的人都明白,他们做的事情当然可能会失败,但无论成败如何,他们总要试试。尝试的最大意义在于,通过实践鼓舞他人。不承担风险,创新是不可能成功的。如果你没有使某些管理层不安,那证明你可能还不够努力。不要让组织的免疫系统阻止或干扰了你的愿景。正如亚马逊的前“灾难大师”Jesse Robbins 所说:“做正确的事情,等着被开除。”
DevOps 会使技术价值流里的所有参与者都受益匪浅,无论我们是开发人员、运维工程师、质量保证工程师、信息安全人员、产品经理或者客户,它都能够带给我们那种开发伟大产品而产生的快感。它提供人性化的工作条件,让我们有更多时间陪伴亲人。它能使团队共同努力、学习、成长、取悦客户并使之获益,同时帮助组织取得成功。